using System; using System.Collections.Generic; using FastReport.DataVisualization.Charting; using System.IO; using System.Drawing; using System.ComponentModel; using FastReport.Utils; using FastReport.Data; using System.Drawing.Drawing2D; using System.Drawing.Design; namespace FastReport.MSChart { /// /// Represents the chart object based on Microsoft Chart control. /// /// /// FastReport uses Microsoft Chart library to display charts. This library is included /// in .Net Framework 4.0. For .Net 3.5 it is available as a separate download here: /// http://www.microsoft.com/downloads/details.aspx?FamilyID=130f7986-bf49-4fe5-9ca8-910ae6ea442c /// This library requires .Net Framework 3.5 SP1. /// To access Microsoft Chart object, use the property. It allows you /// to set up chart appearance. For more information on available properties, refer to the /// MS Chart documentation. /// Chart object may contain one or several series. Each series is represented by two objects: /// /// /// the Series that is handled by MS Chart. It is stored in the /// Chart.Series collection; /// /// /// the object that provides data for MS Chart series. /// It is stored in the collection. /// /// /// Do not operate series objects directly. To add or remove series, use /// the and methods. These methods /// handle Series and MSChartSeries in sync. /// If you have a chart object on your Form and want to print it in FastReport, use /// the method. /// public partial class MSChartObject : ReportComponentBase, IParent { #region Fields private SeriesCollection series; private Chart chart; private DataSourceBase dataSource; private string filter; private bool alignXValues; private string autoSeriesColumn; private string autoSeriesColor; private SortOrder autoSeriesSortOrder; private bool startAutoSeries; private MemoryStream originalChartStream; private DataPoint hotPoint; private bool autoSeriesForce; private bool isPainting; #endregion #region Properties /// /// Gets the collection of objects. /// [Browsable(false)] public SeriesCollection Series { get { return series; } } /// /// Gets a reference to the MS Chart object. /// [Category("Appearance")] public Chart Chart { get { return chart; } } /// /// Gets or set Force automatically created series. /// [Category("Data")] [DefaultValue(false)] public bool AutoSeriesForce { get { return autoSeriesForce; } set { autoSeriesForce = value; } } /// /// Gets or sets the data source. /// [Category("Data")] public DataSourceBase DataSource { get { return dataSource; } set { dataSource = value; } } /// /// Gets or sets the filter expression. /// /// /// This filter will be applied to all series in chart. You may also use the series' /// property to filter each series individually. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string Filter { get { return filter; } set { filter = value; } } /// /// Gets or sets a value indicating that all series' data point should be aligned by its X value. /// /// /// Using this property is necessary to print stacked type series. These series must have /// equal number of data points, and the order of data points must be the same for all series. /// [Category("Data")] [DefaultValue(false)] public bool AlignXValues { get { return alignXValues; } set { alignXValues = 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 set the color for auto-series. /// /// /// If no color is specified, the new series will use the palette defined in the chart. /// [Category("Data")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string AutoSeriesColor { get { return autoSeriesColor; } set { autoSeriesColor = value; } } /// /// Gets or sets sort order for auto-series. /// [Category("Data")] [DefaultValue(SortOrder.None)] public SortOrder AutoSeriesSortOrder { get { return autoSeriesSortOrder; } set { autoSeriesSortOrder = value; } } private DataPoint HotPoint { get { return hotPoint; } set { if (hotPoint != value) { if (Page != null) Page.Refresh(); } hotPoint = value; } } private BandBase ParentBand { get { BandBase parentBand = this.Band; if (parentBand is ChildBand) parentBand = (parentBand as ChildBand).GetTopParentBand; return parentBand; } } private bool IsOnFooter { get { return ParentBand is GroupFooterBand || ParentBand is DataFooterBand; } } #endregion #region Private Methods private void SetChartDefaults() { ChartArea area = new ChartArea("Default"); chart.ChartAreas.Add(area); area.AxisX.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64); area.AxisY.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64); area.AxisX2.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64); area.AxisY2.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64); Legend legend = new Legend("Default"); chart.Legends.Add(legend); Title title = new Title(); chart.Titles.Add(title); title.Visible = false; chart.BorderSkin.SkinStyle = BorderSkinStyle.Emboss; chart.BorderlineColor = Color.DarkGray; chart.BorderlineDashStyle = ChartDashStyle.Solid; chart.BorderlineWidth = 2; } private void ClearAutoSeries() { startAutoSeries = true; if (!String.IsNullOrEmpty(autoSeriesColumn) && !autoSeriesForce) { for (int i = 1; i < Chart.Series.Count; i++) { DeleteSeries(i); } } } private MSChartSeries CloneSeries(MSChartSeries source, int number) { MSChartSeries series = AddSeries(source.SeriesSettings.ChartType); series.Assign(source); Chart tempChart = new Chart(); originalChartStream.Position = 0; tempChart.Serializer.Content = SerializationContents.All; tempChart.Serializer.Load(originalChartStream); Series tempSeries = tempChart.Series[number]; tempChart.Series.Remove(tempSeries); tempSeries.Name = ""; Series tempSeries1 = chart.Series[chart.Series.Count - 1]; chart.Series.Remove(tempSeries1); chart.Series.Add(tempSeries); tempSeries.Points.Clear(); tempChart.Dispose(); tempSeries1.Dispose(); return series; } private IEnumerable MakeAutoSeries(object autoSeriesKey, MSChartSeries[] serieses) { if (serieses == null) { string seriesName = autoSeriesKey.ToString(); MSChartSeries series = null; if (startAutoSeries) series = Series[0]; else { bool found = false; foreach (MSChartSeries s in Series) { if (s.SeriesSettings.Name == seriesName) { series = s; found = true; break; } } if (!found) { series = CloneSeries(Series[0], 0); if (!String.IsNullOrEmpty(AutoSeriesColor)) { object color = Report.Calc(AutoSeriesColor); if (color is Color) series.SeriesSettings.Color = (Color)color; } } } series.SeriesSettings.Name = seriesName; startAutoSeries = false; yield return series; } else { for (int i = 0; i < serieses.Length; i++) { string seriesName = autoSeriesKey.ToString(); if (!String.IsNullOrEmpty(Series[i].AutoSeriesColumn)) seriesName = Report.Calc(Series[i].AutoSeriesColumn).ToString(); MSChartSeries series = null; if (startAutoSeries || !serieses[i].AutoSeriesForce) series = serieses[i]; else { bool found = false; foreach (MSChartSeries s in Series) { if (s.SeriesSettings.Name == seriesName) { series = s; found = true; break; } } if (!found) { series = CloneSeries(serieses[i], i); if (!String.IsNullOrEmpty(AutoSeriesColor)) { object color = Report.Calc(AutoSeriesColor); if (color is Color) series.SeriesSettings.Color = (Color)color; } } } if (serieses[i].AutoSeriesForce) series.SeriesSettings.Name = seriesName; yield return series; } startAutoSeries = false; } } private void SortAutoSeries() { // create a list of series List sortedList = new List(); foreach (MSChartSeries series in Series) { sortedList.Add(new SeriesInfo(series)); } sortedList.Sort(new SeriesComparer(AutoSeriesSortOrder)); // delete original series while (Series.Count > 0) { Series.RemoveAt(0); chart.Series.RemoveAt(0); } // add them in correct order foreach (SeriesInfo info in sortedList) { Series.Add(info.Series); chart.Series.Add(info.ChartSeries); } } private void WireEvents(bool wire) { DataBand dataBand = null; if (ParentBand is GroupFooterBand) dataBand = ((ParentBand as GroupFooterBand).Parent as GroupHeaderBand).GroupDataBand; else if (ParentBand is DataFooterBand) dataBand = ParentBand.Parent as DataBand; // wire/unwire events if (dataBand != null) { if (wire) dataBand.BeforePrint += new EventHandler(dataBand_BeforePrint); else dataBand.BeforePrint -= new EventHandler(dataBand_BeforePrint); } } private void dataBand_BeforePrint(object sender, EventArgs e) { bool firstRow = (sender as DataBand).IsFirstRow; if (firstRow) Series.ResetData(); object match = true; if (!String.IsNullOrEmpty(Filter)) match = Report.Calc(Filter); if (match is bool && (bool)match == true) Series.ProcessData(); } #endregion #region Protected Methods /// protected override void Dispose(bool disposing) { if (disposing && chart != null) { chart.OnModifing -= Chart_OnModifing; chart.Dispose(); chart = null; } base.Dispose(disposing); } #endregion #region Public Methods /// /// Adds a new series. /// /// The type of series. /// The new MSChartSeries object. public MSChartSeries AddSeries(SeriesChartType chartType) { if (chart.ChartAreas.Count == 0) SetChartDefaults(); Series chartSeries = new Series(); chartSeries.ChartType = chartType; chart.Series.Add(chartSeries); MSChartSeries series = new MSChartSeries(); Series.Add(series); series.CreateUniqueName(); return series; } /// /// Deletes a series at a specified index. /// /// Index of series. public void DeleteSeries(int index) { if (index >= 0 && index < chart.Series.Count) { Series series = chart.Series[index]; chart.Series.RemoveAt(index); series.Dispose(); } if (index >= 0 && index < Series.Count) { Series.RemoveAt(index); } } /// /// Assigns chart appearance, series and data from the /// System.Windows.Forms.DataVisualization.Charting.Chart object. /// /// Chart object to assign data from. /// /// Use this method if you have a chart in your application and want to print it in FastReport. /// To do this, put an empty MSChartObject in your report and execute the following code: /// /// report.Load("..."); /// MSChartObject reportChart = report.FindObject("MSChart1") as MSChartObject; /// reportChart.AssignChart(applicationChart); /// report.Show(); /// /// public void AssignChart(Chart sourceChart) { using (MemoryStream ms = new MemoryStream()) { sourceChart.Serializer.Content = SerializationContents.All; sourceChart.Serializer.Save(ms); ms.Position = 0; Chart.Serializer.Load(ms); } } /// public override void Assign(Base source) { base.Assign(source); MSChartObject src = source as MSChartObject; DataSource = src.DataSource; Filter = src.Filter; AlignXValues = src.AlignXValues; AutoSeriesForce = src.AutoSeriesForce; AutoSeriesColumn = src.AutoSeriesColumn; AutoSeriesColor = src.AutoSeriesColor; AutoSeriesSortOrder = src.AutoSeriesSortOrder; using (MemoryStream stream = new MemoryStream()) { src.Chart.Serializer.Content = SerializationContents.All; src.Chart.Serializer.Save(stream); stream.Position = 0; Chart.Serializer.Reset(); Chart.Serializer.Load(stream); } } private Font NewFontDpi(Font prototype) { return new Font(prototype.Name, prototype.Size * 96f / DrawUtils.ScreenDpi, prototype.Style); } private Font OldFontDpi(Font prototype) { return new Font(prototype.Name, prototype.Size * DrawUtils.ScreenDpi / 96f, prototype.Style); } /// public override void Draw(FRPaintEventArgs e) { isPainting = true; base.Draw(e); Rectangle chartRect = new Rectangle((int)Math.Round(AbsLeft), (int)Math.Round(AbsTop), (int)Math.Round(Width), (int)Math.Round(Height)); IGraphicsState state = e.Graphics.Save(); try { if (IsPrinting) { /* chartRect = new Rectangle((int)Math.Round(AbsLeft * e.ScaleX), (int)Math.Round(AbsTop * e.ScaleY), (int)Math.Round(Width * e.ScaleX), (int)Math.Round(Height * e.ScaleY)); // workaround the MS Chart bug - series border is not scaled properly int[] borderWidths = new int[Series.Count]; for (int i = 0; i < Series.Count; i++) { int borderWidth = Series[i].SeriesSettings.BorderWidth; borderWidths[i] = borderWidth; Series[i].SeriesSettings.BorderWidth = (int)Math.Round(borderWidth * e.ScaleX); } FChart.Printing.PrintPaint(e.Graphics, chartRect); for (int i = 0; i < Series.Count; i++) { Series[i].SeriesSettings.BorderWidth = borderWidths[i]; }*/ // PrintPaint method is buggy when printing directly on printer's canvas. // We use temp bitmap instead. using (Bitmap bmp = new Bitmap((int)Math.Round(Width * e.ScaleX), (int)Math.Round(Height * e.ScaleY))) using (GdiGraphics g = new GdiGraphics(bmp)) { g.ScaleTransform(e.ScaleX, e.ScaleY); chart.Printing.PrintPaint(g, new Rectangle(0, 0, (int)Math.Round(Width), (int)Math.Round(Height))); e.Graphics.DrawImage(bmp, new RectangleF((int)Math.Round(AbsLeft * e.ScaleX), (int)Math.Round(AbsTop * e.ScaleY), (int)Math.Round(Width * e.ScaleX), (int)Math.Round(Height * e.ScaleY)), new RectangleF(0, 0, bmp.Width, bmp.Height), GraphicsUnit.Pixel); } } else { bool needScaleFont = DrawUtils.ScreenDpi != 96 && chart.Titles.Count > 0 && chart.Legends.Count > 0 && chart.ChartAreas.Count > 0 && chart.Series.Count > 0; e.Graphics.ScaleTransform(e.ScaleX, e.ScaleY); Color saveBackSecondaryColor = Color.Empty; ChartHatchStyle saveBackHatchStyle = ChartHatchStyle.None; Color saveBorderColor = Color.Empty; ChartDashStyle saveBorderStyle = ChartDashStyle.NotSet; int saveBorderWidth = 0; if (HotPoint != null) { saveBackSecondaryColor = HotPoint.BackSecondaryColor; saveBackHatchStyle = HotPoint.BackHatchStyle; saveBorderColor = HotPoint.BorderColor; saveBorderStyle = HotPoint.BorderDashStyle; saveBorderWidth = HotPoint.BorderWidth; HotPoint.BackHatchStyle = ChartHatchStyle.LightUpwardDiagonal; HotPoint.BackSecondaryColor = Color.White; HotPoint.BorderColor = Color.Orange; HotPoint.BorderDashStyle = ChartDashStyle.Solid; HotPoint.BorderWidth = 2; } // scale chart fonts if (needScaleFont) { foreach (Title t in chart.Titles) t.Font = NewFontDpi(t.Font); foreach (Legend l in chart.Legends) { l.Font = NewFontDpi(l.Font); l.TitleFont = NewFontDpi(l.TitleFont); } foreach (ChartArea a in chart.ChartAreas) foreach (Axis ax in a.Axes) { ax.LabelStyle.Font = NewFontDpi(ax.LabelStyle.Font); ax.TitleFont = NewFontDpi(ax.TitleFont); } foreach (Series s in chart.Series) s.Font = NewFontDpi(s.Font); } chart.Printing.PrintPaint(e.Graphics, chartRect); // set chart fonts back if (needScaleFont) { foreach (Title t in chart.Titles) t.Font = OldFontDpi(t.Font); foreach (Legend l in chart.Legends) { l.Font = OldFontDpi(l.Font); l.TitleFont = OldFontDpi(l.TitleFont); } foreach (ChartArea a in chart.ChartAreas) foreach (Axis ax in a.Axes) { ax.LabelStyle.Font = OldFontDpi(ax.LabelStyle.Font); ax.TitleFont = OldFontDpi(ax.TitleFont); } foreach (Series s in chart.Series) s.Font = OldFontDpi(s.Font); } if (HotPoint != null) { HotPoint.BackSecondaryColor = saveBackSecondaryColor; HotPoint.BackHatchStyle = saveBackHatchStyle; HotPoint.BorderColor = saveBorderColor; HotPoint.BorderDashStyle = saveBorderStyle; HotPoint.BorderWidth = saveBorderWidth; } } } catch (Exception ex) { using (StringFormat sf = new StringFormat()) { sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; e.Graphics.DrawString(ex.Message, DrawUtils.DefaultFont, Brushes.Red, chartRect, sf); } } finally { e.Graphics.Restore(state); DrawMarkers(e); Border.Draw(e, chartRect); isPainting = false; } } /// public override void Serialize(FRWriter writer) { MSChartObject c = writer.DiffObject as MSChartObject; base.Serialize(writer); if (DataSource != null) writer.WriteRef("DataSource", DataSource); if (Filter != c.Filter) writer.WriteStr("Filter", Filter); if (AlignXValues != c.AlignXValues) writer.WriteBool("AlignXValues", AlignXValues); if (AutoSeriesColumn != c.AutoSeriesColumn) writer.WriteStr("AutoSeriesColumn", AutoSeriesColumn); if (AutoSeriesColor != c.AutoSeriesColor) writer.WriteStr("AutoSeriesColor", AutoSeriesColor); if (AutoSeriesSortOrder != c.AutoSeriesSortOrder) writer.WriteValue("AutoSeriesSortOrder", AutoSeriesSortOrder); if (AutoSeriesForce) writer.WriteBool("AutoSeriesForce", AutoSeriesForce); using (MemoryStream stream = new MemoryStream()) { chart.Serializer.Content = SerializationContents.All; chart.Serializer.Save(stream); stream.Position = 0; writer.WriteValue("ChartData", stream); } } /// public override void Deserialize(FRReader reader) { base.Deserialize(reader); if (reader.HasProperty("ChartData")) { string streamStr = reader.ReadStr("ChartData"); using (MemoryStream stream = Converter.FromString(typeof(Stream), streamStr) as MemoryStream) { chart.Serializer.Reset(); chart.Serializer.Load(stream); } } } #endregion #region Report Engine /// public override void InitializeComponent() { base.InitializeComponent(); WireEvents(true); } /// public override void FinalizeComponent() { base.FinalizeComponent(); WireEvents(false); } /// public override string[] GetExpressions() { List expressions = new List(); expressions.AddRange(base.GetExpressions()); if (!String.IsNullOrEmpty(Filter)) expressions.Add(Filter); return expressions.ToArray(); } /// public override void SaveState() { base.SaveState(); if (!String.IsNullOrEmpty(AutoSeriesColumn)) { ClearAutoSeries(); originalChartStream = new MemoryStream(); chart.Serializer.Content = SerializationContents.All; chart.Serializer.Save(originalChartStream); } } /// public override void RestoreState() { base.RestoreState(); ClearAutoSeries(); if (originalChartStream != null) { originalChartStream.Dispose(); originalChartStream = null; } } /// public override void GetData() { base.GetData(); MSChartSeries[] serieses = null; if (AutoSeriesForce) { serieses = new MSChartSeries[Series.Count]; for (int i = 0; i < Series.Count; i++) serieses[i] = Series[i]; } if (DataSource != null && !IsOnFooter) { Series.ResetData(); DataSource.Init(Filter); DataSource.First(); while (DataSource.HasMoreRows) { if (!String.IsNullOrEmpty(AutoSeriesColumn)) { object autoSeriesKey = Report.Calc(AutoSeriesColumn); if (autoSeriesKey != null) { foreach (MSChartSeries series in MakeAutoSeries(autoSeriesKey, serieses)) series.ProcessData(); } } else Series.ProcessData(); DataSource.Next(); } } Series.FinishData(); if (AlignXValues) Chart.AlignDataPointsByAxisLabel(); if (!String.IsNullOrEmpty(AutoSeriesColumn) && AutoSeriesSortOrder != SortOrder.None) SortAutoSeries(); } #endregion #region IParent Members /// public bool CanContain(Base child) { return child is MSChartSeries; } /// public void GetChildObjects(ObjectCollection list) { foreach (MSChartSeries series in Series) { list.Add(series); } } /// public void AddChild(Base child) { if (child is MSChartSeries) Series.Add(child as MSChartSeries); } /// public void RemoveChild(Base child) { if (child is MSChartSeries) Series.Remove(child as MSChartSeries); } /// public int GetChildOrder(Base child) { if (child is MSChartSeries) return Series.IndexOf(child as MSChartSeries); return 0; } /// public void SetChildOrder(Base child, int order) { } /// public void UpdateLayout(float dx, float dy) { } #endregion /// /// Initializes a new instance of the with default settings. /// public MSChartObject() { series = new SeriesCollection(this); chart = new Chart(); FlagProvidesHyperlinkValue = true; chart.OnModifing += Chart_OnModifing; } private void Chart_OnModifing(object sender, EventArgs e) { #if !FRCORE if (Report != null && Report.Designer != null && IsDesigning && !isPainting) Report.Designer.SetModified(); #endif } } /// /// Represents the small chart object (called sparkline) fully based on MSChartObject. /// public partial class SparklineObject : MSChartObject { /// /// Initializes a new instance of the with default settings. /// public SparklineObject() { } } }