#region License Information /* HeuristicLab * Copyright (C) 2002-2015 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.Linq; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Core.Views; using HeuristicLab.Data; using HeuristicLab.MainForm; namespace HeuristicLab.Optimization.BubbleChart { [View("Bubble Chart (Recursive)")] [Content(typeof(RecursiveDataItem), true)] public partial class BubbleChartView : ItemView { private const string Separator = ":"; private enum SizeDimension { Constant = 0 } private enum AxisDimension { Index = 0 } private readonly Dictionary itemToIndexMapping = new Dictionary(); private readonly Dictionary> categoricalMapping = new Dictionary>(); private readonly Dictionary xJitter = new Dictionary(); private readonly Dictionary yJitter = new Dictionary(); private readonly Random random = new Random(); private string XAxisValue { get { return (string)xAxisComboBox.SelectedItem; } } private string YAxisValue { get { return (string)yAxisComboBox.SelectedItem; } } private string SizeAxisValue { get { return (string)sizeComboBox.SelectedItem; } } private bool updating = false; private double xJitterFactor = 0.0; private double yJitterFactor = 0.0; public new RecursiveDataItem Content { get { return (RecursiveDataItem)base.Content; } set { base.Content = value; } } public BubbleChartView() { InitializeComponent(); chart.CustomizeAllChartAreas(); chart.ChartAreas[0].CursorX.Interval = 1; chart.ChartAreas[0].CursorY.Interval = 1; chart.ChartAreas[0].AxisX.ScaleView.Zoomable = true; chart.ChartAreas[0].AxisY.ScaleView.Zoomable = true; } protected override void OnContentChanged() { base.OnContentChanged(); UpdateTreeView(); UpdateLevelControl(); UpdateComboBoxes(); UpdateDataPoints(); //UpdateCaption(); } protected override void SetEnabledStateOfControls() { base.SetEnabledStateOfControls(); levelNumericUpDown.Enabled = Content != null; xJitterTrackBar.Enabled = Content != null; yJitterTrackBar.Enabled = Content != null; xAxisComboBox.Enabled = Content != null; yAxisComboBox.Enabled = Content != null; } #region Tree Queries private IEnumerable GetAvailableItems() { return IterateCheckedNodes() .Select(n => (RecursiveDataItem)n.Tag); } private IEnumerable GetAvailableKeys() { var collector = new List(); GetAvailableKeys(Content, collector); return collector.Distinct(); } private void GetAvailableKeys(RecursiveDataItem node, List collector) { collector.AddRange(node.Data.Keys); foreach (var child in node.Children) GetAvailableKeys(child, collector); } #endregion #region Update Controls private void UpdateLevelControl() { if (Content == null) return; if (treeView.Nodes.Count > 0) levelNumericUpDown.Maximum = IterateAllNodes().Max(t => t.Level); else levelNumericUpDown.Maximum = 0; } private void UpdateTreeView() { treeView.Nodes.Clear(); if (Content != null) treeView.Nodes.Add(CreateTreeNode(Content)); treeView.ExpandAll(); splitContainer.Panel1Collapsed = treeView.Nodes.Count == 0; if (treeView.Nodes.Count > 0) treeView.SelectedNode = treeView.Nodes[0]; } private TreeNode CreateTreeNode(RecursiveDataItem item) { var node = new TreeNode(item.Name) { Tag = item, Checked = true }; foreach (var child in item.Children) node.Nodes.Add(CreateTreeNode(child)); return node; } private void UpdateTreeViewCheckBoxes() { int level = (int)levelNumericUpDown.Value; bool includeChildren = includeChildrenCheckBox.Checked; foreach (var node in IterateAllNodes()) { bool @checked = includeChildren ? node.Level >= level : node.Level == level; if (node.Checked != @checked) node.Checked = @checked; } } private void UpdateComboBoxes() { var selectedXAxis = (string)xAxisComboBox.SelectedItem; var selectedYAxis = (string)yAxisComboBox.SelectedItem; var selectedSizeAxis = (string)sizeComboBox.SelectedItem; xAxisComboBox.Items.Clear(); yAxisComboBox.Items.Clear(); sizeComboBox.Items.Clear(); if (Content != null) { var axisNames = GetAvailableKeys().ToArray(); var additionalAxisDimension = Enum.GetNames(typeof(AxisDimension)); var additionalSizeDimension = Enum.GetNames(typeof(SizeDimension)); //xAxisComboBox.Items.AddRange(additionalAxisDimension); xAxisComboBox.Items.AddRange(axisNames); //yAxisComboBox.Items.AddRange(additionalAxisDimension); yAxisComboBox.Items.AddRange(axisNames); sizeComboBox.Items.AddRange(additionalSizeDimension); sizeComboBox.Items.AddRange(axisNames); bool changed = false; if (selectedXAxis != null && xAxisComboBox.Items.Contains(selectedXAxis)) { xAxisComboBox.SelectedItem = selectedXAxis; changed = true; } if (selectedYAxis != null && yAxisComboBox.Items.Contains(selectedYAxis)) { yAxisComboBox.SelectedItem = selectedYAxis; changed = true; } if (selectedSizeAxis != null && sizeComboBox.Items.Contains(selectedSizeAxis)) { sizeComboBox.SelectedItem = selectedSizeAxis; changed = true; } else sizeComboBox.SelectedItem = SizeDimension.Constant.ToString(); if (changed) { UpdateDataPoints(); UpdateAxisLabels(); } } } private void UpdateDataPoints() { var series = chart.Series[0]; series.Points.Clear(); itemToIndexMapping.Clear(); categoricalMapping.Clear(); RebuildInverseIndex(); chart.ChartAreas[0].AxisX.IsMarginVisible = !(XAxisValue != null && XAxisValue.Contains(AxisDimension.Index.ToString())); chart.ChartAreas[0].AxisY.IsMarginVisible = !(YAxisValue != null && YAxisValue.Contains(AxisDimension.Index.ToString())); if (Content != null && !string.IsNullOrEmpty(XAxisValue) && !string.IsNullOrEmpty(YAxisValue)) { var xss = GetValues(XAxisValue); var yss = GetValues(YAxisValue); if (xss.Count == yss.Count) { for (int i = 0; i < xss.Count; i++) AddPoints(xss[i], yss[i], series); } else if (xss.Count == 1 || yss.Count == 1) { for (int i = 0; i < xss.Count; i++) for (int j = 0; j < yss.Count; j++) AddPoints(xss[i], yss[j], series); } } if (chart.Series[0].Points.Count == 0) { noDataLabel.Visible = true; } else { noDataLabel.Visible = false; UpdateMarkerSizes(); UpdateCursorInterval(); } xJitterTrackBar.Value = 0; yJitterTrackBar.Value = 0; //needed to set axis back to automatic and refresh them, otherwise their values may remain NaN var xAxis = chart.ChartAreas[0].AxisX; var yAxis = chart.ChartAreas[0].AxisY; SetAutomaticUpdateOfAxis(xAxis, true); SetAutomaticUpdateOfAxis(yAxis, true); chart.Refresh(); } void AddPoints(Tuple xs, Tuple ys, Series series) { if (xs.Item1.Length != ys.Item1.Length) return; for (int k = 0; k < xs.Item1.Length; k++) { series.Points.Add(new DataPoint(xs.Item1[k], new[] { ys.Item1[k], 1.0 }) { Tag = Tuple.Create(xs.Item1[k], ys.Item1[k], xs.Item2, ys.Item2) }); } } private List> GetValues(string key) { var collector = new List>(); GetValues(Content, key, collector); return collector; } private void GetValues(RecursiveDataItem node, string key, List> collector) { IItem item; if (node.Data.TryGetValue(key, out item)) { var value = ConvertToDoubles(node, key); collector.Add(Tuple.Create(value, node)); } foreach (var child in node.Children) GetValues(child, key, collector); } private double[] ConvertToDoubles(RecursiveDataItem item, string key) { IItem value = item.Data[key]; var doubleValue = value as DoubleValue; var doubleArray = value as DoubleArray; var intValue = value as IntValue; var intArray = value as IntArray; var timeSpanValue = value as TimeSpanValue; if (doubleValue != null) return new double[1] { doubleValue.Value }; if (intValue != null) return new double[1] { intValue.Value }; if (timeSpanValue != null) return new double[1] { timeSpanValue.Value.TotalSeconds }; if (doubleArray != null) return doubleArray.ToArray(); if (intArray != null) return intArray.Select(v => (double)v).ToArray(); return new double[1] { GetCategoricalValue(key, value.ToString()).Value }; } private IEnumerable> GetValues(IEnumerable items, string key) { if (key == null) yield break; var keyTokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries); if (keyTokens.Length == 1) { if (Enum.IsDefined(typeof(SizeDimension), key)) { var sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), key); yield return GetValue(null, sizeDimension).ToEnumerable(); } else foreach (var item in items) { if (Enum.IsDefined(typeof(AxisDimension), key)) { var axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), key); yield return GetValue(item, axisDimension).ToEnumerable(); } else if (item.Data.ContainsKey(key)) { yield return item.Data[key] as IEnumerable ?? ConvertToDouble(item, key).ToEnumerable(); } } } else if (keyTokens.Length == 2) { string parentName = keyTokens[0]; key = keyTokens[1]; foreach (var item in items.Where(item => item.Name == parentName)) { foreach (var child in item.Children.Where(child => child.Data.ContainsKey(key))) { yield return child.Data[key] as IEnumerable ?? ConvertToDouble(child, key).ToEnumerable(); } } } else throw new InvalidOperationException("key either contains a single key or a child/data key"); } private IEnumerable GetValues(RecursiveDataItem parent, string key) { if (parent == null || string.IsNullOrEmpty(key)) return Enumerable.Empty(); var tokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries); if (tokens.Length == 2) { var parentKey = tokens[0]; var dataKey = tokens[1]; if (parent.Name == parentKey) { return parent.Children .Where(child => child.Data.ContainsKey(dataKey)) .Select(child => ConvertToDouble(child, dataKey)); } return Enumerable.Empty(); } else { var item = parent; if (item == null || string.IsNullOrEmpty(key)) return Enumerable.Empty(); if (Enum.IsDefined(typeof(AxisDimension), key)) { var axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), key); return GetValue(item, axisDimension).ToEnumerable(); } else if (Enum.IsDefined(typeof(SizeDimension), key)) { var sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), key); return GetValue(item, sizeDimension).ToEnumerable(); } else if (item.Data.ContainsKey(key)) { return ConvertToDouble(item, key).ToEnumerable(); } else { return Enumerable.Empty(); } } } private double ConvertToDouble(RecursiveDataItem item, string key) { IItem value = item.Data[key]; var doubleValue = value as DoubleValue; var intValue = value as IntValue; var timeSpanValue = value as TimeSpanValue; double? ret = null; if (doubleValue != null) { if (!double.IsNaN(doubleValue.Value) && !double.IsInfinity(doubleValue.Value)) ret = doubleValue.Value; } else if (intValue != null) ret = intValue.Value; else if (timeSpanValue != null) ret = timeSpanValue.Value.TotalSeconds; else ret = GetCategoricalValue(key, value.ToString()); return ret.Value; } private double? GetCategoricalValue(string key, string value) { if (!categoricalMapping.ContainsKey(key)) { categoricalMapping[key] = new Dictionary(); var orderedCategories = GetAvailableItems().Where(x => x.Data.ContainsKey(key)).Select(x => x.Data[key].ToString()) .Distinct().OrderBy(x => x, new NaturalStringComparer()); int count = 1; foreach (var category in orderedCategories) { categoricalMapping[key].Add(category, count); count++; } } if (!this.categoricalMapping[key].ContainsKey(value)) return null; return this.categoricalMapping[key][value]; } private double GetValue(RecursiveDataItem item, AxisDimension axisDimension) { double value = double.NaN; switch (axisDimension) { case AxisDimension.Index: value = itemToIndexMapping[item]; break; default: throw new ArgumentException("No handling strategy for " + axisDimension.ToString() + " is defined."); } return value; } private double GetValue(RecursiveDataItem item, SizeDimension sizeDimension) { double value = double.NaN; switch (sizeDimension) { case SizeDimension.Constant: value = 5; break; default: throw new ArgumentException("No handling strategy for " + sizeDimension.ToString() + " is defined."); } return value; } private void RebuildInverseIndex() { if (Content != null) { itemToIndexMapping.Clear(); int i = 0; foreach (var item in GetAvailableItems()) { // ToDo: do not add if key (which one?) is not present within item itemToIndexMapping.Add(item, i); i++; } } } private void UpdateAxisLabels() { Axis xAxis = chart.ChartAreas[0].AxisX; Axis yAxis = chart.ChartAreas[0].AxisY; //mkommend: combobox.SelectedIndex could not be used as this changes during hovering over possible values var xSAxisSelected = XAxisValue == null ? null : (string)xAxisComboBox.SelectedItem; var ySAxisSelected = YAxisValue == null ? null : (string)yAxisComboBox.SelectedItem; SetCustomAxisLabels(xAxis, xSAxisSelected); SetCustomAxisLabels(yAxis, ySAxisSelected); if (XAxisValue != null) xAxis.Title = XAxisValue; if (YAxisValue != null) yAxis.Title = YAxisValue; } private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) { this.UpdateAxisLabels(); } private void SetCustomAxisLabels(Axis axis, string key) { if (key == null) return; var tokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries); if (tokens.Length == 2) key = tokens[1]; axis.CustomLabels.Clear(); if (categoricalMapping.ContainsKey(key)) { foreach (var pair in categoricalMapping[key]) { string labelText = pair.Key.ToString(); CustomLabel label = new CustomLabel(); label.ToolTip = labelText; if (labelText.Length > 25) labelText = labelText.Substring(0, 25) + " ... "; label.Text = labelText; label.GridTicks = GridTickTypes.TickMark; label.FromPosition = pair.Value - 0.5; label.ToPosition = pair.Value + 0.5; axis.CustomLabels.Add(label); } } else if (Content.Data.ContainsKey(key) && Content.Data[key] is TimeSpanValue) { // TODO chart.ChartAreas[0].RecalculateAxesScale(); for (double i = axis.Minimum; i <= axis.Maximum; i += axis.LabelStyle.Interval) { TimeSpan time = TimeSpan.FromSeconds(i); string x = string.Format("{0:00}:{1:00}:{2:00}", time.Hours, time.Minutes, time.Seconds); axis.CustomLabels.Add(i - axis.LabelStyle.Interval / 2, i + axis.LabelStyle.Interval / 2, x); } } } private void UpdateMarkerSizes() { var series = chart.Series[0]; if (series.Points.Count <= 0) return; var sizeValues = series.Points.Select(p => p.YValues[1]); double minSizeValue = sizeValues.Min(); double maxSizeValue = sizeValues.Max(); double sizeRange = maxSizeValue - minSizeValue; const int smallestBubbleSize = 5; foreach (DataPoint point in series.Points) { //calculates the relative size of the data point 0 <= relativeSize <= 1 double relativeSize = (point.YValues[1] - minSizeValue); if (sizeRange > double.Epsilon) { relativeSize /= sizeRange; //invert bubble sizes if the value of the trackbar is negative if (sizeTrackBar.Value < 0) relativeSize = Math.Abs(relativeSize - 1); } else relativeSize = 1; double sizeChange = Math.Abs(sizeTrackBar.Value) * relativeSize; point.MarkerSize = (int)Math.Round(sizeChange + smallestBubbleSize); } } private void UpdateDataPointJitter() { var xAxis = this.chart.ChartAreas[0].AxisX; var yAxis = this.chart.ChartAreas[0].AxisY; double xAxisRange = xAxis.Maximum - xAxis.Minimum; double yAxisRange = yAxis.Maximum - yAxis.Minimum; foreach (DataPoint point in chart.Series[0].Points) { var tag = (Tuple)point.Tag; var xItem = tag.Item3; var yItem = tag.Item4; double xValue = tag.Item1; double yValue = tag.Item2; if (!xJitterFactor.IsAlmost(0.0)) xValue += 0.1 * GetXJitter(xItem) * xJitterFactor * (xAxisRange); if (!yJitterFactor.IsAlmost(0.0)) yValue += 0.1 * GetYJitter(yItem) * yJitterFactor * (yAxisRange); point.XValue = xValue; point.YValues[0] = yValue; } if (xJitterFactor.IsAlmost(0.0) && yJitterFactor.IsAlmost(0.0)) { SetAutomaticUpdateOfAxis(xAxis, true); SetAutomaticUpdateOfAxis(yAxis, true); chart.ChartAreas[0].RecalculateAxesScale(); } else { SetAutomaticUpdateOfAxis(xAxis, false); SetAutomaticUpdateOfAxis(yAxis, false); } } private double GetXJitter(RecursiveDataItem item) { if (!this.xJitter.ContainsKey(item)) this.xJitter[item] = random.NextDouble() * 2.0 - 1.0; return this.xJitter[item]; } private double GetYJitter(RecursiveDataItem item) { if (!this.yJitter.ContainsKey(item)) this.yJitter[item] = random.NextDouble() * 2.0 - 1.0; return this.yJitter[item]; } // sets an axis to automatic or restrains it to its current values // this is used that none of the set values is changed when jitter is applied, so that the chart stays the same private void SetAutomaticUpdateOfAxis(Axis axis, bool enabled) { if (enabled) { axis.Maximum = double.NaN; axis.Minimum = double.NaN; axis.MajorGrid.Interval = double.NaN; axis.MajorTickMark.Interval = double.NaN; axis.LabelStyle.Interval = double.NaN; } else { axis.Minimum = axis.Minimum; axis.Maximum = axis.Maximum; axis.MajorGrid.Interval = axis.MajorGrid.Interval; axis.MajorTickMark.Interval = axis.MajorTickMark.Interval; axis.LabelStyle.Interval = axis.LabelStyle.Interval; } } private void UpdateCursorInterval() { double xMin = double.MaxValue; double xMax = double.MinValue; double yMin = double.MaxValue; double yMax = double.MinValue; foreach (var point in chart.Series[0].Points) { if (point.IsEmpty) continue; if (point.XValue < xMin) xMin = point.XValue; if (point.XValue > xMax) xMax = point.XValue; if (point.YValues[0] < yMin) yMin = point.YValues[0]; if (point.YValues[0] > yMax) yMax = point.YValues[0]; } double xRange = 0.0; double yRange = 0.0; if (xMin != double.MaxValue && xMax != double.MinValue) xRange = xMax - xMin; if (yMin != double.MaxValue && yMax != double.MinValue) yRange = yMax - yMin; if (xRange.IsAlmost(0.0)) xRange = 1.0; if (yRange.IsAlmost(0.0)) yRange = 1.0; double xDigits = (int)Math.Log10(xRange) - 3; double yDigits = (int)Math.Log10(yRange) - 3; double xZoomInterval = Math.Pow(10, xDigits); double yZoomInterval = Math.Pow(10, yDigits); this.chart.ChartAreas[0].CursorX.Interval = xZoomInterval; this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval; // TODO //code to handle TimeSpanValues correct //int axisDimensionCount = Enum.GetNames(typeof(AxisDimension)).Count(); //int columnIndex = xAxisComboBox.SelectedIndex - axisDimensionCount; //if (columnIndex >= 0 && Content.GetValue(0, columnIndex) is TimeSpanValue) // this.chart.ChartAreas[0].CursorX.Interval = 1; //columnIndex = yAxisComboBox.SelectedIndex - axisDimensionCount; //if (columnIndex >= 0 && Content.GetValue(0, columnIndex) is TimeSpanValue) // this.chart.ChartAreas[0].CursorY.Interval = 1; } private void chart_MouseMove(object sender, MouseEventArgs e) { if (Control.MouseButtons != MouseButtons.None) return; HitTestResult h = this.chart.HitTest(e.X, e.Y); string newTooltipText = string.Empty; string oldTooltipText; if (h.ChartElementType == ChartElementType.DataPoint) { var tag = (Tuple)((DataPoint)h.Object).Tag; newTooltipText = BuildTooltip(tag); } else if (h.ChartElementType == ChartElementType.AxisLabels) { newTooltipText = ((CustomLabel)h.Object).ToolTip; } oldTooltipText = this.tooltip.GetToolTip(chart); if (newTooltipText != oldTooltipText) this.tooltip.SetToolTip(chart, newTooltipText); } private string BuildTooltip(Tuple tag) { string tooltip; if (tag.Item3 != tag.Item4) tooltip = "X: " + tag.Item3.Name + Environment.NewLine + "Y: " + tag.Item4.Name + Environment.NewLine; else tooltip = tag.Item3.Name + Environment.NewLine; double? xValue = tag.Item1; double? yValue = tag.Item2; //double? sizeValue = this.GetValue(run, (string)sizeComboBox.SelectedItem); string xString = xValue == null ? string.Empty : xValue.Value.ToString(); string yString = yValue == null ? string.Empty : yValue.Value.ToString(); //string sizeString = sizeValue == null ? string.Empty : sizeValue.Value.ToString(); // TODO ////code to handle TimeSpanValues correct //int axisDimensionCount = Enum.GetNames(typeof(AxisDimension)).Count(); //int columnIndex = xAxisComboBox.SelectedIndex - axisDimensionCount; //if (xValue.HasValue && columnIndex > 0 && Content.GetValue(0, columnIndex) is TimeSpanValue) { // TimeSpan time = TimeSpan.FromSeconds(xValue.Value); // xString = string.Format("{0:00}:{1:00}:{2:00.00}", (int)time.TotalHours, time.Minutes, time.Seconds); //} //columnIndex = yAxisComboBox.SelectedIndex - axisDimensionCount; //if (yValue.HasValue && columnIndex > 0 && Content.GetValue(0, columnIndex) is TimeSpanValue) { // TimeSpan time = TimeSpan.FromSeconds(yValue.Value); // yString = string.Format("{0:00}:{1:00}:{2:00.00}", (int)time.TotalHours, time.Minutes, time.Seconds); //} tooltip += xAxisComboBox.SelectedItem + " : " + xString + Environment.NewLine; tooltip += yAxisComboBox.SelectedItem + " : " + yString + Environment.NewLine; //tooltip += sizeComboBox.SelectedItem + " : " + sizeString + Environment.NewLine; return tooltip; } private void chart_MouseDoubleClick(object sender, MouseEventArgs e) { HitTestResult h = this.chart.HitTest(e.X, e.Y, ChartElementType.DataPoint); if (h.ChartElementType == ChartElementType.DataPoint) { var tuple = (Tuple)((DataPoint)h.Object).Tag; IContentView view = MainFormManager.MainForm.ShowContent(tuple.Item3, typeof(RecursiveDataItemView)); if (view != null) { view.ReadOnly = this.ReadOnly; view.Locked = this.Locked; } if (tuple.Item3 != tuple.Item4) { view = MainFormManager.MainForm.ShowContent(tuple.Item4, typeof(RecursiveDataItemView)); if (view != null) { view.ReadOnly = this.ReadOnly; view.Locked = this.Locked; } } this.chart.ChartAreas[0].CursorX.SelectionStart = this.chart.ChartAreas[0].CursorX.SelectionEnd; this.chart.ChartAreas[0].CursorY.SelectionStart = this.chart.ChartAreas[0].CursorY.SelectionEnd; } UpdateAxisLabels(); } #endregion #region Event Handlers private void treeView_AfterCheck(object sender, TreeViewEventArgs e) { if (updating) return; updating = true; UpdateComboBoxes(); UpdateDataPoints(); updating = false; } private void treeView_AfterSelect(object sender, TreeViewEventArgs e) { //UpdateNodeColors(); //UpdateComboBoxes(); //UpdateDataPoints(); } private void axisComboBox_SelectedValueChanged(object sender, EventArgs e) { if (updating) return; updating = true; UpdateDataPoints(); UpdateAxisLabels(); updating = false; } private void includeChildrenCheckBox_CheckedChanged(object sender, EventArgs e) { if (updating) return; updating = true; UpdateTreeViewCheckBoxes(); UpdateComboBoxes(); updating = false; } private void levelNumericUpDown_ValueChanged(object sender, EventArgs e) { if (updating) return; updating = true; UpdateTreeViewCheckBoxes(); UpdateComboBoxes(); updating = false; } private void sizeTrackBar_ValueChanged(object sender, EventArgs e) { UpdateMarkerSizes(); } private void jitterTrackBar_ValueChanged(object sender, EventArgs e) { this.xJitterFactor = xJitterTrackBar.Value / 100.0; this.yJitterFactor = yJitterTrackBar.Value / 100.0; UpdateDataPointJitter(); } #endregion #region Helpers private IEnumerable IterateAllNodes() { return Collect(treeView.Nodes); } private IEnumerable IterateCheckedNodes() { return IterateAllNodes().Where(t => t.Checked); } private IEnumerable Collect(TreeNodeCollection nodes) { foreach (TreeNode node in nodes) { yield return node; foreach (var child in Collect(node.Nodes)) yield return child; } } #endregion } }