Free cookie consent management tool by TermsFeed Policy Generator

source: branches/BubbleChart/HeuristicLab.Optimization.BubbleChart/3.3/BubbleChartView.cs @ 12499

Last change on this file since 12499 was 12499, checked in by pfleck, 9 years ago

#2379

  • Quick-fixed a critical performance bug when changing the checked status of many items in the tree (e.g. via level change)
  • Added a new sample with alps runs.
File size: 13.4 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Windows.Forms;
26using System.Windows.Forms.DataVisualization.Charting;
27using HeuristicLab.Common;
28using HeuristicLab.Core;
29using HeuristicLab.Core.Views;
30using HeuristicLab.Data;
31using HeuristicLab.MainForm;
32
33namespace HeuristicLab.Optimization.BubbleChart {
34  [View("Bubble Chart (Recursive)")]
35  [Content(typeof(RecursiveDataItem), false)]
36  public partial class BubbleChartView : ItemView {
37    private enum SizeDimension { Constant = 0 }
38    private enum AxisDimension { Index = 0 }
39
40    private readonly Dictionary<RecursiveDataItem, int> itemToIndexMapping = new Dictionary<RecursiveDataItem, int>();
41    private readonly Dictionary<string, Dictionary<object, double>> categoricalMapping = new Dictionary<string, Dictionary<object, double>>();
42
43    private string XAxisValue { get { return (string)xAxisComboBox.SelectedItem; } }
44    private string YAxisValue { get { return (string)yAxisComboBox.SelectedItem; } }
45    private string SizeAxisValue { get { return (string)sizeComboBox.SelectedItem; } }
46
47    private bool updating = false;
48
49    public new RecursiveDataItem Content {
50      get { return (RecursiveDataItem)base.Content; }
51      set { base.Content = value; }
52    }
53
54    public BubbleChartView() {
55      InitializeComponent();
56    }
57
58    protected override void OnContentChanged() {
59      base.OnContentChanged();
60
61      UpdateTreeView();
62      UpdateLevelControl();
63
64      UpdateComboBoxes();
65      UpdateDataPoints();
66      //UpdateCaption();
67    }
68    protected override void SetEnabledStateOfControls() {
69      base.SetEnabledStateOfControls();
70      levelNumericUpDown.Enabled = Content != null;
71      // ToDo
72    }
73
74    #region Tree Queries
75    private IEnumerable<RecursiveDataItem> GetAvailableItems() {
76      return IterateCheckedNodes()
77        .Select(n => (RecursiveDataItem)n.Tag);
78    }
79    private IEnumerable<string> GetAvailableKeys() {
80      return GetAvailableItems()
81        .SelectMany(n => n.Data.Keys)
82        .Distinct();
83    }
84    #endregion
85
86    #region Update Controls
87    private void UpdateLevelControl() {
88      if (Content == null) return;
89      if (treeView.Nodes.Count > 0)
90        levelNumericUpDown.Maximum = IterateAllNodes().Max(t => t.Level);
91      else
92        levelNumericUpDown.Maximum = 0;
93    }
94
95    private void UpdateTreeView() {
96      treeView.Nodes.Clear();
97      if (Content != null)
98        treeView.Nodes.Add(CreateTreeNode(Content));
99      treeView.ExpandAll();
100      splitContainer.Panel1Collapsed = treeView.Nodes.Count == 0;
101      if (treeView.Nodes.Count > 0)
102        treeView.SelectedNode = treeView.Nodes[0];
103    }
104    private TreeNode CreateTreeNode(RecursiveDataItem item) {
105      var node = new TreeNode(item.Name) {
106        Tag = item,
107        Checked = true
108      };
109      foreach (var child in item.Children)
110        node.Nodes.Add(CreateTreeNode(child));
111      return node;
112    }
113
114    private void UpdateTreeViewCheckBoxes() {
115      int level = (int)levelNumericUpDown.Value;
116      bool includeChildren = includeChildrenCheckBox.Checked;
117      foreach (var node in IterateAllNodes()) {
118        bool @checked = includeChildren ? node.Level >= level : node.Level == level;
119        if (node.Checked != @checked)
120          node.Checked = @checked;
121      }
122    }
123
124    private void UpdateComboBoxes() {
125      var selectedXAxis = (string)xAxisComboBox.SelectedItem;
126      var selectedYAxis = (string)yAxisComboBox.SelectedItem;
127      var selectedSizeAxis = (string)sizeComboBox.SelectedItem;
128      xAxisComboBox.Items.Clear();
129      yAxisComboBox.Items.Clear();
130      sizeComboBox.Items.Clear();
131
132      if (Content != null) {
133        var axisNames = GetAvailableKeys().ToArray();
134        var additionalAxisDimension = Enum.GetNames(typeof(AxisDimension));
135        var additionalSizeDimension = Enum.GetNames(typeof(SizeDimension));
136
137        xAxisComboBox.Items.AddRange(additionalAxisDimension);
138        xAxisComboBox.Items.AddRange(axisNames);
139        yAxisComboBox.Items.AddRange(additionalAxisDimension);
140        yAxisComboBox.Items.AddRange(axisNames);
141        sizeComboBox.Items.AddRange(additionalSizeDimension);
142        sizeComboBox.Items.AddRange(axisNames);
143
144        bool changed = false;
145        if (selectedXAxis != null && xAxisComboBox.Items.Contains(selectedXAxis)) {
146          xAxisComboBox.SelectedItem = selectedXAxis;
147          changed = true;
148        }
149        if (selectedYAxis != null && yAxisComboBox.Items.Contains(selectedYAxis)) {
150          yAxisComboBox.SelectedItem = selectedYAxis;
151          changed = true;
152        }
153        if (selectedSizeAxis != null && sizeComboBox.Items.Contains(selectedSizeAxis)) {
154          sizeComboBox.SelectedItem = selectedSizeAxis;
155          changed = true;
156        } else sizeComboBox.SelectedItem = SizeDimension.Constant.ToString();
157        if (changed) {
158          UpdateDataPoints();
159          UpdateAxisLabels();
160        }
161      }
162    }
163
164    private void UpdateDataPoints() {
165      var series = chart.Series[0];
166      series.Points.Clear();
167      itemToIndexMapping.Clear();
168      categoricalMapping.Clear();
169      RebuildInverseIndex();
170
171      chart.ChartAreas[0].AxisX.IsMarginVisible = XAxisValue != AxisDimension.Index.ToString();
172      chart.ChartAreas[0].AxisY.IsMarginVisible = YAxisValue != AxisDimension.Index.ToString();
173
174      if (Content != null) {
175        var items = GetAvailableItems();
176        foreach (var item in items) {
177          var x = GetValue(item, XAxisValue);
178          var y = GetValue(item, YAxisValue);
179          var s = GetValue(item, SizeAxisValue);
180          if (x.HasValue && y.HasValue && s.HasValue) {
181            var dataPoint = new DataPoint(x.Value, new[] { y.Value, s.Value });
182            series.Points.Add(dataPoint);
183          }
184        }
185      }
186    }
187    private double? GetValue(RecursiveDataItem item, string key) {
188      if (item == null || string.IsNullOrEmpty(key))
189        return null;
190
191      if (Enum.IsDefined(typeof(AxisDimension), key)) {
192        var axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), key);
193        return GetValue(item, axisDimension);
194      } else if (Enum.IsDefined(typeof(SizeDimension), key)) {
195        var sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), key);
196        return GetValue(item, sizeDimension);
197      } else if (item.Data.ContainsKey(key)) {
198        IItem value = item.Data[key];
199        var doubleValue = value as DoubleValue;
200        var intValue = value as IntValue;
201        var timeSpanValue = value as TimeSpanValue;
202        double? ret = null;
203        if (doubleValue != null) {
204          if (!double.IsNaN(doubleValue.Value) && !double.IsInfinity(doubleValue.Value))
205            ret = doubleValue.Value;
206        } else if (intValue != null)
207          ret = intValue.Value;
208        else if (timeSpanValue != null)
209          ret = timeSpanValue.Value.TotalSeconds;
210        else
211          ret = GetCategoricalValue(item, key, value.ToString());
212        return ret;
213      } else {
214        return null;
215      }
216    }
217    private double? GetCategoricalValue(RecursiveDataItem item, string key, string value) {
218      if (!categoricalMapping.ContainsKey(key)) {
219        categoricalMapping[key] = new Dictionary<object, double>();
220        var orderedCategories =
221          GetAvailableItems().Where(x => x.Data.ContainsKey(key)).Select(x => x.Data[key].ToString())
222            .Distinct().OrderBy(x => x, new NaturalStringComparer());
223        int count = 1;
224        foreach (var category in orderedCategories) {
225          categoricalMapping[key].Add(category, count);
226          count++;
227        }
228      }
229      if (!this.categoricalMapping[key].ContainsKey(value)) return null;
230      return this.categoricalMapping[key][value];
231    }
232    private double GetValue(RecursiveDataItem item, AxisDimension axisDimension) {
233      double value = double.NaN;
234      switch (axisDimension) {
235        case AxisDimension.Index:
236          value = itemToIndexMapping[item];
237          break;
238        default:
239          throw new ArgumentException("No handling strategy for " + axisDimension.ToString() + " is defined.");
240      }
241      return value;
242    }
243    private double GetValue(RecursiveDataItem item, SizeDimension sizeDimension) {
244      double value = double.NaN;
245      switch (sizeDimension) {
246        case SizeDimension.Constant:
247          value = 5;
248          break;
249        default:
250          throw new ArgumentException("No handling strategy for " + sizeDimension.ToString() + " is defined.");
251      }
252      return value;
253    }
254    private void RebuildInverseIndex() {
255      if (Content != null) {
256        itemToIndexMapping.Clear();
257        int i = 0;
258        foreach (var item in GetAvailableItems()) {
259          // ToDo: do not add if key (which one?) is not present within item
260          itemToIndexMapping.Add(item, i);
261          i++;
262        }
263      }
264    }
265
266    private void UpdateAxisLabels() {
267      Axis xAxis = chart.ChartAreas[0].AxisX;
268      Axis yAxis = chart.ChartAreas[0].AxisY;
269      //mkommend: combobox.SelectedIndex could not be used as this changes during hovering over possible values
270      var xSAxisSelected = XAxisValue == null ? null : (string)xAxisComboBox.SelectedItem;
271      var ySAxisSelected = YAxisValue == null ? null : (string)yAxisComboBox.SelectedItem;
272      SetCustomAxisLabels(xAxis, xSAxisSelected);
273      SetCustomAxisLabels(yAxis, ySAxisSelected);
274      if (XAxisValue != null)
275        xAxis.Title = XAxisValue;
276      if (YAxisValue != null)
277        yAxis.Title = YAxisValue;
278    }
279    private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) {
280      this.UpdateAxisLabels();
281    }
282    private void SetCustomAxisLabels(Axis axis, string key) {
283      axis.CustomLabels.Clear();
284      if (key != null && categoricalMapping.ContainsKey(key)) {
285        foreach (var pair in categoricalMapping[key]) {
286          string labelText = pair.Key.ToString();
287          CustomLabel label = new CustomLabel();
288          label.ToolTip = labelText;
289          if (labelText.Length > 25)
290            labelText = labelText.Substring(0, 25) + " ... ";
291          label.Text = labelText;
292          label.GridTicks = GridTickTypes.TickMark;
293          label.FromPosition = pair.Value - 0.5;
294          label.ToPosition = pair.Value + 0.5;
295          axis.CustomLabels.Add(label);
296        }
297      } else if (false && key != null && Content.Data[key] is TimeSpanValue) { // TODO
298        chart.ChartAreas[0].RecalculateAxesScale();
299        for (double i = axis.Minimum; i <= axis.Maximum; i += axis.LabelStyle.Interval) {
300          TimeSpan time = TimeSpan.FromSeconds(i);
301          string x = string.Format("{0:00}:{1:00}:{2:00}", time.Hours, time.Minutes, time.Seconds);
302          axis.CustomLabels.Add(i - axis.LabelStyle.Interval / 2, i + axis.LabelStyle.Interval / 2, x);
303        }
304      }
305    }
306    #endregion
307
308    #region Event Handlers
309    private void treeView_AfterCheck(object sender, TreeViewEventArgs e) {
310      if (updating) return;
311      updating = true;
312      UpdateComboBoxes();
313      UpdateDataPoints();
314      updating = false;
315    }
316    private void treeView_AfterSelect(object sender, TreeViewEventArgs e) {
317      //UpdateNodeColors();
318      //UpdateComboBoxes();
319      //UpdateDataPoints();
320    }
321
322    private void axisComboBox_SelectedValueChanged(object sender, EventArgs e) {
323      if (updating) return;
324      updating = true;
325      UpdateDataPoints();
326      UpdateAxisLabels();
327      updating = false;
328    }
329
330    private void includeChildrenCheckBox_CheckedChanged(object sender, EventArgs e) {
331      if (updating) return;
332      updating = true;
333      UpdateTreeViewCheckBoxes();
334      UpdateComboBoxes();
335      updating = false;
336    }
337
338    private void levelNumericUpDown_ValueChanged(object sender, EventArgs e) {
339      if (updating) return;
340      updating = true;
341      UpdateTreeViewCheckBoxes();
342      UpdateComboBoxes();
343      updating = false;
344    }
345    #endregion
346
347    #region Helpers
348    private IEnumerable<TreeNode> IterateAllNodes() {
349      return Collect(treeView.Nodes);
350    }
351    private IEnumerable<TreeNode> IterateCheckedNodes() {
352      return IterateAllNodes().Where(t => t.Checked);
353    }
354    private IEnumerable<TreeNode> Collect(TreeNodeCollection nodes) {
355      foreach (TreeNode node in nodes) {
356        yield return node;
357        foreach (var child in Collect(node.Nodes))
358          yield return child;
359      }
360    }
361    #endregion
362  }
363}
Note: See TracBrowser for help on using the repository browser.