using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using DelftTools.Controls.Swf.Charting.Customized;
using DelftTools.Controls.Swf.Charting.Tools;
using DelftTools.Utils;
using DelftTools.Utils.Collections;
using DelftTools.Utils.Collections.Generic;
using DelftTools.Utils.Globalization;
using Steema.TeeChart;
using Steema.TeeChart.Drawing;
using Steema.TeeChart.Tools;
namespace DelftTools.Controls.Swf.Charting
{
///
/// Displays series data on a chart
///
public partial class ChartView : UserControl, IChartView
{
///
/// Selected point of the active series has been changed
///
public event EventHandler SelectionPointChanged;
///
/// The visible viewport of the chart has changed either due to a zoom, pan or scroll event
///
public event EventHandler ViewPortChanged;
public event EventHandler GraphResized;
public event EventHandler ToolsActiveChanged;
private const int DisabledBackgroundAlpha = 20;
private int selectedPointIndex = -1;
private bool wheelZoom = true;
private bool afterResize = true;
private bool chartScrolled;
private LegendScrollBar legendScrollBarTool;
private ZoomUsingMouseWheelTool zoomUsingMouseWheelTool;
private IChart chart;
///
/// Displays series data on chart
///
public ChartView()
{
InitializeComponent();
Chart = new Chart();
Tools = new EventedList();
Tools.CollectionChanged += ToolsCollectionChanged;
teeChart.Zoomed += TeeChartZoomed;
teeChart.UndoneZoom += TeeChartUndoneZoom;
teeChart.Scroll += TeeChartScroll;
teeChart.BeforeDraw += TeeChartBeforeDraw;
teeChart.Resize += ChartViewResize;
teeChart.BeforeDrawSeries += ChartBeforeDrawSeries;
teeChart.MouseDown += TeeChartMouseDown;
teeChart.MouseUp += OnMouseUp;
teeChart.MouseLeave += delegate { IsMouseDown = false; };
teeChart.MouseMove += (s, e) => OnMouseMove(e);
teeChart.MouseClick += (s, e) => OnMouseClick(e);
teeChart.AfterDraw += TeeChartAfterDraw;
teeChart.GetAxisLabel += OnGetAxisLabel;
teeChart.BeforeDrawAxes += OnBeforeDrawAxes;
teeChart.KeyUp += (s, e) => OnKeyUp(e); //bubble the keyup events from the chart..otherwise it does not work..
DateTimeLabelFormatProvider = new TimeNavigatableLabelFormatProvider();
RegionalSettingsManager.FormatChanged += RegionalSettingsManagerFormatChanged;
InitializeWheelZoom();
teeChart.Legend.Alignment = LegendAlignments.Bottom;
teeChart.Axes.Left.Labels.ValueFormat = RegionalSettingsManager.RealNumberFormat; //actual format is applied in OnGetAxisLabel
teeChart.Chart.Header.Color = Color.Black; // To avoid blue titles everywhere
Tools.Add(new ExportChartAsImageChartTool(this)
{
Active = true, Enabled = true
});
}
public bool IsMouseDown { get; private set; }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public TimeNavigatableLabelFormatProvider DateTimeLabelFormatProvider { get; set; }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IChart Chart
{
get
{
return chart;
}
set
{
if (chart == value)
{
return;
}
if (legendScrollBarTool != null)
{
teeChart.Tools.Remove(legendScrollBarTool);
legendScrollBarTool = null;
}
chart = value;
teeChart.Chart = ((Chart) chart).chart;
if (zoomUsingMouseWheelTool != null)
{
teeChart.Tools.Remove(zoomUsingMouseWheelTool);
InitializeWheelZoom();
}
AddLegendScrollBarTool();
}
}
public string Title
{
get
{
return chart.Title;
}
set
{
chart.TitleVisible = !string.IsNullOrEmpty(value);
chart.Title = value ?? "";
}
}
///
/// Data: in this case ISeries expected in
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public object Data
{
get
{
return Chart;
}
set
{
if (value == null)
{
//unbind series to prevent updates and memory leaks
foreach (var series in Chart.Series)
{
series.DataSource = null;
}
CleanAnnotations();
Chart.Series.Clear();
}
else
{
Chart = value as IChart;
}
}
}
///
/// Enables zoom using mousewheel
///
public bool WheelZoom
{
get
{
return wheelZoom;
}
set
{
wheelZoom = value;
if (zoomUsingMouseWheelTool != null)
{
zoomUsingMouseWheelTool.Active = wheelZoom;
}
}
}
public bool AllowPanning
{
get
{
return teeChart.Panning.Allow == ScrollModes.Both;
}
set
{
teeChart.Panning.Allow = (value)
? ScrollModes.Both
: ScrollModes.None;
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ChartCoordinateService ChartCoordinateService
{
get
{
return new ChartCoordinateService(chart);
}
}
///
/// The icon of the view used as identifier for a ChartView.
///
public Image Image { get; set; }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IEventedList Tools { get; private set; }
///
/// Set and get the selected Point Index (of the active series)
///
public int SelectedPointIndex
{
get
{
return selectedPointIndex;
}
set
{
if (selectedPointIndex == value)
{
return;
}
selectedPointIndex = value;
if (SelectionPointChanged != null)
{
SelectionPointChanged(this, new EventArgs());
}
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ViewInfo ViewInfo { get; set; }
///
/// Gives the range of the bottom axis (assuming the axis is a of type DateTime)
///
public TimeSpan GetBottomAxisRangeAsDateTime()
{
return TeeChart2DateTime(teeChart.Axes.Bottom.Maximum) -
TeeChart2DateTime(teeChart.Axes.Bottom.Minimum);
}
public void EnsureVisible(object item) {}
public void ZoomToValues(DateTime min, DateTime max)
{
teeChart.Chart.Axes.Bottom.SetMinMax(min, max);
}
public void ZoomToValues(double min, double max)
{
teeChart.Chart.Axes.Bottom.SetMinMax(min, max);
}
public IChartViewTool GetTool()
{
return Tools.FirstOrDefault(t => t is T);
}
///
/// Opens an export dialog.
///
public void ExportAsImage()
{
Chart.ExportAsImage();
}
public void EnableDelete(bool enable)
{
foreach (var selectTool in Tools.OfType())
{
selectTool.HandleDelete = enable;
}
}
///
/// Clean up any resources being used.
///
/// true if managed resources should be disposed; otherwise, false.
protected override void Dispose(bool disposing)
{
RegionalSettingsManager.FormatChanged -= RegionalSettingsManagerFormatChanged;
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
internal void InternalUpdate()
{
if (teeChart.Width > 0 && teeChart.Height > 0)
{
teeChart.Draw();
}
}
private void TeeChartMouseDown(object sender, MouseEventArgs e)
{
IsMouseDown = true;
OnMouseDown(e);
}
private void CleanAnnotations()
{
if (teeChart == null)
{
return;
}
for (var i = 0; i < teeChart.Axes.Count; i++)
{
var axis = teeChart.Axes[i];
if (axis.Tag is Annotation)
{
var annotation = (Annotation) axis.Tag;
teeChart.Tools.Remove(annotation);
axis.Tag = null;
}
}
}
private void OnBeforeDrawAxes(object sender, Graphics3D e)
{
var senderChart = sender as DeltaShellTChart;
if (senderChart == null)
{
return;
}
for (int i = 0; i < senderChart.Axes.Count; i++)
{
var axis = senderChart.Axes[i];
if (!axis.IsDateTime)
{
//Use Number format, huge range. Specific (rounded) format is applied in OnGetAxisLabel due to TeeChart issue: TOOLS-4310
axis.Labels.ValueFormat = "N20";
continue;
}
axis.Labels.DateTimeFormat = DateTimeLabelFormatProvider.CustomDateTimeFormatInfo.FullDateTimePattern;
DateTime min = Steema.TeeChart.Utils.DateTime(axis.Minimum);
DateTime max = Steema.TeeChart.Utils.DateTime(axis.Maximum);
// TODO: move this logic completely out to FunctioBindingList and use DisplayName per property descriptor there
if (senderChart.Series.Count > 0 && senderChart.Series[0].DataSource is IChartSeries)
{
var chartSeries = senderChart.Series[0].DataSource as IChartSeries;
// HACK: parse XValuesDataMember, search for [units] and remove it
string oldAxisTitel = chartSeries.XValuesDataMember;
int indexOfUnitInString = axis.Title.Text.IndexOf("[");
string axisTitle = indexOfUnitInString != -1
? oldAxisTitel.Substring(0, indexOfUnitInString)
: oldAxisTitel;
axis.Title.Caption = DateTimeLabelFormatProvider.ShowUnits
? axisTitle + string.Format("[{0}]", DateTimeLabelFormatProvider.GetUnits(max - min))
: axisTitle;
}
var annotation = axis.Tag as Annotation;
if (DateTimeLabelFormatProvider.ShowRangeLabel)
{
if (annotation == null)
{
annotation = new Annotation(senderChart.Chart)
{
AllowEdit = false,
AutoSize = true,
Left = senderChart.Padding.Left
};
annotation.Shape.Shadow.Visible = false;
senderChart.Tools.Add(annotation);
axis.Tag = annotation;
}
annotation.Shape.Color = senderChart.BackColor;
if (annotation.Shape.Pen.Color != senderChart.BackColor)
{
annotation.Shape.Pen.Dispose();
annotation.Shape.Pen = new ChartPen(senderChart.BackColor);
}
annotation.Top = (senderChart.Height - annotation.Bounds.Height) + senderChart.Padding.Bottom;
//make sure space is reserved for the axis label / annotation
if (string.IsNullOrEmpty(axis.Title.Caption))
{
axis.Title.Caption = " ";
}
string title = DateTimeLabelFormatProvider.GetRangeLabel(min, max);
if (title != null)
{
annotation.Text = title;
}
}
else
{
if (annotation == null)
{
return;
}
senderChart.Tools.Remove(annotation);
axis.Tag = null;
}
}
}
private void OnGetAxisLabel(object sender, GetAxisLabelEventArgs e)
{
if (sender is Axis)
{
var axis = sender as Axis;
if (axis.IsDateTime)
{
DateTime res;
DateTime.TryParse(e.LabelText, out res);
TimeSpan axisRange = GetAxisRange(axis);
e.LabelText = DateTimeLabelFormatProvider.GetLabel(res, axisRange);
}
else
{
//Done here 'manually' per label, due to TeeChart issue: TOOLS-4310
double labelValue;
if (Double.TryParse(e.LabelText, out labelValue))
{
labelValue = Math.Round(labelValue, 13); //do some rounding to prevent TeeChart problem (TOOLS-4310)
e.LabelText = labelValue.ToString(RegionalSettingsManager.RealNumberFormat);
}
}
}
}
private void TeeChartBeforeDraw(object sender, Graphics3D g)
{
if (teeChart.Chart.Axes.Left.Visible &&
(double.IsInfinity(teeChart.Chart.Axes.Left.Maximum - teeChart.Chart.Axes.Left.Minimum)))
{
// check for all axes?
// extra error check to prevent stackoverflow in teechart
throw new InvalidOperationException("Can not draw chart");
}
}
private void TeeChartAfterDraw(object sender, Graphics3D g)
{
if (!Enabled)
{
g.FillRectangle(new SolidBrush(Color.FromArgb(DisabledBackgroundAlpha, Color.Black)), 0, 0, ClientRectangle.Width, ClientRectangle.Height);
}
}
private void InitializeWheelZoom()
{
zoomUsingMouseWheelTool = new ZoomUsingMouseWheelTool(teeChart.Chart)
{
Active = wheelZoom
};
}
private void TeeChartScroll(object sender, EventArgs e)
{
if (ViewPortChanged != null)
{
ViewPortChanged(sender, e);
}
chartScrolled = true;
}
private void TeeChartZoomed(object sender, EventArgs e)
{
if (ViewPortChanged != null)
{
ViewPortChanged(sender, e);
}
}
private void TeeChartUndoneZoom(object sender, EventArgs e)
{
if (ViewPortChanged != null)
{
ViewPortChanged(sender, e);
}
}
private void ToolSelectionChanged(object sender, PointEventArgs e)
{
SelectedPointIndex = e.Index;
}
private void Timer1Tick(object sender, EventArgs e)
{
//check if any series requires an update and get it done..
foreach (var series in Chart.Series)
{
if (series.RefreshRequired)
{
series.Refresh();
}
}
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
base.OnMouseUp(e);
if (Chart == null)
{
return;
}
if (chartScrolled)
{
chartScrolled = false;
return;
}
var contextMenu = new ContextMenuStrip();
if (e.Button == MouseButtons.Right)
{
foreach (IChartViewContextMenuTool tool in Tools.Where(tool => tool is IChartViewContextMenuTool && tool.Active))
{
tool.OnBeforeContextMenu(contextMenu);
}
}
contextMenu.Show(PointToScreen(e.Location));
IsMouseDown = false;
}
private void ToolsCollectionChanged(object sender, NotifyCollectionChangingEventArgs e)
{
var chartTool = e.Item as IChartViewTool;
if (e.Action == NotifyCollectionChangeAction.Remove)
{
var tool = e.Item as Tool;
var index = teeChart.Tools.IndexOf(tool);
if (index >= 0)
{
teeChart.Tools.Remove(tool);
}
if (chartTool != null)
{
chartTool.ActiveChanged -= OnToolsActiveChanged;
}
}
if (e.Action == NotifyCollectionChangeAction.Add)
{
if (chartTool != null)
{
chartTool.ActiveChanged += OnToolsActiveChanged;
}
}
}
private void OnToolsActiveChanged(object sender, EventArgs e)
{
if (ToolsActiveChanged != null)
{
ToolsActiveChanged(this, EventArgs.Empty);
}
}
private void ChartViewResize(object sender, EventArgs e)
{
afterResize = true;
}
private void ChartBeforeDrawSeries(object sender, Graphics3D g)
{
if (!afterResize)
{
return;
}
if (GraphResized != null)
{
GraphResized(this, new EventArgs());
}
afterResize = false;
}
private void RegionalSettingsManagerFormatChanged()
{
teeChart.Refresh();
teeChart.PerformLayout();
Invalidate(true);
}
private void AddLegendScrollBarTool()
{
legendScrollBarTool = new LegendScrollBar(teeChart.Chart)
{
Active = true,
DrawStyle = ScrollBarDrawStyle.WhenNeeded
};
legendScrollBarTool.Pen.Color = Color.Transparent;
legendScrollBarTool.ArrowBrush.Color = Color.DarkGray;
legendScrollBarTool.Brush.Color = SystemColors.Control;
legendScrollBarTool.ThumbBrush.Color = Color.LightGray;
}
private static TimeSpan GetAxisRange(Axis axis)
{
DateTime min = TeeChart2DateTime(axis.Minimum);
DateTime max = TeeChart2DateTime(axis.Maximum);
return max - min;
}
private static DateTime TeeChart2DateTime(double axisValue)
{
return Steema.TeeChart.Utils.DateTime(axisValue);
}
#region TeeChart Factory Methods
public EditPointTool NewEditPointTool()
{
var tool = new EditPointTool(teeChart)
{
ChartView = this
};
Tools.Add(tool);
return tool;
}
public IAddPointTool NewAddPointTool()
{
var tool = new AddPointTool(teeChart.Chart)
{
ChartView = this
};
Tools.Add(tool);
return tool;
}
public RulerTool NewRulerTool()
{
var tool = new RulerTool(teeChart)
{
ChartView = this
};
Tools.Add(tool);
return tool;
}
public SelectPointTool NewSelectPointTool()
{
var tool = new SelectPointTool(teeChart.Chart)
{
ChartView = this
};
Tools.Add(tool);
tool.SelectionChanged += ToolSelectionChanged;
return tool;
}
#endregion
}
}