#region License Information /* HeuristicLab * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using HeuristicLab.Analysis; using HeuristicLab.Analysis.Views; using HeuristicLab.Collections; using HeuristicLab.Core.Views; using HeuristicLab.MainForm; namespace HeuristicLab.DataPreprocessing.Views { [View("Preprocessing DataTable View")] [Content(typeof(PreprocessingDataTable), false)] public partial class PreprocessingDataTableView : ItemView, IConfigureableView { protected List invisibleSeries; protected Dictionary, DataRow> valuesRowsTable; private event EventHandler chartDoubleClick; public new PreprocessingDataTable Content { get { return (PreprocessingDataTable)base.Content; } set { base.Content = value; } } public bool ShowLegend { get { return chart.Legends[0].Enabled; } set { chart.Legends[0].Enabled = value; } } public string XAxisFormat { get { return chart.ChartAreas[0].AxisX.LabelStyle.Format; } set { chart.ChartAreas[0].AxisX.LabelStyle.Format = value; } } public string YAxisFormat { get { return chart.ChartAreas[0].AxisY.LabelStyle.Format; } set { chart.ChartAreas[0].AxisY.LabelStyle.Format = value; } } public IEnumerable Classification { get; set; } public bool IsDetailedChartViewEnabled { get; set; } public PreprocessingDataTableView() { InitializeComponent(); valuesRowsTable = new Dictionary, DataRow>(); invisibleSeries = new List(); chart.CustomizeAllChartAreas(); chart.ChartAreas[0].CursorX.Interval = 1; } #region Event Handler Registration protected override void DeregisterContentEvents() { foreach (DataRow row in Content.Rows) DeregisterDataRowEvents(row); Content.VisualPropertiesChanged -= new EventHandler(Content_VisualPropertiesChanged); Content.Rows.ItemsAdded -= new CollectionItemsChangedEventHandler(Rows_ItemsAdded); Content.Rows.ItemsRemoved -= new CollectionItemsChangedEventHandler(Rows_ItemsRemoved); Content.Rows.ItemsReplaced -= new CollectionItemsChangedEventHandler(Rows_ItemsReplaced); Content.Rows.CollectionReset -= new CollectionItemsChangedEventHandler(Rows_CollectionReset); Content.SelectedRows.ItemsAdded -= new CollectionItemsChangedEventHandler(Rows_ItemsAdded); Content.SelectedRows.ItemsRemoved -= new CollectionItemsChangedEventHandler(Rows_ItemsRemoved); Content.SelectedRows.ItemsReplaced -= new CollectionItemsChangedEventHandler(Rows_ItemsReplaced); Content.SelectedRows.CollectionReset -= new CollectionItemsChangedEventHandler(Rows_CollectionReset); base.DeregisterContentEvents(); } protected override void RegisterContentEvents() { base.RegisterContentEvents(); Content.VisualPropertiesChanged += new EventHandler(Content_VisualPropertiesChanged); Content.Rows.ItemsAdded += new CollectionItemsChangedEventHandler(Rows_ItemsAdded); Content.Rows.ItemsRemoved += new CollectionItemsChangedEventHandler(Rows_ItemsRemoved); Content.Rows.ItemsReplaced += new CollectionItemsChangedEventHandler(Rows_ItemsReplaced); Content.Rows.CollectionReset += new CollectionItemsChangedEventHandler(Rows_CollectionReset); Content.SelectedRows.ItemsAdded += new CollectionItemsChangedEventHandler(SelectedRows_ItemsAdded); Content.SelectedRows.ItemsRemoved += new CollectionItemsChangedEventHandler(SelectedRows_ItemsRemoved); Content.SelectedRows.ItemsReplaced += new CollectionItemsChangedEventHandler(SelectedRows_ItemsReplaced); Content.SelectedRows.CollectionReset += new CollectionItemsChangedEventHandler(SelectedRows_CollectionReset); } protected virtual void RegisterDataRowEvents(DataRow row) { row.NameChanged += new EventHandler(Row_NameChanged); row.VisualPropertiesChanged += new EventHandler(Row_VisualPropertiesChanged); valuesRowsTable.Add(row.Values, row); row.Values.ItemsAdded += new CollectionItemsChangedEventHandler>(Values_ItemsAdded); row.Values.ItemsRemoved += new CollectionItemsChangedEventHandler>(Values_ItemsRemoved); row.Values.ItemsReplaced += new CollectionItemsChangedEventHandler>(Values_ItemsReplaced); row.Values.ItemsMoved += new CollectionItemsChangedEventHandler>(Values_ItemsMoved); row.Values.CollectionReset += new CollectionItemsChangedEventHandler>(Values_CollectionReset); } protected virtual void DeregisterDataRowEvents(DataRow row) { row.Values.ItemsAdded -= new CollectionItemsChangedEventHandler>(Values_ItemsAdded); row.Values.ItemsRemoved -= new CollectionItemsChangedEventHandler>(Values_ItemsRemoved); row.Values.ItemsReplaced -= new CollectionItemsChangedEventHandler>(Values_ItemsReplaced); row.Values.ItemsMoved -= new CollectionItemsChangedEventHandler>(Values_ItemsMoved); row.Values.CollectionReset -= new CollectionItemsChangedEventHandler>(Values_CollectionReset); valuesRowsTable.Remove(row.Values); row.VisualPropertiesChanged -= new EventHandler(Row_VisualPropertiesChanged); row.NameChanged -= new EventHandler(Row_NameChanged); } #endregion protected override void OnContentChanged() { base.OnContentChanged(); invisibleSeries.Clear(); chart.ChartAreas[0].AxisX.Title = string.Empty; chart.ChartAreas[0].AxisY.Title = string.Empty; chart.ChartAreas[0].AxisY2.Title = string.Empty; chart.Series.Clear(); if (Content != null) { if (Classification != null) chart.Titles[0].Text = Content.Name; AddDataRows(Content.Rows); AddSelectedDataRows(Content.SelectedRows); ConfigureChartArea(chart.ChartAreas[0]); RecalculateAxesScale(chart.ChartAreas[0]); } } protected override void SetEnabledStateOfControls() { base.SetEnabledStateOfControls(); chart.Enabled = Content != null; } public void ShowConfiguration() { if (Content != null) { using (var dialog = new DataTableVisualPropertiesDialog(Content)) { dialog.ShowDialog(this); } } else MessageBox.Show("Nothing to configure."); } public bool IsRowEnabled(string name) { return chart.Series.FindByName(name) != null && chart.Series[name].Enabled; } public void SetRowEnabled(string name, bool enabled) { if (chart.Series.FindByName(name) != null) chart.Series[name].Enabled = enabled; } protected virtual void AddDataRows(IEnumerable rows) { foreach (var row in rows) { RegisterDataRowEvents(row); var series = new Series(row.Name); if (row.VisualProperties.DisplayName.Trim() != String.Empty) series.LegendText = row.VisualProperties.DisplayName; else series.LegendText = row.Name; ConfigureSeries(series, row); FillSeriesWithRowValues(series, row); if (IsDetailedChartViewEnabled) { series.LegendText += " Values: " + row.Values.Count; } if (Classification == null) chart.Series.Add(series); } ConfigureChartArea(chart.ChartAreas[0]); RecalculateAxesScale(chart.ChartAreas[0]); UpdateYCursorInterval(); } protected virtual void AddSelectedDataRows(IEnumerable rows) { // only paint selection for line chart if (rows.Count() > 0 && rows.ElementAt(0).VisualProperties.ChartType == DataRowVisualProperties.DataRowChartType.Line) { // add dummy series for selction entry in legend if (rows.Count() > 0 && chart.Series.FindByName("(Selection)") == null) { Series series = new Series("(Selection)"); series.IsVisibleInLegend = true; series.Color = Color.Green; series.BorderWidth = 1; series.BorderDashStyle = ChartDashStyle.Solid; series.BorderColor = Color.Empty; series.ChartType = SeriesChartType.FastLine; chart.Series.Add(series); } foreach (var row in rows) { row.VisualProperties.IsVisibleInLegend = false; row.VisualProperties.Color = Color.Green; //add selected to name in order to avoid naming conflict var series = new Series(row.Name + "(Selected)"); ConfigureSeries(series, row); FillSeriesWithRowValues(series, row); if (Classification == null) chart.Series.Add(series); } } } protected virtual void RemoveSelectedDataRows(IEnumerable rows) { // only remove selection for line chart if (rows.Count() > 0 && rows.ElementAt(0).VisualProperties.ChartType == DataRowVisualProperties.DataRowChartType.Line) { //remove selection entry in legend if (Content.SelectedRows.Count == 0) { Series series = chart.Series["(Selection)"]; chart.Series.Remove(series); } foreach (var row in rows) { Series series = chart.Series[row.Name + "(Selected)"]; chart.Series.Remove(series); } } } protected virtual void RemoveDataRows(IEnumerable rows) { foreach (var row in rows) { DeregisterDataRowEvents(row); Series series = chart.Series[row.Name]; chart.Series.Remove(series); if (invisibleSeries.Contains(series)) invisibleSeries.Remove(series); } RecalculateAxesScale(chart.ChartAreas[0]); } private void ConfigureSeries(Series series, DataRow row) { RemoveCustomPropertyIfExists(series, "PointWidth"); series.BorderWidth = 1; series.BorderDashStyle = ChartDashStyle.Solid; series.BorderColor = Color.Empty; if (row.VisualProperties.Color != Color.Empty) series.Color = row.VisualProperties.Color; else series.Color = Color.Empty; series.IsVisibleInLegend = row.VisualProperties.IsVisibleInLegend; switch (row.VisualProperties.ChartType) { case DataRowVisualProperties.DataRowChartType.Line: series.ChartType = SeriesChartType.FastLine; series.BorderWidth = row.VisualProperties.LineWidth; series.BorderDashStyle = ConvertLineStyle(row.VisualProperties.LineStyle); break; case DataRowVisualProperties.DataRowChartType.Bars: // Bar is incompatible with anything but Bar and StackedBar* if (!chart.Series.Any(x => x.ChartType != SeriesChartType.Bar && x.ChartType != SeriesChartType.StackedBar && x.ChartType != SeriesChartType.StackedBar100)) series.ChartType = SeriesChartType.Bar; else { series.ChartType = SeriesChartType.FastPoint; //default row.VisualProperties.ChartType = DataRowVisualProperties.DataRowChartType.Points; } break; case DataRowVisualProperties.DataRowChartType.Columns: series.ChartType = SeriesChartType.Column; break; case DataRowVisualProperties.DataRowChartType.Points: series.ChartType = SeriesChartType.FastPoint; break; case DataRowVisualProperties.DataRowChartType.Histogram: series.ChartType = SeriesChartType.StackedColumn; series.SetCustomProperty("PointWidth", "1"); if (!series.Color.IsEmpty && series.Color.GetBrightness() < 0.25) series.BorderColor = Color.White; else series.BorderColor = Color.Black; break; case DataRowVisualProperties.DataRowChartType.StepLine: series.ChartType = SeriesChartType.StepLine; series.BorderWidth = row.VisualProperties.LineWidth; series.BorderDashStyle = ConvertLineStyle(row.VisualProperties.LineStyle); break; default: series.ChartType = SeriesChartType.FastPoint; break; } series.YAxisType = row.VisualProperties.SecondYAxis ? AxisType.Secondary : AxisType.Primary; series.XAxisType = row.VisualProperties.SecondXAxis ? AxisType.Secondary : AxisType.Primary; if (row.VisualProperties.DisplayName.Trim() != String.Empty) series.LegendText = row.VisualProperties.DisplayName; else series.LegendText = row.Name; string xAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.XAxisTitle) ? "X" : Content.VisualProperties.XAxisTitle; string yAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.YAxisTitle) ? "Y" : Content.VisualProperties.YAxisTitle; series.ToolTip = series.LegendText + Environment.NewLine + xAxisTitle + " = " + "#INDEX," + Environment.NewLine + yAxisTitle + " = " + "#VAL"; } private void ConfigureChartArea(ChartArea area) { if (Content.VisualProperties.TitleFont != null) chart.Titles[0].Font = Content.VisualProperties.TitleFont; if (!Content.VisualProperties.TitleColor.IsEmpty) chart.Titles[0].ForeColor = Content.VisualProperties.TitleColor; chart.Titles[0].Text = Content.VisualProperties.Title; if (Content.VisualProperties.AxisTitleFont != null) area.AxisX.TitleFont = Content.VisualProperties.AxisTitleFont; if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisX.TitleForeColor = Content.VisualProperties.AxisTitleColor; area.AxisX.Title = Content.VisualProperties.XAxisTitle; if (Content.VisualProperties.AxisTitleFont != null) area.AxisX2.TitleFont = Content.VisualProperties.AxisTitleFont; if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisX2.TitleForeColor = Content.VisualProperties.AxisTitleColor; area.AxisX2.Title = Content.VisualProperties.SecondXAxisTitle; if (Content.VisualProperties.AxisTitleFont != null) area.AxisY.TitleFont = Content.VisualProperties.AxisTitleFont; if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisY.TitleForeColor = Content.VisualProperties.AxisTitleColor; area.AxisY.Title = Content.VisualProperties.YAxisTitle; if (Content.VisualProperties.AxisTitleFont != null) area.AxisY2.TitleFont = Content.VisualProperties.AxisTitleFont; if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisY2.TitleForeColor = Content.VisualProperties.AxisTitleColor; area.AxisY2.Title = Content.VisualProperties.SecondYAxisTitle; area.AxisX.IsLogarithmic = Content.VisualProperties.XAxisLogScale; area.AxisX2.IsLogarithmic = Content.VisualProperties.SecondXAxisLogScale; area.AxisY.IsLogarithmic = Content.VisualProperties.YAxisLogScale; area.AxisY2.IsLogarithmic = Content.VisualProperties.SecondYAxisLogScale; } private void RecalculateAxesScale(ChartArea area) { // Reset the axes bounds so that RecalculateAxesScale() will assign new bounds foreach (Axis a in area.Axes) { a.Minimum = double.NaN; a.Maximum = double.NaN; } area.RecalculateAxesScale(); area.AxisX.IsMarginVisible = false; area.AxisX2.IsMarginVisible = false; if (!Content.VisualProperties.XAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.XAxisMinimumFixedValue)) area.AxisX.Minimum = Content.VisualProperties.XAxisMinimumFixedValue; if (!Content.VisualProperties.XAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.XAxisMaximumFixedValue)) area.AxisX.Maximum = Content.VisualProperties.XAxisMaximumFixedValue; if (!Content.VisualProperties.SecondXAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.SecondXAxisMinimumFixedValue)) area.AxisX2.Minimum = Content.VisualProperties.SecondXAxisMinimumFixedValue; if (!Content.VisualProperties.SecondXAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.SecondXAxisMaximumFixedValue)) area.AxisX2.Maximum = Content.VisualProperties.SecondXAxisMaximumFixedValue; if (!Content.VisualProperties.YAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.YAxisMinimumFixedValue)) area.AxisY.Minimum = Content.VisualProperties.YAxisMinimumFixedValue; if (!Content.VisualProperties.YAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.YAxisMaximumFixedValue)) area.AxisY.Maximum = Content.VisualProperties.YAxisMaximumFixedValue; if (!Content.VisualProperties.SecondYAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.SecondYAxisMinimumFixedValue)) area.AxisY2.Minimum = Content.VisualProperties.SecondYAxisMinimumFixedValue; if (!Content.VisualProperties.SecondYAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.SecondYAxisMaximumFixedValue)) area.AxisY2.Maximum = Content.VisualProperties.SecondYAxisMaximumFixedValue; if (area.AxisX.Minimum >= area.AxisX.Maximum) area.AxisX.Maximum = area.AxisX.Minimum + 1; if (area.AxisX2.Minimum >= area.AxisX2.Maximum) area.AxisX2.Maximum = area.AxisX2.Minimum + 1; if (area.AxisY.Minimum >= area.AxisY.Maximum) area.AxisY.Maximum = area.AxisY.Minimum + 1; if (area.AxisY2.Minimum >= area.AxisY2.Maximum) area.AxisY2.Maximum = area.AxisY2.Minimum + 1; } protected virtual void UpdateYCursorInterval() { double interestingValuesRange = ( from series in chart.Series where series.Enabled let values = (from point in series.Points where !point.IsEmpty select point.YValues[0]).DefaultIfEmpty(1.0) let range = values.Max() - values.Min() where range > 0.0 select range ).DefaultIfEmpty(1.0).Min(); double digits = (int)Math.Log10(interestingValuesRange) - 3; double yZoomInterval = Math.Pow(10, digits); this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval; } #region Event Handlers #region Content Event Handlers private void Content_VisualPropertiesChanged(object sender, EventArgs e) { if (InvokeRequired) Invoke(new EventHandler(Content_VisualPropertiesChanged), sender, e); else { ConfigureChartArea(chart.ChartAreas[0]); RecalculateAxesScale(chart.ChartAreas[0]); // axes min/max could have changed } } #endregion #region Rows Event Handlers private void SelectedRows_CollectionReset(object sender, CollectionItemsChangedEventArgs e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler(Rows_ItemsReplaced), sender, e); else { RemoveSelectedDataRows(e.OldItems); AddSelectedDataRows(e.Items); } } private void SelectedRows_ItemsReplaced(object sender, CollectionItemsChangedEventArgs e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler(Rows_ItemsReplaced), sender, e); else { RemoveSelectedDataRows(e.OldItems); AddSelectedDataRows(e.Items); } } private void SelectedRows_ItemsRemoved(object sender, CollectionItemsChangedEventArgs e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler(Rows_ItemsRemoved), sender, e); else { RemoveSelectedDataRows(e.Items); } } private void SelectedRows_ItemsAdded(object sender, CollectionItemsChangedEventArgs e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler(Rows_ItemsAdded), sender, e); else { AddSelectedDataRows(e.Items); } } private void Rows_ItemsAdded(object sender, CollectionItemsChangedEventArgs e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler(Rows_ItemsAdded), sender, e); else { AddDataRows(e.Items); } } private void Rows_ItemsRemoved(object sender, CollectionItemsChangedEventArgs e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler(Rows_ItemsRemoved), sender, e); else { RemoveDataRows(e.Items); } } private void Rows_ItemsReplaced(object sender, CollectionItemsChangedEventArgs e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler(Rows_ItemsReplaced), sender, e); else { RemoveDataRows(e.OldItems); AddDataRows(e.Items); } } private void Rows_CollectionReset(object sender, CollectionItemsChangedEventArgs e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler(Rows_CollectionReset), sender, e); else { RemoveDataRows(e.OldItems); AddDataRows(e.Items); } } #endregion #region Row Event Handlers private void Row_VisualPropertiesChanged(object sender, EventArgs e) { if (InvokeRequired) Invoke(new EventHandler(Row_VisualPropertiesChanged), sender, e); else { DataRow row = (DataRow)sender; Series series = chart.Series[row.Name]; series.Points.Clear(); ConfigureSeries(series, row); FillSeriesWithRowValues(series, row); RecalculateAxesScale(chart.ChartAreas[0]); } } private void Row_NameChanged(object sender, EventArgs e) { if (InvokeRequired) Invoke(new EventHandler(Row_NameChanged), sender, e); else { DataRow row = (DataRow)sender; chart.Series[row.Name].Name = row.Name; } } #endregion #region Values Event Handlers private void Values_ItemsAdded(object sender, CollectionItemsChangedEventArgs> e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler>(Values_ItemsAdded), sender, e); else { DataRow row = null; valuesRowsTable.TryGetValue((IObservableList)sender, out row); if (row != null) { Series rowSeries = chart.Series[row.Name]; if (!invisibleSeries.Contains(rowSeries)) { rowSeries.Points.Clear(); FillSeriesWithRowValues(rowSeries, row); RecalculateAxesScale(chart.ChartAreas[0]); UpdateYCursorInterval(); } } } } private void Values_ItemsRemoved(object sender, CollectionItemsChangedEventArgs> e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler>(Values_ItemsRemoved), sender, e); else { DataRow row = null; valuesRowsTable.TryGetValue((IObservableList)sender, out row); if (row != null) { Series rowSeries = chart.Series[row.Name]; if (!invisibleSeries.Contains(rowSeries)) { rowSeries.Points.Clear(); FillSeriesWithRowValues(rowSeries, row); RecalculateAxesScale(chart.ChartAreas[0]); UpdateYCursorInterval(); } } } } private void Values_ItemsReplaced(object sender, CollectionItemsChangedEventArgs> e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler>(Values_ItemsReplaced), sender, e); else { DataRow row = null; valuesRowsTable.TryGetValue((IObservableList)sender, out row); if (row != null) { Series rowSeries = chart.Series[row.Name]; if (!invisibleSeries.Contains(rowSeries)) { if (row.VisualProperties.ChartType == DataRowVisualProperties.DataRowChartType.Histogram) { rowSeries.Points.Clear(); FillSeriesWithRowValues(rowSeries, row); } else { foreach (IndexedItem item in e.Items) { if (IsInvalidValue(item.Value)) rowSeries.Points[item.Index].IsEmpty = true; else { rowSeries.Points[item.Index].YValues = new double[] { item.Value }; rowSeries.Points[item.Index].IsEmpty = false; } } } RecalculateAxesScale(chart.ChartAreas[0]); UpdateYCursorInterval(); } } } } private void Values_ItemsMoved(object sender, CollectionItemsChangedEventArgs> e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler>(Values_ItemsMoved), sender, e); else { DataRow row = null; valuesRowsTable.TryGetValue((IObservableList)sender, out row); if (row != null) { Series rowSeries = chart.Series[row.Name]; if (!invisibleSeries.Contains(rowSeries)) { rowSeries.Points.Clear(); FillSeriesWithRowValues(rowSeries, row); RecalculateAxesScale(chart.ChartAreas[0]); UpdateYCursorInterval(); } } } } private void Values_CollectionReset(object sender, CollectionItemsChangedEventArgs> e) { if (InvokeRequired) Invoke(new CollectionItemsChangedEventHandler>(Values_CollectionReset), sender, e); else { DataRow row = null; valuesRowsTable.TryGetValue((IObservableList)sender, out row); if (row != null) { Series rowSeries = chart.Series[row.Name]; if (!invisibleSeries.Contains(rowSeries)) { rowSeries.Points.Clear(); FillSeriesWithRowValues(rowSeries, row); RecalculateAxesScale(chart.ChartAreas[0]); UpdateYCursorInterval(); } } } } #endregion #endregion #region Chart Event Handlers private void chart_MouseDown(object sender, MouseEventArgs e) { HitTestResult result = chart.HitTest(e.X, e.Y); if (result.ChartElementType == ChartElementType.LegendItem) { ToggleSeriesVisible(result.Series); } } private void chart_MouseMove(object sender, MouseEventArgs e) { HitTestResult result = chart.HitTest(e.X, e.Y); if (result.ChartElementType == ChartElementType.LegendItem) this.Cursor = Cursors.Hand; else this.Cursor = Cursors.Default; } private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) { foreach (LegendItem legendItem in e.LegendItems) { var series = chart.Series[legendItem.SeriesName]; if (series != null) { bool seriesIsInvisible = invisibleSeries.Contains(series); foreach (LegendCell cell in legendItem.Cells) { cell.ForeColor = seriesIsInvisible ? Color.Gray : Color.Black; } } } } #endregion private void ToggleSeriesVisible(Series series) { if (!Content.Rows.Any(x => x.Name == series.Name)) return; if (!invisibleSeries.Contains(series)) { series.Points.Clear(); invisibleSeries.Add(series); } else { invisibleSeries.Remove(series); if (Content != null) { var row = (from r in Content.Rows where r.Name == series.Name select r).Single(); FillSeriesWithRowValues(series, row); this.chart.Legends[series.Legend].ForeColor = Color.Black; RecalculateAxesScale(chart.ChartAreas[0]); UpdateYCursorInterval(); } } } private void FillSeriesWithRowValues(Series series, DataRow row) { switch (row.VisualProperties.ChartType) { case DataRowVisualProperties.DataRowChartType.Histogram: CalculateHistogram(series, row); break; default: { bool yLogarithmic = series.YAxisType == AxisType.Primary ? Content.VisualProperties.YAxisLogScale : Content.VisualProperties.SecondYAxisLogScale; bool xLogarithmic = series.XAxisType == AxisType.Primary ? Content.VisualProperties.XAxisLogScale : Content.VisualProperties.SecondXAxisLogScale; for (int i = 0; i < row.Values.Count; i++) { var value = row.Values[i]; var point = new DataPoint(); point.XValue = row.VisualProperties.StartIndexZero && !xLogarithmic ? i : i + 1; if (IsInvalidValue(value) || (yLogarithmic && value <= 0)) point.IsEmpty = true; else point.YValues = new double[] { value }; series.Points.Add(point); } } break; } } // get minimum ignores nan values private double GetMinimum(IEnumerable values) { double min = Double.MaxValue; foreach (double value in values) { if (!Double.IsNaN(value) && value < min) min = value; } return min; } //get maximium ignores nan values private double GetMaximum(IEnumerable values) { double max = Double.MinValue; foreach (double value in values) { if (!Double.IsNaN(value) && value > max) max = value; } return max; } protected virtual void CalculateHistogram(Series series, DataRow row) { if (Classification != null) { var valuesPerClass = row.Values.Select((i, index) => new { i, j = Classification.ToList()[index] }) .GroupBy((x) => x.j) .ToDictionary(x => x.Key, x => x.Select(v => v.i) .ToList()); chart.Titles.Add(row.Name); int featureOverallValueCount = 0; if (IsDetailedChartViewEnabled) featureOverallValueCount = row.Values.Count(x => !IsInvalidValue(x)); foreach (KeyValuePair> entry in valuesPerClass) { var s = new Series(row.Name + entry.Key); ConfigureSeries(s, row); AddPointsToHistogramSeries(s, row, entry.Value); s.LegendText = entry.Key.ToString(); if (IsDetailedChartViewEnabled) { int featureValueCount = entry.Value.Count(x => !IsInvalidValue(x)); s.LegendText += " Values: "; s.LegendText += (featureOverallValueCount > 0) ? string.Format("{0} ({1:F2}%)", featureValueCount, (featureValueCount / (double)featureOverallValueCount) * 100) : "0"; } chart.Series.Add(s); } } else { series.Points.Clear(); ConfigureSeries(series, row); AddPointsToHistogramSeries(series, row, null); } } private void AddPointsToHistogramSeries(Series series, DataRow row, List values) { if (!row.Values.Any()) return; int bins = row.VisualProperties.Bins; double minValue = GetMinimum(row.Values); double maxValue = GetMaximum(row.Values); double intervalWidth = (maxValue - minValue) / bins; if (intervalWidth < 0) return; if (intervalWidth == 0) { series.Points.AddXY(minValue, row.Values.Count); return; } if (!row.VisualProperties.ExactBins) { intervalWidth = HumanRoundRange(intervalWidth); minValue = Math.Floor(minValue / intervalWidth) * intervalWidth; maxValue = Math.Ceiling(maxValue / intervalWidth) * intervalWidth; } double intervalCenter = intervalWidth / 2; double min = 0.0, max = 0.0; if (!Double.IsNaN(Content.VisualProperties.XAxisMinimumFixedValue) && !Content.VisualProperties.XAxisMinimumAuto) min = Content.VisualProperties.XAxisMinimumFixedValue; else min = minValue; if (!Double.IsNaN(Content.VisualProperties.XAxisMaximumFixedValue) && !Content.VisualProperties.XAxisMaximumAuto) max = Content.VisualProperties.XAxisMaximumFixedValue; else max = maxValue + intervalWidth; double axisInterval = intervalWidth / row.VisualProperties.ScaleFactor; var area = chart.ChartAreas[0]; area.AxisX.Interval = axisInterval; series.SetCustomProperty("PointWidth", "1"); // 0.8 is the default value // get the range or intervals which define the grouping of the frequency values var doubleRange = DoubleRange(min, max, intervalWidth).Skip(1).ToList(); if (values == null) { values = row.Values.ToList(); ; } // aggregate the row values by unique key and frequency value var valueFrequencies = (from v in values where !IsInvalidValue(v) orderby v group v by v into g select new Tuple(g.First(), g.Count())).ToList(); // shift the chart to the left so the bars are placed on the intervals if (Classification != null || (valueFrequencies.Any() && valueFrequencies.First().Item1 < doubleRange.First())) { series.Points.Add(new DataPoint(min - intervalWidth, 0)); series.Points.Add(new DataPoint(max + intervalWidth, 0)); } // add data points int j = 0; foreach (var d in doubleRange) { double sum = 0.0; // sum the frequency values that fall within the same interval while (j < valueFrequencies.Count && valueFrequencies[j].Item1 < d) { sum += valueFrequencies[j].Item2; ++j; } string xAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.XAxisTitle) ? "X" : Content.VisualProperties.XAxisTitle; string yAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.YAxisTitle) ? "Y" : Content.VisualProperties.YAxisTitle; DataPoint newDataPoint = new DataPoint(d - intervalCenter, sum); newDataPoint.ToolTip = xAxisTitle + ": [" + (d - intervalWidth) + "-" + d + ")" + Environment.NewLine + yAxisTitle + ": " + sum; int overallValueCount = row.Values.Count(); if (overallValueCount > 0) newDataPoint.ToolTip += string.Format(" ({0:F2}%)", (sum / overallValueCount) * 100); series.Points.Add(newDataPoint); } } public event EventHandler ChartDoubleClick { add { chartDoubleClick += value; } remove { chartDoubleClick -= value; } } #region Helpers public static IEnumerable DoubleRange(double min, double max, double step) { double i; for (i = min; i <= max; i += step) yield return i; if (i != max + step) yield return i; } protected void RemoveCustomPropertyIfExists(Series series, string property) { if (series.IsCustomPropertySet(property)) series.DeleteCustomProperty(property); } private double HumanRoundRange(double range) { double base10 = Math.Pow(10.0, Math.Floor(Math.Log10(range))); double rounding = range / base10; if (rounding <= 1.5) rounding = 1; else if (rounding <= 2.25) rounding = 2; else if (rounding <= 3.75) rounding = 2.5; else if (rounding <= 7.5) rounding = 5; else rounding = 10; return rounding * base10; } private double HumanRoundMax(double max) { double base10; if (max > 0) base10 = Math.Pow(10.0, Math.Floor(Math.Log10(max))); else base10 = Math.Pow(10.0, Math.Ceiling(Math.Log10(-max))); double rounding = (max > 0) ? base10 : -base10; while (rounding < max) rounding += base10; return rounding; } private ChartDashStyle ConvertLineStyle(DataRowVisualProperties.DataRowLineStyle dataRowLineStyle) { switch (dataRowLineStyle) { case DataRowVisualProperties.DataRowLineStyle.Dash: return ChartDashStyle.Dash; case DataRowVisualProperties.DataRowLineStyle.DashDot: return ChartDashStyle.DashDot; case DataRowVisualProperties.DataRowLineStyle.DashDotDot: return ChartDashStyle.DashDotDot; case DataRowVisualProperties.DataRowLineStyle.Dot: return ChartDashStyle.Dot; case DataRowVisualProperties.DataRowLineStyle.NotSet: return ChartDashStyle.NotSet; case DataRowVisualProperties.DataRowLineStyle.Solid: return ChartDashStyle.Solid; default: return ChartDashStyle.NotSet; } } protected static bool IsInvalidValue(double x) { return double.IsNaN(x) || x < (double)decimal.MinValue || x > (double)decimal.MaxValue; } #endregion //bubble double click event with data table view as sender private void chart_DoubleClick(object sender, EventArgs e) { if (chartDoubleClick != null) chartDoubleClick(this, e); } } }