#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.Drawing; using System.Windows.Forms; namespace HeuristicLab.Visualization { public class Chart : IChart { private PointD originalLowerLeft; private PointD originalUpperRight; protected List zoomHistory; protected List renderStages; private bool enabled; public bool Enabled { get { return enabled; } set { if (enabled == value) return; enabled = value; OnRedrawRequired(); } } private Color backgroundColor; public Color BackgroundColor { get { return backgroundColor; } set { if (backgroundColor == value) return; backgroundColor = value; OnRedrawRequired(); } } protected bool SuppressRedraw { get; set; } public bool SuppressEvents { get; set; } private PointD myLowerLeft; public virtual PointD LowerLeft { get { return myLowerLeft; } } private PointD myUpperRight; public virtual PointD UpperRight { get { return myUpperRight; } } public virtual SizeD Size { get { return new SizeD(UpperRight.X - LowerLeft.X, UpperRight.Y - LowerLeft.Y); } } private Size mySizeInPixels; public Size SizeInPixels { get { return mySizeInPixels; } } public SizeD PixelToWorldRatio { get { return new SizeD(Size.Width / SizeInPixels.Width, Size.Height / SizeInPixels.Height); } } public SizeD WorldToPixelRatio { get { return new SizeD(SizeInPixels.Width / Size.Width, SizeInPixels.Height / Size.Height); } } public double Scale { get; set; } public double MinimumZoomDistance { get; set; } public IGroup Group { get; protected set; } public Chart(PointD lowerLeft, PointD upperRight) { zoomHistory = new List(); SetPosition(lowerLeft, upperRight); mySizeInPixels = new Size((int)Size.Width, (int)Size.Height); backgroundColor = Color.White; Scale = 1.0; Group = new Group(this); renderStages = new List { new BackgroundColorRenderStage(this), new GridRenderStage(this), new GroupRenderStage(this), new EnabledRenderStage(this) }; Group.RedrawRequired += GroupOnRedrawRequired; MinimumZoomDistance = 0.01; } public Chart(double x1, double y1, double x2, double y2) : this(new PointD(x1, y1), new PointD(x2, y2)) { } public PointD TransformPixelToWorld(Point point) { var x = LowerLeft.X + point.X * PixelToWorldRatio.Width; var y = LowerLeft.Y + (SizeInPixels.Height - point.Y) * PixelToWorldRatio.Height; return new PointD(x, y); } public SizeD TransformPixelToWorld(Size size) { var width = size.Width * PixelToWorldRatio.Width; var height = size.Height * PixelToWorldRatio.Height; return new SizeD(width, height); } public Point TransformWorldToPixel(PointD point) { var x = (int)((point.X - LowerLeft.X) * WorldToPixelRatio.Width); var y = (int)((UpperRight.Y - point.Y) * WorldToPixelRatio.Height); return new Point(x, y); } public Size TransformWorldToPixel(SizeD size) { var width = (int)(size.Width * WorldToPixelRatio.Width); var height = (int)(size.Height * WorldToPixelRatio.Height); return new Size(width, height); } public virtual void SetPosition(PointD lowerLeft, PointD upperRight) { if ((lowerLeft.X >= upperRight.X) || (lowerLeft.Y >= upperRight.Y)) throw new ArgumentException("Lower left point is greater or equal than upper right point"); originalLowerLeft = lowerLeft; originalUpperRight = upperRight; if (zoomHistory != null) zoomHistory.Clear(); myLowerLeft = lowerLeft; myUpperRight = upperRight; OnRedrawRequired(); } public virtual void Move(Offset delta) { myLowerLeft += delta; myUpperRight += delta; OnRedrawRequired(); } public virtual void Zoom(Point mousePosition, double delta) { var cursor = TransformPixelToWorld(mousePosition); double zoomLevel = (100 + delta) / 100; double width = this.UpperRight.X - this.LowerLeft.X; double height = this.UpperRight.Y - this.LowerLeft.Y; double newWidth = width * zoomLevel; double newHeight = height * zoomLevel; double xAdaption = (UpperRight.X - cursor.X) / width; double yAdaption = (UpperRight.Y - cursor.Y) / height; ZoomIn(new PointD(cursor.X - newWidth * (1 - xAdaption), cursor.Y - newHeight * (1 - yAdaption)), new PointD(cursor.X + newWidth * xAdaption, cursor.Y + newHeight * yAdaption)); OnRedrawRequired(); } public virtual void ZoomIn(Point mousePosition) { Zoom(mousePosition, -10); } public virtual void ZoomIn(PointD lowerLeft, PointD upperRight) { // maintain a 1:1 ratio between x and y axis var pixels = SizeInPixels; var diagonal = upperRight - lowerLeft; var windowRatio = pixels.Height / (double)pixels.Width; var viewRatio = diagonal.DY / diagonal.DX; if (viewRatio < windowRatio) { var neededHeight = windowRatio * diagonal.DX; var diff = (neededHeight - diagonal.DY) / 2.0; lowerLeft.Y -= diff; upperRight.Y += diff; } else { var neededWidth = diagonal.DY / windowRatio; var diff = (neededWidth - diagonal.DX) / 2.0; lowerLeft.X -= diff; upperRight.X += diff; } if ((lowerLeft - upperRight).Length < MinimumZoomDistance) return; zoomHistory.Insert(0, LowerLeft - lowerLeft); zoomHistory.Insert(0, UpperRight - upperRight); myLowerLeft = lowerLeft; myUpperRight = upperRight; OnRedrawRequired(); } public virtual void ZoomIn(Point lowerLeft, Point upperRight) { ZoomIn(TransformPixelToWorld(lowerLeft), TransformPixelToWorld(upperRight)); } public virtual void ZoomOut() { if (zoomHistory.Count > 0) { Offset upperRight = zoomHistory[0]; zoomHistory.RemoveAt(0); Offset lowerLeft = zoomHistory[0]; zoomHistory.RemoveAt(0); myLowerLeft = LowerLeft + lowerLeft; myUpperRight = UpperRight + upperRight; OnRedrawRequired(); } else { var lowerLeft = new PointD(myLowerLeft.X - Size.Width / 4, myLowerLeft.Y - Size.Height / 4); var upperRight = new PointD(myUpperRight.X + Size.Width / 4, myUpperRight.Y + Size.Height / 4); ZoomIn(lowerLeft, upperRight); if (zoomHistory.Count >= 2) { zoomHistory.RemoveRange(0, 2); } } } public virtual void Unzoom() { SetPosition(originalLowerLeft, originalUpperRight); } public virtual void IntoForeground(IPrimitive primitive) { Group.IntoForeground(primitive); } public virtual void IntoBackground(IPrimitive primitive) { Group.IntoBackground(primitive); } public virtual void OneLayerUp(IPrimitive primitive) { Group.OneLayerUp(primitive); } public virtual void OneLayerDown(IPrimitive primitive) { Group.OneLayerDown(primitive); } public virtual Cursor GetCursor(Point point) { return Group.GetCursor(TransformPixelToWorld(point)) ?? Cursors.Default; } public virtual string GetToolTipText(Point point) { return Group.GetToolTipText(TransformPixelToWorld(point)); } public virtual IPrimitive GetPrimitive(Point point) { return Group.GetPrimitive(TransformPixelToWorld(point)); } public virtual IEnumerable GetAllPrimitives(Point point) { return Group.GetAllPrimitives(TransformPixelToWorld(point)); } public virtual void Render(Graphics graphics, int width, int height) { mySizeInPixels = new Size(width, height); foreach (var renderStage in renderStages) renderStage.Render(graphics); } public event EventHandler RedrawRequired; protected virtual void OnRedrawRequired() { if (SuppressRedraw) return; var handler = RedrawRequired; if (handler != null) handler(this, EventArgs.Empty); } public void RaiseRedrawRequired() { var handler = RedrawRequired; if (handler != null) handler(this, EventArgs.Empty); } private void GroupOnRedrawRequired(object sender, EventArgs e) { OnRedrawRequired(); } } }