Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2379 Added level/depth based selection for the datasource.
Updated sample.

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