Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2379 Added size track-bar similar to the original bubble-chart.

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