Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.CEDMA.Charting/3.3/BubbleChart.cs @ 2133

Last change on this file since 2133 was 2133, checked in by gkronber, 15 years ago
  • Removed commented source lines in BubbleChart
  • Only show string, double or int attributes in the tool tip (enumerations with sub-entries are hidden)

#286

File size: 13.9 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2008 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.Text;
25using System.Drawing;
26using System.Linq;
27using HeuristicLab.Charting;
28using System.Windows.Forms;
29using HeuristicLab.CEDMA.Core;
30using HeuristicLab.PluginInfrastructure;
31using HeuristicLab.Core;
32using HeuristicLab.CEDMA.DB.Interfaces;
33using System.Diagnostics;
34
35namespace HeuristicLab.CEDMA.Charting {
36  public class BubbleChart : Chart {
37    private const string X_JITTER = "__X_JITTER";
38    private const string Y_JITTER = "__Y_JITTER";
39    private const double maxXJitterPercent = 0.05;
40    private const double maxYJitterPercent = 0.05;
41    private const int minBubbleSize = 5;
42    private const int maxBubbleSize = 30;
43    private const int maxAlpha = 255;
44    private const int minAlpha = 50;
45    private static readonly Color defaultColor = Color.Blue;
46    private static readonly Color selectionColor = Color.Red;
47
48    private double xJitterFactor = 0.0;
49    private double yJitterFactor = 0.0;
50    private string xDimension;
51    private string yDimension;
52    private string sizeDimension;
53    private bool invertSize;
54    private double minX = double.PositiveInfinity;
55    private double minY = double.PositiveInfinity;
56    private double maxX = double.NegativeInfinity;
57    private double maxY = double.NegativeInfinity;
58    private List<ResultsEntry> records;
59    private Results results;
60    private Dictionary<IPrimitive, ResultsEntry> primitiveToEntryDictionary;
61    private Random random = new Random();
62    private Group points;
63
64    public BubbleChart(Results results, PointD lowerLeft, PointD upperRight)
65      : base(lowerLeft, upperRight) {
66      records = new List<ResultsEntry>();
67      primitiveToEntryDictionary = new Dictionary<IPrimitive, ResultsEntry>();
68      this.results = results;
69
70      foreach (var resultsEntry in results.GetEntries()) {
71        if (resultsEntry.Get(X_JITTER) == null)
72          resultsEntry.Set(X_JITTER, random.NextDouble() * 2.0 - 1.0);
73        if (resultsEntry.Get(Y_JITTER) == null)
74          resultsEntry.Set(Y_JITTER, random.NextDouble() * 2.0 - 1.0);
75        records.Add(resultsEntry);
76      }
77
78      results.Changed += new EventHandler(results_Changed);
79    }
80
81    void results_Changed(object sender, EventArgs e) {
82      Repaint();
83      EnforceUpdate();
84    }
85
86    public BubbleChart(Results results, double x1, double y1, double x2, double y2)
87      : this(results, new PointD(x1, y1), new PointD(x2, y2)) {
88    }
89
90    public void SetBubbleSizeDimension(string dimension, bool inverted) {
91      this.sizeDimension = dimension;
92      this.invertSize = inverted;
93      Repaint();
94      EnforceUpdate();
95    }
96
97    public void ShowXvsY(string xDimension, string yDimension) {
98      if (this.xDimension != xDimension || this.yDimension != yDimension) {
99        this.xDimension = xDimension;
100        this.yDimension = yDimension;
101
102        ResetViewSize();
103        Repaint();
104        ZoomToViewSize();
105      }
106    }
107
108    internal void SetJitter(double xJitterFactor, double yJitterFactor) {
109      this.xJitterFactor = xJitterFactor * maxXJitterPercent * Size.Width;
110      this.yJitterFactor = yJitterFactor * maxYJitterPercent * Size.Height;
111      Repaint();
112      EnforceUpdate();
113    }
114
115    private void Repaint() {
116      if (xDimension == null || yDimension == null) return;
117      double maxSize = 1;
118      double minSize = 1;
119      if (sizeDimension != null && results.OrdinalVariables.Contains(sizeDimension)) {
120        var sizes = records
121          .Select(x => Convert.ToDouble(x.Get(sizeDimension)))
122          .Where(size => !double.IsInfinity(size) && size != double.MaxValue && size != double.MinValue)
123          .OrderBy(r => r);
124        minSize = sizes.ElementAt((int)(sizes.Count() * 0.1));
125        maxSize = sizes.ElementAt((int)(sizes.Count() * 0.9));
126      } else {
127        minSize = 1;
128        maxSize = 1;
129      }
130      UpdateEnabled = false;
131      Group.Clear();
132      primitiveToEntryDictionary.Clear();
133      points = new Group(this);
134      Group.Add(new Axis(this, 0, 0, AxisType.Both));
135      UpdateViewSize(0, 0, 5);
136      foreach (ResultsEntry r in records) {
137        List<double> xs = new List<double>();
138        List<double> ys = new List<double>();
139        List<object> actualXValues = new List<object>();
140        List<object> actualYValues = new List<object>();
141        int size;
142        if (results.OrdinalVariables.Contains(xDimension)) {
143          xs.Add(Convert.ToDouble(r.Get(xDimension)) + (double)r.Get(X_JITTER) * xJitterFactor);
144          actualXValues.Add(r.Get(xDimension));
145        } else if (results.CategoricalVariables.Contains(xDimension)) {
146          xs.Add(results.IndexOfCategoricalValue(xDimension, r.Get(xDimension)) + (double)r.Get(X_JITTER) * xJitterFactor);
147          actualXValues.Add(r.Get(xDimension));
148        } else if (results.MultiDimensionalCategoricalVariables.Contains(xDimension)) {
149          var path = xDimension.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
150          IEnumerable<ResultsEntry> subEntries = (IEnumerable<ResultsEntry>)r.Get(path.ElementAt(0));
151          foreach (ResultsEntry subEntry in subEntries) {
152            xs.Add(results.IndexOfCategoricalValue(xDimension, subEntry.Get(path.ElementAt(1))) + (double)r.Get(X_JITTER) * xJitterFactor);
153            actualXValues.Add(subEntry.Get(path.ElementAt(1)));
154          }
155        } else if (results.MultiDimensionalOrdinalVariables.Contains(xDimension)) {
156          var path = xDimension.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
157          IEnumerable<ResultsEntry> subEntries = (IEnumerable<ResultsEntry>)r.Get(path.ElementAt(0));
158          foreach (ResultsEntry subEntry in subEntries) {
159            xs.Add(Convert.ToDouble(subEntry.Get(path.ElementAt(1))) + (double)r.Get(X_JITTER) * xJitterFactor);
160            actualXValues.Add(subEntry.Get(path.ElementAt(1)));
161          }
162        } else {
163          xs.Add(double.NaN);
164          actualXValues.Add("NaN");
165        }
166        if (results.OrdinalVariables.Contains(yDimension)) {
167          ys.Add(Convert.ToDouble(r.Get(yDimension)) + (double)r.Get(Y_JITTER) * yJitterFactor);
168          actualYValues.Add(r.Get(yDimension));
169        } else if (results.CategoricalVariables.Contains(yDimension)) {
170          ys.Add(results.IndexOfCategoricalValue(yDimension, r.Get(yDimension)) + (double)r.Get(Y_JITTER) * yJitterFactor);
171          actualYValues.Add(r.Get(yDimension));
172        } else if (results.MultiDimensionalCategoricalVariables.Contains(yDimension)) {
173          var path = yDimension.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
174          IEnumerable<ResultsEntry> subEntries = (IEnumerable<ResultsEntry>)r.Get(path.ElementAt(0));
175          foreach (ResultsEntry subEntry in subEntries) {
176            ys.Add(results.IndexOfCategoricalValue(yDimension, subEntry.Get(path.ElementAt(1))) + (double)r.Get(Y_JITTER) * yJitterFactor);
177            actualYValues.Add(subEntry.Get(path.ElementAt(1)));
178          }
179        } else if (results.MultiDimensionalOrdinalVariables.Contains(yDimension)) {
180          var path = yDimension.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
181          IEnumerable<ResultsEntry> subEntries = (IEnumerable<ResultsEntry>)r.Get(path.ElementAt(0));
182          foreach (ResultsEntry subEntry in subEntries) {
183            ys.Add(Convert.ToDouble(subEntry.Get(path.ElementAt(1))) + (double)r.Get(Y_JITTER) * yJitterFactor);
184            actualYValues.Add(subEntry.Get(path.ElementAt(1)));
185          }
186        } else {
187          ys.Add(double.NaN);
188          actualYValues.Add("NaN");
189        }
190        size = CalculateSize(Convert.ToDouble(r.Get(sizeDimension)), minSize, maxSize);
191        Debug.Assert(xs.Count() == ys.Count() || xs.Count() == 1 || ys.Count() == 1);
192        int n = Math.Max(xs.Count(), ys.Count());
193        for (int i = 0; i < n; i++) {
194          double x = xs[Math.Min(i, xs.Count() - 1)];
195          double y = ys[Math.Min(i, ys.Count() - 1)];
196          string actualXValue = actualXValues[Math.Min(i, actualXValues.Count() - 1)].ToString();
197          string actualYValue = actualYValues[Math.Min(i, actualYValues.Count() - 1)].ToString();
198          if (double.IsInfinity(x) || x == double.MaxValue || x == double.MinValue) x = double.NaN;
199          if (double.IsInfinity(y) || y == double.MaxValue || y == double.MinValue) y = double.NaN;
200          if (!double.IsNaN(x) && !double.IsNaN(y)) {
201            UpdateViewSize(x, y, size);
202            int alpha = CalculateAlpha(size);
203            Pen pen = new Pen(Color.FromArgb(alpha, r.Selected ? selectionColor : defaultColor));
204            Brush brush = pen.Brush;
205            FixedSizeCircle c = new FixedSizeCircle(this, x, y, size, pen, brush);
206            c.ToolTipText = xDimension + " = " + actualXValue + Environment.NewLine +
207              yDimension + " = " + actualYValue + Environment.NewLine +
208              r.GetToolTipText();
209            points.Add(c);
210            if (!r.Selected) c.IntoBackground();
211            primitiveToEntryDictionary[c] = r;
212          }
213        }
214      }
215      Group.Add(points);
216      UpdateEnabled = true;
217    }
218
219    private int CalculateSize(double size, double minSize, double maxSize) {
220      if (double.IsNaN(size) || double.IsInfinity(size) || size == double.MaxValue || size == double.MinValue) return minBubbleSize;
221      if (size > maxSize) size = maxSize;
222      if (size < minSize) size = minSize;
223      if (Math.Abs(maxSize - minSize) < 1.0E-10) return minBubbleSize;
224      double sizeDifference = ((size - minSize) / (maxSize - minSize) * (maxBubbleSize - minBubbleSize));
225      if (invertSize) return maxBubbleSize - (int)sizeDifference;
226      else return minBubbleSize + (int)sizeDifference;
227    }
228
229    private int CalculateAlpha(int size) {
230      return maxAlpha - (int)((double)(size - minBubbleSize) / (double)(maxBubbleSize - minBubbleSize) * (double)(maxAlpha - minAlpha));
231    }
232
233    private void ZoomToViewSize() {
234      if (minX < maxX && minY < maxY) {
235        // enlarge view by 5% on each side
236        double width = maxX - minX;
237        double height = maxY - minY;
238        minX = minX - width * 0.05;
239        maxX = maxX + width * 0.05;
240        minY = minY - height * 0.05;
241        maxY = maxY + height * 0.05;
242        ZoomIn(minX, minY, maxX, maxY);
243      }
244    }
245
246    private void UpdateViewSize(double x, double y, double size) {
247      if (x - size < minX) minX = x - size;
248      if (x + size > maxX) maxX = x + size;
249      if (y - size < minY) minY = y + size;
250      if (y + size > maxY) maxY = y + size;
251    }
252
253    private void ResetViewSize() {
254      minX = double.PositiveInfinity;
255      maxX = double.NegativeInfinity;
256      minY = double.PositiveInfinity;
257      maxY = double.NegativeInfinity;
258    }
259
260    internal ResultsEntry GetResultsEntry(Point point) {
261      ResultsEntry r = null;
262      IPrimitive p = points.GetPrimitive(TransformPixelToWorld(point));
263      if (p != null) {
264        primitiveToEntryDictionary.TryGetValue(p, out r);
265      }
266      return r;
267    }
268
269    public override void MouseDrag(Point start, Point end, MouseButtons button) {
270      if (button == MouseButtons.Left && Mode == ChartMode.Select) {
271        PointD a = TransformPixelToWorld(start);
272        PointD b = TransformPixelToWorld(end);
273        double minX = Math.Min(a.X, b.X);
274        double minY = Math.Min(a.Y, b.Y);
275        double maxX = Math.Max(a.X, b.X);
276        double maxY = Math.Max(a.Y, b.Y);
277        HeuristicLab.Charting.Rectangle rect = new HeuristicLab.Charting.Rectangle(this, minX, minY, maxX, maxY);
278
279        List<IPrimitive> primitives = new List<IPrimitive>();
280        primitives.AddRange(points.Primitives);
281
282        foreach (FixedSizeCircle p in primitives) {
283          if (rect.ContainsPoint(p.Point)) {
284            ResultsEntry r;
285            primitiveToEntryDictionary.TryGetValue(p, out r);
286            if (r != null) r.ToggleSelected();
287          }
288        }
289        if (primitives.Count() > 0) results.FireChanged();
290      } else {
291        base.MouseDrag(start, end, button);
292      }
293    }
294
295    public override void MouseClick(Point point, MouseButtons button) {
296      if (button == MouseButtons.Left) {
297        ResultsEntry r = GetResultsEntry(point);
298        if (r != null) {
299          r.ToggleSelected();
300          results.FireChanged();
301        }
302      } else {
303        base.MouseClick(point, button);
304      }
305    }
306
307    public override void MouseDoubleClick(Point point, MouseButtons button) {
308      if (button == MouseButtons.Left) {
309        ResultsEntry entry = GetResultsEntry(point);
310        if (entry != null) {
311          string serializedData = (string)entry.Get(Ontology.SerializedData.Uri.Replace(Ontology.CedmaNameSpace, ""));
312          var model = (IItem)PersistenceManager.RestoreFromGZip(Convert.FromBase64String(serializedData));
313          PluginManager.ControlManager.ShowControl(model.CreateView());
314        }
315      } else {
316        base.MouseDoubleClick(point, button);
317      }
318    }
319  }
320}
Note: See TracBrowser for help on using the repository browser.