#region License Information
/* HeuristicLab
* Copyright (C) 2002-2016 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 IGrid Grid { 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);
Grid = new Grid(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();
}
}
}