Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2379
Adapted dimension matching for NSGA-II Sample.
Changed default views.

File size: 19.1 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;
24using System.Collections.Generic;
25using System.Linq;
26using System.Windows.Forms;
27using System.Windows.Forms.DataVisualization.Charting;
28using HeuristicLab.Common;
29using HeuristicLab.Core;
30using HeuristicLab.Core.Views;
31using HeuristicLab.Data;
32using HeuristicLab.MainForm;
33
34namespace HeuristicLab.Optimization.BubbleChart {
35  [View("Bubble Chart (Recursive)")]
36  [Content(typeof(RecursiveDataItem), true)]
37  public partial class BubbleChartView : ItemView {
38    private const string Separator = ":";
39
40    private enum SizeDimension { Constant = 0 }
41    private enum AxisDimension { Index = 0 }
42
43    private readonly Dictionary<RecursiveDataItem, int> itemToIndexMapping = new Dictionary<RecursiveDataItem, int>();
44    private readonly Dictionary<string, Dictionary<object, double>> categoricalMapping = new Dictionary<string, Dictionary<object, double>>();
45
46    private string XAxisValue { get { return (string)xAxisComboBox.SelectedItem; } }
47    private string YAxisValue { get { return (string)yAxisComboBox.SelectedItem; } }
48    private string SizeAxisValue { get { return (string)sizeComboBox.SelectedItem; } }
49
50    private bool updating = false;
51
52    public new RecursiveDataItem Content {
53      get { return (RecursiveDataItem)base.Content; }
54      set { base.Content = value; }
55    }
56
57    public BubbleChartView() {
58      InitializeComponent();
59
60      chart.CustomizeAllChartAreas();
61      chart.ChartAreas[0].CursorX.Interval = 1;
62      chart.ChartAreas[0].CursorY.Interval = 1;
63      chart.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
64      chart.ChartAreas[0].AxisY.ScaleView.Zoomable = true;
65    }
66
67    protected override void OnContentChanged() {
68      base.OnContentChanged();
69
70      UpdateTreeView();
71      UpdateLevelControl();
72
73      UpdateComboBoxes();
74      UpdateDataPoints();
75      //UpdateCaption();
76    }
77    protected override void SetEnabledStateOfControls() {
78      base.SetEnabledStateOfControls();
79      levelNumericUpDown.Enabled = Content != null;
80      // ToDo
81    }
82
83    #region Tree Queries
84    private IEnumerable<RecursiveDataItem> GetAvailableItems() {
85      return IterateCheckedNodes()
86        .Select(n => (RecursiveDataItem)n.Tag);
87    }
88    private IEnumerable<string> GetAvailableKeys() {
89      var collector = new List<string>();
90      GetAvailableKeys(Content, collector);
91      return collector.Distinct();
92    }
93    private void GetAvailableKeys(RecursiveDataItem node, List<string> collector) {
94      collector.AddRange(node.Data.Keys);
95
96      foreach (var child in node.Children)
97        GetAvailableKeys(child, collector);
98    }
99    #endregion
100
101    #region Update Controls
102    private void UpdateLevelControl() {
103      if (Content == null) return;
104      if (treeView.Nodes.Count > 0)
105        levelNumericUpDown.Maximum = IterateAllNodes().Max(t => t.Level);
106      else
107        levelNumericUpDown.Maximum = 0;
108    }
109
110    private void UpdateTreeView() {
111      treeView.Nodes.Clear();
112      if (Content != null)
113        treeView.Nodes.Add(CreateTreeNode(Content));
114      treeView.ExpandAll();
115      splitContainer.Panel1Collapsed = treeView.Nodes.Count == 0;
116      if (treeView.Nodes.Count > 0)
117        treeView.SelectedNode = treeView.Nodes[0];
118    }
119    private TreeNode CreateTreeNode(RecursiveDataItem item) {
120      var node = new TreeNode(item.Name) {
121        Tag = item,
122        Checked = true
123      };
124      foreach (var child in item.Children)
125        node.Nodes.Add(CreateTreeNode(child));
126      return node;
127    }
128
129    private void UpdateTreeViewCheckBoxes() {
130      int level = (int)levelNumericUpDown.Value;
131      bool includeChildren = includeChildrenCheckBox.Checked;
132      foreach (var node in IterateAllNodes()) {
133        bool @checked = includeChildren ? node.Level >= level : node.Level == level;
134        if (node.Checked != @checked)
135          node.Checked = @checked;
136      }
137    }
138
139    private void UpdateComboBoxes() {
140      var selectedXAxis = (string)xAxisComboBox.SelectedItem;
141      var selectedYAxis = (string)yAxisComboBox.SelectedItem;
142      var selectedSizeAxis = (string)sizeComboBox.SelectedItem;
143      xAxisComboBox.Items.Clear();
144      yAxisComboBox.Items.Clear();
145      sizeComboBox.Items.Clear();
146
147      if (Content != null) {
148        var axisNames = GetAvailableKeys().ToArray();
149        var additionalAxisDimension = Enum.GetNames(typeof(AxisDimension));
150        var additionalSizeDimension = Enum.GetNames(typeof(SizeDimension));
151
152        //xAxisComboBox.Items.AddRange(additionalAxisDimension);
153        xAxisComboBox.Items.AddRange(axisNames);
154        //yAxisComboBox.Items.AddRange(additionalAxisDimension);
155        yAxisComboBox.Items.AddRange(axisNames);
156        sizeComboBox.Items.AddRange(additionalSizeDimension);
157        sizeComboBox.Items.AddRange(axisNames);
158
159        bool changed = false;
160        if (selectedXAxis != null && xAxisComboBox.Items.Contains(selectedXAxis)) {
161          xAxisComboBox.SelectedItem = selectedXAxis;
162          changed = true;
163        }
164        if (selectedYAxis != null && yAxisComboBox.Items.Contains(selectedYAxis)) {
165          yAxisComboBox.SelectedItem = selectedYAxis;
166          changed = true;
167        }
168        if (selectedSizeAxis != null && sizeComboBox.Items.Contains(selectedSizeAxis)) {
169          sizeComboBox.SelectedItem = selectedSizeAxis;
170          changed = true;
171        } else sizeComboBox.SelectedItem = SizeDimension.Constant.ToString();
172        if (changed) {
173          UpdateDataPoints();
174          UpdateAxisLabels();
175        }
176      }
177    }
178
179    private void UpdateDataPoints() {
180      var series = chart.Series[0];
181      series.Points.Clear();
182      itemToIndexMapping.Clear();
183      categoricalMapping.Clear();
184      RebuildInverseIndex();
185
186      chart.ChartAreas[0].AxisX.IsMarginVisible = !(XAxisValue != null && XAxisValue.Contains(AxisDimension.Index.ToString()));
187      chart.ChartAreas[0].AxisY.IsMarginVisible = !(YAxisValue != null && YAxisValue.Contains(AxisDimension.Index.ToString()));
188
189      if (Content == null || string.IsNullOrEmpty(XAxisValue) || string.IsNullOrEmpty(YAxisValue)) return;
190
191      var xss = GetValues(XAxisValue);
192      var yss = GetValues(YAxisValue);
193
194      if (xss.Count == yss.Count) {
195        for (int i = 0; i < xss.Count; i++)
196          AddPoints(xss[i], yss[i], series);
197      } else if (xss.Count == 1 || yss.Count == 1) {
198        for (int i = 0; i < xss.Count; i++)
199          for (int j = 0; j < yss.Count; j++)
200            AddPoints(xss[i], yss[j], series);
201      }
202    }
203    void AddPoints(double[] xs, double[] ys, Series series) {
204      if (xs.Length != ys.Length) return;
205      for (int k = 0; k < xs.Length; k++) {
206        series.Points.Add(new DataPoint(xs[k], new[] { ys[k], 1.0 }));
207      }
208    }
209
210    private List<double[]> GetValues(string key) {
211      var collector = new List<double[]>();
212      GetValues(Content, key, collector);
213      return collector;
214    }
215    private void GetValues(RecursiveDataItem node, string key, List<double[]> collector) {
216      IItem item;
217      if (node.Data.TryGetValue(key, out item)) {
218        var value = ConvertToDoubles(node, key);
219        collector.Add(value);
220      }
221
222      foreach (var child in node.Children)
223        GetValues(child, key, collector);
224    }
225
226    private double[] ConvertToDoubles(RecursiveDataItem item, string key) {
227      IItem value = item.Data[key];
228      var doubleValue = value as DoubleValue;
229      var doubleArray = value as DoubleArray;
230      var intValue = value as IntValue;
231      var intArray = value as IntArray;
232      var timeSpanValue = value as TimeSpanValue;
233      if (doubleValue != null) return new double[1] { doubleValue.Value };
234      if (intValue != null) return new double[1] { intValue.Value };
235      if (timeSpanValue != null) return new double[1] { timeSpanValue.Value.TotalSeconds };
236      if (doubleArray != null) return doubleArray.ToArray();
237      if (intArray != null) return intArray.Select(v => (double)v).ToArray();
238      return new double[1] { GetCategoricalValue(key, value.ToString()).Value };
239    }
240
241    private IEnumerable<IEnumerable<double>> GetValues(IEnumerable<RecursiveDataItem> items, string key) {
242      if (key == null) yield break;
243      var keyTokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
244      if (keyTokens.Length == 1) {
245        if (Enum.IsDefined(typeof(SizeDimension), key)) {
246          var sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), key);
247          yield return GetValue(null, sizeDimension).ToEnumerable();
248        } else
249          foreach (var item in items) {
250            if (Enum.IsDefined(typeof(AxisDimension), key)) {
251              var axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), key);
252              yield return GetValue(item, axisDimension).ToEnumerable();
253            } else if (item.Data.ContainsKey(key)) {
254              yield return item.Data[key] as IEnumerable<double> ?? ConvertToDouble(item, key).ToEnumerable();
255            }
256          }
257      } else if (keyTokens.Length == 2) {
258        string parentName = keyTokens[0];
259        key = keyTokens[1];
260        foreach (var item in items.Where(item => item.Name == parentName)) {
261          foreach (var child in item.Children.Where(child => child.Data.ContainsKey(key))) {
262            yield return child.Data[key] as IEnumerable<double> ?? ConvertToDouble(child, key).ToEnumerable();
263          }
264        }
265      } else throw new InvalidOperationException("key either contains a single key or a child/data key");
266
267    }
268
269    private IEnumerable<double> GetValues(RecursiveDataItem parent, string key) {
270      if (parent == null || string.IsNullOrEmpty(key))
271        return Enumerable.Empty<double>();
272      var tokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
273      if (tokens.Length == 2) {
274        var parentKey = tokens[0];
275        var dataKey = tokens[1];
276        if (parent.Name == parentKey) {
277          return parent.Children
278            .Where(child => child.Data.ContainsKey(dataKey))
279            .Select(child => ConvertToDouble(child, dataKey));
280        }
281        return Enumerable.Empty<double>();
282      } else {
283        var item = parent;
284        if (item == null || string.IsNullOrEmpty(key))
285          return Enumerable.Empty<double>();
286
287        if (Enum.IsDefined(typeof(AxisDimension), key)) {
288          var axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), key);
289          return GetValue(item, axisDimension).ToEnumerable();
290        } else if (Enum.IsDefined(typeof(SizeDimension), key)) {
291          var sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), key);
292          return GetValue(item, sizeDimension).ToEnumerable();
293        } else if (item.Data.ContainsKey(key)) {
294
295          return ConvertToDouble(item, key).ToEnumerable();
296        } else {
297          return Enumerable.Empty<double>();
298        }
299      }
300    }
301
302    private double ConvertToDouble(RecursiveDataItem item, string key) {
303      IItem value = item.Data[key];
304      var doubleValue = value as DoubleValue;
305      var intValue = value as IntValue;
306      var timeSpanValue = value as TimeSpanValue;
307      double? ret = null;
308      if (doubleValue != null) {
309        if (!double.IsNaN(doubleValue.Value) && !double.IsInfinity(doubleValue.Value))
310          ret = doubleValue.Value;
311      } else if (intValue != null)
312        ret = intValue.Value;
313      else if (timeSpanValue != null)
314        ret = timeSpanValue.Value.TotalSeconds;
315      else
316        ret = GetCategoricalValue(key, value.ToString());
317      return ret.Value;
318    }
319
320    private double? GetCategoricalValue(string key, string value) {
321      if (!categoricalMapping.ContainsKey(key)) {
322        categoricalMapping[key] = new Dictionary<object, double>();
323        var orderedCategories =
324          GetAvailableItems().Where(x => x.Data.ContainsKey(key)).Select(x => x.Data[key].ToString())
325            .Distinct().OrderBy(x => x, new NaturalStringComparer());
326        int count = 1;
327        foreach (var category in orderedCategories) {
328          categoricalMapping[key].Add(category, count);
329          count++;
330        }
331      }
332      if (!this.categoricalMapping[key].ContainsKey(value)) return null;
333      return this.categoricalMapping[key][value];
334    }
335    private double GetValue(RecursiveDataItem item, AxisDimension axisDimension) {
336      double value = double.NaN;
337      switch (axisDimension) {
338        case AxisDimension.Index:
339          value = itemToIndexMapping[item];
340          break;
341        default:
342          throw new ArgumentException("No handling strategy for " + axisDimension.ToString() + " is defined.");
343      }
344      return value;
345    }
346    private double GetValue(RecursiveDataItem item, SizeDimension sizeDimension) {
347      double value = double.NaN;
348      switch (sizeDimension) {
349        case SizeDimension.Constant:
350          value = 5;
351          break;
352        default:
353          throw new ArgumentException("No handling strategy for " + sizeDimension.ToString() + " is defined.");
354      }
355      return value;
356    }
357    private void RebuildInverseIndex() {
358      if (Content != null) {
359        itemToIndexMapping.Clear();
360        int i = 0;
361        foreach (var item in GetAvailableItems()) {
362          // ToDo: do not add if key (which one?) is not present within item
363          itemToIndexMapping.Add(item, i);
364          i++;
365        }
366      }
367    }
368
369    private void UpdateAxisLabels() {
370      Axis xAxis = chart.ChartAreas[0].AxisX;
371      Axis yAxis = chart.ChartAreas[0].AxisY;
372      //mkommend: combobox.SelectedIndex could not be used as this changes during hovering over possible values
373      var xSAxisSelected = XAxisValue == null ? null : (string)xAxisComboBox.SelectedItem;
374      var ySAxisSelected = YAxisValue == null ? null : (string)yAxisComboBox.SelectedItem;
375      SetCustomAxisLabels(xAxis, xSAxisSelected);
376      SetCustomAxisLabels(yAxis, ySAxisSelected);
377      if (XAxisValue != null)
378        xAxis.Title = XAxisValue;
379      if (YAxisValue != null)
380        yAxis.Title = YAxisValue;
381    }
382    private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) {
383      this.UpdateAxisLabels();
384    }
385    private void SetCustomAxisLabels(Axis axis, string key) {
386      if (key == null) return;
387      var tokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
388      if (tokens.Length == 2)
389        key = tokens[1];
390      axis.CustomLabels.Clear();
391      if (categoricalMapping.ContainsKey(key)) {
392        foreach (var pair in categoricalMapping[key]) {
393          string labelText = pair.Key.ToString();
394          CustomLabel label = new CustomLabel();
395          label.ToolTip = labelText;
396          if (labelText.Length > 25)
397            labelText = labelText.Substring(0, 25) + " ... ";
398          label.Text = labelText;
399          label.GridTicks = GridTickTypes.TickMark;
400          label.FromPosition = pair.Value - 0.5;
401          label.ToPosition = pair.Value + 0.5;
402          axis.CustomLabels.Add(label);
403        }
404      } else if (Content.Data.ContainsKey(key) && Content.Data[key] is TimeSpanValue) { // TODO
405        chart.ChartAreas[0].RecalculateAxesScale();
406        for (double i = axis.Minimum; i <= axis.Maximum; i += axis.LabelStyle.Interval) {
407          TimeSpan time = TimeSpan.FromSeconds(i);
408          string x = string.Format("{0:00}:{1:00}:{2:00}", time.Hours, time.Minutes, time.Seconds);
409          axis.CustomLabels.Add(i - axis.LabelStyle.Interval / 2, i + axis.LabelStyle.Interval / 2, x);
410        }
411      }
412    }
413
414    private void UpdateMarkerSizes() {
415      var series = chart.Series[0];
416      if (series.Points.Count <= 0) return;
417
418      var sizeValues = series.Points.Select(p => p.YValues[1]);
419      double minSizeValue = sizeValues.Min();
420      double maxSizeValue = sizeValues.Max();
421      double sizeRange = maxSizeValue - minSizeValue;
422
423      const int smallestBubbleSize = 5;
424
425      foreach (DataPoint point in series.Points) {
426        //calculates the relative size of the data point  0 <= relativeSize <= 1
427        double relativeSize = (point.YValues[1] - minSizeValue);
428        if (sizeRange > double.Epsilon) {
429          relativeSize /= sizeRange;
430
431          //invert bubble sizes if the value of the trackbar is negative
432          if (sizeTrackBar.Value < 0) relativeSize = Math.Abs(relativeSize - 1);
433        } else relativeSize = 1;
434
435        double sizeChange = Math.Abs(sizeTrackBar.Value) * relativeSize;
436        point.MarkerSize = (int)Math.Round(sizeChange + smallestBubbleSize);
437      }
438    }
439    #endregion
440
441    #region Event Handlers
442    private void treeView_AfterCheck(object sender, TreeViewEventArgs e) {
443      if (updating) return;
444      updating = true;
445      UpdateComboBoxes();
446      UpdateDataPoints();
447      updating = false;
448    }
449    private void treeView_AfterSelect(object sender, TreeViewEventArgs e) {
450      //UpdateNodeColors();
451      //UpdateComboBoxes();
452      //UpdateDataPoints();
453    }
454
455    private void axisComboBox_SelectedValueChanged(object sender, EventArgs e) {
456      if (updating) return;
457      updating = true;
458      UpdateDataPoints();
459      UpdateAxisLabels();
460      updating = false;
461    }
462
463    private void includeChildrenCheckBox_CheckedChanged(object sender, EventArgs e) {
464      if (updating) return;
465      updating = true;
466      UpdateTreeViewCheckBoxes();
467      UpdateComboBoxes();
468      updating = false;
469    }
470
471    private void levelNumericUpDown_ValueChanged(object sender, EventArgs e) {
472      if (updating) return;
473      updating = true;
474      UpdateTreeViewCheckBoxes();
475      UpdateComboBoxes();
476      updating = false;
477    }
478
479    private void sizeTrackBar_ValueChanged(object sender, EventArgs e) {
480      UpdateMarkerSizes();
481    }
482    #endregion
483
484    #region Helpers
485    private IEnumerable<TreeNode> IterateAllNodes() {
486      return Collect(treeView.Nodes);
487    }
488    private IEnumerable<TreeNode> IterateCheckedNodes() {
489      return IterateAllNodes().Where(t => t.Checked);
490    }
491    private IEnumerable<TreeNode> Collect(TreeNodeCollection nodes) {
492      foreach (TreeNode node in nodes) {
493        yield return node;
494        foreach (var child in Collect(node.Nodes))
495          yield return child;
496      }
497    }
498    #endregion
499  }
500}
Note: See TracBrowser for help on using the repository browser.