using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Design; using System.Drawing.Drawing2D; using System.Drawing.Text; using System.Windows.Forms; using DevComponents.DotNetBar.Charts.Style; namespace DevComponents.DotNetBar.Charts { /// /// Represents the collection of PieSeries. /// [Editor("DevComponents.Charts.Design.ChartSeriesCollectionEditor, DevComponents.Charts.Design, " + "Version=14.1.0.37, Culture=neutral, PublicKeyToken=90f470f34c89ccaf", typeof(UITypeEditor))] public class ChartPieSeriesCollection : CustomNamedCollection { #region GetUniqueName public string GetUniqueName() { return (GetUniqueName("Series")); } #endregion } [TypeConverter(typeof(PieSeriesConverter))] public class PieSeries : BaseSeries, ISeriesPointContainer, ILegendItemEx { #region Private variables private States _States; private PieSeriesPointCollection _SeriesPoints; private double _ExplodedMargin = double.NaN; private Point _PieCenter; private List _PieRings; private int _RingWeightTotal; private Tbool _CenterFirstSlice = Tbool.NotSet; private Tbool _ShowAllRings = Tbool.NotSet; private Tbool _ShowOtherSlice = Tbool.NotSet; private PieSeriesPointCollection _ActiveSeriesPointSet; private MouseClickSliceAction _MouseClickSliceAction = MouseClickSliceAction.NotSet; private MouseDoubleClickSliceAction _MouseDoubleClickSliceAction = MouseDoubleClickSliceAction.NotSet; private PieSelectionMode _PieSelectionMode = PieSelectionMode.NotSet; private PieSeriesPoint _MouseDownPsp; private int _MouseDownRadius; private int _MouseDownOffset; private Tbool _EnableDragDetach = Tbool.NotSet; private SymbolDef _ScaleSymDef = new SymbolDef(); private float _SymFontScale = -1; private SliceVisualLayout _SliceVisualLayout; private int _RingWeight = 100; private int _RingWeightEx; private PaletteGroup _PaletteGroup = PaletteGroup.NotSet; private Color[] _CustomPalette; private double _PieOuterExtent; #endregion #region Constructors public PieSeries() : this(null) { } public PieSeries(string name) { Name = name; SeriesType = SeriesType.Pie; InitDefaultStates(); } #endregion #region InitDefaultStates private void InitDefaultStates() { SetState(States.ShowInnerRingsEx, true); } #endregion #region Public properties #region CenterFirstSlice /// /// Gets or sets whether the first pie slice is centered /// on the starting angle or starts on the starting angle. Default is false. /// [DefaultValue(Tbool.NotSet), Category("Display")] [Description("Indicates whether the first pie slice is centered on the starting angle or starts on the starting angle. Default is false.")] public Tbool CenterFirstSlice { get { return (_CenterFirstSlice); } set { if (value != _CenterFirstSlice) { _CenterFirstSlice = value; OnPropertyChangedEx("CenterFirstSlice", VisualChangeType.Layout); } } } #endregion #region CustomPalette /// /// Gets or sets the custom palette for the series. /// [DefaultValue(null), Category("Appearance")] [Description("Indicates the custom palette for the series.")] public Color[] CustomPalette { get { return (_CustomPalette); } set { _CustomPalette = value; OnPropertyChangedEx("CustomPalette", VisualChangeType.Layout); } } #endregion #region EnableDragDetach /// /// Gets or sets whether the user can detach associated series elements by Click and drag. /// Note that MouseClickSelect must not be set to None or NotSet. /// [DefaultValue(Tbool.NotSet), Category("Behavior")] [Description("Indicates whether the user can detach associated series elements by Click and drag. Note that MouseClickSelect must not be set to None or NotSet.")] public Tbool EnableDragDetach { get { return (_EnableDragDetach); } set { if (value != _EnableDragDetach) { _EnableDragDetach = value; OnPropertyChangedEx("EnableDragDetach", VisualChangeType.Layout); } } } #region GetEnableDragDetach internal bool GetEnableDragDetach(PieChart pieChart) { Tbool enable = EnableDragDetach; if (enable == Tbool.NotSet) enable = pieChart.EnableDragDetach; return (enable == Tbool.True); } #endregion #endregion #region ExplodedMargin /// /// Gets or sets the exploded Margin for the series when the pie /// is Exploded. The Margin is is measured as a percentage of the pie /// radius (if less than 1) or the absolute pixel amount (if greater than 1). /// [DefaultValue(double.NaN), Category("Layout")] [Description("Indicates the exploded Margin for the series when the pie is Exploded. The Margin is is measured as a percentage of the pie radius (if <= 1) or the absolute pixel amount (if > 1).")] public double ExplodedMargin { get { return (_ExplodedMargin); } set { if (value != _ExplodedMargin) { _ExplodedMargin = value; OnPropertyChangedEx("ExplodedMargin", VisualChangeType.Layout); } } } #endregion #region MouseClickSliceAction /// /// Gets or sets the action used when the user clicks on a pie slice. /// [DefaultValue(MouseClickSliceAction.NotSet), Category("Behavior")] [Description("Indicates the action used when the user clicks on a pie slice.")] public MouseClickSliceAction MouseClickSliceAction { get { return (_MouseClickSliceAction); } set { if (value != _MouseClickSliceAction) { _MouseClickSliceAction = value; OnPropertyChanged("MouseClickSliceAction"); } } } #region GetMouseClickSliceAction private MouseClickSliceAction GetMouseClickSliceAction(PieChart pieChart) { MouseClickSliceAction mca = (MouseClickSliceAction != MouseClickSliceAction.NotSet) ? MouseClickSliceAction : pieChart.MouseClickSliceAction; return ((mca != MouseClickSliceAction.NotSet) ? mca : MouseClickSliceAction.None); } #endregion #endregion #region MouseDoubleClickSliceAction /// /// Gets or sets the action used when the user double-clicks on a pie slice. /// [DefaultValue(MouseDoubleClickSliceAction.NotSet), Category("Behavior")] [Description("Indicates the action used when the user clicks on a pie slice.")] public MouseDoubleClickSliceAction MouseDoubleClickSliceAction { get { return (_MouseDoubleClickSliceAction); } set { if (value != _MouseDoubleClickSliceAction) { _MouseDoubleClickSliceAction = value; OnPropertyChanged("MouseDoubleClickSliceAction"); } } } #region GetMouseDoubleClickSliceAction private MouseDoubleClickSliceAction GetMouseDoubleClickSliceAction(PieChart pieChart) { MouseDoubleClickSliceAction mca = (MouseDoubleClickSliceAction != MouseDoubleClickSliceAction.NotSet) ? MouseDoubleClickSliceAction : pieChart.MouseDoubleClickSliceAction; return ((mca != MouseDoubleClickSliceAction.NotSet) ? mca : MouseDoubleClickSliceAction.None); } #endregion #endregion #region PaletteGroup /// /// Gets or sets the palette color group to use (Light/Medium/Dark/Color1/MonoBlue/etc). /// [DefaultValue(PaletteGroup.NotSet), Category("Appearance")] [Description("Indicates the palette color group to use (Light/Medium/Dark/Color1/MonoBlue/etc.).")] public PaletteGroup PaletteGroup { get { return (_PaletteGroup); } set { if (value != _PaletteGroup) { _PaletteGroup = value; OnPropertyChangedEx("PaletteGroup", VisualChangeType.Recalc); } } } #endregion #region PieSelectionMode /// /// Gets or sets the mode used to perfoem pie selections. /// [DefaultValue(PieSelectionMode.NotSet), Category("Behavior")] [Description("Indicates the mode used to perform pie selections.")] public PieSelectionMode PieSelectionMode { get { return (_PieSelectionMode); } set { if (value != _PieSelectionMode) { _PieSelectionMode = value; OnPropertyChanged("PieSelectionMode"); } } } #region GetPieSelectionMode internal PieSelectionMode GetPieSelectionMode(PieChart pieChart) { PieSelectionMode psm = PieSelectionMode; if (psm == PieSelectionMode.NotSet) psm = pieChart.PieSelectionMode; return ((psm != PieSelectionMode.NotSet) ? psm : PieSelectionMode.Slice); } #endregion #endregion #region ReversePaletteColors /// /// Gets or sets whether default palette colors are utilized in reverse order. /// [DefaultValue(false), Category("Behavior")] [Description("Indicates whether default palette colors are utilized in reverse order.")] public virtual bool ReversePaletteColors { get { return (TestState(States.ReversePaletteColors)); } set { if (value != ReversePaletteColors) { SetState(States.ReversePaletteColors, value); OnPropertyChangedEx("ReversePaletteColors", VisualChangeType.Layout); } } } #endregion #region RingWeight /// /// Gets or sets the 'relative' total combined thickness of all series rings, as /// compared to the relative thickness of other series combined rings. /// rings. /// [DefaultValue(100), Category("Layout")] [Description("Indicates the 'relative' total combined thickness of all series rings, as compared to the relative thickness of other series combined rings.. Default is 100.")] public int RingWeight { get { return (_RingWeight); } set { if (value != _RingWeight) { _RingWeight = value; OnPropertyChangedEx("RingWeight", Style.VisualChangeType.Recalc); } } } #endregion #region SeriesPoints /// /// Gets or sets the series point data collection. /// [Category("Data")] [Description("Indicates the series point data collection.")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public PieSeriesPointCollection SeriesPoints { get { if (_SeriesPoints == null) { _SeriesPoints = new PieSeriesPointCollection(); _SeriesPoints.Parent = this; _SeriesPoints.CollectionChanged += SeriesPoints_CollectionChanged; } return (_SeriesPoints); } set { if (value != _SeriesPoints) { if (_SeriesPoints != null) { _SeriesPoints.Parent = null; _SeriesPoints.CollectionChanged -= SeriesPoints_CollectionChanged; } _SeriesPoints = value; if (_SeriesPoints != null) { _SeriesPoints.Parent = this; _SeriesPoints.CollectionChanged += SeriesPoints_CollectionChanged; } } } } #region SeriesPoints_CollectionChanged void SeriesPoints_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { SeriesRangeChanged = true; SetPointNotify(e.OldItems, false); SetPointNotify(e.NewItems, true); InvalidateLayout(); } #region SetPointNotify private void SetPointNotify(IList list, bool notify) { if (list != null) { for (int i = 0; i < list.Count; i++) { PieSeriesPoint psp = list[i] as PieSeriesPoint; if (psp != null) { if (notify == true) { psp.Parent = SeriesPoints; psp.PropertyChanged += Sp_PropertyChanged; } else { psp.Parent = null; psp.PropertyChanged -= Sp_PropertyChanged; } } } } } #endregion #region Sp_PropertyChanged void Sp_PropertyChanged(object sender, PropertyChangedEventArgs e) { VisualPropertyChangedEventArgs vce = e as VisualPropertyChangedEventArgs; if (vce != null) { if (vce.ChangeType == VisualChangeType.Recalc) { InvalidateRecalc(); } else if (vce.ChangeType == VisualChangeType.Layout) { InvalidateLayout(); } else { PieSeriesPoint psp = sender as PieSeriesPoint; if (psp != null) { InvalidateRender(psp.Bounds); InvalidateRender(psp.PathBounds); } else { InvalidateRender(); } } if (ChartControl != null) ChartControl.GlobalUpdateCount++; } } #endregion #endregion #endregion #region ShowAllRings /// /// Gets or sets whether all series rings are shown when rendering the chart. /// If false, only one ring at a time is shown, with the ability to 'drill' up /// or down, to display adjacent rings. /// [DefaultValue(Tbool.NotSet), Category("Appearance")] [Description("Indicates whether all series Rings are shown when rendering the chart. If false, only one ring at a time is shown, with the ability to 'drill' up or down, to display adjacent rings.")] public Tbool ShowAllRings { get { return (_ShowAllRings); } set { if (value != _ShowAllRings) { _ShowAllRings = value; OnPropertyChangedEx("ShowAllRings", VisualChangeType.Layout); } } } #region ShowAllRingsEx internal bool ShowAllRingsEx(PieChart pieChart) { if (ShowAllRings != Tbool.NotSet) return (ShowAllRings != Tbool.False); return (pieChart.ShowAllRings != Tbool.False); } #endregion #endregion #region ShowOtherSlice /// /// Gets or sets whether to consolidate smaller pie slices into a single slice that /// represents 'other' data values, or whether to display those smaller slices /// as separate pie slices. /// [DefaultValue(Tbool.NotSet), Category("Appearance")] [Description("Indicates whether to consolidate smaller pie slices into a single slice that represents 'other' data values, or whether to display those smaller slices as separate pie slices.")] public Tbool ShowOtherSlice { get { return (_ShowOtherSlice); } set { if (value != _ShowOtherSlice) { _ShowOtherSlice = value; OnPropertyChangedEx("ShowOtherSlice", VisualChangeType.Layout); } } } #region ShowOtherSliceEx internal bool ShowOtherSliceEx(PieChart pieChart) { if (ShowOtherSlice != Tbool.NotSet) return (ShowOtherSlice == Tbool.True); return (pieChart.ShowOtherSlice == Tbool.True); } #endregion #endregion #region SubSliceVisualLayout /// /// Gets or sets the default layout of the subordinate or nested series slices. /// [Category("Layout")] [Description("Indicates the default layout of the subordinate or nested series slices.")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public SliceVisualLayout SubSliceVisualLayout { get { if (_SliceVisualLayout == null) SubSliceVisualLayout = new SliceVisualLayout(this); return (_SliceVisualLayout); } set { if (value != _SliceVisualLayout) { if (_SliceVisualLayout != null) _SliceVisualLayout.PropertyChanged -= SliceVisualLayout_PropertyChanged; _SliceVisualLayout = value; if (_SliceVisualLayout != null) _SliceVisualLayout.PropertyChanged += SliceVisualLayout_PropertyChanged; OnPropertyChangedEx("SliceVisualLayout", VisualChangeType.Recalc); } } } #region SliceVisualLayout_PropertyChanged void SliceVisualLayout_PropertyChanged(object sender, PropertyChangedEventArgs e) { VisualPropertyChangedEventArgs vce = e as VisualPropertyChangedEventArgs; if (vce != null) { OnPropertyChangedEx(vce.PropertyName, vce.ChangeType); if (ChartControl != null) ChartControl.GlobalUpdateCount++; } } #endregion #endregion #region Visible /// /// Gets or sets whether the series is visible or not. /// [DefaultValue(true), Category("Appearance")] [Description("Indicates whether the series is visible or not.")] public override bool Visible { get { return (base.Visible); } set { if (Visible != value) { base.Visible = value; SeriesRangeChanged = true; } } } #endregion #endregion #region Internal properties #region ActiveSeriesPointCollection internal PieSeriesPointCollection ActiveSeriesPointCollection { get { return (_ActiveSeriesPointSet); } set { _ActiveSeriesPointSet = value; } } #endregion #region IsInnerRingSeries internal bool IsInnerRingSeries { get { return (TestState(States.IsInnerRingSeries)); } set { SetState(States.IsInnerRingSeries, value); } } #endregion #region IsOffset internal bool IsOffset { get { return (TestState(States.IsOffset)); } set { SetState(States.IsOffset, value); } } #endregion #region IsOuterRingSeries internal bool IsOuterRingSeries { get { return (TestState(States.IsOuterRingSeries)); } set { SetState(States.IsOuterRingSeries, value); } } #endregion #region PieCenter internal Point PieCenter { get { return (_PieCenter); } set { _PieCenter = value; } } #endregion #region PieOuterExtent internal double PieOuterExtent { get { return (_PieOuterExtent); } set { _PieOuterExtent = value; } } #endregion #region PieRings internal List PieRings { get { if (_PieRings == null) _PieRings = new List(); return (_PieRings); } } #endregion #region RingWeightTotal internal int RingWeightTotal { get { return (_RingWeightTotal); } set { _RingWeightTotal = value; } } #endregion #region RingWeightEx internal int RingWeightEx { get { return (_RingWeightEx); } set { _RingWeightEx = value; } } #endregion #region SymFontScale internal float SymFontScale { get { return (_SymFontScale); } set { _SymFontScale = value; } } #endregion #endregion #region RenderOverride protected override void RenderOverride(ChartRenderInfo renderInfo) { if (SeriesPoints.Count > 0) { PieChart pieChart = Parent as PieChart; Point pt = pieChart.GetGlobalAdjustedPoint(pieChart.ContentBounds.Location); RenderBounds = new Rectangle(pt, pieChart.ContentBounds.Size); if (RenderBounds.Width > 0 && RenderBounds.Height > 0) RenderPieSeries(renderInfo); } } #region RenderPieSeries private void RenderPieSeries(ChartRenderInfo renderInfo) { Graphics g = renderInfo.Graphics; PieChart pieChart = Parent as PieChart; PieSeriesPointCollection spc = GetActiveSeriesPointCollection(); RenderPieSeriesEx(g, renderInfo.ClipRectangle, pieChart, spc); } #region RenderPieSeriesEx private void RenderPieSeriesEx(Graphics g, Rectangle clip, PieChart pieChart, PieSeriesPointCollection spc) { if (spc != null) spc = spc.PieSlices; if (spc != null) { foreach (PieSeriesPoint psp in spc) { if (psp.PieRing != null) { if (psp.PieRing.IsDisplayed == true) RenderSlice(g, clip, pieChart, spc, psp); } } } } #region RenderSlice private void RenderSlice(Graphics g, Rectangle clip, PieChart pieChart, PieSeriesPointCollection spc, PieSeriesPoint psp) { if (PspNeedsDisplayed(pieChart, psp, clip) == true) { Region rgn = null; int outerRadius = (int)psp.OuterRadius; int innerRadius = (int)psp.InnerRadius; ChartSliceVisualStyle sstyle = psp.GetEffectiveSliceStyle(pieChart, this); if (psp.IsEmpty == false) RenderReferenceLines(g, pieChart, spc, psp, false); RenderSliceEx(g, ref rgn, pieChart, spc, psp, outerRadius, innerRadius, sstyle); if (psp.IsEmpty == false) { if (psp.IsOther == true) { if (ShowAllRingsEx(pieChart) == true) { List pieRings = psp.PieRing.ChartSeries.PieRings; outerRadius = innerRadius; innerRadius = pieRings[pieRings.Count - 1].InnerRadius; RenderSliceOtherSpace(g, ref rgn, pieChart, psp, outerRadius, innerRadius, sstyle); } } } psp.PathBounds = (rgn != null) ? Rectangle.Ceiling(rgn.GetBounds(g)) : Rectangle.Empty; if (psp.IsEmpty == false) { if (sstyle.ImageOverlay == ImageOverlay.Bottom) RenderSliceFigure(g, rgn, pieChart, psp, sstyle); if ((sstyle.ImageOverlay == ImageOverlay.Middle) || (sstyle.ImageOverlay == ImageOverlay.NotSet)) { RenderSliceFigure(g, rgn, pieChart, psp, sstyle); } RenderSliceLabel(g, pieChart, psp, sstyle); if (sstyle.ImageOverlay == ImageOverlay.Top) RenderSliceFigure(g, rgn, pieChart, psp, sstyle); RenderReferenceLines(g, pieChart, spc, psp, true); } //using (StringFormat sf = new StringFormat()) //{ // sf.Alignment = StringAlignment.Center; // sf.LineAlignment = StringAlignment.Center; // g.DrawString(psp.RenderCount.ToString(), // SystemFonts.DefaultFont, Brushes.Black, psp.PathBounds, sf); //} //psp.RenderCount++; } if (ShowAllRingsEx(pieChart) == true) { if (psp.SeriesPoints != null) RenderPieSeriesEx(g, clip, pieChart, psp.SeriesPoints); } } #region PspNeedsDisplayed private bool PspNeedsDisplayed(PieChart pieChart, PieSeriesPoint psp, Rectangle clip) { if (psp.IsDisplayedEx == true) { if (psp.SweepAngleEx != 0) { Rectangle r = psp.Bounds; if (r.Width > 0 && r.Height > 0) { if (psp.InnerRadius < psp.OuterRadius) { if (psp.PathBounds.IsEmpty == true || clip.IntersectsWith(psp.PathBounds) == true) return (true); } } } } return (false); } #endregion #region RenderSliceEx private void RenderSliceEx(Graphics g, ref Region rgn, PieChart pieChart, PieSeriesPointCollection spc, PieSeriesPoint psp, int outerRadius, int innerRadius, ChartSliceVisualStyle sstyle) { int width = outerRadius - innerRadius; int extentWidth = (int)(psp.SliceExtent * width); bool renderShading = (sstyle.EnableShading == Tbool.True); SliceRenderType renderType = SliceRenderType.FullSlice; if (psp.HasExtent == true) { int extentRadius = innerRadius + extentWidth; if (extentWidth < width) { renderType = SliceRenderType.ExtentSlice; if (psp.ShowSliceWhiteSpaceEx == true) { RenderSliceWhiteSpace(g, ref rgn, pieChart, psp, outerRadius, extentRadius, renderShading, sstyle); renderShading = false; } } outerRadius = extentRadius; } RenderSliceExtent(g, ref rgn, pieChart, psp, outerRadius, innerRadius, renderShading, renderType, sstyle); } #region RenderSliceWhiteSpace private void RenderSliceWhiteSpace(Graphics g, ref Region rgn, PieChart pieChart, PieSeriesPoint psp, int outerRadius, int innerRadius, bool renderShading, ChartSliceVisualStyle sstyle) { Background background = sstyle.WhiteSpaceBackground; ChartLineVisualStyle border = sstyle.WhiteSpaceBorder; GraphicsState gs = g.Save(); g.TranslateTransform((float)psp.SliceCenter.X, (float)psp.SliceCenter.Y); g.RotateTransform((float)(psp.CenterAngle) % 360); Rectangle r = new Rectangle(); r.X = r.Y = -outerRadius; r.Width = r.Height = (outerRadius * 2); float angle = (float)GetSweepAngle(psp); // Try to blend the edges of the slice better. //if (background.IsDisplayable == true && border.IsDisplayable == false) // angle += .1f; ChartControl chartControl = ChartControl; using (GraphicsPath path = new GraphicsPath()) { path.AddArc(r, -angle / 2, angle); if (psp.SliceExtent == 0) { path.AddLine(Point.Empty, Point.Empty); path.CloseFigure(); if (chartControl.DoPreRenderSliceEvent( g, path, pieChart, this, psp, SliceRenderType.ExtentWhitespace) == false) { Rectangle pb = Rectangle.Truncate(path.GetBounds()); if (psp.IsEmpty == false) { if (background.IsDisplayable == true) { using (Brush br = background.GetBrush(pb)) g.FillPath(br, path); } } if (border.IsDisplayable == true) { using (Pen pen = new Pen(border.LineColor, Dpi.Width(border.LineWidth))) { if (border.LinePattern != LinePattern.NotSet) pen.DashStyle = (DashStyle)border.LinePattern; g.DrawPath(pen, path); } } chartControl.DoPostRenderSliceEvent( g, path, pieChart, this, psp, SliceRenderType.ExtentWhitespace); } if (psp.IsEmpty == false) { if (renderShading == true) RenderShading(g, psp, path); } path.Transform(g.Transform); if (rgn == null) rgn = new Region(path); } else { int pcount = path.PointCount; Rectangle t = new Rectangle(); t.X = t.Y = (-innerRadius); t.Width = t.Height = (innerRadius * 2); path.AddArc(t, angle / 2, -angle); PointF ppt1 = path.PathPoints[pcount]; PointF ppt2 = path.GetLastPoint(); path.CloseFigure(); if (chartControl.DoPreRenderSliceEvent( g, path, pieChart, this, psp, SliceRenderType.ExtentWhitespace) == false) { if (psp.IsEmpty == false) { if (background.IsDisplayable == true) { t = Rectangle.Ceiling(path.GetBounds()); Rectangle t2 = t; t2.Width = outerRadius - (int)psp.InnerRadius; t2.X = -t2.Width; using (Brush br = background.GetBrush(t2)) g.FillPath(br, path); } if (renderShading == true) RenderShading(g, psp, path); } path.Transform(g.Transform); if (rgn == null) rgn = new Region(path); if (border.IsDisplayable == true) { path.Reset(); using (Pen pen = new Pen(border.LineColor, Dpi.Width(border.LineWidth))) { if (border.LinePattern != LinePattern.NotSet) pen.DashStyle = (DashStyle)border.LinePattern; path.AddLine(ppt2, ppt2); path.AddArc(r, -angle / 2, angle); path.AddLine(ppt1, ppt1); g.DrawPath(pen, path); } } chartControl.DoPostRenderSliceEvent( g, path, pieChart, this, psp, SliceRenderType.ExtentWhitespace); } } } g.Restore(gs); } #endregion #region RenderSliceExtent private void RenderSliceExtent(Graphics g, ref Region rgn, PieChart pieChart, PieSeriesPoint psp, int outerRadius, int innerRadius, bool renderShading, SliceRenderType renderType, ChartSliceVisualStyle sstyle) { if (outerRadius - innerRadius > 0) { GraphicsState gs = g.Save(); g.TranslateTransform((float)psp.SliceCenter.X, (float)psp.SliceCenter.Y); g.RotateTransform((float)(psp.CenterAngle) % 360); Rectangle r = new Rectangle(); r.X = r.Y = -outerRadius; r.Width = r.Height = (outerRadius * 2); float angle = (float)GetSweepAngle(psp); using (GraphicsPath path = new GraphicsPath()) { path.AddArc(r, -angle / 2, angle); if (psp.InnerRadius == 0) { path.AddLine(Point.Empty, Point.Empty); } else { Rectangle t = new Rectangle(); t.X = t.Y = -innerRadius; t.Width = t.Height = innerRadius * 2; path.AddArc(t, angle / 2, -angle); } path.CloseFigure(); ChartControl chartControl = ChartControl; if (chartControl.DoPreRenderSliceEvent(g, path, pieChart, this, psp, renderType) == false) { if (psp.IsEmpty == false) { if (sstyle.Background.IsDisplayable == true) { r = Rectangle.Ceiling(path.GetBounds()); if (psp.HasExtent == true) { switch (sstyle.ExtentFillRange) { case ExtentFillRange.ByRing: r.Width = psp.PieRing.ExtentRadius - innerRadius; break; case ExtentFillRange.BySlice: PieSeriesPointCollection spc = psp.Parent as PieSeriesPointCollection; if (spc != null) { double sliceExtent = spc.PieMaxExtent / spc.PieOuterExtent; int width = (int)(psp.OuterRadius - psp.InnerRadius); int extentWidth = (int)(sliceExtent * width); r.Width = extentWidth; } break; } } using (Brush br = sstyle.Background.GetBrush(r)) g.FillPath(br, path); } } if (sstyle.Border.IsDisplayable == true) { using (Pen pen = new Pen( sstyle.Border.LineColor, Dpi.Width(sstyle.Border.LineWidth))) { if (sstyle.Border.LinePattern != LinePattern.NotSet) pen.DashStyle = (DashStyle)sstyle.Border.LinePattern; g.DrawPath(pen, path); } } chartControl.DoPostRenderSliceEvent(g, path, pieChart, this, psp, renderType); } if (psp.IsEmpty == false) { if (renderShading == true) RenderShading(g, psp, path); } path.Transform(g.Transform); if (rgn == null) rgn = new Region(path); else rgn.Union(path); g.Restore(gs); } } } #region GetSweepAngle private double GetSweepAngle(PieSeriesPoint psp) { double sweepAngle = psp.SweepAngleEx; if (sweepAngle > 360) sweepAngle %= 360; return (sweepAngle); } #endregion #endregion #endregion #region RenderSliceOtherSpace private void RenderSliceOtherSpace(Graphics g, ref Region rgn, PieChart pieChart, PieSeriesPoint psp, int outerRadius, int innerRadius, ChartSliceVisualStyle sstyle) { if (outerRadius - innerRadius > 0) { GraphicsState gs = g.Save(); g.TranslateTransform((float)psp.SliceCenter.X, (float)psp.SliceCenter.Y); g.RotateTransform((float)(psp.CenterAngle) % 360); Rectangle r = new Rectangle(); r.X = r.Y = -outerRadius; r.Width = r.Height = (outerRadius * 2); float angle = (float)GetSweepAngle(psp); Background background = sstyle.WhiteSpaceBackground; ChartLineVisualStyle border = sstyle.WhiteSpaceBorder; ChartControl chartControl = ChartControl; using (GraphicsPath path = new GraphicsPath()) { path.AddArc(r, -angle / 2, angle); if (innerRadius == 0) { path.AddLine(Point.Empty, Point.Empty); path.CloseFigure(); if (chartControl.DoPreRenderSliceEvent( g, path, pieChart, this, psp, SliceRenderType.OtherWhitespace) == false) { Rectangle pb = Rectangle.Truncate(path.GetBounds()); if (background.IsEmpty == false) { using (Brush br = background.GetBrush(pb)) g.FillPath(br, path); } if (border.IsDisplayable == true) { using (Pen pen = new Pen(border.LineColor, Dpi.Width(border.LineWidth))) { if (border.LinePattern != LinePattern.NotSet) pen.DashStyle = (DashStyle)border.LinePattern; g.DrawPath(pen, path); } } chartControl.DoPostRenderSliceEvent( g, path, pieChart, this, psp, SliceRenderType.OtherWhitespace); } path.Transform(g.Transform); if (rgn == null) rgn = new Region(path); else rgn.Union(path); } else { int pcount = path.PointCount; Rectangle t = new Rectangle(); t.X = t.Y = -innerRadius; t.Width = t.Height = (innerRadius * 2); PointF ppt1 = path.PathPoints[0]; PointF ppt2 = path.GetLastPoint(); path.AddArc(t, angle / 2, -angle); path.CloseFigure(); if (chartControl.DoPreRenderSliceEvent( g, path, pieChart, this, psp, SliceRenderType.OtherWhitespace) == false) { Rectangle pb = Rectangle.Ceiling(path.GetBounds()); if (background.IsEmpty == false) { using (Brush br = background.GetBrush(pb)) g.FillPath(br, path); } path.Transform(g.Transform); if (rgn == null) rgn = new Region(path); else rgn.Union(path); if (border.IsDisplayable == true) { using (Pen pen = new Pen(border.LineColor, Dpi.Width(border.LineWidth))) { if (border.LinePattern != LinePattern.NotSet) pen.DashStyle = (DashStyle)border.LinePattern; path.Reset(); path.AddLine(ppt1, ppt1); path.AddArc(t, -angle / 2, angle); path.AddLine(ppt2, ppt2); g.DrawPath(pen, path); } } chartControl.DoPostRenderSliceEvent( g, path, pieChart, this, psp, SliceRenderType.OtherWhitespace); } else { path.Transform(g.Transform); if (rgn == null) rgn = new Region(path); else rgn.Union(path); } } } g.Restore(gs); } } #endregion #region RenderShading private void RenderShading(Graphics g, PieSeriesPoint psp, GraphicsPath path) { PieChart pieChart = Parent as PieChart; int radius = (int)((psp.HasExtent == true) ? psp.ExtentRadius : psp.OuterRadius); Rectangle r = new Rectangle(); r.Inflate(radius, radius); Region clip = g.Clip; g.SetClip(path, CombineMode.Intersect); using (GraphicsPath path2 = new GraphicsPath()) { path2.AddEllipse(r); PathGradientBrush pbr = new PathGradientBrush(path2); ColorBlend cb = new ColorBlend(3); Color[] colors = new Color[3]; colors[0] = Color.FromArgb(100, Color.Black); colors[1] = Color.Transparent; colors[2] = Color.Transparent; cb.Colors = colors; float[] cp = new float[3]; cp[0] = 0f; cp[1] = .07f; cp[2] = 1f; cb.Positions = cp; pbr.InterpolationColors = cb; g.FillEllipse(pbr, r); } g.Clip = clip; } #endregion #region RenderReferenceLines private void RenderReferenceLines(Graphics g, PieChart pieChart, PieSeriesPointCollection spc, PieSeriesPoint psp, bool onTop) { if (spc.PieOuterExtent > 0) { ISeriesPointContainer ispc = spc.Parent; if (ispc != null) { if (psp.ReferenceLineDisplayOnTopEx == onTop) { PieRefLineDisplayMode mode = psp.ReferenceLineDisplayModeEx; if ((mode != PieRefLineDisplayMode.NotSet) && ((mode & PieRefLineDisplayMode.None) != PieRefLineDisplayMode.None)) { ChartSliceVisualStyle sstyle = psp.GetEffectiveSliceStyle(pieChart, this); int width = psp.OuterRadius - psp.InnerRadius; float start = (float)psp.StartAngleEx; float sweep = (float)psp.SweepAngleEx; if ((mode & PieRefLineDisplayMode.ExtentAverage) == PieRefLineDisplayMode.ExtentAverage) { int avg = (int)((spc.PieAverageExtent / spc.PieOuterExtent) * width); RenderReferenceLine(g, psp, start, sweep, avg + psp.InnerRadius, sstyle.ExtentAverageRefLineStyle); } if ((mode & PieRefLineDisplayMode.ExtentMinimum) == PieRefLineDisplayMode.ExtentMinimum) { int min = (int)((spc.PieMinExtent / spc.PieOuterExtent) * width); RenderReferenceLine(g, psp, start, sweep, min + psp.InnerRadius, sstyle.ExtentMinRefLineStyle); } if ((mode & PieRefLineDisplayMode.ExtentMaximum) == PieRefLineDisplayMode.ExtentMaximum) { int max = (int)((spc.PieMaxExtent / spc.PieOuterExtent) * width); RenderReferenceLine(g, psp, start, sweep, max + psp.InnerRadius, sstyle.ExtentMaxRefLineStyle); } if ((mode & PieRefLineDisplayMode.ExtentOuterRadius) == PieRefLineDisplayMode.ExtentOuterRadius) { RenderReferenceLine(g, psp, start, sweep, psp.OuterRadius, sstyle.ExtentOuterRefLineStyle); } if ((mode & PieRefLineDisplayMode.UserDefined) == PieRefLineDisplayMode.UserDefined) { List lines = psp.ReferenceLinesEx; if (lines.Count > 0) { foreach (PieReferenceLine line in lines) { if (line.Visible == true) { double radius = (line.Value / spc.PieOuterExtent) * width; RenderReferenceLine(g, psp, start, sweep, radius + psp.InnerRadius, line.EffectiveStyle); } } } } } } } } } #region RenderReferenceLine private void RenderReferenceLine(Graphics g, PieSeriesPoint psp, float start, float sweep, double radius, PieReferenceLineVisualStyle rstyle) { if (radius > 0) { if (rstyle.IsDisplayable == true) { Rectangle r = new Rectangle(); r.Location = psp.SliceCenter; r.Inflate((int)radius, (int)radius); using (Pen pen = new Pen(rstyle.LineColor, rstyle.LineWidth)) { if (rstyle.LinePattern != LinePattern.NotSet) pen.DashStyle = (DashStyle)rstyle.LinePattern; g.DrawArc(pen, r, start, sweep); } } } } #endregion #endregion #region RenderSliceFigure private void RenderSliceFigure(Graphics g, Region rgn, PieChart pieChart, PieSeriesPoint psp, ChartSliceVisualStyle sstyle) { object figure = sstyle.GetFigure(); if (figure != null) { double radius = GetFigureRadius(psp, sstyle); double centerAngle = psp.CenterAngle; double angle = GetImageAngle(psp, centerAngle, sstyle); Point pt = psp.GetRayEndPoint(angle, radius); Size srcSize = sstyle.GetFigureSizeEx(g); srcSize.Height = Dpi.DescaleHeight(srcSize.Height); srcSize.Width = Dpi.DescaleWidth(srcSize.Width); Rectangle srcRect = new Rectangle(pt, Size.Empty); Rectangle destRect = srcRect; double scaleFactor = GetImageSideFactor(psp, radius, srcSize); Size destSize = srcRect.Size; if (scaleFactor != 1) { double dh = (double)srcSize.Width / srcSize.Height; destSize = new Size((int)(dh * scaleFactor * 2), (int)scaleFactor * 2); } srcRect.Inflate(srcSize.Width / 2, srcSize.Height / 2); destRect.Inflate(destSize.Width / 2, destSize.Height / 2); bool scaleImage = (double.IsNaN(sstyle.ImageScale) == false); Region clip = null; if (scaleImage == false) { if (sstyle.ImageCropMode == SliceImageCropMode.ClipImage) { clip = g.Clip; g.SetClip(rgn, CombineMode.Intersect); } else if (sstyle.ImageCropMode == SliceImageCropMode.HideImage) { if (destRect.Contains(srcRect) == false) return; } destRect = srcRect; } psp.PathBounds = Rectangle.Union(psp.PathBounds, destRect); GraphicsState gState = null; if (sstyle.ImageAutoRotate == Tbool.True || sstyle.ImageRotation != 0) { gState = g.Save(); destRect.X = -(destRect.Width / 2); destRect.Y = -(destRect.Height / 2); g.TranslateTransform(pt.X, pt.Y); if (sstyle.ImageAutoRotate == Tbool.True) g.RotateTransform((float)angle); if (sstyle.ImageRotation != 0) g.RotateTransform((float)sstyle.ImageRotation); } if (scaleImage == true) { if (sstyle.ImageScale != 1) { int width = (int)(destRect.Width * sstyle.ImageScale); int height = (int)(destRect.Height * sstyle.ImageScale); destRect.X += (destRect.Width / 2); destRect.Y += (destRect.Height / 2); destRect.Size = Size.Empty; destRect.Inflate(width / 2, height / 2); } } if (sstyle.IsSymbolFigure == true) RenderSliceSymbol(g, pieChart, (SymbolDef)figure, destRect, sstyle); else RenderSliceImage(g, (Image)figure, srcRect, destRect); if (sstyle.ImageAutoRotate == Tbool.True || sstyle.ImageRotation != 0) g.Restore(gState); if (clip != null) g.Clip = clip; } } #region GetImageAngle private double GetImageAngle( PieSeriesPoint psp, double centerAngle, ChartSliceVisualStyle sstyle) { double offset = sstyle.ImageAngleOffset; if (offset == 0) return (centerAngle); if ((uint)offset < 1) return (centerAngle + offset * psp.SweepAngleEx); return (centerAngle + offset); } #endregion #region GetImageSideFactor /// /// Calculates the max 'a' side of the image. /// /// /// /// /// private double GetImageSideFactor( PieSeriesPoint psp, double radius, Size size) { double maxRadius = GetMaxImageRadius(psp, radius); double scaleFactor = (double)(size.Width * size.Width) / (size.Height * size.Height); double side = Math.Sqrt((maxRadius * maxRadius) / (scaleFactor + 1)); return (side); } #region GetMaxImageRadius /// /// Gets the max radius of a circle image at the given ray radius. /// /// /// /// private double GetMaxImageRadius(PieSeriesPoint psp, double radius) { double angle = psp.SweepAngleEx / 2; if (angle >= 90) return (radius); double radians = MathHelper.ToRadians(angle); double maxRad = radius * Math.Sin(radians); //double dr = (int)(radius * radius); //double maxRad = 2 * dr - 2 * (dr * Math.Cos(radians)); //maxRad = Math.Sqrt(maxRad); return (maxRad); } #endregion #endregion #region GetFigureRadius private double GetFigureRadius(PieSeriesPoint psp, ChartSliceVisualStyle sstyle) { double radius = psp.OuterRadius - psp.InnerRadius; double extentRadius = psp.InnerRadius + radius * psp.SliceExtent; double innerRadius = GetAnchorRadius(psp, extentRadius, sstyle.ImageInnerRadiusAnchor); double outerRadius = GetAnchorRadius(psp, extentRadius, sstyle.ImageOuterRadiusAnchor); double delta = outerRadius - innerRadius; if (delta == 0) delta = radius; if (Math.Abs(sstyle.ImageRadiusOffset) <= 1) return (innerRadius + delta * sstyle.ImageRadiusOffset); else return (innerRadius + sstyle.ImageRadiusOffset * (delta > 0 ? 1 : -1)); } #region GetAnchorRadius private double GetAnchorRadius( PieSeriesPoint psp, double extentRadius, SliceImageRadiusAnchor anchor) { switch (anchor) { case SliceImageRadiusAnchor.ExtentRadius: return (extentRadius); case SliceImageRadiusAnchor.InnerRadius: return (psp.InnerRadius); case SliceImageRadiusAnchor.OuterRadius: return (psp.OuterRadius); } return (psp.InnerRadius); } #endregion #endregion #region RenderSliceSymbol private void RenderSliceSymbol(Graphics g, PieChart pieChart, SymbolDef symDef, Rectangle destRect, ChartSliceVisualStyle sstyle) { TextRenderingHint hint = g.TextRenderingHint; g.TextRenderingHint = TextRenderingHint.AntiAlias; if (double.IsNaN(sstyle.ImageScale) == true) { using (StringFormat sf = new StringFormat()) { sf.LineAlignment = StringAlignment.Center; sf.Alignment = StringAlignment.Center; using (Brush br = new SolidBrush(symDef.SymbolColor)) g.DrawString(symDef.SymbolRealized, symDef.SymbolFont, br, destRect, sf); } } else { float n = Math.Max(destRect.Width, destRect.Height); destRect.Y += Dpi.Width4; if (_SymFontScale <= 0) { _ScaleSymDef.SymbolSize = n; _ScaleSymDef.SymbolSet = symDef.SymbolSet; _SymFontScale = n / _ScaleSymDef.SymbolFont.Height * .8f; } float fn = _SymFontScale * n; _ScaleSymDef.SymbolSize = fn; _ScaleSymDef.SymbolSet = symDef.SymbolSet; using (StringFormat sf = new StringFormat()) { sf.LineAlignment = StringAlignment.Center; sf.Alignment = StringAlignment.Center; using (Brush br = new SolidBrush(symDef.SymbolColor)) g.DrawString(symDef.SymbolRealized, _ScaleSymDef.SymbolFont, br, destRect, sf); } } g.TextRenderingHint = hint; } #endregion #region RenderSliceImage private void RenderSliceImage(Graphics g, Image image, Rectangle srcRect, Rectangle destRect) { g.DrawImage(image, destRect, new Rectangle(Point.Empty, srcRect.Size), GraphicsUnit.Pixel); } #endregion #endregion #region RenderSliceLabel internal void RenderSliceLabel(Graphics g, PieChart pieChart, PieSeriesPoint psp, ChartSliceVisualStyle sstyle) { if (IsInnerSliceLabelVisible(pieChart, psp) == true) { SliceInnerLabelVisualStyle lstyle = sstyle.SliceInnerLabelStyle; psp.InnerLabelDisplayed = false; string text = GetSliceLabel(pieChart, psp, true, psp.InnerSliceLabelEx); ChartControl chartControl = ChartControl; bool displayed = true; if (chartControl.DoRenderSliceInnerLabelEvent(g, pieChart, psp, text, ref displayed) == false) { if (string.IsNullOrEmpty(text) == false) { double maxRadius = psp.OuterRadius; if (lstyle.OuterRadiusOffsetBase == OuterRadiusOffsetBase.ExtentRadius) maxRadius = psp.InnerRadius + (psp.OuterRadius - psp.InnerRadius) * psp.SliceExtent; SliceLabelOrientation orientation = psp.InnerLabelOrientationEx; switch (orientation) { case SliceLabelOrientation.Adaptive: psp.InnerLabelDisplayed = RenderAdaptiveLabel(g, pieChart, psp, maxRadius, text, lstyle); break; case SliceLabelOrientation.Custom: psp.InnerLabelDisplayed = RenderCustomLabel(g, pieChart, psp, maxRadius, text, lstyle); break; case SliceLabelOrientation.Parallel: psp.InnerLabelDisplayed = RenderParallelLabel(g, pieChart, psp, maxRadius, text, lstyle); break; case SliceLabelOrientation.Perpendicular: psp.InnerLabelDisplayed = RenderPerpendicularLabel(g, pieChart, psp, maxRadius, text, lstyle); break; default: psp.InnerLabelDisplayed = RenderHorizontalLabel(g, pieChart, psp, maxRadius, text, lstyle); break; } } } } else { psp.LastPathBounds = Rectangle.Empty; } } #region IsInnerSliceLabelVisible private bool IsInnerSliceLabelVisible(PieChart pieChart, PieSeriesPoint psp) { switch (psp.SliceLabelDisplayModeEx) { case SliceLabelDisplayMode.NotSet: case SliceLabelDisplayMode.Inner: case SliceLabelDisplayMode.InnerAndOuter: case SliceLabelDisplayMode.InnerXorOuter: return (IsLabelDisplayed(pieChart, psp)); } return (false); } #region IsLabelDisplayed internal bool IsLabelDisplayed(PieChart pieChart, PieSeriesPoint psp) { SliceLabelVisibility labVis = psp.SliceLabelVisibilityEx; if ((labVis == SliceLabelVisibility.NotSet) || (labVis & SliceLabelVisibility.Always) == SliceLabelVisibility.Always) return (true); if ((labVis & SliceLabelVisibility.Never) == SliceLabelVisibility.Never) return (false); if (pieChart.ShowSliceLabelsOnEntry == true && pieChart.IsMouseOver == true) return (true); if ((labVis & SliceLabelVisibility.SliceMouseOver) == SliceLabelVisibility.SliceMouseOver) { if (psp == pieChart.HitPsp) return (true); } if (((labVis & SliceLabelVisibility.SelectionModeMouseOver) == SliceLabelVisibility.SelectionModeMouseOver) || ((labVis & SliceLabelVisibility.SliceMouseOver) == SliceLabelVisibility.SliceMouseOver)) { if (IsHighLightedPsp(pieChart, psp) == true) return (true); } if ((labVis & SliceLabelVisibility.SliceSelect) == SliceLabelVisibility.SliceSelect) { if (psp.IsSelected == true) return (true); } if ((labVis & SliceLabelVisibility.SliceDetach) == SliceLabelVisibility.SliceDetach) { if (psp.IsDetached == true) return (true); } return (false); } #endregion #endregion #region RenderAdaptiveLabel internal bool RenderAdaptiveLabel(Graphics g, PieChart pieChart, PieSeriesPoint psp, double maxRadius, string text, SliceInnerLabelVisualStyle lstyle) { double innerOffset = GetLableInnerOffset(psp, maxRadius, lstyle); double outerOffset = GetLableOuterOffset(psp, maxRadius, lstyle); if (outerOffset - innerOffset > 0) { double centerAngle = psp.CenterAngle % 360; float fontAngle = 0; if (lstyle.AutoOrientLabel != Tbool.False) { if (centerAngle < 180) fontAngle += 180; } float c = (float)(Math.PI * outerOffset * 2); if (c > 0) { RectangleF[] cBounds = GetAdaptiveCharBounds(g, psp, text, lstyle); if (centerAngle < 180) { return (PaintAdaptiveFlipText(g, pieChart, psp, outerOffset, innerOffset, fontAngle, text, cBounds, lstyle)); } return (PaintAdaptiveText(g, pieChart, psp, outerOffset, innerOffset, fontAngle, text, cBounds, lstyle)); } } return (false); } #region GetAdaptiveCharBounds private RectangleF[] GetAdaptiveCharBounds(Graphics g, PieSeriesPoint psp, string text, SliceInnerLabelVisualStyle lstyle) { RectangleF[] charBounds = psp.CharBounds; if (charBounds == null || charBounds.Length < text.Length) { charBounds = new RectangleF[text.Length]; int n = (int)Math.Ceiling((double)text.Length / 32); using (StringFormat sf = new StringFormat()) { sf.FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.MeasureTrailingSpaces; for (int i = 0; i < text.Length; i += 32) { int len = 32; if (i + len > text.Length) len = text.Length - i; MeasureAdaptiveText(g, sf, text, i, len, charBounds, lstyle); } } psp.CharBounds = charBounds; } return (charBounds); } #region MeasureAdaptiveText private void MeasureAdaptiveText(Graphics g, StringFormat sf, string text, int index, int len, RectangleF[] charBounds, SliceInnerLabelVisualStyle lstyle) { CharacterRange[] crs = new CharacterRange[len]; string s = text.Substring(index, len); for (int j = 0; j < len; j++) crs[j] = new CharacterRange(j, 1); sf.SetMeasurableCharacterRanges(crs); Rectangle r = new Rectangle(0, 0, 5000, 5000); Region[] rgns = g.MeasureCharacterRanges(s, lstyle.Font, r, sf); for (int i = 0; i < len; i++) charBounds[index + i] = rgns[i].GetBounds(g); if (index > 0) { float dx = charBounds[index - 1].X + charBounds[index - 1].Width - charBounds[index].X; for (int i = 0; i < len; i++) charBounds[index + i].X += dx; } } #endregion #endregion #region PaintAdaptiveText private bool PaintAdaptiveText(Graphics g, PieChart pieChart, PieSeriesPoint psp, double outerOffset, double innerOffset, float fontAngle, string text, RectangleF[] cBounds, SliceInnerLabelVisualStyle lstyle) { bool clipped; List wpsLines = GetAdaptiveLines(psp, outerOffset, innerOffset, text, cBounds, out clipped, lstyle); if (wpsLines != null && wpsLines.Count > 0) { psp.InnerLabelClipped = clipped; if (clipped == true) { if (psp.SliceLabelCropModeEx == SliceLabelCropMode.Hide) return (false); } double startAngle; double endAngle; NormalizeSliceAngles(psp, out startAngle, out endAngle); double startRadians = MathHelper.ToRadians((float)(startAngle + lstyle.AngleMargin)); double endRadians = MathHelper.ToRadians((float)(endAngle - lstyle.AngleMargin)); if (startRadians < endRadians) { TextRenderingHint renderingHint = g.TextRenderingHint; g.TextRenderingHint = TextRenderingHint.AntiAlias; float height = cBounds[0].Height; float halfHeight = height / 2; using (Brush br = new SolidBrush(lstyle.TextColor)) { foreach (WordPosLine wpsLine in wpsLines) { double offset = wpsLine.Offset; if (wpsLine.Wps.Count > 0) { double dx = GetAlignmentOffset(wpsLine, cBounds, lstyle); double xRadians = startRadians + (dx / offset); foreach (WordPos wp in wpsLine.Wps) { for (int j = 0; j < wp.Text.Length; j++) { RectangleF tf = cBounds[wp.Index + j]; float cRadians = (float)(tf.Width / offset); if (xRadians + cRadians > (float)endRadians + .5) break; xRadians += (cRadians / 2); float z = (float)(offset - halfHeight); float y = (float)(psp.SliceCenter.Y + z * Math.Sin(xRadians)); float x = (float)(psp.SliceCenter.X + z * Math.Cos(xRadians)); int n = (int)Math.Max(tf.Width, tf.Height); GraphicsState gState = g.Save(); g.TranslateTransform(x, y); g.RotateTransform(90 + (float)MathHelper.ToDegrees((float)xRadians)); g.DrawString(wp.Text[j].ToString(), lstyle.Font, br, -tf.Width/2, -tf.Height / 2); g.Restore(gState); xRadians += (cRadians / 2); } if (wp.Index + wp.Length < cBounds.Length) xRadians += cBounds[wp.Index + wp.Length].Width / offset; } } } } if ((outerOffset > psp.OuterRadius) || (wpsLines[wpsLines.Count - 1].Offset < psp.InnerRadius)) { Rectangle t = GetBoundingArcRect( psp, startAngle, endAngle, outerOffset + height); psp.PathBounds = Rectangle.Union(psp.PathBounds, t); if (psp.PathBounds.Equals(psp.LastPathBounds) == false) { psp.LastPathBounds = psp.PathBounds; InvalidateRender(psp.PathBounds); } } g.TextRenderingHint = renderingHint; return (true); } } return (false); } #region GetBoundingArcRect private Rectangle GetBoundingArcRect( PieSeriesPoint psp, double startAngle, double endAngle, double outerOffset) { Rectangle r = new Rectangle(psp.SliceCenter, Size.Empty); double n = startAngle; n += 89; n = (int)(n / 90) * 90; while (n <= endAngle) { UpdateAngleBounds(psp, n, outerOffset, ref r); n += 90; } if (startAngle % 90 != 0) UpdateAngleBounds(psp, startAngle, outerOffset, ref r); if (endAngle % 90 != 0) UpdateAngleBounds(psp, endAngle, outerOffset, ref r); return (r); } #region UpdateAngleBounds private void UpdateAngleBounds(PieSeriesPoint psp, double angle, double outerOffset, ref Rectangle r) { Point pts = psp.GetRayEndPoint(angle, outerOffset); if (pts.X < r.X) { r.Width += (r.X - pts.X); r.X = pts.X; } else if (pts.X > r.Right) { r.Width += (pts.X - r.Right); } if (pts.Y < r.Y) { r.Height += (r.Y - pts.Y); r.Y = pts.Y; } else if (pts.Y > r.Bottom) { r.Height += (pts.Y - r.Bottom); } } #endregion #endregion #region GetAdaptiveLines private List GetAdaptiveLines( PieSeriesPoint psp, double outerOffset, double innerOffset, string text, RectangleF[] cBounds, out bool clipped, SliceInnerLabelVisualStyle lstyle) { if (psp.WordPosLines == null) { psp.WordPosLines = GetAdaptiveLinesEx(psp, outerOffset, innerOffset, text, cBounds, out clipped, lstyle); psp.InnerLabelClipped = clipped; } clipped = psp.InnerLabelClipped; return (psp.WordPosLines); } #region GetAdaptiveLinesEx private List GetAdaptiveLinesEx( PieSeriesPoint psp, double outerOffset, double innerOffset, string text, RectangleF[] cBounds, out bool clipped, SliceInnerLabelVisualStyle lstyle) { float height = cBounds[0].Height; int maxLineCount = (int)((outerOffset - innerOffset) / height); SliceLabelCropMode cropMode = psp.SliceLabelCropModeEx; if (cropMode != SliceLabelCropMode.Hide) maxLineCount = 1000; if (lstyle.MaxLineCount >= 0) { if (maxLineCount > lstyle.MaxLineCount) maxLineCount = lstyle.MaxLineCount; } List wpsLines = null; if (maxLineCount > 0) { List wps = GetWordWidths(text, cBounds); double offset = outerOffset; bool middle = false; switch (lstyle.Alignment) { case SliceLabelAlignment.MiddleLeft: case SliceLabelAlignment.MiddleRight: case SliceLabelAlignment.MiddleCenter: offset = (outerOffset + innerOffset) / 2; middle = true; break; case SliceLabelAlignment.InnerLeft: case SliceLabelAlignment.InnerRight: case SliceLabelAlignment.InnerCenter: clipped = false; while (offset > innerOffset) { List ilines = FitAdaptiveLines(psp, offset, -height, cBounds, maxLineCount, wps, out clipped, lstyle); if (ilines != null) { if (wpsLines == null || clipped == false) wpsLines = ilines; if (clipped == true) break; wps = GetWordWidths(text, cBounds); offset -= height; } else { break; } } return (wpsLines); default: List lines = FitAdaptiveLines(psp, offset, -height, cBounds, maxLineCount, wps, out clipped, lstyle); return (lines); } for (int i = 0; i < maxLineCount; i++) { List lines = FitAdaptiveLines(psp, offset, -height, cBounds, (i + 1), wps, out clipped, lstyle); if (lines != null) { if (clipped == false) return (lines); wpsLines = lines; wps = GetWordWidths(text, cBounds); if ((middle == false) || (i % 2 == 0)) offset += height; } else { break; } } } clipped = false; return (wpsLines); } #region FitAdaptiveLines private List FitAdaptiveLines( PieSeriesPoint psp, double offset, float height, RectangleF[] cBounds, int count, List wps, out bool clipped, SliceInnerLabelVisualStyle lstyle) { List wpsLines = new List(); int wordIndex = 0; while (wpsLines.Count < count) { int lineWordCount = 0; double arcWidth = GetArcWidth(psp, offset - cBounds[0].Height / 4, lstyle); if (arcWidth <= 0) break; WordPosLine wpsLine = new WordPosLine(); wpsLine.Offset = offset; wpsLine.ArcWidth = arcWidth; while (wordIndex < wps.Count) { WordPos wp = wps[wordIndex]; double wordLength = GetWordLength(wp, cBounds, lineWordCount); if (wp.Text.Equals("\n") == true) { if (wpsLine.Wps.Count == 0) wpsLine.Wps.Add(wp); wordIndex++; break; } if (wpsLine.ArcWidth - (wpsLine.LineWidth + wordLength) < 0) { if (lineWordCount == 0 || wpsLines.Count + 1 == count) { wpsLine = FitNonBrokenLine(psp, offset, count - wpsLines.Count, wpsLines, wpsLine, wp, cBounds, height, out clipped, lstyle); if (clipped == true || wpsLine == null) { if (wpsLine != null && wpsLine.Wps.Count > 0) wpsLines.Add(wpsLine); return (wpsLines); } offset = wpsLine.Offset; } else { break; } } else { wpsLine.LineWidth += wordLength; wpsLine.Wps.Add(wp); } wordIndex++; lineWordCount++; } if (wpsLine.Wps.Count > 0) { offset = wpsLine.Offset; wpsLines.Add(wpsLine); } if (wordIndex >= wps.Count) break; offset += height; } clipped = (wordIndex < wps.Count); return (wpsLines); } #region FitNonBrokenLine private WordPosLine FitNonBrokenLine(PieSeriesPoint psp, double offset, int maxCount, List wpsLines, WordPosLine wpsLine, WordPos wp, RectangleF[] cBounds, float height, out bool clipped, SliceInnerLabelVisualStyle lstyle) { double length; int count = GetNonBrokenCount(wpsLine, wp, cBounds, out length); clipped = true; if (length == 0) return (wpsLine); int rindex = wp.Index; while (count > 0) { WordPos wp2 = new WordPos(); wp2.Index = wp.Index; wp2.Length = count; wp2.Text = wp.Text.Substring((wp.Index - rindex), count); wpsLine.Wps.Add(wp2); wp.Index += count; wp.Length -= count; if (--maxCount <= 0) break; if (wp.Length <= 0) break; wpsLines.Add(wpsLine); offset += height; wpsLine = new WordPosLine(); wpsLine.Offset = offset; wpsLine.ArcWidth = GetArcWidth(psp, offset - cBounds[0].Height / 4, lstyle); count = GetNonBrokenCount(wpsLine, wp, cBounds, out length); } clipped = (wp.Length > 0); return (wpsLine); } #region GetNonBrokenCount private int GetNonBrokenCount( WordPosLine wpsLine, WordPos wp, RectangleF[] cBounds, out double length) { length = 0; if (wpsLine.Wps.Count > 0 && wp.Index > 0) { double width = cBounds[wp.Index - 1].Width; if (wpsLine.ArcWidth - (wpsLine.LineWidth + width) < 0) return (0); wpsLine.LineWidth += width; length += width; } for (int i = 0; i < wp.Length; i++) { double width = cBounds[wp.Index + i].Width; if (wpsLine.ArcWidth - (wpsLine.LineWidth + width) < 0) return (i); wpsLine.LineWidth += width; length += width; } return (wp.Length); } #endregion #endregion #endregion #endregion #endregion #endregion #region PaintAdaptiveFlipText private bool PaintAdaptiveFlipText(Graphics g, PieChart pieChart, PieSeriesPoint psp, double outerOffset, double innerOffset, float fontAngle, string text, RectangleF[] cBounds, SliceInnerLabelVisualStyle lstyle) { bool clipped; List wpsLines = GetAdaptiveFlipLines(psp, outerOffset, innerOffset, text, cBounds, out clipped, lstyle); if (wpsLines != null && wpsLines.Count > 0) { if (clipped == true) { if (psp.SliceLabelCropModeEx == SliceLabelCropMode.Hide) return (false); } double startAngle; double endAngle; NormalizeSliceAngles(psp, out startAngle, out endAngle); double endRadians = MathHelper.ToRadians(startAngle + lstyle.AngleMargin); double startRadians = MathHelper.ToRadians(endAngle - lstyle.AngleMargin); float height = cBounds[0].Height; float halfHeight = height / 2; TextRenderingHint renderingHint = g.TextRenderingHint; g.TextRenderingHint = TextRenderingHint.AntiAlias; using (Brush br = new SolidBrush(lstyle.TextColor)) { foreach (WordPosLine wpsLine in wpsLines) { double offset = wpsLine.Offset; if (wpsLine.Wps.Count > 0) { double dx = GetAlignmentOffset(wpsLine, cBounds, lstyle); double xRadians = startRadians - dx / offset; foreach (WordPos wp in wpsLine.Wps) { for (int j = 0; j < wp.Text.Length; j++) { if (xRadians < (float)endRadians) break; RectangleF tf = cBounds[wp.Index + j]; float cRadians = (float)(tf.Width / offset); xRadians -= (cRadians / 2); float z = (float)(offset - halfHeight); float y = (float)((float)psp.SliceCenter.Y + z * Math.Sin(xRadians)); float x = (float)((float)psp.SliceCenter.X + z * Math.Cos(xRadians)); GraphicsState gState = g.Save(); g.TranslateTransform(x, y); g.RotateTransform(-90 + (float)MathHelper.ToDegrees(xRadians)); g.DrawString(wp.Text[j].ToString(), lstyle.Font, br, -tf.Width / 2, -tf.Height / 2); g.Restore(gState); xRadians -= (cRadians / 2); } if (wp.Index + wp.Length < cBounds.Length) xRadians -= cBounds[wp.Index + wp.Length].Width / offset; } } } } if ((outerOffset > psp.OuterRadius) || (wpsLines.Count > 0 && wpsLines[0].Offset < psp.InnerRadius)) { Rectangle t = GetBoundingArcRect( psp, startAngle, endAngle, outerOffset + height); psp.PathBounds = Rectangle.Union(psp.PathBounds, t); if (psp.PathBounds.Equals(psp.LastPathBounds) == false) { psp.LastPathBounds = psp.PathBounds; InvalidateRender(psp.PathBounds); } } g.TextRenderingHint = renderingHint; return (true); } return (false); } #region GetAdaptiveFlipLines private List GetAdaptiveFlipLines( PieSeriesPoint psp, double outerOffset, double innerOffset, string text, RectangleF[] cBounds, out bool clipped, SliceInnerLabelVisualStyle lstyle) { if (psp.WordPosLines == null) { psp.WordPosLines = GetAdaptiveFlipLinesEx(psp, outerOffset, innerOffset, text, cBounds, out clipped, lstyle); psp.InnerLabelClipped = clipped; } clipped = psp.InnerLabelClipped; return (psp.WordPosLines); } #region GetAdaptiveFlipLinesEx private List GetAdaptiveFlipLinesEx( PieSeriesPoint psp, double outerOffset, double innerOffset, string text, RectangleF[] cBounds, out bool clipped, SliceInnerLabelVisualStyle lstyle) { float height = cBounds[0].Height; int maxLineCount = (int)Math.Abs((outerOffset - innerOffset) / height); SliceLabelCropMode cropMode = psp.SliceLabelCropModeEx; if (cropMode == SliceLabelCropMode.NoAction) maxLineCount = 1000; if (lstyle.MaxLineCount >= 0) { if (maxLineCount > lstyle.MaxLineCount) maxLineCount = lstyle.MaxLineCount; } List wpsLines = null; clipped = false; if (maxLineCount > 0) { List wps = GetWordWidths(text, cBounds); double offset = outerOffset; bool middle = false; switch (lstyle.Alignment) { case SliceLabelAlignment.MiddleLeft: case SliceLabelAlignment.MiddleRight: case SliceLabelAlignment.MiddleCenter: offset = (outerOffset + innerOffset) / 2; height = -height; middle = true; break; case SliceLabelAlignment.InnerLeft: case SliceLabelAlignment.InnerRight: case SliceLabelAlignment.InnerCenter: clipped = false; offset = innerOffset; while (offset < outerOffset) { List ilines = FitAdaptiveLines(psp, offset, height, cBounds, maxLineCount, wps, out clipped, lstyle); if (ilines != null && ilines.Count > 0) return (ilines); wps = GetWordWidths(text, cBounds); offset += height; } return (wpsLines); default: height = -height; break; } for (int i = 0; i < maxLineCount; i++) { List lines = FitAdaptiveLines(psp, offset, Math.Abs(height), cBounds, i + 1, wps, out clipped, lstyle); if (lines != null) { if (clipped == false) return (lines); wpsLines = lines; wps = GetWordWidths(text, cBounds); if ((middle == false) || (i % 2 == 0)) offset += height; } else { break; } } } return (wpsLines); } #endregion #endregion #endregion #region NormalizeSliceAngles private void NormalizeSliceAngles( PieSeriesPoint psp, out double startAngle, out double endAngle) { startAngle = psp.StartAngleEx; endAngle = psp.StartAngleEx + psp.SweepAngleEx; if (startAngle > endAngle) { double temp = startAngle; startAngle = endAngle; endAngle = temp; } } #endregion #region GetArcWidth private double GetArcWidth( PieSeriesPoint psp, double offset, SliceInnerLabelVisualStyle lstyle) { double margin = lstyle.AngleMargin * 2; double sweep = Math.Abs(psp.SweepAngleEx); double width = offset * MathHelper.ToRadians(sweep - margin); return (width); } #endregion #region GetWordLength private double GetWordLength( WordPos wp, RectangleF[] cBounds, int lineWordCount) { double length = 0; if (lineWordCount > 0) length += cBounds[wp.Index - 1].Width; for (int i = 0; i < wp.Length; i++) length += cBounds[wp.Index + i].Width; return (length); } #endregion #region GetWordWidths private List GetWordWidths(string text, RectangleF[] cBounds) { List wps = new List(); int index = 0; while (index < text.Length) { int length = GetNextWord(text, ref index); if (length > 0) { WordPos wp = new WordPos(); wp.Index = index; wp.Length = length; wp.Text = text.Substring(index, length); wps.Add(wp); index += length; } } return (wps); } #region GetNextWord private int GetNextWord(string text, ref int index) { while (index < text.Length) { if (text[index] == '\n') return (1); if (Char.IsWhiteSpace(text[index]) == false) break; index++; } int eindex = index; while (eindex < text.Length) { if (Char.IsWhiteSpace(text[eindex]) == true) break; eindex++; } return (eindex - index); } #endregion #endregion #region GetAlignmentOffset private double GetAlignmentOffset( WordPosLine wpsLine, RectangleF[] tbounds, SliceInnerLabelVisualStyle lstyle) { switch (lstyle.Alignment) { case SliceLabelAlignment.OuterRight: case SliceLabelAlignment.MiddleRight: case SliceLabelAlignment.InnerRight: return (wpsLine.ArcWidth - wpsLine.LineWidth); case SliceLabelAlignment.OuterCenter: case SliceLabelAlignment.MiddleCenter: case SliceLabelAlignment.InnerCenter: return ((wpsLine.ArcWidth - wpsLine.LineWidth) / 2); default: return (0); } } #endregion #endregion #region RenderCustomLabel internal bool RenderCustomLabel(Graphics g, PieChart pieChart, PieSeriesPoint psp, double maxRadius, string text,SliceInnerLabelVisualStyle lstyle) { double angle = GetCustomLabelAngle(psp, lstyle); double offset = GetCustomLableOffset(psp, maxRadius, lstyle); Rectangle r = GetCustomLabelRect(psp, maxRadius, angle, offset, lstyle); angle += lstyle.CustomFontAngle; if (lstyle.AutoRotate != Tbool.True) angle -= (psp.CenterAngle - 90); angle = (angle + 360000) % 360; double fontAngle = 0; if (lstyle.AutoOrientLabel != Tbool.False) { if (angle < 180) fontAngle += 180; } r = GetSliceLabelBounds(g, pieChart, psp, text, r, lstyle); if (r.Width > 0 && r.Height > 0) { GraphicsState gState = g.Save(); TextRenderingHint renderingHint = g.TextRenderingHint; g.TextRenderingHint = TextRenderingHint.AntiAlias; using (StringFormat sf = new StringFormat()) { lstyle.GetStringFormatFlags(sf, angle < 180); g.TranslateTransform(r.X, r.Y); g.RotateTransform((float)(angle + fontAngle + 90)); r.X = -(int)(r.Width / 2); r.Y = -(int)(r.Height / 2); if (lstyle.Background.IsEmpty == false) { using (Brush br = lstyle.Background.GetBrush(r)) g.FillRectangle(br, r); } using (Brush br = new SolidBrush(lstyle.TextColor)) g.DrawString(text, lstyle.Font, br, r, sf); if (ChartControl.DesignerHosted == true) { using (Pen pen = new Pen(Color.Plum)) { pen.DashStyle = DashStyle.Dash; g.DrawRectangle(pen, r); } } } g.Restore(gState); return (true); } return (false); } #region GetCustomLabelRect private Rectangle GetCustomLabelRect(PieSeriesPoint psp, double maxRadius, double angle, double offset, SliceInnerLabelVisualStyle lstyle) { Rectangle r = new Rectangle(); r.Location = psp.GetRayEndPoint(angle, offset); r.Width = (int)GetCustomLabelWidth(psp, offset, lstyle); r.Height = (int)GetCustomLabelHeight(psp, maxRadius, offset, lstyle); return (r); } #endregion #region GetCustomLabelHeight private double GetCustomLabelHeight(PieSeriesPoint psp, double maxRadius, double offset, SliceInnerLabelVisualStyle lstyle) { double height = maxRadius - psp.InnerRadius; if (double.IsNaN(lstyle.CustomHeight) == false) { if (Math.Abs(lstyle.CustomHeight) < 1) return (lstyle.CustomHeight * height); return (lstyle.CustomHeight); } return (height * .5); } #endregion #region GetCustomLabelWidth private double GetCustomLabelWidth( PieSeriesPoint psp, double offset, SliceInnerLabelVisualStyle lstyle) { double width = GetArcWidth(psp, offset, lstyle); if (double.IsNaN(lstyle.CustomWidth) == false) { if (Math.Abs(lstyle.CustomWidth) < 1) return (lstyle.CustomWidth * width); return (lstyle.CustomWidth); } return (width * .8); } #endregion #region GetCustomLableOffset private double GetCustomLableOffset( PieSeriesPoint psp, double maxRadius, SliceInnerLabelVisualStyle lstyle) { double offset = lstyle.CustomRadiusOffset; if (double.IsNaN(offset) == false) { if (Math.Abs(offset) < 1) offset = (maxRadius - psp.InnerRadius) * offset; return (psp.InnerRadius + offset); } return (psp.InnerRadius + (maxRadius - psp.InnerRadius) * .7); } #endregion #region GetCustomLabelAngle private double GetCustomLabelAngle(PieSeriesPoint psp, SliceInnerLabelVisualStyle lstyle) { double angle = (psp.StartAngleEx + psp.SweepAngleEx / 2) % 360; if (double.IsNaN(lstyle.CustomAngleOffset) == false) { double offset = lstyle.CustomAngleOffset; if (Math.Abs(offset) < 1) angle += (psp.SweepAngleEx * offset); else angle += offset; } return (angle); } #endregion #endregion #region RenderParallelLabel internal bool RenderParallelLabel(Graphics g, PieChart pieChart, PieSeriesPoint psp, double maxRadius, string text, SliceInnerLabelVisualStyle lstyle) { double centerAngle = psp.CenterAngle % 360; double innerOffset = GetLableInnerOffset(psp, maxRadius, lstyle); double outerOffset = GetLableOuterOffset(psp, maxRadius, lstyle); float fontAngle = 0; if (lstyle.AutoOrientLabel != Tbool.False) { if (centerAngle >= 90 && centerAngle < 270) fontAngle += 180; } Point lpt = psp.GetRayEndPoint( centerAngle, (innerOffset + outerOffset) / 2); Rectangle r = new Rectangle(); r.Location = lpt; r.Width = (int)(outerOffset - innerOffset); r.Height = (int)GetArcWidth(psp, innerOffset, lstyle); r = GetSliceLabelBounds(g, pieChart, psp, text, r, lstyle); if (r.Width > 0 && r.Height > 0) { GraphicsState gState = g.Save(); TextRenderingHint renderingHint = g.TextRenderingHint; g.TextRenderingHint = TextRenderingHint.AntiAlias; using (StringFormat sf = new StringFormat()) { GetStringFormatFlags(lstyle, sf, fontAngle != 0); r.X = -(int)(r.Width / 2); r.Y = -(int)(r.Height / 2); g.TranslateTransform(lpt.X, lpt.Y); g.RotateTransform((float)centerAngle + fontAngle); if (lstyle.Background.IsEmpty == false) { using (Brush br = lstyle.Background.GetBrush(r)) g.FillRectangle(br, r); } using (Brush br = new SolidBrush(lstyle.TextColor)) g.DrawString(text, lstyle.Font, br, r, sf); } g.Restore(gState); return (true); } return (false); } #region GetStringFormatFlags private void GetStringFormatFlags( SliceInnerLabelVisualStyle lstyle, StringFormat sf, bool flip) { if (lstyle.AllowWrap == Tbool.False || lstyle.MaxLineCount == 1) sf.FormatFlags |= StringFormatFlags.NoWrap; sf.Trimming = StringTrimming.EllipsisCharacter; switch (lstyle.Alignment) { case SliceLabelAlignment.OuterLeft: sf.Alignment = (flip ? StringAlignment.Near : StringAlignment.Far); // Outer/inner sf.LineAlignment = (flip ? StringAlignment.Far : StringAlignment.Near); // Left/right break; case SliceLabelAlignment.InnerLeft: sf.Alignment = (flip ? StringAlignment.Far : StringAlignment.Near); sf.LineAlignment = (flip ? StringAlignment.Far : StringAlignment.Near); break; case SliceLabelAlignment.MiddleLeft: sf.Alignment = StringAlignment.Center; sf.LineAlignment = (flip ? StringAlignment.Far : StringAlignment.Near); break; case SliceLabelAlignment.OuterRight: sf.Alignment = (flip ? StringAlignment.Near : StringAlignment.Far); sf.LineAlignment = (flip ? StringAlignment.Near : StringAlignment.Far); break; case SliceLabelAlignment.InnerRight: sf.Alignment = (flip ? StringAlignment.Far : StringAlignment.Near); sf.LineAlignment = (flip ? StringAlignment.Near : StringAlignment.Far); break; case SliceLabelAlignment.MiddleRight: sf.Alignment = StringAlignment.Center; sf.LineAlignment = (flip ? StringAlignment.Near : StringAlignment.Far); break; case SliceLabelAlignment.OuterCenter: sf.Alignment = (flip ? StringAlignment.Near : StringAlignment.Far); sf.LineAlignment = StringAlignment.Center; break; case SliceLabelAlignment.InnerCenter: sf.Alignment = (flip ? StringAlignment.Far : StringAlignment.Near); sf.LineAlignment = StringAlignment.Center; break; case SliceLabelAlignment.NotSet: case SliceLabelAlignment.MiddleCenter: sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; break; } } #endregion #endregion #region RenderPerpendicularLabel internal bool RenderPerpendicularLabel(Graphics g, PieChart pieChart, PieSeriesPoint psp, double maxRadius, string text, SliceInnerLabelVisualStyle lstyle) { double innerOffset = GetLableInnerOffset(psp, maxRadius, lstyle); double outerOffset = GetLableOuterOffset(psp, maxRadius, lstyle); if (outerOffset - innerOffset > 0) { double centerAngle = psp.CenterAngle % 360; Point lpt = psp.GetRayEndPoint(centerAngle, outerOffset); float fontAngle = 0; if (lstyle.AutoOrientLabel != Tbool.False) { if (centerAngle < 180) fontAngle += 180; } Rectangle r = GetInscribedRectP(psp, maxRadius, centerAngle, outerOffset, innerOffset, lpt, lstyle); r = GetSliceLabelBounds(g, pieChart, psp, text, r, lstyle); if (r.Width > 0 && r.Height > 0) { GraphicsState gState = g.Save(); TextRenderingHint renderingHint = g.TextRenderingHint; g.TextRenderingHint = TextRenderingHint.AntiAlias; g.TranslateTransform(lpt.X, lpt.Y); g.RotateTransform((float)((centerAngle + fontAngle + 90) % 360)); if (lstyle.Background.IsEmpty == false) { using (Brush br = lstyle.Background.GetBrush(r)) g.FillRectangle(br, r); } using (StringFormat sf = new StringFormat()) { lstyle.GetStringFormatFlags(sf, (centerAngle < 180)); using (Brush br = new SolidBrush(lstyle.TextColor)) g.DrawString(text, lstyle.Font, br, r, sf); } g.Restore(gState); return (true); } } return (false); } #region GetInscribedRectP private Rectangle GetInscribedRectP(PieSeriesPoint psp, double maxRadius, double centerAngle, double outerOffset, double innerOffset, Point lpt, SliceInnerLabelVisualStyle lstyle) { if (psp.InscribedRectValid == false) { double sweepAngle = psp.SweepAngleEx / 2 - lstyle.AngleMargin; double b = (innerOffset * Math.Tan(MathHelper.ToRadians(sweepAngle))); double h = (outerOffset - innerOffset); double maxb = Math.Sqrt(maxRadius * maxRadius - outerOffset * outerOffset); if (b > maxb) b = maxb; h = (h / lstyle.Font.Height) * lstyle.Font.Height; Rectangle r = new Rectangle(); r.Location = new Point((int)-b, (centerAngle < 180) ? (int)-h : 0); r.Width = (int)(b * 2); r.Height = (int)h; psp.InscribedRect = r; } return (psp.InscribedRect); } #endregion #endregion #region RenderHorizontalLabel internal bool RenderHorizontalLabel(Graphics g, PieChart pieChart, PieSeriesPoint psp, double maxRadius, string text, SliceInnerLabelVisualStyle lstyle) { double innerOffset = GetLableInnerOffset(psp, maxRadius, lstyle); Rectangle r = GetInscribedRectH(g, psp, psp.CenterAngle % 360, maxRadius, innerOffset, lstyle); r = GetSliceLabelBounds(g, pieChart, psp, text, r, lstyle); if (r.Width > 0 && r.Height > 0) { if (lstyle.Background.IsDisplayable == true) { using (Brush br = lstyle.Background.GetBrush(r)) g.FillRectangle(br, r); } using (StringFormat sf = new StringFormat()) { lstyle.GetStringFormatFlags(sf, false); using (Brush br = new SolidBrush(lstyle.TextColor)) g.DrawString(text, lstyle.Font, br, r, sf); } if (ChartControl.DesignerHosted == true) { using (Pen pen = new Pen(Color.Plum)) { pen.DashStyle = DashStyle.Dash; g.DrawRectangle(pen, r); } } return (true); } return (false); } #region GetInscribedRectH private Rectangle GetInscribedRectH(Graphics g, PieSeriesPoint psp, double centerAngle, double outerOffset, double innerOffset, SliceInnerLabelVisualStyle lstyle) { if (psp.InscribedRectValid == false) { double startAngle = psp.StartAngleEx % 360; double endAngle = startAngle + psp.SweepAngleEx; if (psp.SweepAngleEx > 180) { startAngle = centerAngle - 88; endAngle = centerAngle + 88; } Rectangle r; if (centerAngle > 270) { r = GetInscribedRectQuad4(g, psp, startAngle, endAngle, centerAngle, outerOffset, innerOffset, lstyle); } else if (centerAngle > 180) { r = GetInscribedRectQuad3(g, psp, startAngle, endAngle, centerAngle, outerOffset, innerOffset, lstyle); } else if (centerAngle > 90) { r = GetInscribedRectQuad2(g, psp, startAngle, endAngle, centerAngle, outerOffset, innerOffset, lstyle); } else { r = GetInscribedRectQuad1(g, psp, startAngle, endAngle, centerAngle, outerOffset, innerOffset, lstyle); } r.Inflate(-2, -2); psp.InscribedRect = r; } return (psp.InscribedRect); } #region GetInscribedRectQuad1 private Rectangle GetInscribedRectQuad1(Graphics g, PieSeriesPoint psp, double startAngle, double endAngle, double centerAngle, double outerOffset, double innerOffset, SliceInnerLabelVisualStyle lstyle) { Point ptCenter = psp.SliceCenter; centerAngle = NormalizeCenterAngleQuad1(centerAngle, startAngle, endAngle); Point ptcInner = psp.GetRayEndPoint(centerAngle, innerOffset); Point ptcOuter = psp.GetRayEndPoint(centerAngle, outerOffset); Point ptLeftRay = psp.GetRayEndPoint(startAngle, outerOffset); Point ptRightRay = psp.GetRayEndPoint(endAngle, outerOffset); Point ptPie1, ptPie2; int count = FindCircleIntersect(ptCenter, outerOffset, ptcOuter, new Point(ptcOuter.X + 1, ptcOuter.Y), out ptPie1, out ptPie2, true); if (count != 2) return (Rectangle.Empty); Point ptLeftLower = new Point(ptPie1.X, ptcOuter.Y); Point pt1; if (FindLineIntersect(ptcOuter, ptLeftLower, ptCenter, ptRightRay, out pt1) == true) { if ((uint)(ptcOuter.X - pt1.X) < ptcOuter.X - ptLeftLower.X) ptLeftLower.X = pt1.X; } Point ptPie3, ptPie4; count = FindCircleIntersect(ptCenter, outerOffset, ptcOuter, new Point(ptcOuter.X, ptcOuter.Y + 10), out ptPie3, out ptPie4, false); if (count != 2) return (Rectangle.Empty); Point ptRightUpper = new Point(ptcOuter.X, ptPie3.Y); if (FindLineIntersect(ptcOuter, new Point(ptcOuter.X, ptcOuter.Y + 10), ptCenter, ptLeftRay, out pt1) == true) { if ((uint)(ptcOuter.Y - pt1.Y) < (ptcOuter.Y - ptRightUpper.Y)) ptRightUpper.Y = pt1.Y; } if (centerAngle < 40) { Point ray = psp.GetRayEndPoint(0, innerOffset); if (Math.Abs(ray.X - ptcOuter.X) < Math.Abs(ptLeftLower.X - ptcOuter.X)) ptLeftLower.X = ray.X; } else if (centerAngle > 50) { Point ray = psp.GetRayEndPoint(90, innerOffset); if (Math.Abs(ray.Y - ptcOuter.Y) < Math.Abs(ptRightUpper.Y - ptcOuter.Y)) ptRightUpper.Y = ray.Y; } else { if (ptLeftLower.X < ptcInner.X) { if (FindLineIntersect(ptCenter, ptLeftRay, ptcInner, new Point(ptcInner.X, ptcInner.Y + 10), out pt1) == true) { if (Math.Abs(pt1.X - ptcOuter.X) < Math.Abs(ptLeftLower.X - ptcOuter.X)) ptLeftLower.X = pt1.X; } } if (ptRightUpper.Y <= ptcInner.Y) { if (FindLineIntersect(ptCenter, ptRightRay, ptcInner, new Point(ptcInner.X + 10, ptcInner.Y), out pt1) == true) { if (Math.Abs(pt1.Y - ptcOuter.Y) < Math.Abs(ptRightUpper.Y - ptcOuter.Y)) ptRightUpper.Y = pt1.Y; } } } if (psp.SweepAngleEx < 180) { if (FindLineIntersect(ptCenter, ptRightRay, ptRightUpper, new Point(ptRightUpper.X + 10, ptRightUpper.Y), out pt1) == true) { if (Math.Abs(pt1.X - ptcOuter.X) < Math.Abs(ptLeftLower.X - ptcOuter.X)) ptLeftLower.X = pt1.X; } if (FindLineIntersect(ptCenter, ptLeftRay, ptLeftLower, new Point(ptLeftLower.X, ptLeftLower.Y + 10), out pt1) == true) { if (Math.Abs(pt1.Y - ptcOuter.Y) < Math.Abs(ptRightUpper.Y - ptcOuter.Y)) ptRightUpper.Y = pt1.Y; } } Rectangle r = new Rectangle(); r.Location = new Point(ptLeftLower.X, ptRightUpper.Y); r.Width = ptcOuter.X - ptLeftLower.X; r.Height = ptcOuter.Y - ptRightUpper.Y; return (r); } #region NormalizeCenterAngleQuad1 private double NormalizeCenterAngleQuad1( double centerAngle, double startAngle, double endAngle) { if (centerAngle > 70) { centerAngle -= (centerAngle - 70) / 2; if (centerAngle - 2 < 0) centerAngle += 360; if (centerAngle - 2 <= startAngle) centerAngle = (startAngle + 2) % 360; } else if (centerAngle < 20) { centerAngle += (20 - centerAngle) / 2; if (centerAngle + 2 >= endAngle) centerAngle = endAngle - 2; } return (centerAngle); } #endregion #endregion #region GetInscribedRectQuad2 private Rectangle GetInscribedRectQuad2(Graphics g, PieSeriesPoint psp, double startAngle, double endAngle, double centerAngle, double outerOffset, double innerOffset, SliceInnerLabelVisualStyle lstyle) { Point ptCenter = psp.SliceCenter; centerAngle = NormalizeCenterAngleQuad2(centerAngle, startAngle, endAngle); Point ptcInner = psp.GetRayEndPoint(centerAngle, innerOffset); Point ptcOuter = psp.GetRayEndPoint(centerAngle, outerOffset); Point ptLeftRay = psp.GetRayEndPoint(startAngle, outerOffset); Point ptRightRay = psp.GetRayEndPoint(endAngle, outerOffset); Point ptPie1, ptPie2; int count = FindCircleIntersect(ptCenter, outerOffset, ptcOuter, new Point(ptcOuter.X + 1, ptcOuter.Y), out ptPie1, out ptPie2, true); if (count != 2) return (Rectangle.Empty); Point ptRightLower = new Point(ptPie2.X, ptcOuter.Y); Point pt1; if (FindLineIntersect(ptcOuter, ptRightLower, ptCenter, ptLeftRay, out pt1) == true) { if ((uint)(pt1.X - ptcOuter.X) < (ptRightLower.X - ptcOuter.X)) ptRightLower.X = pt1.X; } Point ptPie3, ptPie4; count = FindCircleIntersect(ptCenter, outerOffset, ptcOuter, new Point(ptcOuter.X, ptcOuter.Y + 10), out ptPie3, out ptPie4, false); if (count != 2) return (Rectangle.Empty); Point ptLeftUpper = new Point(ptcOuter.X, ptPie3.Y); if (FindLineIntersect(ptcOuter, new Point(ptcOuter.X, ptcOuter.Y + 10), ptCenter, ptRightRay, out pt1) == true) { if ((uint)(ptcOuter.Y - pt1.Y) < (ptcOuter.Y - ptLeftUpper.Y)) ptLeftUpper.Y = pt1.Y; } if (centerAngle < 130) { Point ray = psp.GetRayEndPoint(90, innerOffset); if (Math.Abs(ray.Y - ptcOuter.Y) < Math.Abs(ptLeftUpper.Y - ptcOuter.Y)) ptLeftUpper.Y = ray.Y; } else if (centerAngle > 140) { Point ray = psp.GetRayEndPoint(180, innerOffset); if (Math.Abs(ray.X - ptcOuter.X) < Math.Abs(ptRightLower.X - ptcOuter.X)) ptRightLower.X = ray.X; } else { if (ptRightLower.X > ptcInner.X) { if (FindLineIntersect(ptCenter, ptLeftRay, ptcInner, new Point(ptcInner.X, ptcInner.Y + 10), out pt1) == true) { if (Math.Abs(pt1.X - ptcOuter.X) < Math.Abs(ptRightLower.X - ptcOuter.X)) ptRightLower.X = pt1.X; } } if (ptLeftUpper.Y <= ptcInner.Y) { if (FindLineIntersect(ptCenter, ptRightRay, ptcInner, new Point(ptcInner.X + 10, ptcInner.Y), out pt1) == true) { if (Math.Abs(pt1.Y - ptcOuter.Y) < Math.Abs(ptLeftUpper.Y - ptcOuter.Y)) ptLeftUpper.Y = pt1.Y; } } } if (psp.SweepAngleEx < 180) { if (FindLineIntersect(ptCenter, ptRightRay, ptRightLower, new Point(ptRightLower.X, ptRightLower.Y + 10), out pt1) == true) { if (Math.Abs(pt1.Y - ptcOuter.Y) < Math.Abs(ptLeftUpper.Y - ptcOuter.Y)) ptLeftUpper.Y = pt1.Y; } if (FindLineIntersect(ptCenter, ptLeftRay, ptLeftUpper, new Point(ptLeftUpper.X + 10, ptLeftUpper.Y), out pt1) == true) { if (Math.Abs(pt1.X - ptcOuter.X) < Math.Abs(ptRightLower.X - ptcOuter.X)) ptRightLower.X = pt1.X; } } Rectangle r = new Rectangle(); r.Location = ptLeftUpper; r.Width = ptRightLower.X - ptcOuter.X; r.Height = ptcOuter.Y - ptLeftUpper.Y; return (r); } #region NormalizeCenterAngleQuad2 private double NormalizeCenterAngleQuad2( double centerAngle, double startAngle, double endAngle) { if (centerAngle > 160) { centerAngle -= (centerAngle - 160) / 2; if (centerAngle - 2 <= startAngle) centerAngle = startAngle + 2; } else if (centerAngle < 110) { centerAngle += (110 - centerAngle) / 2; if (centerAngle + 2 >= endAngle) centerAngle = endAngle - 2; } return (centerAngle); } #endregion #endregion #region GetInscribedRectQuad3 private Rectangle GetInscribedRectQuad3(Graphics g, PieSeriesPoint psp, double startAngle, double endAngle, double centerAngle, double outerOffset, double innerOffset, SliceInnerLabelVisualStyle lstyle) { Point ptCenter = psp.SliceCenter; centerAngle = NormalizeCenterAngleQuad3(centerAngle, startAngle, endAngle); Point ptcInner = psp.GetRayEndPoint(centerAngle, innerOffset); Point ptcOuter = psp.GetRayEndPoint(centerAngle, outerOffset); Point ptLeftRay = psp.GetRayEndPoint(startAngle, outerOffset); Point ptRightRay = psp.GetRayEndPoint(endAngle, outerOffset); Point ptPie1, ptPie2; int count = FindCircleIntersect(ptCenter, outerOffset, ptcOuter, new Point(ptcOuter.X + 1, ptcOuter.Y), out ptPie1, out ptPie2, true); if (count != 2) return (Rectangle.Empty); Point ptRightUpper = new Point(ptPie2.X, ptcOuter.Y); Point pt1; if (FindLineIntersect(ptcOuter, ptRightUpper, ptCenter, ptRightRay, out pt1) == true) { if (pt1.X - ptcOuter.X > 0) { if ((uint)(pt1.X - ptcOuter.X) < (ptRightUpper.X - ptcOuter.X)) ptRightUpper.X = pt1.X; } } Point ptPie3, ptPie4; count = FindCircleIntersect(ptCenter, outerOffset, ptcOuter, new Point(ptcOuter.X, ptcOuter.Y + 10), out ptPie3, out ptPie4, false); if (count != 2) return (Rectangle.Empty); Point ptLeftLower = new Point(ptcOuter.X, ptPie4.Y); if (FindLineIntersect(ptcOuter, new Point(ptcOuter.X, ptcOuter.Y + 10), ptCenter, ptLeftRay, out pt1) == true) { if (pt1.Y - ptcOuter.Y > 0) { if ((uint)(pt1.Y - ptcOuter.Y) < (ptLeftLower.Y - ptcOuter.Y)) ptLeftLower.Y = pt1.Y; } } if (centerAngle < 220) { Point ray = psp.GetRayEndPoint(180, innerOffset); if (Math.Abs(ray.X - ptcOuter.X) < Math.Abs(ptRightUpper.X - ptcOuter.X)) ptRightUpper.X = ray.X; } else if (centerAngle > 230) { Point ray = psp.GetRayEndPoint(270, innerOffset); if (Math.Abs(ray.Y - ptcOuter.Y) < Math.Abs(ptLeftLower.Y - ptcOuter.Y)) ptLeftLower.Y = ray.Y; } else { if (ptLeftLower.Y >= ptcInner.Y) { if (FindLineIntersect(ptCenter, ptLeftRay, ptcInner, new Point(ptcInner.X + 10, ptcInner.Y), out pt1) == true) { if (Math.Abs(pt1.Y - ptcOuter.Y) < Math.Abs(ptLeftLower.Y - ptcOuter.Y)) ptLeftLower.Y = pt1.Y; } } if (ptRightUpper.X > ptcInner.X) { if (FindLineIntersect(ptCenter, ptRightRay, ptcInner, new Point(ptcInner.X, ptcInner.Y + 10), out pt1) == true) { if (Math.Abs(pt1.X - ptcOuter.X) < Math.Abs(ptRightUpper.X - ptcOuter.X)) ptRightUpper.X = pt1.X; } } } if (psp.SweepAngleEx < 180) { if (FindLineIntersect(ptCenter, ptRightRay, ptLeftLower, new Point(ptLeftLower.X + 10, ptLeftLower.Y), out pt1) == true) { if (Math.Abs(pt1.X - ptcOuter.X) < Math.Abs(ptRightUpper.X - ptcOuter.X)) ptRightUpper.X = pt1.X; } if (FindLineIntersect(ptCenter, ptLeftRay, ptRightUpper, new Point(ptRightUpper.X, ptRightUpper.Y + 10), out pt1) == true) { if (Math.Abs(pt1.Y - ptcOuter.Y) < Math.Abs(ptLeftLower.Y - ptcOuter.Y)) ptLeftLower.Y = pt1.Y; } } Rectangle r = new Rectangle(); r.Location = ptcOuter; r.Width = ptRightUpper.X - ptcOuter.X; r.Height = ptLeftLower.Y - ptcOuter.Y; return (r); } #region NormalizeCenterAngleQuad3 private double NormalizeCenterAngleQuad3( double centerAngle, double startAngle, double endAngle) { if (centerAngle > 250) { centerAngle -= (centerAngle - 250) / 2; if (centerAngle - startAngle < 2) centerAngle = startAngle + 2; } else if (centerAngle < 200) { centerAngle += (200 - centerAngle) / 2; if (centerAngle + 2 >= endAngle) centerAngle = endAngle - 2; } return (centerAngle); } #endregion #endregion #region GetInscribedRectQuad4 private Rectangle GetInscribedRectQuad4(Graphics g, PieSeriesPoint psp, double startAngle, double endAngle, double centerAngle, double outerOffset, double innerOffset, SliceInnerLabelVisualStyle lstyle) { Point ptCenter = psp.SliceCenter; centerAngle = NormalizeCenterAngleQuad4(centerAngle, startAngle, endAngle); Point ptcInner = psp.GetRayEndPoint(centerAngle, innerOffset); Point ptcOuter = psp.GetRayEndPoint(centerAngle, outerOffset); Point ptLeftRay = psp.GetRayEndPoint(startAngle, outerOffset); Point ptRightRay = psp.GetRayEndPoint(endAngle, outerOffset); Point ptPie1, ptPie2; int count = FindCircleIntersect(ptCenter, outerOffset, ptcOuter, new Point(ptcOuter.X + 1, ptcOuter.Y), out ptPie1, out ptPie2, true); if (count != 2) return (Rectangle.Empty); Point ptLeftUpper = new Point((ptcOuter.Y < ptCenter.Y) ? ptPie1.X : ptPie2.X, ptcOuter.Y); Point pt1; if (FindLineIntersect(ptcOuter, ptLeftUpper, ptCenter, ptLeftRay, out pt1) == true) { if (ptcOuter.X - pt1.X > 0) { if ((uint)(ptcOuter.X - pt1.X) < ptcOuter.X - ptLeftUpper.X) ptLeftUpper.X = pt1.X; } } Point ptPie3, ptPie4; count = FindCircleIntersect(ptCenter, outerOffset, ptcOuter, new Point(ptcOuter.X, ptcOuter.Y + 10), out ptPie3, out ptPie4, false); if (count != 2) return (Rectangle.Empty); Point ptRightLower = new Point(ptcOuter.X, (ptcOuter.X < ptCenter.X) ? ptPie3.Y : ptPie4.Y); if (FindLineIntersect(ptcOuter, new Point(ptcOuter.X, ptcOuter.Y + 10), ptCenter, ptRightRay, out pt1) == true) { if (pt1.Y - ptcOuter.Y > 0) { if ((uint)(pt1.Y - ptcOuter.Y) < (ptRightLower.Y - ptcOuter.Y)) ptRightLower.Y = pt1.Y; } } if (centerAngle < 310) { Point ray = psp.GetRayEndPoint(270, innerOffset); if (Math.Abs(ray.Y - ptcOuter.Y) < Math.Abs(ptRightLower.Y - ptcOuter.Y)) ptRightLower.Y = ray.Y; } else if (centerAngle > 320) { Point ray = psp.GetRayEndPoint(0, innerOffset); if (Math.Abs(ray.X - ptcOuter.X) < Math.Abs(ptLeftUpper.X - ptcOuter.X)) ptLeftUpper.X = ray.X; } else { if (ptLeftUpper.X <= ptcInner.X) { if (FindLineIntersect(ptCenter, ptLeftRay, ptcInner, new Point(ptcInner.X, ptcInner.Y + 10), out pt1) == true) { if (Math.Abs(pt1.X - ptcOuter.X) < Math.Abs(ptLeftUpper.X - ptcOuter.X)) ptLeftUpper.X = pt1.X; } } if (ptRightLower.Y >= ptcInner.Y) { if (FindLineIntersect(ptCenter, ptRightRay, ptcInner, new Point(ptcInner.X + 10, ptcInner.Y), out pt1) == true) { if (Math.Abs(pt1.Y - ptcOuter.Y) < Math.Abs(ptRightLower.Y - ptcOuter.Y)) ptRightLower.Y = pt1.Y; } } } if (psp.SweepAngleEx < 180) { if (FindLineIntersect(ptCenter, ptRightRay, ptLeftUpper, new Point(ptLeftUpper.X, ptLeftUpper.Y + 10), out pt1) == true) { if (Math.Abs(pt1.Y - ptcOuter.Y) < Math.Abs(ptRightLower.Y - ptcOuter.Y)) ptRightLower.Y = pt1.Y; } if (FindLineIntersect(ptCenter, ptLeftRay, ptRightLower, new Point(ptRightLower.X + 10, ptRightLower.Y), out pt1) == true) { if (Math.Abs(pt1.X - ptcOuter.X) < Math.Abs(ptLeftUpper.X - ptcOuter.X)) ptLeftUpper.X = pt1.X; } } Rectangle r = new Rectangle(); r.Location = ptLeftUpper; r.Width = ptRightLower.X - ptLeftUpper.X; r.Height = ptRightLower.Y - ptLeftUpper.Y; return (r); } #region NormalizeCenterAngleQuad4 private double NormalizeCenterAngleQuad4( double centerAngle, double startAngle, double endAngle) { if (centerAngle > 340) { centerAngle -= (centerAngle - 340) / 2; if (centerAngle - 2 <= startAngle) centerAngle = startAngle + 2; } else if (centerAngle < 290) { centerAngle += (290 - centerAngle) / 2; if (centerAngle + 2 >= endAngle) centerAngle = endAngle - 2; } return (centerAngle); } #endregion #endregion #region FindLineIntersect private bool FindLineIntersect( Point ps1, Point pe1, Point ps2, Point pe2, out Point ptOut) { float A1 = pe1.Y - ps1.Y; float B1 = ps1.X - pe1.X; float C1 = A1 * ps1.X + B1 * ps1.Y; float A2 = pe2.Y - ps2.Y; float B2 = ps2.X - pe2.X; float C2 = A2 * ps2.X + B2 * ps2.Y; float delta = A1 * B2 - A2 * B1; if (delta == 0) { ptOut = Point.Empty; return (false); } ptOut = new Point( (int)((B2 * C1 - B1 * C2) / delta), (int)((A1 * C2 - A2 * C1) / delta)); return (true); } #endregion #region FindCircleIntersect private int FindCircleIntersect(Point ptc, double radius, Point pt1, Point pt2, out Point ptOut1, out Point ptOut2, bool normx) { int dx = pt2.X - pt1.X; int dy = pt2.Y - pt1.Y; int a = dx * dx + dy * dy; int b = 2 * (dx * (pt1.X - ptc.X) + dy * (pt1.Y - ptc.Y)); double c = (pt1.X - ptc.X) * (pt1.X - ptc.X) + (pt1.Y - ptc.Y) * (pt1.Y - ptc.Y) - radius * radius; double det = b * b - 4 * a * c; if ((a <= 0.0000001) || (det < 0)) { ptOut1 = Point.Empty; ptOut2 = Point.Empty; return (0); } else if (det == 0) { double t1 = -b / (2 * a); ptOut1 = new Point((int)(pt1.X + t1 * dx), (int)(pt1.Y + t1 * dy)); ptOut2 = Point.Empty; return (1); } else { double t2 = (float)((-b + Math.Sqrt(det)) / (2 * a)); double t3 = (float)((-b - Math.Sqrt(det)) / (2 * a)); ptOut1 = new Point((int)(pt1.X + t2 * dx), (int)(pt1.Y + t2 * dy)); ptOut2 = new Point((int)(pt1.X + t3 * dx), (int)(pt1.Y + t3 * dy)); if ((normx == true && ptOut1.X > ptOut2.X) || (normx == false && ptOut1.Y > ptOut2.Y)) { Point ptTemp = ptOut1; ptOut1 = ptOut2; ptOut2 = ptTemp; } return (2); } } #endregion #endregion #endregion #region GetLableInnerOffset internal double GetLableInnerOffset (PieSeriesPoint psp, double maxRadius, SliceInnerLabelVisualStyle lstyle) { double offset; if (Math.Abs(lstyle.InnerRadiusOffset) >= 1) offset = lstyle.InnerRadiusOffset; else offset = (maxRadius - psp.InnerRadius) * lstyle.InnerRadiusOffset; offset += psp.InnerRadius; if (lstyle.MaxLineCount >= 0) { double outerOffset = psp.InnerRadius; if (lstyle.OuterRadiusOffset > -1 && lstyle.OuterRadiusOffset < 1) outerOffset += (lstyle.OuterRadiusOffset * maxRadius); else outerOffset += lstyle.OuterRadiusOffset; double fh = lstyle.Font.GetHeight() * lstyle.MaxLineCount; fh = outerOffset - fh; if (fh > offset) offset = fh; } return (offset); } #endregion #region GetLableOuterOffset internal double GetLableOuterOffset( PieSeriesPoint psp, double maxRadius, SliceInnerLabelVisualStyle lstyle) { double offset; if (Math.Abs(lstyle.OuterRadiusOffset) >= 1) offset = lstyle.OuterRadiusOffset; else offset = (maxRadius * lstyle.OuterRadiusOffset); return (maxRadius + offset); } #endregion #endregion #endregion #endregion #region GetSliceLabel internal string GetSliceLabel( PieChart pieChart, PieSeriesPoint psp, bool inner, string label) { if (string.IsNullOrEmpty(label) == false) { string text = (string)label.Clone(); if (ChartControl.DoGetSliceLabelEvent(pieChart, this, psp, inner, ref text) == false) text = psp.GetLabelText(text); return (text); } return (null); } #endregion #region GetSliceLabelBounds private Rectangle GetSliceLabelBounds(Graphics g, PieChart pieChart, PieSeriesPoint psp, string text, Rectangle r, SliceInnerLabelVisualStyle lstyle) { if (r.Width > 0 && r.Height > 0) { SizeF sz = g.MeasureString(text, lstyle.Font, r.Width); Size size = sz.ToSize(); psp.InnerLabelClipped = (size.Width > r.Width || size.Height > r.Height); if (psp.InnerLabelClipped == true) { SliceLabelCropMode cropMode = psp.SliceLabelCropModeEx; if (cropMode == SliceLabelCropMode.Hide) return (Rectangle.Empty); else if (cropMode == SliceLabelCropMode.Clip) { int height = GetTruncatedHeight(r.Height, lstyle); if (height > 0) r.Height = height; } else { r.Height = size.Height; } } } return (r); } #region GetTruncatedHeight private int GetTruncatedHeight(int height, SliceInnerLabelVisualStyle lstyle) { int n = (int)(height / lstyle.Font.Height); if (lstyle.MaxLineCount > 0) { if (n > lstyle.MaxLineCount) n = lstyle.MaxLineCount; } return (lstyle.Font.Height * n); } #endregion #endregion #endregion #region IsHighLightedPsp internal bool IsHighLightedPsp(PieChart pieChart, PieSeriesPoint psp) { if (pieChart.Legend.TrackingMode == LegendTrackingMode.ChartAndLegend) { if (LegendItem != null && LegendItem.IsLegendHitItem == true) return (true); if (psp.LegendItem != null && psp.LegendItem.IsLegendHitItem == true) return (true); } PieSeriesPoint hitPsp = pieChart.HitPsp; if (hitPsp != null) { PieSelectionMode psm = GetPieSelectionMode(pieChart); switch (psm) { case PieSelectionMode.Pie: return (true); case PieSelectionMode.Ring: return (psp.PieRing == hitPsp.PieRing); case PieSelectionMode.Series: return (psp.ChartSeries == hitPsp.ChartSeries); case PieSelectionMode.Slice: if (ShowAllRingsEx(pieChart) == true) return (psp.RootPsp == hitPsp.RootPsp); else return (psp == hitPsp); case PieSelectionMode.Point: return (psp == hitPsp); } } return (false); } #endregion #endregion #region Render internal override void Render(ChartRenderInfo renderInfo) { if (Displayed == true) { Rectangle bounds = BoundsRelative; if (renderInfo.ClipRectangle.IntersectsWith(bounds)) RenderOverride(renderInfo); } } #endregion #region UpdatePalette internal bool UpdatePalette(PieChart pieChart) { if (PaletteGroup == PaletteGroup.NotSet) return (false); Color[] palette = (PaletteGroup == PaletteGroup.Custom) ? (CustomPalette.Length > 0 ? CustomPalette : pieChart.CustomPalette) : BaseChart.PaletteGroups[(int)PaletteGroup]; if (palette == null || palette.Length == 0) palette = BaseChart.PaletteColor1; int colorIndex = 0; DefaultPaletteColor = pieChart.GetPaletteColor(colorIndex++, palette, ReversePaletteColors); if (PieRings != null) { foreach (PieRing pieRing in PieRings) { foreach (PieSeriesPoint psp in pieRing.Psps) { psp.DefaultPaletteColor = pieChart.GetPaletteColor(colorIndex++, palette, ReversePaletteColors); } } } return (true); } #endregion #region AddSeriesPoint internal override void AddSeriesPoint(object valueX, object[] valuesY, object dataItem) { PieSeriesPoint sp = new PieSeriesPoint(valueX, valuesY); sp.DataItem = dataItem; SeriesPoints.Add(sp); } #endregion #region ClearSeriesPoints internal override void ClearSeriesPoints() { SeriesPoints.Clear(); } #endregion #region GetActiveSeriesPointCollection /// /// Gets the current 'active' SeriesPointCollection (useful when /// 'ShowInnerRings' is set to false). /// /// public PieSeriesPointCollection GetActiveSeriesPointCollection() { PieChart pieChart = Parent as PieChart; if (pieChart != null) { return ((ShowAllRingsEx(pieChart) == true) ? SeriesPoints : (ActiveSeriesPointCollection ?? SeriesPoints)); } return (null); } #endregion #region SetActiveSeriesPointCollection /// /// Sets the current 'active' SeriesPointCollection (useful when /// 'ShowInnerRings' is set to false). /// /// public void SetActiveSeriesPointCollection(PieSeriesPointCollection spc) { if (spc == null || ValidSeriesPointSet(SeriesPoints, spc) == true) ActiveSeriesPointCollection = spc; else throw new Exception("Not a valid PieSeriesPointSet for the series."); InvalidateLayout(); } #region ValidSeriesPointSet private bool ValidSeriesPointSet(PieSeriesPointCollection spc, PieSeriesPointCollection aspc) { if (aspc == spc) return (true); if (spc.PieSlices != null) { foreach (PieSeriesPoint psp in spc.PieSlices) { if (psp.SeriesPoints == aspc) return (true); if (ValidSeriesPointSet(psp.SeriesPoints, aspc) == true) return (true); } } return (false); } #endregion #endregion #region GetPieSeriesPointCollection /// /// Gets the SeriesPointCollection containing the given PieSeriesPoint /// /// /// public PieSeriesPointCollection GetPieSeriesPointCollection(PieSeriesPoint psp) { return (psp.Parent as PieSeriesPointCollection); } #endregion #region GetPieLabels internal List GetPieLabels(Graphics g, PieChart pieChart) { List pieLabels = new List(); if (ShowAllRingsEx(pieChart) == true) { PieRing pieRing = GetOuterRing(pieChart); if (pieRing != null) { foreach (PieSeriesPoint psp in pieRing.Psps) { SetPieLabel(g, pieChart, psp); if (psp.PieLabel != null) pieLabels.Add(psp.PieLabel); } } } else { PieSeriesPointCollection spc = GetActiveSeriesPointCollection(); if (spc != null) { foreach (PieSeriesPoint psp in spc) { SetPieLabel(g, pieChart, psp); if (psp.PieLabel != null) pieLabels.Add(psp.PieLabel); } } } return (pieLabels); } #region SetPieLabel private void SetPieLabel( Graphics g, PieChart pieChart, PieSeriesPoint psp) { psp.PieLabel = null; if (psp.IsDisplayed == true && psp.SweepAngleEx != 0 && psp.IsInOtherEx == false && psp.IsEmpty == false) { ChartSliceVisualStyle sstyle = psp.GetEffectiveSliceStyle(pieChart, this); if (IsOuterSliceLabelVisible(pieChart, psp) == true) { SliceOuterLabelVisualStyle lstyle = sstyle.SliceOuterLabelStyle; SliceLabelDisplayMode displayMode = psp.SliceLabelDisplayModeEx; if (displayMode == SliceLabelDisplayMode.Outer || displayMode == SliceLabelDisplayMode.InnerAndOuter || displayMode == SliceLabelDisplayMode.NotSet) { string outerlabel = psp.OuterSliceLabelEx; if (string.IsNullOrEmpty(outerlabel) == false) SetPieLabelEx(g, pieChart, psp, outerlabel, lstyle); } else { if (psp.InnerLabelDisplayed == false) { string outerlabel = psp.OuterSliceLabelEx; if (string.IsNullOrEmpty(outerlabel) == false) { SetPieLabelEx(g, pieChart, psp, outerlabel, lstyle); } else { string innerlabel = psp.InnerSliceLabelEx; if (string.IsNullOrEmpty(innerlabel) == false) SetPieLabelEx(g, pieChart, psp, innerlabel, lstyle); } } } } } } #endregion #region GetOuterRing private PieRing GetOuterRing(PieChart pieChart) { if (ShowAllRingsEx(pieChart) == false) { PieSeriesPointCollection spc = (ActiveSeriesPointCollection ?? SeriesPoints); return (spc.PieRing); } if (PieRings.Count > 0) { foreach (PieRing pieRing in PieRings) { if (pieRing.IsOuterRing == true) return (pieRing); } return (PieRings[0]); } return (null); } #endregion #region IsOuterSliceLabelVisible private bool IsOuterSliceLabelVisible(PieChart pieChart, PieSeriesPoint psp) { if (psp.SliceLabelVisibilityEx == SliceLabelVisibility.Never) return (false); switch (psp.SliceLabelDisplayModeEx) { case SliceLabelDisplayMode.Outer: case SliceLabelDisplayMode.InnerAndOuter: case SliceLabelDisplayMode.InnerXorOuter: return (true); } return (false); } #endregion #region SetPieLabelEx private void SetPieLabelEx(Graphics g, PieChart pieChart, PieSeriesPoint psp, string text, SliceOuterLabelVisualStyle lstyle) { string s = GetSliceLabel(pieChart, psp, false, text); if (String.IsNullOrEmpty(s) == false) { PieLabel pl = new PieLabel(psp, s); pl.LabelSize = MeasurePieLabel(g, pieChart, psp, pl, 0, lstyle); psp.PieLabel = pl; } } #region MeasurePieLabel internal Size MeasurePieLabel(Graphics g, PieChart pieChart, PieSeriesPoint psp, PieLabel pl, int cwidth, SliceOuterLabelVisualStyle lstyle) { float fheight = lstyle.Font.GetHeight(); int maxWidth = GetMaxPieTextWidth(g, pieChart, lstyle); int maxHeight = GetMaxPieTextHeight(g, pieChart, fheight, lstyle); int minWidth = GetMinPieTextWidth(g, pieChart, lstyle); int minHeight = GetMinPieTextHeight(g, pieChart, fheight, lstyle); using (StringFormat sf = new StringFormat()) { lstyle.GetStringFormatFlags(sf, false); if (cwidth != 0) cwidth -= Dpi.Width(lstyle.Padding.Horizontal); Size size = g.MeasureString(pl.Label, lstyle.Font, cwidth != 0 ? cwidth : maxWidth, sf).ToSize(); size.Width += Dpi.Width1; size = AdjustForPadding(pieChart, psp, size); size.Height = MathHelper.Clamp(size.Height, minHeight, maxHeight); size.Width = MathHelper.Clamp(size.Width, minWidth, maxWidth); if (lstyle.MaxLineCount > 1) { int lineHeight = (int)(Math.Ceiling(fheight)) * lstyle.MaxLineCount; if (size.Height > lineHeight) size.Height = lineHeight; } return (size); } } #region GetMaxPieTextWidth private int GetMaxPieTextWidth( Graphics g, PieChart pieChart, SliceOuterLabelVisualStyle lstyle) { double maxWidth = lstyle.MaxLabelWidth; if (double.IsNaN(maxWidth) == false) { if (Math.Abs(maxWidth) >= 1) return ((int)maxWidth); } else { maxWidth = .5; } return ((int)(pieChart.ContentBoundsEx.Width * maxWidth)); } #endregion #region GetMaxPieTextHeight private int GetMaxPieTextHeight( Graphics g, PieChart pieChart, float fheight, SliceOuterLabelVisualStyle lstyle) { double maxHeight = lstyle.MaxLabelHeight; if (double.IsNaN(maxHeight) == false) { if (Math.Abs(maxHeight) >= 1) return ((int)maxHeight); } else { maxHeight = .5; } double height = pieChart.ContentBoundsEx.Height * maxHeight; int n = (int)(height / fheight); return ((int)(fheight * n)); } #endregion #region GetMinPieTextWidth private int GetMinPieTextWidth( Graphics g, PieChart pieChart, SliceOuterLabelVisualStyle lstyle) { double minWidth = lstyle.MinLabelWidth; if (double.IsNaN(minWidth) == false) { if (Math.Abs(minWidth) >= 1) return ((int)minWidth); return ((int)(pieChart.ContentBoundsEx.Width * minWidth)); } return (0); } #endregion #region GetMinPieTextHeight private int GetMinPieTextHeight( Graphics g, PieChart pieChart, float fheight, SliceOuterLabelVisualStyle lstyle) { double minHeight = lstyle.MinLabelHeight; if (double.IsNaN(minHeight) == false) { if (Math.Abs(minHeight) >= 1) return ((int)minHeight); double height = pieChart.ContentBoundsEx.Height * minHeight; int n = (int)(height / fheight); return ((int)(fheight * n)); } return ((int)fheight); } #endregion #region AdjustForPadding private Size AdjustForPadding(PieChart pieChart, PieSeriesPoint psp, Size size) { SliceOuterLabelVisualStyle sstyle = psp.GetEffectiveSliceStyle(pieChart, psp.ChartSeries).SliceOuterLabelStyle; size.Width += Dpi.Width(sstyle.Padding.Horizontal); size.Height += Dpi.Width(sstyle.Padding.Vertical); return (size); } #endregion #endregion #endregion #endregion #region GetOtherPieSeriesPoint /// /// Gets the 'Other' PieSeriesPoint /// /// public PieSeriesPoint GetOtherPieSeriesPoint() { return (SeriesPoints.OtherSlicePsp); } #endregion #region Mouse support #region ProcessMouseEnter internal void ProcessMouseEnter(EventArgs e, PieChart pieChart) { SetToolTip(pieChart.ShowToolTips ? GetToolTipText(pieChart) : string.Empty); } #region SetToolTip private static string _toolTipText; private void SetToolTip(string toolTipText) { ChartControl chartControl = ChartControl; if (chartControl != null) { if (chartControl.ToolTipText != _toolTipText) chartControl.ToolTip.Hide(chartControl); chartControl.ToolTipText = toolTipText; } } #endregion #region GetToolTipText private string GetToolTipText(PieChart pieChart) { PieSeriesPoint psp = pieChart.HitPsp; if (psp == null || "".Equals(psp.ToolTipText)) return (string.Empty); if (_toolTipText == null) { ChartControl chartControl = ChartControl; string s = ""; if (chartControl.DoGetToolTipEvent(pieChart, this, psp, ref s) == false) s = psp.ToolTipTextEx; _toolTipText = psp.FormatItemText(s); } return (_toolTipText); } #endregion #endregion #region ProcessMouseLeave internal void ProcessMouseLeave(EventArgs e, PieChart pieChart) { ChartControl chartControl = ChartControl; if (chartControl != null) chartControl.ToolTipText = ""; _toolTipText = null; } #endregion #region ProcessMouseDown internal bool ProcessMouseDown( MouseEventArgs e, PieChart pieChart, PieSeriesPoint psp) { pieChart.IsDragging = false; pieChart.PspDragType = PspDragType.None; if ((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left) { _MouseDownPsp = psp; Point cpt = pieChart.GetLocalAdjustedPoint(pieChart.CenterPoint); Point rpt = e.Location; _MouseDownRadius = MathHelper.GetPointRadius(ref rpt, cpt); if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift) { _MouseDownOffset = (int)pieChart.ExplodedOffsetEx; } else { PieSeriesPoint rootPsp = (ShowAllRingsEx(pieChart) == true) ? psp.RootPsp : psp; _MouseDownOffset = (double.IsNaN(rootPsp.DetachedOffset) == false) ? (int)rootPsp.DetachedOffset : 0; } MouseClickSliceAction mca = GetMouseClickSliceAction(pieChart); switch (mca) { case MouseClickSliceAction.Explode: case MouseClickSliceAction.Detach: InitPspDrag(pieChart, psp); break; case MouseClickSliceAction.Select: ProcessSliceSelect(e, pieChart, psp); break; } return (true); } return (false); } #region InitPspDrag private bool InitPspDrag(PieChart pieChart, PieSeriesPoint psp) { if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift) { if (pieChart.EnableShiftDragExplode == true) { pieChart.PspDragType = PspDragType.Explode; return (true); } } else { if (GetEnableDragDetach(pieChart) == true) { pieChart.PspDragType = PspDragType.Detach; return (psp.IsSelected); } } return (false); } #endregion #region ProcessSliceSelect private void ProcessSliceSelect( MouseEventArgs e, PieChart pieChart, PieSeriesPoint psp) { bool select = true; if ((pieChart.MultiSelect == true) && (Control.ModifierKeys & Keys.Control) == Keys.Control) { select = !psp.IsSelected; } else { if (InitPspDrag(pieChart, psp) == true) return; int selCount = pieChart.GetSelectionCount(); if (selCount != 1 || psp.IsSelected == false) pieChart.SetSelected(false); } ProcessSliceSelectEx(pieChart, psp, select); } #region ProcessSliceSelectEx private void ProcessSliceSelectEx( PieChart pieChart, PieSeriesPoint psp, bool select) { PieSelectionMode psm = GetPieSelectionMode(pieChart); switch (psm) { case PieSelectionMode.Pie: pieChart.SetSelected(select); break; case PieSelectionMode.Series: SelectPspSeries(SeriesPoints, true, select); break; case PieSelectionMode.Ring: SelectPspRing(psp, select); break; case PieSelectionMode.Slice: SelectPspSlice(pieChart, psp, select); break; case PieSelectionMode.Point: psp.IsSelected = select; break; } ChartControl.DoPieSelectionChangedEvent(pieChart, this, psp, psm); } #region SelectPspSeries private void SelectPspSeries( PieSeriesPointCollection spc, bool showInner, bool select) { if (spc != null) { foreach (PieSeriesPoint psp in spc) { if (psp.AllowSelect == true) { psp.IsSelected = select; if (showInner == true) SelectPspSeries(psp.SeriesPoints, true, select); } } } } #endregion #region SelectPspRing private void SelectPspRing(PieSeriesPoint psp, bool select) { if (psp.PieRing != null) { if (psp.PieRing.AllowSelect == true) { foreach (PieSeriesPoint rpsp in psp.PieRing.Psps) { if (rpsp.AllowSelect == true) rpsp.IsSelected = select; } } } } #endregion #region SelectPspSlice private void SelectPspSlice( PieChart pieChart, PieSeriesPoint psp, bool select) { PieSeriesPoint rootPsp = (ShowAllRingsEx(pieChart) == true) ? psp.RootPsp : psp; if (rootPsp != null) { if (rootPsp.AllowSelect == true) { rootPsp.IsSelected = select; SelectPspSliceEx(rootPsp.SeriesPoints, select); } } } #region SelectPspSliceEx private void SelectPspSliceEx(PieSeriesPointCollection spc, bool select) { if (spc != null) { foreach (PieSeriesPoint psp in spc) { if (psp.AllowSelect == true) { psp.IsSelected = select; SelectPspSliceEx(psp.SeriesPoints, select); } } } } #endregion #endregion #endregion #endregion #endregion #region ProcessMouseMove internal void ProcessMouseMove(MouseEventArgs e, PieChart pieChart) { if (IsMouseDown == true && _MouseDownPsp != null) ProcessMouseDownMove(e, pieChart); } #region ProcessMouseDownMove private bool ProcessMouseDownMove(MouseEventArgs e, PieChart pieChart) { Point pt = e.Location; if (pieChart.IsDragging == false) { if (Math.Abs(MouseDownPoint.X - pt.X) > 3 || Math.Abs(MouseDownPoint.Y - pt.Y) > 3) { pieChart.IsDragging = true; } } else { int deltaOffset = GetDeltaOffset(pieChart, pt); MouseClickSliceAction mca = GetMouseClickSliceAction(pieChart); switch (pieChart.PspDragType) { case PspDragType.Detach: if (GetEnableDragDetach(pieChart) == true) DetachSelectedPsps(pieChart, mca, deltaOffset); break; case PspDragType.Explode: if (pieChart.EnableShiftDragExplode == true) ExplodePie(pieChart, deltaOffset); break; } } return (true); } #region GetDeltaOffset private int GetDeltaOffset(PieChart pieChart, Point pt) { PieSeriesPoint psp = pieChart.HitPsp; if (psp != null) { Point cpt = pieChart.GetLocalAdjustedPoint(pieChart.CenterPoint); Point rpt = pt; double radius = MathHelper.GetPointRadius(ref rpt, cpt); if (ShowAllRingsEx(pieChart) == true) { double pspAngle = MathHelper.GetPointAngle(rpt); double angle = psp.CenterAngle - pspAngle; double delta = Math.Cos(MathHelper.ToRadians(angle)) * radius; return ((int)delta); } return ((int)radius); } return (0); } #endregion #region DetachSelectedPsps private void DetachSelectedPsps( PieChart pieChart, MouseClickSliceAction mca, int deltaOffset) { int offset = Math.Max(0, deltaOffset - _MouseDownRadius + _MouseDownOffset); offset = Math.Min(offset, pieChart.MaxDetachedOffset); PieSeriesPoint pspRoot = ShowAllRingsEx(pieChart) ? _MouseDownPsp.RootPsp : _MouseDownPsp; PieSelectionMode psm = GetPieSelectionMode(pieChart); bool detachChange = false; switch (psm) { case PieSelectionMode.Pie: foreach (PieSeries series in pieChart.ChartSeries) { if (series.IsDisplayed == true) { foreach (PieSeriesPoint psp in series.SeriesPoints) detachChange |= DetachPsp(psp, offset); } } break; case PieSelectionMode.Series: case PieSelectionMode.Ring: if (pspRoot.PieRing.AllowDetach == true) { foreach (PieSeriesPoint psp in pspRoot.PieRing.Psps) detachChange |= DetachPsp(psp, offset); } break; case PieSelectionMode.Slice: case PieSelectionMode.Point: if (mca == MouseClickSliceAction.Select) { if (pspRoot.PieRing.AllowDetach == true) { foreach (PieSeriesPoint psp in pspRoot.PieRing.Psps) { if (psp.IsSelected == true) detachChange |= DetachPsp(psp, offset); } } } else { detachChange |= DetachPsp(pspRoot, offset); } break; } ChartControl.DoPieDetachChangedEvent(pieChart, this, psm); } #region DetachPsp private void DetachPsp(PieSeriesPoint psp) { DetachPsp(psp, psp.DetachedOffset); } private bool DetachPsp(PieSeriesPoint psp, double offset) { if (psp.AllowDetach == true) { if (psp.DetachedOffset != offset || psp.IsDetached != (offset > 0)) { psp.DetachedOffset = offset; psp.IsDetached = (offset > 0); return (true); } } return (false); } #endregion #endregion #region ExplodePie private void ExplodePie(PieChart pieChart, int deltaOffset) { int offset = Math.Max(0, deltaOffset - _MouseDownRadius + _MouseDownOffset); offset = Math.Min(offset, pieChart.MaxExplodedOffset); if (pieChart.ExplodedOffset != offset || pieChart.IsExploded != (offset > 0)) { pieChart.IsExploded = (offset > 0); pieChart.ExplodedOffset = offset; ChartControl.DoPieExplodeChangedEvent(pieChart, this); } } #endregion #endregion #endregion #region ProcessMouseUp internal void ProcessMouseUp(MouseEventArgs e, PieChart pieChart) { if (pieChart.IsDragging == false) { PieSeriesPoint psp = pieChart.HitPsp; if (psp != null && psp == _MouseDownPsp) { ChartControl chartControl = ChartControl; if (chartControl.DoChartMouseClickEvent(pieChart, psp, e) == false) { MouseClickSliceAction mca = GetMouseClickSliceAction(pieChart); switch (mca) { case MouseClickSliceAction.Explode: pieChart.IsExploded = !pieChart.IsExploded; break; case MouseClickSliceAction.Detach: DetachPsp(ShowAllRingsEx(pieChart) ? psp.RootPsp : psp); break; case MouseClickSliceAction.Select: if ((Control.ModifierKeys & Keys.Control) == Keys.None) { int selCount = pieChart.GetSelectionCount(); if (selCount != 1 || psp.IsSelected == false) pieChart.SetSelected(false); ProcessSliceSelectEx(pieChart, psp, true); } break; } } } } } #endregion #region ProcessMouseDoubleClick internal bool ProcessMouseDoubleClick( MouseEventArgs e, PieChart pieChart, PieSeriesPoint psp) { if (e.Button == MouseButtons.Left) { MouseDoubleClickSliceAction mdca = GetMouseDoubleClickSliceAction(pieChart); switch (mdca) { case MouseDoubleClickSliceAction.Detach: psp.IsDetached = !psp.IsDetached; break; case MouseDoubleClickSliceAction.Explode: pieChart.IsExploded = !pieChart.IsExploded; break; case MouseDoubleClickSliceAction.ChangeActiveRing: Keys keys = Control.ModifierKeys; if ((keys & Keys.Shift) == Keys.Shift) ProcessPieDrillUp(pieChart, psp); else if ((keys & Keys.Control) == Keys.None) ProcessPieDrillDown(pieChart, psp); break; } return (true); } return (false); } #region ProcessPieDrillUp private void ProcessPieDrillUp(PieChart pieChart, PieSeriesPoint psp) { PieSeriesPointCollection spc1 = psp.Parent as PieSeriesPointCollection; if (spc1 != null) { PieSeriesPoint psp2 = spc1.Parent as PieSeriesPoint; if (psp2 != null) { PieSeriesPointCollection spc2 = GetPieSeriesPointCollection(psp2); if (spc2 != null) { ChartControl chartControl = ChartControl; if (chartControl.DoPieRingLevelChangingEvent(pieChart, spc1, spc2) == false) { SetActiveSeriesPointCollection(spc2); chartControl.DoPieRingLevelChangedEvent(pieChart, spc1, spc2); } } } } } #endregion #region ProcessPieDrillDown private void ProcessPieDrillDown(PieChart pieChart, PieSeriesPoint psp) { if (psp.ChartSeries.ShowAllRingsEx(pieChart) == false) { PieSeriesPointCollection spc1 = psp.Parent as PieSeriesPointCollection; if (psp.SeriesPoints.Count > 0) { ChartControl chartControl = ChartControl; PieSeriesPointCollection spc2 = psp.SeriesPoints; if (chartControl.DoPieRingLevelChangingEvent(pieChart, spc1, spc2) == false) { SetActiveSeriesPointCollection(psp.SeriesPoints); chartControl.DoPieRingLevelChangedEvent(pieChart, spc1, spc2); } } } } #endregion #endregion #endregion #region SetSelected /// /// Selects or clears all series PieSeriesPoints. /// /// public void SetSelected(bool select) { PieSeriesPointCollection spc = SeriesPoints; spc.SetSelected(select); if (spc.OtherSlicePsp != null) spc.OtherSlicePsp.IsSelected = select; } #endregion #region GetVisibleSelectionCount /// /// Gets the count of the currently Visible selected pie series points. /// /// public int GetVisibleSelectionCount() { int count = GetVisibleSelectionCountEx(SeriesPoints); return (count); } #region GetVisibleSelectionCountEx private int GetVisibleSelectionCountEx(PieSeriesPointCollection spc) { int count = 0; if (spc != null && spc.Count > 0) { foreach (PieSeriesPoint psp in spc) { if (psp.IsSelected == true && psp.Visible == true) count++; count += GetVisibleSelectionCountEx(psp.SeriesPoints); } if (spc.OtherSlicePsp != null) { if (spc.OtherSlicePsp.IsSelected == true && spc.OtherSlicePsp.Visible == true) count++; } } return (count); } #endregion #endregion #region GetVisibleSelectedPoints /// /// Gets a list of the currently Visible selected pie series points. /// /// public List GetVisibleSelectedPoints() { List list = new List(); GetVisibleSelectedPointsEx(list, SeriesPoints); return (list); } #region GetVisibleSelectedPointsEx private void GetVisibleSelectedPointsEx( List list, PieSeriesPointCollection spc) { if (spc != null && spc.Count > 0) { foreach (PieSeriesPoint psp in spc) { if (psp.IsSelected == true && psp.Visible == true) list.Add(psp); GetVisibleSelectedPointsEx(list, psp.SeriesPoints); } if (spc.OtherSlicePsp != null) { if (spc.OtherSlicePsp.IsSelected == true && spc.OtherSlicePsp.Visible == true) list.Add(spc.OtherSlicePsp); } } } #endregion #endregion #region GetSelectionCount /// /// Gets a count of the selected pie series points. /// /// public int GetSelectionCount() { int count = GetSelectionCountEx(SeriesPoints); return (count); } #region GetSelectionCountEx private int GetSelectionCountEx(PieSeriesPointCollection spc) { int count = 0; if (spc != null && spc.Count > 0) { foreach (PieSeriesPoint psp in spc) { if (psp.IsSelected == true) count++; count += GetSelectionCountEx(psp.SeriesPoints); } if (spc.OtherSlicePsp != null) { if (spc.OtherSlicePsp.IsSelected == true ) count++; } } return (count); } #endregion #endregion #region GetSelectedPoints /// /// Gets a list of the currently selected pie series points. /// /// public List GetSelectedPoints() { List list = new List(); GetSelectedPointsEx(list, SeriesPoints); return (list); } #region GetSelectedPointsEx private void GetSelectedPointsEx( List list, PieSeriesPointCollection spc) { if (spc != null && spc.Count > 0) { foreach (PieSeriesPoint psp in spc) { if (psp.IsSelected == true) list.Add(psp); GetSelectedPointsEx(list, psp.SeriesPoints); } if (spc.OtherSlicePsp != null) { if (spc.OtherSlicePsp.IsSelected == true) list.Add(spc.OtherSlicePsp); } } } #endregion #endregion #region Style handling #region StyleChanged protected override void StyleChanged(object sender, PropertyChangedEventArgs e) { base.StyleChanged(sender, e); if (sender is ChartLegendItemVisualStyles && LegendItem != null) InvalidateRender(LegendItem.Bounds); } #endregion #endregion #region InvalidateRender public override void InvalidateRender() { BaseChart chart = Parent as BaseChart; if (chart != null) chart.InvalidateRender(); } #endregion #region InvalidateRecalc internal override void InvalidateRecalc() { SeriesRangeChanged = true; base.InvalidateRecalc(); } #endregion #region ILegendItem #region GetLegendItem public override ChartLegendItem GetLegendItem() { LegendItem = null; if (ShowInLegend == true) { PieChart pieChart = Parent as PieChart; if (pieChart != null) { if (pieChart.VisibleSeriesCount > 1) { if (pieChart.Legend.ShowPieSeriesInLegend == true) { LegendItem = base.GetLegendItem(); LegendItem.ShowCheckBox = pieChart.Legend.ShowPieSeriesCheckBoxes; } } } } return (LegendItem); } #endregion #region AddSubLegendItems internal override void AddSubLegendItems(List list) { PieChart pieChart = Parent as PieChart; if (pieChart != null) { if (ShowInLegend == true) { ChartLegend legend = pieChart.Legend; if (ShowAllRingsEx(pieChart) == true) { if (PieRings != null && PieRings.Count > 0) { bool addRings = ((PieRings.Count > 1) && (pieChart.Legend.ShowPieRingsInLegend == true)); foreach (PieRing pieRing in PieRings) { if (addRings == true) { ChartLegendItem litem = pieRing.GetLegendItem(); if (litem != null) { litem.ShowCheckBox = legend.ShowPieRingCheckBoxes; list.Add(litem); } } if (legend.ShowPieSeriesPointsInLegend == true) AddSubLegendItemsEx(legend, list, pieRing.GetLegendItems()); } } } else { PieSeriesPointCollection spc = GetActiveSeriesPointCollection(); if (spc != null) { if (spc.PieSlices != null) spc = spc.PieSlices; if (spc != null) { foreach (PieSeriesPoint psp in spc) { if (psp.IsDisplayable == true) AddSubLegendItemsEx(legend, list, psp.GetLegendItems()); } } } } } } } #region AddSubLegendItemsEx private void AddSubLegendItemsEx(ChartLegend legend, List list, List items) { if (items != null && items.Count > 0) { foreach (ChartLegendItem item in items) { item.ShowCheckBox = legend.ShowPieSeriesPointCheckBoxes; if (legend.CombineLikeItems == true) { ChartLegendItem likeItem = FindLikeItems(list, item); if (likeItem == null) { item.ChartControl = ChartControl; likeItem = new ChartLegendItem(); likeItem.Parent = legend; likeItem.ChartControl = ChartControl; likeItem.Name = item.Name; likeItem.ItemText = item.ItemText; likeItem.IsEnabled = item.IsEnabled; list.Add(likeItem); } item.LikeItem = likeItem; likeItem.ChartItems.AddRange(item.ChartItems); } else { list.Add(item); } } } } #region FindLikeItems private ChartLegendItem FindLikeItems(List list, ChartLegendItem item) { string itemName = item.Name ?? item.ItemText; if (String.IsNullOrEmpty(itemName) == false) { foreach (ChartLegendItem likeItem in list) { string likeName = likeItem.Name ?? likeItem.ItemText; if (String.IsNullOrEmpty(likeName) == false) { if (likeName.Equals(itemName) == true) return (likeItem); } } } return (null); } #endregion #endregion #endregion #region GetLegendItemColorEx internal override Color GetLegendItemColorEx() { return (Color.Empty); } #endregion #region RenderLegendItemMarkerEx internal override void RenderLegendItemMarkerEx( Graphics g, ChartLegendItem litem, ChartLegendItemVisualStyle style) { Rectangle bounds = litem.MarkerBounds; bounds.Width--; bounds.Height--; Color color = GetLegendItemColor(); using (GraphicsPath path = new GraphicsPath()) { path.AddEllipse(bounds); using (Brush br = new SolidBrush(color)) g.FillPath(br, path); } } #endregion #region TrackLegendItem /// /// Gets whether legend item tracking is enabled. /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool TrackLegendItem { get { PieChart pieChart = Parent as PieChart; if (pieChart != null) { return (pieChart.Legend.TrackingMode == LegendTrackingMode.ChartAndLegend || pieChart.Legend.TrackingMode == LegendTrackingMode.Legend); } return (false); } } #endregion #region FormatItemText /// /// Formats the provided item text. /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public string FormatItemText(string text) { return (text); } #endregion #region OnCheckStateChanged internal override void OnCheckStateChanged() { if (LegendItem != null) LegendItem.UpdateCheckState(); foreach (PieRing pieRing in PieRings) { if (pieRing.LegendItem != null) { pieRing.LegendItem.IsEnabled = pieRing.IsEnabled; } else { foreach (PieSeriesPoint psp in pieRing.Psps) { if (psp.LegendItem != null) psp.LegendItem.IsEnabled = psp.IsEnabled; } } } base.OnCheckStateChanged(); } #endregion #endregion #region Copy/CopyTo public override ChartVisualElement Copy() { ChartSeries copy = new ChartSeries(); CopyTo(copy); return (copy); } public override void CopyTo(ChartVisualElement copy) { PieSeries c = copy as PieSeries; if (c != null) { base.CopyTo(c); c.CenterFirstSlice = CenterFirstSlice; if (_CustomPalette != null) { c.CustomPalette = new Color[CustomPalette.Length]; CustomPalette.CopyTo(c.CustomPalette, 0); } c.EnableDragDetach = EnableDragDetach; c.ExplodedMargin = ExplodedMargin; c.MouseClickSliceAction = MouseClickSliceAction; c.MouseDoubleClickSliceAction = MouseDoubleClickSliceAction; c.PaletteGroup = PaletteGroup; c.PieSelectionMode = PieSelectionMode; c.ReversePaletteColors = ReversePaletteColors; c.RingWeight = RingWeight; c.SubSliceVisualLayout = (_SliceVisualLayout != null) ? _SliceVisualLayout.Copy() : null; foreach (PieSeriesPoint psp in SeriesPoints) c.SeriesPoints.Add(psp.Copy()); c.ShowAllRings = ShowAllRings; c.ShowOtherSlice = ShowOtherSlice; c.SubSliceVisualLayout = (_SliceVisualLayout != null) ? SubSliceVisualLayout.Copy() : null; } } #endregion #region GetSerialData internal override SerialElementCollection GetSerialData(string serialName) { SerialElementCollection sec = new SerialElementCollection(); if (serialName != null) { if (serialName.Equals("") == true) serialName = "PieSeries"; sec.AddStartElement(serialName); } sec.AddValue("CenterFirstSlice", CenterFirstSlice, Tbool.NotSet); if (_CustomPalette != null && _CustomPalette.Length > 0) { sec.AddStartElement("CustomPalette count=\"" + _CustomPalette.Length + "\""); foreach (Color color in _CustomPalette) sec.AddValue("PaletteColor", color); sec.AddEndElement("CustomPalette"); } sec.AddValue("EnableDragDetach", EnableDragDetach, Tbool.NotSet); sec.AddValue("ExplodedMargin", ExplodedMargin, double.NaN); sec.AddValue("MouseClickSliceAction", MouseClickSliceAction, MouseClickSliceAction.NotSet); sec.AddValue("MouseDoubleClickSliceAction", MouseDoubleClickSliceAction, MouseDoubleClickSliceAction.NotSet); sec.AddValue("PaletteGroup", PaletteGroup, PaletteGroup.NotSet); sec.AddValue("PieSelectionMode", PieSelectionMode, PieSelectionMode.NotSet); sec.AddValue("ReversePaletteColors", ReversePaletteColors, false); sec.AddValue("RingWeight", RingWeight, 100); if (_SeriesPoints != null) { PieSeriesPointCollection spc = _SeriesPoints; if (spc.Count > 0) { sec.AddStartElement("SeriesPoints count=\"" + spc.Count + "\""); foreach (PieSeriesPoint psp in spc) sec.AddElement(psp.GetSerialData()); sec.AddEndElement("SeriesPoints"); } } sec.AddValue("ShowAllRings", ShowAllRings, Tbool.NotSet); sec.AddValue("ShowOtherSlice", ShowOtherSlice, Tbool.NotSet); if (_SliceVisualLayout != null) sec.AddElement(_SliceVisualLayout.GetSerialData(true)); sec.AddElement(base.GetSerialData(null)); if (serialName != null) sec.AddEndElement(serialName); return (sec); } #endregion #region PutSerialData #region ProcessValue internal override void ProcessValue(SerialElement se) { ChartXy chartXy = Parent as ChartXy; switch (se.Name) { case "CenterFirstSlice": CenterFirstSlice = (Tbool)se.GetValueEnum(typeof(Tbool)); break; case "EnableDragDetach": EnableDragDetach = (Tbool)se.GetValueEnum(typeof(Tbool)); break; case "ExplodedMargin": ExplodedMargin = double.Parse(se.StringValue); break; case "MouseClickSliceAction": MouseClickSliceAction = (MouseClickSliceAction)se.GetValueEnum(typeof(MouseClickSliceAction)); break; case "MouseDoubleClickSliceAction": MouseDoubleClickSliceAction = (MouseDoubleClickSliceAction)se.GetValueEnum(typeof(MouseDoubleClickSliceAction)); break; case "PaletteColor": CustomPalette[se.ValueIndex] = se.GetValueColor(); break; case "PaletteGroup": PaletteGroup = (PaletteGroup)se.GetValueEnum(typeof(PaletteGroup)); break; case "PieSelectionMode": PieSelectionMode = (PieSelectionMode)se.GetValueEnum(typeof(PieSelectionMode)); break; case "ReversePaletteColors": ReversePaletteColors = bool.Parse(se.StringValue); break; case "RingWeight": RingWeight = int.Parse(se.StringValue); break; case "ShowAllRings": ShowAllRings = (Tbool)se.GetValueEnum(typeof(Tbool)); break; case "ShowOtherSlice": ShowOtherSlice = (Tbool)se.GetValueEnum(typeof(Tbool)); break; default: base.ProcessValue(se); break; } } #endregion #region ProcessCollection internal override void ProcessCollection(SerialElement se) { SerialElementCollection sec = se.Sec; switch (se.Name) { case "CustomPalette": if (se.ArrayCount > 0) { CustomPalette = new Color[se.ArrayCount]; sec.PutSerialData(this); } break; case "SeriesPoint": PieSeriesPoint psp = new PieSeriesPoint(); sec.PutSerialData(psp); SeriesPoints.Add(psp); break; case "SeriesPoints": if (se.ArrayCount > 0) { SeriesPoints = new PieSeriesPointCollection(); sec.PutSerialData(this); } break; case "SliceVisualLayout": sec.PutSerialData(SubSliceVisualLayout); break; default: base.ProcessCollection(se); break; } } #endregion #endregion #region Series States [Flags] private enum States : uint { FilterIgnoreMatchCase = (1U << 0), IsSorted = (1U << 1), IsRotated = (1U << 2), StackQualitativePoints = (1U << 3), DisplayLinePointsOnTop = (1U << 4), EnableEmptyValues = (1U << 5), ShowEmptyLines = (1U << 6), ShowEmptyPoints = (1U << 7), ShowOriginValueLabels = (1U << 8), ShowHiLoBarMedianLines = (1U << 9), NeedToUpdateBindings = (1U << 10), IsInnerRingSeries = (1U << 11), IsOuterRingSeries = (1U << 12), IsOffset = (1U << 13), IsDetached = (1U << 14), ShowInnerRingsEx = (1U << 15), ReversePaletteColors = (1U << 16), } #region TestState private bool TestState(States state) { return ((_States & state) == state); } #endregion #region SetState private void SetState(States state, bool value) { if (value == true) _States |= state; else _States &= ~state; } #endregion #endregion #region IDisposable public override void Dispose() { Visible = false; SeriesPoints = null; SubSliceVisualLayout = null; base.Dispose(); } #endregion } #region WordPos internal class WordPos { public string Text; public int Index; public int Length; } #endregion #region WordPosLine internal class WordPosLine { public double ArcWidth; public double LineWidth; public double Offset; public List Wps = new List(); } #endregion #region enums #region SliceRenderType public enum SliceRenderType { /// /// Full slice. /// FullSlice, /// /// Extent slice (partial slice). /// ExtentSlice, /// /// Extentwhitespace. /// ExtentWhitespace, /// /// 'Other' slice whitespace. /// OtherWhitespace, } #endregion #region ExtentFillRange public enum ExtentFillRange { /// /// Not set (default is ByPoint) /// NotSet = 0, /// /// Extents are filled according to each individual /// PieSeriesPoint extent value. /// ByPoint, /// /// Extents are filled according to the slice /// maximum extent value. /// BySlice, /// /// Extents are filled according to the associated /// PieRing maximum extent value. /// ByRing, } #endregion #region SliceImageCropMode public enum SliceImageCropMode { NotSet = -1, NoAction, ClipImage, HideImage, } #endregion #region SliceImageRadiusAnchor /// /// Defines the anchor for the slice image (ie. the image's bounding edge). /// public enum SliceImageRadiusAnchor { /// /// NotSet /// NotSet = -1, /// /// The pie InnerRadius will be the anchor. /// InnerRadius, /// /// The pie ExtentRadius will be the anchor. The 'ExtentRadius' denotes /// how far the slice extends out from the slice center - which may not /// be as far as the OuterRadius (specified by the ValuesY[1] value). /// ExtentRadius, /// /// The pie OuterRadius will be the anchor. /// OuterRadius, } #endregion #region PieRefLineDisplayMode [Flags] /// /// Defines the type of ReferenceLine to display. /// public enum PieRefLineDisplayMode { /// /// NotSet /// NotSet = 0, /// /// No Reference lines /// None = (1 << 0), /// /// Extent Average Line. /// ExtentAverage = (1 << 3), /// /// Extent Minimum Line. /// ExtentMaximum = (1 << 2), /// /// Extent Minimum Line. /// ExtentMinimum = (1 << 1), /// /// Outer Extent Line. /// ExtentOuterRadius = (1 << 4), /// /// All User defined Lines. /// UserDefined = (1 << 5), } #endregion #endregion }