#region License Information /* HeuristicLab * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Core.Views; using HeuristicLab.Data; using HeuristicLab.MainForm; namespace HeuristicLab.Optimization.BubbleChart { [View("Bubble Chart (Recursive)")] [Content(typeof(RecursiveDataItem), false)] public partial class BubbleChartView : ItemView { private enum SizeDimension { Constant = 0 } private enum AxisDimension { Index = 0 } private readonly Dictionary itemToIndexMapping = new Dictionary(); private readonly Dictionary> categoricalMapping = new Dictionary>(); private string XAxisValue { get { return (string)xAxisComboBox.SelectedItem; } } private string YAxisValue { get { return (string)yAxisComboBox.SelectedItem; } } private string SizeAxisValue { get { return (string)sizeComboBox.SelectedItem; } } private bool updating = false; public new RecursiveDataItem Content { get { return (RecursiveDataItem)base.Content; } set { base.Content = value; } } public BubbleChartView() { InitializeComponent(); } protected override void OnContentChanged() { base.OnContentChanged(); UpdateTreeView(); UpdateLevelControl(); UpdateComboBoxes(); UpdateDataPoints(); //UpdateCaption(); } protected override void SetEnabledStateOfControls() { base.SetEnabledStateOfControls(); levelNumericUpDown.Enabled = Content != null; // ToDo } #region Tree Queries private IEnumerable GetAvailableItems() { return IterateCheckedNodes() .Select(n => (RecursiveDataItem)n.Tag); } private IEnumerable GetAvailableKeys() { return GetAvailableItems() .SelectMany(n => n.Data.Keys) .Distinct(); } #endregion #region Update Controls private void UpdateLevelControl() { if (Content == null) return; if (treeView.Nodes.Count > 0) levelNumericUpDown.Maximum = IterateAllNodes().Max(t => t.Level); else levelNumericUpDown.Maximum = 0; } private void UpdateTreeView() { treeView.Nodes.Clear(); if (Content != null) treeView.Nodes.Add(CreateTreeNode(Content)); treeView.ExpandAll(); splitContainer.Panel1Collapsed = treeView.Nodes.Count == 0; if (treeView.Nodes.Count > 0) treeView.SelectedNode = treeView.Nodes[0]; } private TreeNode CreateTreeNode(RecursiveDataItem item) { var node = new TreeNode(item.Name) { Tag = item, Checked = true }; foreach (var child in item.Children) node.Nodes.Add(CreateTreeNode(child)); return node; } private void UpdateTreeViewCheckBoxes() { int level = (int)levelNumericUpDown.Value; bool includeChildren = includeChildrenCheckBox.Checked; foreach (var node in IterateAllNodes()) { bool @checked = includeChildren ? node.Level >= level : node.Level == level; if (node.Checked != @checked) node.Checked = @checked; } } private void UpdateComboBoxes() { var selectedXAxis = (string)xAxisComboBox.SelectedItem; var selectedYAxis = (string)yAxisComboBox.SelectedItem; var selectedSizeAxis = (string)sizeComboBox.SelectedItem; xAxisComboBox.Items.Clear(); yAxisComboBox.Items.Clear(); sizeComboBox.Items.Clear(); if (Content != null) { var axisNames = GetAvailableKeys().ToArray(); var additionalAxisDimension = Enum.GetNames(typeof(AxisDimension)); var additionalSizeDimension = Enum.GetNames(typeof(SizeDimension)); xAxisComboBox.Items.AddRange(additionalAxisDimension); xAxisComboBox.Items.AddRange(axisNames); yAxisComboBox.Items.AddRange(additionalAxisDimension); yAxisComboBox.Items.AddRange(axisNames); sizeComboBox.Items.AddRange(additionalSizeDimension); sizeComboBox.Items.AddRange(axisNames); bool changed = false; if (selectedXAxis != null && xAxisComboBox.Items.Contains(selectedXAxis)) { xAxisComboBox.SelectedItem = selectedXAxis; changed = true; } if (selectedYAxis != null && yAxisComboBox.Items.Contains(selectedYAxis)) { yAxisComboBox.SelectedItem = selectedYAxis; changed = true; } if (selectedSizeAxis != null && sizeComboBox.Items.Contains(selectedSizeAxis)) { sizeComboBox.SelectedItem = selectedSizeAxis; changed = true; } else sizeComboBox.SelectedItem = SizeDimension.Constant.ToString(); if (changed) { UpdateDataPoints(); UpdateAxisLabels(); } } } private void UpdateDataPoints() { var series = chart.Series[0]; series.Points.Clear(); itemToIndexMapping.Clear(); categoricalMapping.Clear(); RebuildInverseIndex(); chart.ChartAreas[0].AxisX.IsMarginVisible = XAxisValue != AxisDimension.Index.ToString(); chart.ChartAreas[0].AxisY.IsMarginVisible = YAxisValue != AxisDimension.Index.ToString(); if (Content != null) { var items = GetAvailableItems(); foreach (var item in items) { var x = GetValue(item, XAxisValue); var y = GetValue(item, YAxisValue); var s = GetValue(item, SizeAxisValue); if (x.HasValue && y.HasValue && s.HasValue) { var dataPoint = new DataPoint(x.Value, new[] { y.Value, s.Value }); series.Points.Add(dataPoint); } } } } private double? GetValue(RecursiveDataItem item, string key) { if (item == null || string.IsNullOrEmpty(key)) return null; if (Enum.IsDefined(typeof(AxisDimension), key)) { var axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), key); return GetValue(item, axisDimension); } else if (Enum.IsDefined(typeof(SizeDimension), key)) { var sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), key); return GetValue(item, sizeDimension); } else if (item.Data.ContainsKey(key)) { IItem value = item.Data[key]; var doubleValue = value as DoubleValue; var intValue = value as IntValue; var timeSpanValue = value as TimeSpanValue; double? ret = null; if (doubleValue != null) { if (!double.IsNaN(doubleValue.Value) && !double.IsInfinity(doubleValue.Value)) ret = doubleValue.Value; } else if (intValue != null) ret = intValue.Value; else if (timeSpanValue != null) ret = timeSpanValue.Value.TotalSeconds; else ret = GetCategoricalValue(item, key, value.ToString()); return ret; } else { return null; } } private double? GetCategoricalValue(RecursiveDataItem item, string key, string value) { if (!categoricalMapping.ContainsKey(key)) { categoricalMapping[key] = new Dictionary(); var orderedCategories = GetAvailableItems().Where(x => x.Data.ContainsKey(key)).Select(x => x.Data[key].ToString()) .Distinct().OrderBy(x => x, new NaturalStringComparer()); int count = 1; foreach (var category in orderedCategories) { categoricalMapping[key].Add(category, count); count++; } } if (!this.categoricalMapping[key].ContainsKey(value)) return null; return this.categoricalMapping[key][value]; } private double GetValue(RecursiveDataItem item, AxisDimension axisDimension) { double value = double.NaN; switch (axisDimension) { case AxisDimension.Index: value = itemToIndexMapping[item]; break; default: throw new ArgumentException("No handling strategy for " + axisDimension.ToString() + " is defined."); } return value; } private double GetValue(RecursiveDataItem item, SizeDimension sizeDimension) { double value = double.NaN; switch (sizeDimension) { case SizeDimension.Constant: value = 5; break; default: throw new ArgumentException("No handling strategy for " + sizeDimension.ToString() + " is defined."); } return value; } private void RebuildInverseIndex() { if (Content != null) { itemToIndexMapping.Clear(); int i = 0; foreach (var item in GetAvailableItems()) { // ToDo: do not add if key (which one?) is not present within item itemToIndexMapping.Add(item, i); i++; } } } private void UpdateAxisLabels() { Axis xAxis = chart.ChartAreas[0].AxisX; Axis yAxis = chart.ChartAreas[0].AxisY; //mkommend: combobox.SelectedIndex could not be used as this changes during hovering over possible values var xSAxisSelected = XAxisValue == null ? null : (string)xAxisComboBox.SelectedItem; var ySAxisSelected = YAxisValue == null ? null : (string)yAxisComboBox.SelectedItem; SetCustomAxisLabels(xAxis, xSAxisSelected); SetCustomAxisLabels(yAxis, ySAxisSelected); if (XAxisValue != null) xAxis.Title = XAxisValue; if (YAxisValue != null) yAxis.Title = YAxisValue; } private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) { this.UpdateAxisLabels(); } private void SetCustomAxisLabels(Axis axis, string key) { axis.CustomLabels.Clear(); if (key != null && categoricalMapping.ContainsKey(key)) { foreach (var pair in categoricalMapping[key]) { string labelText = pair.Key.ToString(); CustomLabel label = new CustomLabel(); label.ToolTip = labelText; if (labelText.Length > 25) labelText = labelText.Substring(0, 25) + " ... "; label.Text = labelText; label.GridTicks = GridTickTypes.TickMark; label.FromPosition = pair.Value - 0.5; label.ToPosition = pair.Value + 0.5; axis.CustomLabels.Add(label); } } else if (false && key != null && Content.Data[key] is TimeSpanValue) { // TODO chart.ChartAreas[0].RecalculateAxesScale(); for (double i = axis.Minimum; i <= axis.Maximum; i += axis.LabelStyle.Interval) { TimeSpan time = TimeSpan.FromSeconds(i); string x = string.Format("{0:00}:{1:00}:{2:00}", time.Hours, time.Minutes, time.Seconds); axis.CustomLabels.Add(i - axis.LabelStyle.Interval / 2, i + axis.LabelStyle.Interval / 2, x); } } } #endregion #region Event Handlers private void treeView_AfterCheck(object sender, TreeViewEventArgs e) { if (updating) return; updating = true; UpdateComboBoxes(); UpdateDataPoints(); updating = false; } private void treeView_AfterSelect(object sender, TreeViewEventArgs e) { //UpdateNodeColors(); //UpdateComboBoxes(); //UpdateDataPoints(); } private void axisComboBox_SelectedValueChanged(object sender, EventArgs e) { if (updating) return; updating = true; UpdateDataPoints(); UpdateAxisLabels(); updating = false; } private void includeChildrenCheckBox_CheckedChanged(object sender, EventArgs e) { if (updating) return; updating = true; UpdateTreeViewCheckBoxes(); UpdateComboBoxes(); updating = false; } private void levelNumericUpDown_ValueChanged(object sender, EventArgs e) { if (updating) return; updating = true; UpdateTreeViewCheckBoxes(); UpdateComboBoxes(); updating = false; } #endregion #region Helpers private IEnumerable IterateAllNodes() { return Collect(treeView.Nodes); } private IEnumerable IterateCheckedNodes() { return IterateAllNodes().Where(t => t.Checked); } private IEnumerable Collect(TreeNodeCollection nodes) { foreach (TreeNode node in nodes) { yield return node; foreach (var child in Collect(node.Nodes)) yield return child; } } #endregion } }