using System; using System.Collections.Generic; using FastReport.DataVisualization.Charting; using System.ComponentModel; using System.Drawing; using FastReport.Data; using FastReport.Utils; using System.Drawing.Design; namespace FastReport.MSChart { /// /// Specifies how the series points are sorted. /// public enum SortBy { /// /// Points are not sorted. /// None, /// /// Points are sorted by X value. /// XValue, /// /// Points are sorted by Y value. /// YValue } /// /// Specifies the direction in which the series points are sorted. /// public enum ChartSortOrder { /// /// Points are sorted in ascending order. /// Ascending, /// /// Points are sorted in descending order. /// Descending } /// /// Specifies how the series points are grouped. /// public enum GroupBy { /// /// Points are not grouped. /// None, /// /// Points are grouped by X value. /// XValue, /// /// Points are grouped by number specified in the . /// Number, /// /// Points are grouped by Years. /// Years, /// /// Points are grouped by Months. /// Months, /// /// Points are grouped by Weeks. /// Weeks, /// /// Points are grouped by Days. /// Days, /// /// Points are grouped by Hours. /// Hours, /// /// Points are grouped by Minutes. /// Minutes, /// /// Points are grouped by Seconds. /// Seconds, /// /// Points are grouped by Milliseconds. /// Milliseconds } /// /// Specifies which pie value to explode. /// public enum PieExplode { /// /// Do not explode pie values. /// None, /// /// Explode the biggest value. /// BiggestValue, /// /// Explode the lowest value. /// LowestValue, /// /// Explode the value specified in the property. /// SpecificValue } /// /// Specifies which data points to collect into one point. /// public enum Collect { /// /// Do not collect points. /// None, /// /// Show top N points (N value is specified in the /// property), collect other points into one. /// TopN, /// /// Show bottom N points (N value is specified in the /// property), collect other points into one. /// BottomN, /// /// Collect points which have Y value less than specified /// in the property. /// LessThan, /// /// Collect points which have Y value less than percent specified /// in the property. /// LessThanPercent, /// /// Collect points which have Y value greater than specified /// in the property. /// GreaterThan, /// /// Collect points which have Y value greater than percent specified /// in the property. /// GreaterThanPercent } /// /// Represents a MS Chart series wrapper. /// /// /// This class provides a data for MS Chart series. The series itself is stored inside the /// MS Chart and is accessible via the property. /// You don't need to create an instance of this class directly. Instead, use the /// method. /// public class MSChartSeries : Base { #region Fields private string filter; private SortBy sortBy; private ChartSortOrder sortOrder; private GroupBy groupBy; private float groupInterval; private TotalType groupFunction; private Collect collect; private float collectValue; private string collectedItemText; private Color collectedItemColor; private PieExplode pieExplode; private string pieExplodeValue; private string xValue; private string yValue1; private string yValue2; private string yValue3; private string yValue4; private string color; private string label; private bool autoSeriesForce; private string autoSeriesColumn; #endregion #region Properties /// /// Gets os sets the data filter expression. /// /// /// The filter is applied for this series only. You can also use the /// property to set a filter that will be applied to all /// series in a chart. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string Filter { get { return filter; } set { filter = value; } } /// /// Gets or set the data column or expression for automatically created series. /// /// /// In order to create auto-series, you need to define one series that will be used as a /// template for new series, and set up the property. /// The value of this property will be a name of new series. If there is no series /// with such name yet, the new series will be added. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string AutoSeriesColumn { get { return autoSeriesColumn; } set { autoSeriesColumn = value; } } /// /// Gets or sets the sort method used to sort data points. /// /// /// You have to specify the property as well. Data points in this series /// will be sorted according selected sort criteria and order. /// [Category("Data")] [DefaultValue(SortBy.None)] public SortBy SortBy { get { return sortBy; } set { sortBy = value; } } /// /// Gets or set Force automatically created series. /// [Category("Data")] [DefaultValue(true)] public bool AutoSeriesForce { get { return autoSeriesForce; } set { autoSeriesForce = value; } } /// /// Gets or sets the sort order used to sort data points. /// /// /// You have to specify the property as well. Data points in this series /// will be sorted according selected sort criteria and order. /// [Category("Data")] [DefaultValue(ChartSortOrder.Ascending)] public ChartSortOrder SortOrder { get { return sortOrder; } set { sortOrder = value; } } /// /// Gets or sets the group method used to group data points. /// /// /// This property is mainly used when series is filled with data with several identical X values. /// In this case, you need to set the property to XValue. All identical data points will be /// grouped into one point, their Y values will be summarized. You can choose the summary function /// using the property. /// [Category("Data")] [DefaultValue(GroupBy.None)] public GroupBy GroupBy { get { return groupBy; } set { groupBy = value; } } /// /// Gets or sets the group interval. /// /// /// This value is used if property is set to Number. /// [Category("Data")] [DefaultValue(1f)] public float GroupInterval { get { return groupInterval; } set { groupInterval = value; } } /// /// Gets or sets the function used to group data points. /// [Category("Data")] [DefaultValue(TotalType.Sum)] public TotalType GroupFunction { get { return groupFunction; } set { groupFunction = value; } } /// /// Gets or sets the collect method used to collect several data points into one. /// /// /// This instrument for data processing allows to collect several series points into one point. /// The collected point will be displaed using the text specified in the /// property and color specified in the property. /// For example, to display top 5 values, set this property to TopN and specify /// N value (5) in the property. /// [Category("Data")] [DefaultValue(Collect.None)] public Collect Collect { get { return collect; } set { collect = value; } } /// /// Gets or sets the collect value used to collect several data points into one. /// /// /// This property is used if the property is set to any value other than None. /// [Category("Data")] [DefaultValue(0f)] public float CollectValue { get { return collectValue; } set { collectValue = value; } } /// /// Gets or sets the text for the collected value. /// [Category("Data")] public string CollectedItemText { get { return collectedItemText; } set { collectedItemText = value; } } /// /// Gets or sets the color for the collected value. /// /// /// If this property is set to Transparent (by default), the default palette color /// will be used to display a collected point. /// [Category("Data")] public Color CollectedItemColor { get { return collectedItemColor; } set { collectedItemColor = value; } } /// /// Gets or sets the method used to explode values in pie-type series. /// [Category("Data")] [DefaultValue(PieExplode.None)] public PieExplode PieExplode { get { return pieExplode; } set { pieExplode = value; } } /// /// Gets or sets the value that must be exploded. /// /// /// This property is used if property is set /// to SpecificValue. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string PieExplodeValue { get { return pieExplodeValue; } set { pieExplodeValue = value; } } /// /// Gets or sets the data column or expression that returns the X value of data point. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string XValue { get { return xValue; } set { xValue = value; } } /// /// Gets or sets the data column or expression that returns the first Y value of data point. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string YValue1 { get { return yValue1; } set { yValue1 = value; } } /// /// Gets or sets the data column or expression returns the second Y value of data point. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string YValue2 { get { return yValue2; } set { yValue2 = value; } } /// /// Gets or sets the data column or expression returns the third Y value of data point. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string YValue3 { get { return yValue3; } set { yValue3 = value; } } /// /// Gets or sets the data column or expression returns the fourth Y value of data point. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string YValue4 { get { return yValue4; } set { yValue4 = value; } } /// /// Gets or sets the data column or expression that returns the color of data point. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string Color { get { return color; } set { color = value; } } /// /// Gets or sets the data column or expression returns the label value of data point. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string Label { get { return label; } set { label = value; } } /// /// Gets a reference to MS Chart Series object. /// /// /// Use this property to set many options available for the Series object. These options /// include: visual appearance, labels, marks, value types. Refer to the Microsoft Chart control /// documentation to learn more. /// [Category("Appearance")] public Series SeriesSettings { get { MSChartObject parent = Parent as MSChartObject; if (parent == null) return null; int index = parent.Series.IndexOf(this); if (index < parent.Chart.Series.Count) return parent.Chart.Series[index]; return null; } } /// /// Gets a number of Y value per data point. /// /// /// Number of Y values depends on series type. Most of series have only one Y value. Financial /// series such as Stock and Candlestick, use four Y values. /// [Browsable(false)] public int YValuesPerPoint { get { return SeriesSettings.YValuesPerPoint; } } /// /// This property is not relevant to this class. /// [Browsable(false)] public new Restrictions Restrictions { get { return base.Restrictions; } set { base.Restrictions = value; } } #endregion #region Private Methods private object Calc(string expression) { if (!String.IsNullOrEmpty(expression)) return Report.Calc(expression); return null; } private void InternalProcessData() { object xValue = Calc(XValue); if (xValue != null) { string[] expressions = new string[] { YValue1, YValue2, YValue3, YValue4 }; object[] values = new object[YValuesPerPoint]; for (int i = 0; i < YValuesPerPoint; i++) { values[i] = Calc(expressions[i]); } DataPoint point = SeriesSettings.Points[SeriesSettings.Points.AddXY(xValue, values)]; // color object color = Calc(Color); if (color != null) { if (color is System.Drawing.Color) point.Color = (System.Drawing.Color)color; else if (color is Int32) point.Color = System.Drawing.Color.FromArgb(255, System.Drawing.Color.FromArgb((int)color)); else if (color is String) point.Color = System.Drawing.Color.FromName((string)color); } // label object label = Calc(Label); if (label != null) point.Label = label.ToString(); } } private void SortData() { SeriesSettings.Sort(new DataPointComparer(SortBy, SortOrder)); } private void CollectValues() { double others = 0; if (Collect == Collect.TopN || Collect == Collect.BottomN) { for (int i = (int)CollectValue; i < SeriesSettings.Points.Count; ) { others += SeriesSettings.Points[i].YValues[0]; SeriesSettings.Points.RemoveAt(i); } } else { double totalValue = 0; if (Collect == Collect.LessThanPercent || Collect == Collect.GreaterThanPercent) { foreach (DataPoint point in SeriesSettings.Points) { totalValue += point.YValues[0]; } } for (int i = 0; i < SeriesSettings.Points.Count; ) { double value = SeriesSettings.Points[i].YValues[0]; if ((Collect == Collect.LessThan && value < CollectValue) || (Collect == Collect.GreaterThan && value > CollectValue) || (Collect == Collect.LessThanPercent && value / totalValue * 100 < CollectValue) || (Collect == Collect.GreaterThanPercent && value / totalValue * 100 > CollectValue)) { others += value; SeriesSettings.Points.RemoveAt(i); } else i++; } } if (others > 0 && !String.IsNullOrEmpty(CollectedItemText)) { SeriesSettings.Points.AddXY(CollectedItemText, others); DataPoint point = SeriesSettings.Points[SeriesSettings.Points.Count - 1]; if (CollectedItemColor != System.Drawing.Color.Transparent) point.Color = CollectedItemColor; } } private void GroupData() { Chart chart = (Parent as MSChartObject).Chart; string function = ""; switch (GroupFunction) { case TotalType.Sum: function = "SUM"; break; case TotalType.Min: function = "MIN"; break; case TotalType.Max: function = "MAX"; break; case TotalType.Avg: function = "AVE"; break; case TotalType.Count: function = "COUNT"; break; } if (GroupBy == GroupBy.XValue) chart.DataManipulator.GroupByAxisLabel(function, SeriesSettings); else { IntervalType type = IntervalType.Number; switch (GroupBy) { case GroupBy.Years: type = IntervalType.Years; break; case GroupBy.Months: type = IntervalType.Months; break; case GroupBy.Weeks: type = IntervalType.Weeks; break; case GroupBy.Days: type = IntervalType.Days; break; case GroupBy.Hours: type = IntervalType.Hours; break; case GroupBy.Minutes: type = IntervalType.Minutes; break; case GroupBy.Seconds: type = IntervalType.Seconds; break; case GroupBy.Milliseconds: type = IntervalType.Milliseconds; break; } chart.DataManipulator.Group(function, GroupInterval, type, SeriesSettings); } } private void ExplodePoint() { if (SeriesSettings.Points.Count == 0) return; if (PieExplode == PieExplode.SpecificValue) { object pieExplodeValue = Calc(PieExplodeValue); if (pieExplodeValue != null) { foreach (DataPoint point in SeriesSettings.Points) { if (point.AxisLabel == pieExplodeValue.ToString()) { point["Exploded"] = "true"; break; } } } } else { List points = new List(); foreach (DataPoint point in SeriesSettings.Points) { points.Add(point); } points.Sort(new DataPointComparer(SortBy.YValue, ChartSortOrder.Ascending)); DataPoint explodePoint = null; if (PieExplode == PieExplode.BiggestValue) explodePoint = points[points.Count - 1]; else explodePoint = points[0]; explodePoint["Exploded"] = "true"; } } #endregion #region Public Methods /// public override void Assign(Base source) { base.Assign(source); MSChartSeries src = source as MSChartSeries; Filter = src.Filter; SortOrder = src.SortOrder; SortBy = src.SortBy; GroupBy = src.GroupBy; GroupInterval = src.GroupInterval; GroupFunction = src.GroupFunction; Collect = src.Collect; CollectValue = src.CollectValue; CollectedItemText = src.CollectedItemText; CollectedItemColor = src.CollectedItemColor; PieExplode = src.PieExplode; PieExplodeValue = src.PieExplodeValue; AutoSeriesForce = src.AutoSeriesForce; AutoSeriesColumn = src.AutoSeriesColumn; XValue = src.XValue; YValue1 = src.YValue1; YValue2 = src.YValue2; YValue3 = src.YValue3; YValue4 = src.YValue4; Color = src.Color; Label = src.Label; } /// public override void Serialize(FRWriter writer) { MSChartSeries s = writer.DiffObject as MSChartSeries; base.Serialize(writer); if (Filter != s.Filter) writer.WriteStr("Filter", Filter); if (SortOrder != s.SortOrder) writer.WriteValue("SortOrder", SortOrder); if (SortBy != s.SortBy) writer.WriteValue("SortBy", SortBy); if (GroupBy != s.GroupBy) writer.WriteValue("GroupBy", GroupBy); if (GroupInterval != s.GroupInterval) writer.WriteFloat("GroupInterval", GroupInterval); if (GroupFunction != s.GroupFunction) writer.WriteValue("GroupFunction", GroupFunction); if (Collect != s.Collect) writer.WriteValue("Collect", Collect); if (CollectValue != s.CollectValue) writer.WriteFloat("CollectValue", CollectValue); if (CollectedItemText != s.CollectedItemText) writer.WriteStr("CollectedItemText", CollectedItemText); if (CollectedItemColor != s.CollectedItemColor) writer.WriteValue("CollectedItemColor", CollectedItemColor); if (PieExplode != s.PieExplode) writer.WriteValue("PieExplode", PieExplode); if (PieExplodeValue != s.PieExplodeValue) writer.WriteStr("PieExplodeValue", PieExplodeValue); if (XValue != s.XValue) writer.WriteStr("XValue", XValue); if (YValue1 != s.YValue1) writer.WriteStr("YValue1", YValue1); if (YValue2 != s.YValue2) writer.WriteStr("YValue2", YValue2); if (YValue3 != s.YValue3) writer.WriteStr("YValue3", YValue3); if (YValue4 != s.YValue4) writer.WriteStr("YValue4", YValue4); if (Color != s.Color) writer.WriteStr("Color", Color); if (Label != s.Label) writer.WriteStr("Label", Label); if (!AutoSeriesForce) writer.WriteBool("AutoSeriesForce", AutoSeriesForce); if (AutoSeriesColumn != s.AutoSeriesColumn) writer.WriteStr("AutoSeriesColumn", AutoSeriesColumn); } /// public override void Deserialize(FRReader reader) { base.Deserialize(reader); if (reader.HasProperty("GroupByXValue")) GroupBy = GroupBy.XValue; } /// /// Clears all data points in this series. /// public void ClearValues() { SeriesSettings.Points.Clear(); } /// /// Adds a data point with specified X and Y values. /// /// X value. /// Array of Y values. /// /// Note: number of values in the yValues parameter must be the same as value returned /// by the property. /// public void AddValue(object xValue, params object[] yValues) { SeriesSettings.Points.AddXY(xValue, yValues); } internal void ProcessData() { object match = true; if (!String.IsNullOrEmpty(Filter)) match = Report.Calc(Filter); if (match is bool && (bool)match == true) InternalProcessData(); } internal void FinishData() { // sort is required if we group by value, not by axis label bool sortThenGroup = GroupBy != GroupBy.XValue; if (!sortThenGroup) { if (GroupBy != GroupBy.None) GroupData(); } // sort if (Collect == Collect.TopN) { SortBy = SortBy.YValue; SortOrder = ChartSortOrder.Descending; } else if (Collect == Collect.BottomN) { SortBy = SortBy.YValue; SortOrder = ChartSortOrder.Ascending; } if (SortBy != SortBy.None) SortData(); // group if (sortThenGroup) { if (GroupBy != GroupBy.None) GroupData(); } // collect topn values if (Collect != Collect.None) CollectValues(); // explode values if (PieExplode != PieExplode.None) ExplodePoint(); } internal void CreateDummyData() { SeriesSettings.Points.Clear(); SeriesSettings.Points.AddXY("A", 1); SeriesSettings.Points.AddXY("B", 3); SeriesSettings.Points.AddXY("C", 2); SeriesSettings.Points.AddXY("D", 4); } /// public override string[] GetExpressions() { List expressions = new List(); if (!String.IsNullOrEmpty(PieExplodeValue)) expressions.Add(PieExplodeValue); if (!String.IsNullOrEmpty(XValue)) expressions.Add(XValue); if (!String.IsNullOrEmpty(YValue1)) expressions.Add(YValue1); if (!String.IsNullOrEmpty(YValue2)) expressions.Add(YValue2); if (!String.IsNullOrEmpty(YValue3)) expressions.Add(YValue3); if (!String.IsNullOrEmpty(YValue4)) expressions.Add(YValue4); if (!String.IsNullOrEmpty(Color)) expressions.Add(Color); if (!String.IsNullOrEmpty(Label)) expressions.Add(Label); if (!String.IsNullOrEmpty(Filter)) expressions.Add(Filter); return expressions.ToArray(); } #endregion /// /// Creates a new instance of the class with default settings. /// public MSChartSeries() { filter = ""; groupInterval = 1; collectedItemText = ""; collectedItemColor = System.Drawing.Color.Transparent; pieExplodeValue = ""; xValue = ""; yValue1 = ""; yValue2 = ""; yValue3 = ""; yValue4 = ""; color = ""; label = ""; autoSeriesForce = true; BaseName = "Series"; } } }