Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2379 Fixed wrong keys for categorical mapping.

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