/*
Copyright (C) 2008 Webyog Softworks Private Limited
This file is a part of Visifire Charts.
Visifire is a 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.
You should have received a copy of the GNU General Public License
along with Visifire Charts. If not, see .
If GPL is not suitable for your products or company, Webyog provides Visifire
under a flexible commercial license designed to meet your specific usage and
distribution requirements. If you have already obtained a commercial license
from Webyog, you can use this file under those license terms.
*/
#if WPF
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
#else
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
#endif
using System.Linq;
using Visifire.Commons;
namespace Visifire.Charts
{
///
/// Visifire.Charts.LineChartShapeParams class
///
internal class LineChartShapeParams
{
internal PointCollection Points { get; set; }
internal PointCollection ShadowPoints { get; set; }
internal GeometryGroup LineGeometryGroup { get; set; }
internal GeometryGroup LineShadowGeometryGroup { get; set; }
internal Brush LineColor { get; set; }
internal Double LineThickness { get; set; }
internal Boolean Lighting { get; set; }
internal DoubleCollection LineStyle { get; set; }
internal Boolean ShadowEnabled { get; set; }
}
///
/// Visifire.Charts.LineChart class
///
internal class LineChart
{
#region Public Methods
#endregion
#region Public Properties
#endregion
#region Public Events And Delegates
#endregion
#region Protected Methods
#endregion
#region Internal Properties
#endregion
#region Private Properties
#endregion
#region Private Delegates
#endregion
#region Private Methods
///
/// Returns marker for DataPoint
///
/// Chart
/// Marker position
/// DataPoint
/// Whether YValue is positive or negative
/// Marker
private static Marker GetMarkerForDataPoint(Chart chart, Double position, DataPoint dataPoint, Boolean isPositive)
{
Size markerSize = new Size((Double)dataPoint.MarkerSize, (Double)dataPoint.MarkerSize);
String labelText = (Boolean)dataPoint.LabelEnabled ? dataPoint.TextParser(dataPoint.LabelText) : "";
Boolean markerBevel = false;
dataPoint.Marker = new Marker((MarkerTypes)dataPoint.MarkerType, (Double)dataPoint.MarkerScale, markerSize, markerBevel, dataPoint.MarkerColor, labelText);
//dataPoint.Marker.ShadowEnabled =(Boolean) dataPoint.ShadowEnabled;
ApplyMarkerShapeProperties(dataPoint, markerSize);
if (true && !String.IsNullOrEmpty(labelText))
{
dataPoint.Marker.FontColor = Chart.CalculateDataPointLabelFontColor(dataPoint.Chart as Chart, dataPoint, dataPoint.LabelFontColor, LabelStyles.OutSide);
dataPoint.Marker.FontFamily = dataPoint.LabelFontFamily;
dataPoint.Marker.FontSize = (Double)dataPoint.LabelFontSize;
dataPoint.Marker.FontStyle = (FontStyle)dataPoint.LabelFontStyle;
dataPoint.Marker.FontWeight = (FontWeight)dataPoint.LabelFontWeight;
dataPoint.Marker.TextBackground = dataPoint.LabelBackground;
if (!Double.IsNaN(dataPoint.LabelAngle) && dataPoint.LabelAngle != 0)
{
dataPoint.Marker.LabelAngle = dataPoint.LabelAngle;
dataPoint.Marker.TextOrientation = Orientation.Vertical;
if (isPositive)
{
dataPoint.Marker.TextAlignmentX = AlignmentX.Center;
dataPoint.Marker.TextAlignmentY = AlignmentY.Top;
}
else
{
dataPoint.Marker.TextAlignmentX = AlignmentX.Center;
dataPoint.Marker.TextAlignmentY = AlignmentY.Bottom;
}
dataPoint.Marker.LabelStyle = (LabelStyles)dataPoint.LabelStyle;
}
dataPoint.Marker.CreateVisual();
if (Double.IsNaN(dataPoint.LabelAngle) || dataPoint.LabelAngle == 0)
{
dataPoint.Marker.TextAlignmentX = AlignmentX.Center;
if (isPositive)
{
if (dataPoint.LabelStyle == LabelStyles.OutSide && !dataPoint.IsLabelStyleSet && !dataPoint.Parent.IsLabelStyleSet)
{
//if (position < dataPoint.Marker.MarkerActualSize.Height || dataPoint.LabelStyle == LabelStyles.Inside)
if (position - dataPoint.Marker.MarkerActualSize.Height - dataPoint.Marker.MarkerSize.Height / 2 < 0 || dataPoint.LabelStyle == LabelStyles.Inside)
dataPoint.Marker.TextAlignmentY = AlignmentY.Bottom;
else
dataPoint.Marker.TextAlignmentY = AlignmentY.Top;
}
else if(dataPoint.LabelStyle == LabelStyles.OutSide)
dataPoint.Marker.TextAlignmentY = AlignmentY.Top;
else
dataPoint.Marker.TextAlignmentY = AlignmentY.Bottom;
}
else
{
if (dataPoint.LabelStyle == LabelStyles.OutSide && !dataPoint.IsLabelStyleSet && !dataPoint.Parent.IsLabelStyleSet)
{
if (position + dataPoint.Marker.MarkerActualSize.Height + dataPoint.Marker.MarkerSize.Height / 2 > chart.PlotArea.BorderElement.Height || dataPoint.LabelStyle == LabelStyles.Inside)
dataPoint.Marker.TextAlignmentY = AlignmentY.Top;
else
dataPoint.Marker.TextAlignmentY = AlignmentY.Bottom;
}
else if (dataPoint.LabelStyle == LabelStyles.OutSide)
dataPoint.Marker.TextAlignmentY = AlignmentY.Bottom;
else
dataPoint.Marker.TextAlignmentY = AlignmentY.Top;
}
}
}
dataPoint.Marker.Control = chart;
dataPoint.Marker.Tag = new ElementData() { Element = dataPoint };
dataPoint.Marker.CreateVisual();
dataPoint.Marker.Visual.Opacity = dataPoint.InternalOpacity * dataPoint.Parent.InternalOpacity;
ApplyDefaultInteractivityForMarker(dataPoint);
return dataPoint.Marker;
}
///
/// Apply default interactivity for Marker
///
/// DataPoint
private static void ApplyDefaultInteractivityForMarker(DataPoint dataPoint)
{
if ((Boolean)dataPoint.MarkerEnabled)
{
if (!dataPoint.Parent.MovingMarkerEnabled)
{
dataPoint.Marker.MarkerShape.MouseEnter += delegate(object sender, MouseEventArgs e)
{
if (!dataPoint.Selected)
{
Shape shape = sender as Shape;
shape.Stroke = new SolidColorBrush(Colors.Red);
shape.StrokeThickness = dataPoint.Marker.BorderThickness;
}
};
dataPoint.Marker.MarkerShape.MouseLeave += delegate(object sender, MouseEventArgs e)
{
if (!dataPoint.Selected)
{
Shape shape = sender as Shape;
shape.Stroke = dataPoint.Marker.BorderColor;
shape.StrokeThickness = dataPoint.Marker.BorderThickness;
}
};
}
}
else
{
HideDataPointMarker(dataPoint);
}
}
///
/// Hides a DataPoint Marker
///
///
private static void HideDataPointMarker(DataPoint dataPoint)
{
Brush tarnsparentColor = new SolidColorBrush(Colors.Transparent);
dataPoint.Marker.MarkerShape.Fill = tarnsparentColor;
dataPoint.Marker.MarkerShape.Stroke = tarnsparentColor;
if (dataPoint.Marker.MarkerShadow != null)
{
dataPoint.Marker.MarkerShadow.Fill = tarnsparentColor;
dataPoint.Marker.MarkerShadow.Stroke = tarnsparentColor;
}
if (dataPoint.Marker.BevelLayer != null)
dataPoint.Marker.BevelLayer.Visibility = Visibility.Collapsed;
}
///
/// Create line in 2D and place inside a canvas
///
/// Line parameters
/// line path reference
/// line shadow path reference
/// Canvas
private static Canvas GetLine2D(DataSeries tagReference, LineChartShapeParams lineParams, out Path line, out Path lineShadow, List pointCollectionList, List shadowPointCollectionList)
{
Canvas visual = new Canvas();
line = new Path() { Tag = new ElementData() { Element = tagReference } };
line.StrokeLineJoin = PenLineJoin.Round;
line.StrokeStartLineCap = PenLineCap.Round;
line.StrokeEndLineCap = PenLineCap.Round;
line.Stroke = lineParams.Lighting ? Graphics.GetLightingEnabledBrush(lineParams.LineColor, "Linear", new Double[] { 0.65, 0.55 }) : lineParams.LineColor;
line.StrokeThickness = lineParams.LineThickness;
line.StrokeDashArray = lineParams.LineStyle;
line.Data = GetPathGeometry(pointCollectionList);
if (lineParams.ShadowEnabled)
{
lineShadow = new Path() { Tag = new ElementData() { Element = tagReference } };
lineShadow.Stroke = new SolidColorBrush(Colors.Gray);
lineShadow.StrokeThickness = lineParams.LineThickness +0.24;
lineShadow.Opacity = 0.5;
lineShadow.StrokeLineJoin = PenLineJoin.Round;
lineShadow.StrokeStartLineCap = PenLineCap.Round;
lineShadow.StrokeEndLineCap = PenLineCap.Round;
lineShadow.Data = GetPathGeometry(shadowPointCollectionList);
TranslateTransform tt = new TranslateTransform() { X = 1.26, Y = 1.26 };
lineShadow.RenderTransform = tt;
visual.Children.Add(lineShadow);
//System.Windows.Media.Effects.PixelShader e1 = new System.Windows.Media.Effects.PixelShader();
//e1.UriSource = System.Windows.Media.Effects.ShaderEffect.ImplicitInput;
////e1.BlurRadius = lineParams.LineThickness + 1;
//////e1.Direction = 145;
////e1.ShadowDepth = 2;
//line.Effect = System.Windows.Media.Effects.ShaderEffect.ImplicitInput;
}
else
lineShadow = null;
visual.Children.Add(line);
return visual;
}
///
/// Get PathGeometry for Line and Shadow
///
/// List of points collection
/// Geometry
private static Geometry GetPathGeometry(List pointCollectionList)
{
GeometryGroup gg = new GeometryGroup();
foreach (PointCollection pointCollection in pointCollectionList)
{
PathGeometry geometry = new PathGeometry();
PathFigure pathFigure = new PathFigure();
if (pointCollection.Count > 0)
pathFigure.StartPoint = new Point(pointCollection[0].X, pointCollection[0].Y);
PolyLineSegment segment = new PolyLineSegment();
segment.Points = pointCollection;
pathFigure.Segments.Add(segment);
geometry.Figures.Add(pathFigure);
gg.Children.Add(geometry);
}
return gg;
}
///
/// Apply marker properties
///
/// DataPoint
/// Marker size
private static void ApplyMarkerShapeProperties(DataPoint dataPoint, Size markerSize)
{
dataPoint.Marker.MarkerSize = markerSize;
dataPoint.Marker.BorderColor = dataPoint.MarkerBorderColor;
dataPoint.Marker.BorderThickness = ((Thickness)dataPoint.MarkerBorderThickness).Left;
dataPoint.Marker.MarkerFillColor = dataPoint.MarkerColor;
}
///
/// Apply animation for line chart
///
/// Line chart canvas
/// Storyboard
/// Whether canvas is line canvas
/// Storyboard
private static Storyboard ApplyLineChartAnimation(DataSeries currentDataSeries, Panel canvas, Storyboard storyboard, Boolean isLineCanvas)
{
LinearGradientBrush opacityMaskBrush = new LinearGradientBrush() { StartPoint = new Point(0, 0.5), EndPoint = new Point(1, 0.5) };
// Create gradients for opacity mask animation
GradientStop GradStop1 = new GradientStop() { Color = Colors.White, Offset = 0 };
GradientStop GradStop2 = new GradientStop() { Color = Colors.White, Offset = 0 };
GradientStop GradStop3 = new GradientStop() { Color = Colors.Transparent, Offset = 0.01 };
GradientStop GradStop4 = new GradientStop() { Color = Colors.Transparent, Offset = 1 };
// Add gradients to gradient stop list
opacityMaskBrush.GradientStops.Add(GradStop1);
opacityMaskBrush.GradientStops.Add(GradStop2);
opacityMaskBrush.GradientStops.Add(GradStop3);
opacityMaskBrush.GradientStops.Add(GradStop4);
canvas.OpacityMask = opacityMaskBrush;
double beginTime = (isLineCanvas) ? 0.25 + 0.5 : 0.5;
DoubleCollection values = Graphics.GenerateDoubleCollection(0, 1);
DoubleCollection timeFrames = Graphics.GenerateDoubleCollection(0, 1);
List splines = AnimationHelper.GenerateKeySplineList(new Point(0, 0), new Point(1, 1), new Point(0, 0), new Point(1, 1));
storyboard.Children.Add(AnimationHelper.CreateDoubleAnimation(currentDataSeries, GradStop2, "(GradientStop.Offset)", beginTime, timeFrames, values, splines));
values = Graphics.GenerateDoubleCollection(0.01, 1);
timeFrames = Graphics.GenerateDoubleCollection(0, 1);
splines = AnimationHelper.GenerateKeySplineList(new Point(0, 0), new Point(1, 1), new Point(0, 0), new Point(1, 1));
storyboard.Children.Add(AnimationHelper.CreateDoubleAnimation(currentDataSeries, GradStop3, "(GradientStop.Offset)", beginTime, timeFrames, values, splines));
return storyboard;
}
#endregion
#region Internal Methods
///
/// Returns the visual object for line chart
///
/// PlotArea width
/// PlotArea height
/// PlotDetails
/// List of line series
/// Chart
/// PlankDepth
/// Whether animation is enabled for chart
/// Canvas
internal static Canvas GetVisualObjectForLineChart(Double width, Double height, PlotDetails plotDetails, List seriesList, Chart chart, Double plankDepth, bool animationEnabled)
{
if (Double.IsNaN(width) || Double.IsNaN(height) || width <= 0 || height <= 0) return null;
DataSeries currentDataSeries = null;
Canvas visual = new Canvas() { Width = width, Height = height }; // Canvas for line chart
Canvas labelCanvas = new Canvas() { Width = width, Height = height }; // Canvas for placing labels
Canvas lineChartCanvas = new Canvas() { Width = width, Height = height };
Double depth3d = plankDepth / (plotDetails.Layer3DCount == 0 ? 1 : plotDetails.Layer3DCount) * (chart.View3D ? 1 : 0);
Double visualOffset = depth3d * (plotDetails.SeriesDrawingIndex[seriesList[0]] + 1 - (plotDetails.Layer3DCount == 0 ? 0 : 1));
// Set visual canvas position
visual.SetValue(Canvas.TopProperty, visualOffset);
visual.SetValue(Canvas.LeftProperty, -visualOffset);
Double xPosition, yPosition;
_listOfDataSeries = new List();
Boolean isMovingMarkerEnabled = false; // Whether moving marker is enabled for atleast one series
Double minimumXValue = Double.MaxValue;
Double maximumXValue = Double.MinValue;
foreach (DataSeries series in seriesList)
{
if ((Boolean)series.Enabled == false)
continue;
_listOfDataSeries.Add(series);
List pointCollectionList = new List();
List shadowPointCollectionList = new List();
PlotGroup plotGroup = series.PlotGroup;
LineChartShapeParams lineParams = new LineChartShapeParams();
minimumXValue = Math.Min(minimumXValue, plotGroup.MinimumX);
maximumXValue = Math.Max(maximumXValue, plotGroup.MaximumX);
series.Faces = new Faces();
series.Faces.Parts = new List();
#region Set LineParms
lineParams.Points = new PointCollection();
lineParams.ShadowPoints = new PointCollection();
lineParams.LineGeometryGroup = new GeometryGroup();
lineParams.LineThickness = (Double)series.LineThickness;
lineParams.LineColor = series.Color;
lineParams.LineStyle = ExtendedGraphics.GetDashArray(series.LineStyle);
lineParams.Lighting = (Boolean)series.LightingEnabled;
lineParams.ShadowEnabled = series.ShadowEnabled;
if (series.ShadowEnabled)
lineParams.LineShadowGeometryGroup = new GeometryGroup();
#endregion
series.VisualParams = lineParams;
Point variableStartPoint = new Point(), endPoint = new Point();
Boolean IsStartPoint = true;
//Polyline polyline, PolylineShadow;
//Canvas line2dCanvas = new Canvas();
//Canvas lineCanvas;
foreach (DataPoint dataPoint in series.InternalDataPoints)
{
if (dataPoint.Enabled == false)
continue;
if (Double.IsNaN(dataPoint.InternalYValue))
{
xPosition = Double.NaN;
yPosition = Double.NaN;
IsStartPoint = true;
}
else
{
xPosition = Graphics.ValueToPixelPosition(0, width, (Double)plotGroup.AxisX.InternalAxisMinimum, (Double)plotGroup.AxisX.InternalAxisMaximum, dataPoint.InternalXValue);
yPosition = Graphics.ValueToPixelPosition(height, 0, (Double)plotGroup.AxisY.InternalAxisMinimum, (Double)plotGroup.AxisY.InternalAxisMaximum, dataPoint.InternalYValue);
// Create Marker
Marker marker = GetMarkerForDataPoint(chart, yPosition, dataPoint, dataPoint.InternalYValue > 0);
marker.AddToParent(labelCanvas, xPosition, yPosition, new Point(0.5, 0.5));
#region Generate GeometryGroup for line and line shadow
if (IsStartPoint)
{
variableStartPoint = new Point(xPosition, yPosition);
}
else
endPoint = new Point(xPosition, yPosition);
if (!IsStartPoint)
{
//lineParams.LineGeometryGroup.Children.Add(new LineGeometry() { StartPoint = startPoint, EndPoint = endPoint });
//if (lineParams.ShadowEnabled)
// lineParams.LineShadowGeometryGroup.Children.Add(new LineGeometry() { StartPoint = startPoint, EndPoint = endPoint });
variableStartPoint = endPoint;
IsStartPoint = false;
}
else
{
IsStartPoint = !IsStartPoint;
if (lineParams.Points.Count > 0)
{
pointCollectionList.Add(lineParams.Points);
shadowPointCollectionList.Add(lineParams.ShadowPoints);
}
lineParams.Points = new PointCollection();
lineParams.ShadowPoints = new PointCollection();
}
#endregion Generate GeometryGroup for line and line shadow
lineParams.Points.Add(new Point(xPosition, yPosition));
if (lineParams.ShadowEnabled)
lineParams.ShadowPoints.Add(new Point(xPosition, yPosition));
}
}
pointCollectionList.Add(lineParams.Points);
shadowPointCollectionList.Add(lineParams.ShadowPoints);
series.Faces = new Faces();
series.Faces.Parts = new List();
Path polyline, PolylineShadow;
Canvas line2dCanvas = GetLine2D(series, lineParams, out polyline, out PolylineShadow, pointCollectionList, shadowPointCollectionList);
line2dCanvas.Width = width;
line2dCanvas.Height = height;
series.Faces.Parts.Add(polyline);
lineChartCanvas.Children.Add(line2dCanvas);
series.Faces.Visual = line2dCanvas;
series.Faces.LabelCanvas = labelCanvas;
// Apply animation
if (animationEnabled)
{
if (series.Storyboard == null)
series.Storyboard = new Storyboard();
currentDataSeries = series;
// Apply animation to the lines
series.Storyboard = ApplyLineChartAnimation(currentDataSeries, line2dCanvas, series.Storyboard, true);
}
// Create Moving Marker
if (series.MovingMarkerEnabled)
{
Double movingMarkerSize = (Double)series.MarkerSize * (Double)series.MarkerScale * MOVING_MARKER_SCALE;
if (movingMarkerSize < 6)
movingMarkerSize = 6;
Ellipse movingMarker = new Ellipse() { Visibility= Visibility.Collapsed, IsHitTestVisible = false, Height = movingMarkerSize, Width = movingMarkerSize, Fill = lineParams.LineColor };
labelCanvas.Children.Add(movingMarker);
series._movingMarker = movingMarker;
}
else
series._movingMarker = null;
isMovingMarkerEnabled = isMovingMarkerEnabled || series.MovingMarkerEnabled;
}
// Detach attached events
chart.ChartArea.PlotAreaCanvas.MouseMove -= PlotAreaCanvas_MouseMove;
chart.ChartArea.PlotAreaCanvas.MouseLeave -= PlotAreaCanvas_MouseLeave;
chart.ChartArea.PlotAreaCanvas.MouseEnter -= PlotAreaCanvas_MouseEnter;
if (isMovingMarkerEnabled)
{
chart.ChartArea.PlotAreaCanvas.MouseMove += new MouseEventHandler(PlotAreaCanvas_MouseMove);
chart.ChartArea.PlotAreaCanvas.MouseLeave += new MouseEventHandler(PlotAreaCanvas_MouseLeave);
chart.ChartArea.PlotAreaCanvas.MouseEnter += new MouseEventHandler(PlotAreaCanvas_MouseEnter);
}
visual.Children.Add(lineChartCanvas);
visual.Children.Add(labelCanvas);
//Double plotGroupCount = (from c in chart.PlotDetails.PlotGroups
// where c.AxisY.AxisType == AxisTypes.Secondary
// select c).Count();
RectangleGeometry clipRectangle = new RectangleGeometry();
Double clipLeft = 0;
Double clipTop = -depth3d;
Double clipWidth = width + depth3d;
Double clipHeight = height + depth3d + chart.ChartArea.PLANK_THICKNESS + 6;
AreaChart.GetClipCoordinates(chart, ref clipLeft, ref clipTop, ref clipWidth, ref clipHeight, minimumXValue, maximumXValue);
clipRectangle.Rect = new Rect(clipLeft, clipTop, clipWidth, clipHeight);
visual.Clip = clipRectangle;
// If animation is not enabled or if there are no series in the serieslist the dont apply animation
if (animationEnabled && seriesList.Count > 0)
{
// Apply animation to the label canvas
currentDataSeries = seriesList[0];
if (currentDataSeries.Storyboard == null)
currentDataSeries.Storyboard = new Storyboard();
currentDataSeries.Storyboard = ApplyLineChartAnimation(currentDataSeries, labelCanvas, currentDataSeries.Storyboard, false);
}
clipRectangle = new RectangleGeometry();
clipRectangle.Rect = new Rect(0, -depth3d, width + depth3d, height + chart.ChartArea.PLANK_DEPTH);
lineChartCanvas.Clip = clipRectangle;
return visual;
}
///
/// MouseEnter event handler for MouseEnter event over PlotAreaCanvas
///
/// object
/// MouseEventArgs
static void PlotAreaCanvas_MouseEnter(object sender, MouseEventArgs e)
{
_isMouseEnteredInPlotArea = true;
}
///
/// MouseLeave event handler for MouseLeave event over PlotAreaCanvas
///
/// object
/// MouseEventArgs
static void PlotAreaCanvas_MouseLeave(object sender, MouseEventArgs e)
{
_isMouseEnteredInPlotArea = false;
// Disable Moving marker for PlotArea Canvas
foreach (DataSeries ds in _listOfDataSeries)
{
if (ds._movingMarker != null)
{
ds._movingMarker.Visibility = Visibility.Collapsed;
}
}
}
///
/// MouseMove event handler for MouseMove event over PlotAreaCanvas
///
/// object
/// MouseEventArgs
static void PlotAreaCanvas_MouseMove(object sender, MouseEventArgs e)
{
(sender as FrameworkElement).Dispatcher.BeginInvoke(new Action