Init
This commit is contained in:
32
DataDisplay/Common/DrawingHelper.cs
Normal file
32
DataDisplay/Common/DrawingHelper.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DataDisplay.Common
|
||||
{
|
||||
public class DrawingHelper
|
||||
{
|
||||
private DrawingHelper()
|
||||
{
|
||||
}
|
||||
public static void DrawIsoTriangle(SKCanvas canvas, SKPoint origin, int length, SKPaint paintStroke, SKPaint paintFill)
|
||||
{
|
||||
SKPath path = new SKPath();
|
||||
|
||||
float lengthF = (float)length;
|
||||
float hLength = lengthF / 2f;
|
||||
float height = (float)Math.Sqrt(Math.Pow(lengthF, 2) - Math.Pow(hLength, 2));
|
||||
|
||||
height/=1.15f; // reduce the height by 15%
|
||||
|
||||
path.MoveTo(origin.X, origin.Y);
|
||||
path.LineTo(origin.X - hLength, origin.Y + height);
|
||||
path.LineTo(origin.X + hLength, origin.Y + height);
|
||||
path.LineTo(origin.X, origin.Y);
|
||||
path.Close();
|
||||
canvas.DrawPath(path, paintStroke);
|
||||
canvas.DrawPath(path, paintFill);
|
||||
}
|
||||
}
|
||||
}
|
||||
162
DataDisplay/Common/GridLines.cs
Normal file
162
DataDisplay/Common/GridLines.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using MarketData.Utils;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace DataDisplay.Common
|
||||
{
|
||||
public class GridLines
|
||||
{
|
||||
public delegate Type GetXDataType();
|
||||
public delegate Type GetYDataType();
|
||||
public delegate String XDataFormatter(Object data); // custom formatter for XData
|
||||
public delegate String YDataFormatter(Object data); // custom formatter for YData
|
||||
|
||||
private SKPaint paintGrid=new SKPaint{Style=SKPaintStyle.Stroke,Color=new SKColor((uint)SKColors.LightGray),StrokeWidth=1f};
|
||||
private SKPaint paintAxis=new SKPaint{Style=SKPaintStyle.Stroke,Color=new SKColor((uint)SKColors.Black),StrokeWidth=3f};
|
||||
private SKPaint paintHorizontalLabelText=new SKPaint{TextSize=24f,IsAntialias=true,Color=new SKColor((uint)SKColors.Black),IsStroke=false,Typeface=SKTypeface.FromFamilyName("Calibri",SKFontStyleWeight.Bold,SKFontStyleWidth.Normal,SKFontStyleSlant.Upright)}; // dates
|
||||
private SKPaint paintVerticalLabelText=new SKPaint{TextSize=24f,IsAntialias=true,Color=new SKColor((uint)SKColors.Black),IsStroke=false,Typeface=SKTypeface.FromFamilyName("Calibri",SKFontStyleWeight.Bold,SKFontStyleWidth.Normal,SKFontStyleSlant.Upright)}; // values
|
||||
private GetXDataType getXDataType=null;
|
||||
private GetYDataType getYDataType=null;
|
||||
private XDataFormatter xDataFormatter;
|
||||
private YDataFormatter yDataFormatter;
|
||||
private Dictionary<String,String> uniqueVerticalLabels;
|
||||
private Dictionary<String,String> uniqueHorizontalLabels;
|
||||
|
||||
public GridLines(double xGridLines,double yGridLines)
|
||||
{
|
||||
XGridLines=xGridLines;
|
||||
YGridLines=yGridLines;
|
||||
uniqueVerticalLabels=new Dictionary<String,String>();
|
||||
uniqueHorizontalLabels=new Dictionary<String,String>();
|
||||
}
|
||||
public double XGridLines{get;private set;}
|
||||
public double YGridLines{get;private set;}
|
||||
|
||||
public GetXDataType AssignXDataType
|
||||
{
|
||||
get{return getXDataType;}
|
||||
set{getXDataType=value;}
|
||||
}
|
||||
|
||||
public GetYDataType AssignGetYDataType
|
||||
{
|
||||
get{return getYDataType;}
|
||||
set{getYDataType=value;}
|
||||
}
|
||||
|
||||
public XDataFormatter AssignXDataFormatter
|
||||
{
|
||||
get{return xDataFormatter;}
|
||||
set{xDataFormatter=value;}
|
||||
}
|
||||
|
||||
public YDataFormatter AssignYDataFormatter
|
||||
{
|
||||
get{return yDataFormatter;}
|
||||
set{yDataFormatter=value;}
|
||||
}
|
||||
|
||||
public void Render(SKCanvas canvas,PointMapping pointMapping,SKPaint axisPaint=null,SKPaint gridPaint=null)
|
||||
{
|
||||
if(null==axisPaint)axisPaint=paintAxis;
|
||||
if(null==gridPaint)gridPaint=paintGrid;
|
||||
SKPoint p1;
|
||||
SKPoint p2;
|
||||
|
||||
double xLineFactor = pointMapping.XRange/XGridLines;
|
||||
double yLineFactor = pointMapping.YRange/YGridLines;
|
||||
|
||||
if(0==xLineFactor)xLineFactor=.01;
|
||||
if(0==yLineFactor)yLineFactor=.01;
|
||||
|
||||
// Draw horizontal line grid - the labels go along the left
|
||||
for (double yIndex = pointMapping.YDataExtentMin; yIndex <= pointMapping.YDataExtent+.05; yIndex += yLineFactor)
|
||||
{
|
||||
p1 = pointMapping.MapPoint(new SKPoint((float)pointMapping.XDataExtentMin, (float)yIndex));
|
||||
p2 = pointMapping.MapPoint(new SKPoint((float)pointMapping.XDataExtent, (float)yIndex));
|
||||
if(yIndex==pointMapping.YDataExtentMin)canvas.DrawPoints(SKPointMode.Polygon, new SKPoint[] { p1, p2 }, axisPaint);
|
||||
else canvas.DrawPoints(SKPointMode.Polygon, new SKPoint[] { p1, p2 }, gridPaint);
|
||||
p1.X=0f;
|
||||
DisplayXAxisLabel(canvas,pointMapping,p1,yIndex);
|
||||
}
|
||||
// Draw vertical line grid - the labels go along the bottom
|
||||
for (double xIndex = pointMapping.XDataExtentMin; xIndex <= pointMapping.XDataExtent+.05; xIndex += xLineFactor)
|
||||
{
|
||||
p1 = pointMapping.MapPoint(new SKPoint((float)xIndex,(float)pointMapping.YDataExtentMin));
|
||||
p2 = pointMapping.MapPoint(new SKPoint((float)xIndex, (float)pointMapping.YDataExtent));
|
||||
if(xIndex==pointMapping.XDataExtentMin)canvas.DrawPoints(SKPointMode.Polygon, new SKPoint[] { p1, p2 }, axisPaint);
|
||||
else canvas.DrawPoints(SKPointMode.Polygon, new SKPoint[] { p1, p2 }, gridPaint);
|
||||
p1.Y=(float)pointMapping.Height;
|
||||
DisplayYAxisLabel(canvas,pointMapping,p1,xIndex);
|
||||
}
|
||||
}
|
||||
// The labels along the left of the chart
|
||||
private void DisplayXAxisLabel(SKCanvas canvas,PointMapping pointMapping,SKPoint p1,double yIndex)
|
||||
{
|
||||
String strLabel;
|
||||
double percentage=(yIndex - pointMapping.YDataExtentMin) / pointMapping.YRange;
|
||||
Type dataType=typeof(double);
|
||||
if(null!=getYDataType)dataType=getYDataType();
|
||||
if(dataType.Equals(typeof(DateTime)))
|
||||
{
|
||||
DateTime axisDate=new DateTime((long)((pointMapping.YDataExtentMin+(pointMapping.YRange*percentage))*10000000000.0));
|
||||
if(null!=yDataFormatter)strLabel=yDataFormatter(axisDate);
|
||||
else strLabel=Utility.DateTimeToStringMMSYY(axisDate);
|
||||
}
|
||||
else if(dataType.Equals(typeof(double)))
|
||||
{
|
||||
double value=pointMapping.YDataExtentMin+(pointMapping.YRange*percentage);
|
||||
if(null!=yDataFormatter)strLabel=yDataFormatter(value);
|
||||
else
|
||||
{
|
||||
if(value>=1000)strLabel=Utility.FormatNumber(value,0,true);
|
||||
else strLabel=Utility.FormatNumber(value,2,true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double value=pointMapping.YDataExtentMin+(pointMapping.YRange*percentage);
|
||||
strLabel=Utility.FormatNumber(value,0,true);
|
||||
}
|
||||
if(uniqueVerticalLabels.ContainsKey(strLabel))return;
|
||||
uniqueVerticalLabels.Add(strLabel,strLabel);
|
||||
if(null!=strLabel)canvas.DrawText(strLabel, p1, paintVerticalLabelText);
|
||||
}
|
||||
// The labels along the bottom of the chart
|
||||
private void DisplayYAxisLabel(SKCanvas canvas,PointMapping pointMapping,SKPoint p1,double xIndex)
|
||||
{
|
||||
String strLabel=null;
|
||||
double percentage=(xIndex-pointMapping.XDataExtentMin)/pointMapping.XRange;
|
||||
Type dataType=typeof(double);
|
||||
if(null!=getXDataType())dataType=getXDataType();
|
||||
|
||||
if (dataType.Equals(typeof(DateTime)))
|
||||
{
|
||||
DateTime axisDate=new DateTime((long)((pointMapping.XDataExtentMin+(pointMapping.XRange*percentage))*10000000000.0));
|
||||
if(null!=xDataFormatter)strLabel=xDataFormatter(axisDate);
|
||||
else strLabel=Utility.DateTimeToStringMMSYY(axisDate);
|
||||
}
|
||||
else if(dataType.Equals(typeof(double)))
|
||||
{
|
||||
double value=pointMapping.XDataExtentMin+(pointMapping.XRange*percentage);
|
||||
if(null!=xDataFormatter)strLabel=xDataFormatter(value);
|
||||
else
|
||||
{
|
||||
if(value>=1000)strLabel=Utility.FormatNumber(value,0,true);
|
||||
else strLabel=Utility.FormatNumber(value,2,true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double value=pointMapping.XDataExtentMin+(pointMapping.XRange*percentage);
|
||||
strLabel=Utility.FormatNumber(value,0,true);
|
||||
}
|
||||
if(uniqueHorizontalLabels.ContainsKey(strLabel))return;
|
||||
uniqueHorizontalLabels.Add(strLabel,strLabel);
|
||||
if(null!=strLabel)canvas.DrawText(strLabel, p1, paintHorizontalLabelText);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
DataDisplay/Common/PointMapping.cs
Normal file
50
DataDisplay/Common/PointMapping.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DataDisplay.Common
|
||||
{
|
||||
public class PointMapping
|
||||
{
|
||||
public PointMapping(double width,double height,double xDataExtent,double xDataExtentMin,double yDataExtent,double yDataExtentMin,double xMarginExtent=0.00,double yMarginExtent=0.00)
|
||||
{
|
||||
Width=width;
|
||||
Height=height;
|
||||
XMargin=xMarginExtent;
|
||||
YMargin=yMarginExtent;
|
||||
XDataExtent=xDataExtent;
|
||||
XDataExtentMin=xDataExtentMin;
|
||||
YDataExtent=yDataExtent;
|
||||
YDataExtentMin=yDataExtentMin;
|
||||
XScalingFactor=(Width-(XMargin*2))/(XDataExtent-XDataExtentMin);
|
||||
YScalingFactor=(Height-(YMargin*2))/(YDataExtent-YDataExtentMin);
|
||||
}
|
||||
// MapPoint will both scale the given point and translate the given point from an upper left origin system to a bottom left origin system
|
||||
public SKPoint MapPoint(SKPoint sourcePoint)
|
||||
{
|
||||
SKPoint mappedPoint=new SKPoint((float)((sourcePoint.X-XDataExtentMin)*XScalingFactor),(float)(Height-((sourcePoint.Y-YDataExtentMin)*YScalingFactor)));
|
||||
mappedPoint.X+=(float)XMargin; // offset by the xMargin
|
||||
mappedPoint.Y-=(float)YMargin; // offset by the yMargin
|
||||
return mappedPoint;
|
||||
}
|
||||
// TranslatePoint will only translate the given point from an upper left origin system to a bottom left origin system
|
||||
public SKPoint TranslatePoint(SKPoint sourcePoint)
|
||||
{
|
||||
SKPoint mappedPoint=new SKPoint((float)sourcePoint.X,(float)(Height-sourcePoint.Y-1));
|
||||
return mappedPoint;
|
||||
}
|
||||
public double Width{get;private set;}
|
||||
public double Height{get;private set;}
|
||||
public double XDataExtent{get;private set;}
|
||||
public double XDataExtentMin{get;private set;}
|
||||
public double XRange{get{return XDataExtent-XDataExtentMin;}}
|
||||
public double YDataExtent{get;private set;}
|
||||
public double YDataExtentMin{get;private set;}
|
||||
public double YRange{get{return YDataExtent-YDataExtentMin;}}
|
||||
public double XMargin{get;private set;}
|
||||
public double YMargin{get;private set;}
|
||||
public double XScalingFactor{get;private set;}
|
||||
public double YScalingFactor{get;private set;}
|
||||
}
|
||||
}
|
||||
58
DataDisplay/Controls/RenderView.cs
Normal file
58
DataDisplay/Controls/RenderView.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using SkiaSharp.Views.Forms;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace DataDisplay.Controls
|
||||
{
|
||||
public class CanvasView : SKCanvasView
|
||||
{
|
||||
public static readonly BindableProperty RendererProperty = BindableProperty.Create(
|
||||
nameof(Renderer),
|
||||
typeof(Renderers.IRenderer),
|
||||
typeof(CanvasView),
|
||||
null,
|
||||
defaultBindingMode: BindingMode.TwoWay,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
((CanvasView)bindable).RendererChanged((Renderers.IRenderer)oldValue, (Renderers.IRenderer)newValue);
|
||||
});
|
||||
|
||||
// callback appelée lors de la modificiation de la propriété "Renderer"
|
||||
void RendererChanged(Renderers.IRenderer currentRenderer, Renderers.IRenderer newRenderer)
|
||||
{
|
||||
if (currentRenderer != newRenderer)
|
||||
{
|
||||
// détacher l'événement de l'ancien renderer
|
||||
if (currentRenderer != null)
|
||||
currentRenderer.RefreshRequested -= Renderer_RefreshRequested;
|
||||
|
||||
// attacher l'événement du nouveau renderer
|
||||
if (newRenderer != null)
|
||||
newRenderer.RefreshRequested += Renderer_RefreshRequested;
|
||||
|
||||
// rafraichir le contrôle
|
||||
InvalidateSurface();
|
||||
}
|
||||
}
|
||||
|
||||
// Causes refreshing during an event triggered by the ISKRenderer interface
|
||||
void Renderer_RefreshRequested(object sender, EventArgs e)
|
||||
{
|
||||
InvalidateSurface();
|
||||
}
|
||||
|
||||
protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
|
||||
{
|
||||
if (null == Renderer) return;
|
||||
Renderer.PaintSurface(e.Surface, e.Info);
|
||||
}
|
||||
|
||||
public Renderers.IRenderer Renderer
|
||||
{
|
||||
get { return (Renderers.IRenderer)GetValue(RendererProperty); }
|
||||
set { SetValue(RendererProperty, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
19
DataDisplay/DataDisplay.csproj
Normal file
19
DataDisplay/DataDisplay.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp.Views.Forms" Version="1.68.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Renderers\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MarketDataLib\MarketDataLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
25
DataDisplay/DataDisplay.sln
Normal file
25
DataDisplay/DataDisplay.sln
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30204.135
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataDisplay", "DataDisplay.csproj", "{11EFBEE6-2BFD-4D05-AD34-C5B0A894EB99}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{11EFBEE6-2BFD-4D05-AD34-C5B0A894EB99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{11EFBEE6-2BFD-4D05-AD34-C5B0A894EB99}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{11EFBEE6-2BFD-4D05-AD34-C5B0A894EB99}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{11EFBEE6-2BFD-4D05-AD34-C5B0A894EB99}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DFC8F4F9-9B94-400D-8E39-6561192D5486}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
129
DataDisplay/DataSource/CompositeDataSource.cs
Normal file
129
DataDisplay/DataSource/CompositeDataSource.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Collections;
|
||||
using Xamarin.Forms;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace DataDisplay.DataSource
|
||||
{
|
||||
public class DataSourceHelper
|
||||
{
|
||||
private DataSourceHelper()
|
||||
{
|
||||
}
|
||||
public static IEnumerable<SKPoint> GetPoints(IPointDataSource dataSource)
|
||||
{
|
||||
if (dataSource == null) throw new ArgumentNullException("dataSource");
|
||||
using (IPointEnumerator enumerator = dataSource.GetEnumerator(null))
|
||||
{
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
Point xPoint=new Point();
|
||||
enumerator.GetCurrent(ref xPoint);
|
||||
yield return new SKPoint((float)xPoint.X,(float)xPoint.Y);
|
||||
xPoint=new Point();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Data source that is a composer from several other data sources.</summary>
|
||||
public class CompositeDataSource : IPointDataSource {
|
||||
/// <summary>Initializes a new instance of the <see cref="CompositeDataSource"/> class</summary>
|
||||
public CompositeDataSource() { }
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
dataParts.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CompositeDataSource"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dataSources">Data sources.</param>
|
||||
public CompositeDataSource(params IPointDataSource[] dataSources) {
|
||||
if (dataSources == null)
|
||||
throw new ArgumentNullException("dataSources");
|
||||
|
||||
foreach (var dataSource in dataSources) {
|
||||
AddDataPart(dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<IPointDataSource> dataParts = new List<IPointDataSource>();
|
||||
|
||||
public IEnumerable<IPointDataSource> DataParts {
|
||||
get { return dataParts; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds data part.
|
||||
/// </summary>
|
||||
/// <param name="dataPart">The data part.</param>
|
||||
public void AddDataPart(IPointDataSource dataPart) {
|
||||
if (dataPart == null)
|
||||
throw new ArgumentNullException("dataPart");
|
||||
|
||||
dataParts.Add(dataPart);
|
||||
dataPart.DataChanged += OnPartDataChanged;
|
||||
}
|
||||
|
||||
private void OnPartDataChanged(object sender, EventArgs e) {
|
||||
RaiseDataChanged();
|
||||
}
|
||||
|
||||
#region IPointSource Members
|
||||
|
||||
public event EventHandler DataChanged;
|
||||
protected void RaiseDataChanged() {
|
||||
if (DataChanged != null) {
|
||||
DataChanged(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public IPointEnumerator GetEnumerator(BindableObject context) {
|
||||
return new CompositeEnumerator(this, context);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private sealed class CompositeEnumerator : IPointEnumerator {
|
||||
private readonly IEnumerable<IPointEnumerator> enumerators;
|
||||
|
||||
public CompositeEnumerator(CompositeDataSource dataSource, BindableObject context) {
|
||||
enumerators = dataSource.dataParts.Select(part => part.GetEnumerator(context)).ToList();
|
||||
}
|
||||
|
||||
#region IChartPointEnumerator Members
|
||||
|
||||
public bool MoveNext() {
|
||||
bool res = false;
|
||||
foreach (var enumerator in enumerators) {
|
||||
res |= enumerator.MoveNext();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public void ApplyMappings(BindableObject glyph) {
|
||||
foreach (var enumerator in enumerators) {
|
||||
enumerator.ApplyMappings(glyph);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetCurrent(ref Point p) {
|
||||
foreach (var enumerator in enumerators) {
|
||||
enumerator.GetCurrent(ref p);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
foreach (var enumerator in enumerators) {
|
||||
enumerator.Dispose();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
14
DataDisplay/DataSource/DataSourceExtensions.cs
Normal file
14
DataDisplay/DataSource/DataSourceExtensions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DataDisplay.DataSource
|
||||
{
|
||||
public static class DataSourceExtensions
|
||||
{
|
||||
public static CompositeDataSource Join(this IPointDataSource ds1, IPointDataSource ds2)
|
||||
{
|
||||
return new CompositeDataSource(ds1, ds2);
|
||||
}
|
||||
}
|
||||
}
|
||||
110
DataDisplay/DataSource/EnumerableDataSource.cs
Normal file
110
DataDisplay/DataSource/EnumerableDataSource.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace DataDisplay.DataSource
|
||||
{
|
||||
public class EnumerableDataSource<T> : EnumerableDataSourceBase<T>
|
||||
{
|
||||
public EnumerableDataSource(IEnumerable<T> data) : base(data) { }
|
||||
|
||||
public EnumerableDataSource(IEnumerable data) : base(data) { }
|
||||
|
||||
private readonly List<Mapping<T>> mappings = new List<Mapping<T>>();
|
||||
private Func<T, double> xMapping;
|
||||
private Func<T, double> yMapping;
|
||||
private Func<T, Point> xyMapping;
|
||||
|
||||
public Func<T, double> XMapping
|
||||
{
|
||||
get { return xMapping; }
|
||||
set { SetXMapping(value); }
|
||||
}
|
||||
|
||||
public Func<T, double> YMapping
|
||||
{
|
||||
get { return yMapping; }
|
||||
set { SetYMapping(value); }
|
||||
}
|
||||
|
||||
public Func<T, Point> XYMapping
|
||||
{
|
||||
get { return xyMapping; }
|
||||
set { SetXYMapping(value); }
|
||||
}
|
||||
|
||||
public void SetXMapping(Func<T, double> mapping)
|
||||
{
|
||||
if (mapping == null)
|
||||
throw new ArgumentNullException("mapping");
|
||||
|
||||
this.xMapping = mapping;
|
||||
RaiseDataChanged();
|
||||
}
|
||||
|
||||
public void SetYMapping(Func<T, double> mapping)
|
||||
{
|
||||
if (mapping == null)
|
||||
throw new ArgumentNullException("mapping");
|
||||
|
||||
this.yMapping = mapping;
|
||||
RaiseDataChanged();
|
||||
}
|
||||
|
||||
public void SetXYMapping(Func<T, Point> mapping)
|
||||
{
|
||||
if (mapping == null)
|
||||
throw new ArgumentNullException("mapping");
|
||||
|
||||
this.xyMapping = mapping;
|
||||
RaiseDataChanged();
|
||||
}
|
||||
|
||||
public void AddMapping(BindableProperty property, Func<T, object> mapping)
|
||||
{
|
||||
if (property == null)
|
||||
throw new ArgumentNullException("property");
|
||||
if (mapping == null)
|
||||
throw new ArgumentNullException("mapping");
|
||||
|
||||
mappings.Add(new Mapping<T> { Property = property, F = mapping });
|
||||
}
|
||||
|
||||
public override IPointEnumerator GetEnumerator(BindableObject context)
|
||||
{
|
||||
return new EnumerablePointEnumerator<T>(this);
|
||||
}
|
||||
|
||||
internal void FillPoint(T elem, ref Point point)
|
||||
{
|
||||
if (xyMapping != null)
|
||||
{
|
||||
point = xyMapping(elem);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (xMapping != null)
|
||||
{
|
||||
point.X = xMapping(elem);
|
||||
}
|
||||
if (yMapping != null)
|
||||
{
|
||||
point.Y = yMapping(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void ApplyMappings(BindableObject target, T elem)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
foreach (var mapping in mappings)
|
||||
{
|
||||
target.SetValue(mapping.Property, mapping.F(elem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
DataDisplay/DataSource/EnumerableDataSourceBase.cs
Normal file
57
DataDisplay/DataSource/EnumerableDataSourceBase.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace DataDisplay.DataSource
|
||||
{
|
||||
/// <summary>Base class for all sources who receive data for charting
|
||||
/// from any IEnumerable of T</summary>
|
||||
/// <typeparam name="T">Type of items in IEnumerable</typeparam>
|
||||
public abstract class EnumerableDataSourceBase<T> : IPointDataSource {
|
||||
private IEnumerable data;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the data.
|
||||
/// </summary>
|
||||
/// <value>The data.</value>
|
||||
public IEnumerable Data {
|
||||
get { return data; }
|
||||
set {
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
data = value;
|
||||
|
||||
var observableCollection = data as INotifyCollectionChanged;
|
||||
if (observableCollection != null) {
|
||||
observableCollection.CollectionChanged += observableCollection_CollectionChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected EnumerableDataSourceBase(IEnumerable<T> data) : this((IEnumerable)data) { }
|
||||
|
||||
protected EnumerableDataSourceBase(IEnumerable data) {
|
||||
if (data == null)
|
||||
throw new ArgumentNullException("data");
|
||||
Data = data;
|
||||
}
|
||||
|
||||
private void observableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
RaiseDataChanged();
|
||||
}
|
||||
|
||||
public event EventHandler DataChanged;
|
||||
public void RaiseDataChanged() {
|
||||
if (DataChanged != null) {
|
||||
DataChanged(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract IPointEnumerator GetEnumerator(BindableObject context);
|
||||
}
|
||||
}
|
||||
37
DataDisplay/DataSource/EnumerablePointEnumerator.cs
Normal file
37
DataDisplay/DataSource/EnumerablePointEnumerator.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Windows;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace DataDisplay.DataSource
|
||||
{
|
||||
/// <summary>This enumerator enumerates given enumerable object as sequence of points</summary>
|
||||
/// <typeparam name="T">Type parameter of source IEnumerable</typeparam>
|
||||
public sealed class EnumerablePointEnumerator<T> : IPointEnumerator {
|
||||
private readonly EnumerableDataSource<T> dataSource;
|
||||
private readonly IEnumerator enumerator;
|
||||
|
||||
public EnumerablePointEnumerator(EnumerableDataSource<T> dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
enumerator = dataSource.Data.GetEnumerator();
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
try{return enumerator.MoveNext();}
|
||||
catch(InvalidOperationException){return false;}
|
||||
}
|
||||
|
||||
public void GetCurrent(ref Point p) {
|
||||
dataSource.FillPoint((T)enumerator.Current, ref p);
|
||||
}
|
||||
|
||||
public void ApplyMappings(BindableObject target) {
|
||||
dataSource.ApplyMappings(target, (T)enumerator.Current);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
//enumerator.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
DataDisplay/DataSource/IPointDataSource.cs
Normal file
17
DataDisplay/DataSource/IPointDataSource.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace DataDisplay.DataSource
|
||||
{
|
||||
/// <summary>Data source that returns sequence of 2D points.</summary>
|
||||
public interface IPointDataSource
|
||||
{
|
||||
|
||||
/// <summary>Returns object to enumerate points in source.</summary>
|
||||
IPointEnumerator GetEnumerator(BindableObject context);
|
||||
|
||||
/// <summary>This event is raised when contents of source are changed.</summary>
|
||||
event EventHandler DataChanged;
|
||||
}
|
||||
}
|
||||
22
DataDisplay/DataSource/IPointEnumerator.cs
Normal file
22
DataDisplay/DataSource/IPointEnumerator.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace DataDisplay.DataSource
|
||||
{
|
||||
public interface IPointEnumerator : IDisposable {
|
||||
/// <summary>Move to next point in sequence</summary>
|
||||
/// <returns>True if successfully moved to next point
|
||||
/// or false if end of sequence is reached</returns>
|
||||
bool MoveNext();
|
||||
|
||||
/// <summary>Stores current value(s) in given point.</summary>
|
||||
/// <param name="p">Reference to store value</param>
|
||||
/// <remarks>Depending on implementing class this method may set only X or Y
|
||||
/// fields in specified point. That's why GetCurrent is a regular method and
|
||||
/// not a property as in standard enumerators</remarks>
|
||||
void GetCurrent(ref Point p);
|
||||
|
||||
void ApplyMappings(BindableObject target);
|
||||
}
|
||||
}
|
||||
16
DataDisplay/DataSource/Mapping.cs
Normal file
16
DataDisplay/DataSource/Mapping.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace DataDisplay.DataSource
|
||||
{
|
||||
/// <summary>Mapping class holds information about mapping of TSource type
|
||||
/// to some DependencyProperty.</summary>
|
||||
/// <typeparam name="TSource">Mapping source type.</typeparam>
|
||||
internal sealed class Mapping<TSource> {
|
||||
/// <summary>Property that will be set.</summary>
|
||||
internal BindableProperty Property { get; set; }
|
||||
/// <summary>Function that computes value for property from TSource type.</summary>
|
||||
internal Func<TSource, object> F { get; set; }
|
||||
}
|
||||
}
|
||||
77
DataDisplay/Graph/LineGraph.cs
Normal file
77
DataDisplay/Graph/LineGraph.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using DataDisplay.Common;
|
||||
using DataDisplay.DataSource;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace DataDisplay.Graph
|
||||
{
|
||||
public class LineGraph
|
||||
{
|
||||
private SKPoint[] points=null;
|
||||
private CompositeDataSource compositeDataSource;
|
||||
private SKPaint paint;
|
||||
|
||||
public LineGraph(SKPaint paint,CompositeDataSource compositeDataSource)
|
||||
{
|
||||
this.compositeDataSource = compositeDataSource;
|
||||
this.paint=paint;
|
||||
points=GetSKPoints();
|
||||
}
|
||||
public void SetPaint(SKPaint paint)
|
||||
{
|
||||
this.paint=paint;
|
||||
}
|
||||
public void SetDataSource(CompositeDataSource compositeDataSource)
|
||||
{
|
||||
this.compositeDataSource=compositeDataSource;
|
||||
points=GetSKPoints();
|
||||
}
|
||||
public void Render(SKCanvas canvas,PointMapping pointMapping)
|
||||
{
|
||||
if(null==points)return;
|
||||
SKPoint[] mappedPoints = new SKPoint[points.Length];
|
||||
for (int index = 0; index < points.Length; index++) mappedPoints[index] = pointMapping.MapPoint(points[index]);
|
||||
canvas.DrawPoints(SKPointMode.Polygon,mappedPoints,paint);
|
||||
}
|
||||
public SKPoint[] GetSKPoints()
|
||||
{
|
||||
IEnumerable<SKPoint> pointsEnumerable=DataSourceHelper.GetPoints(compositeDataSource);
|
||||
IEnumerator<SKPoint> pointsEnumerator=pointsEnumerable.GetEnumerator();
|
||||
List<SKPoint> points=new List<SKPoint>();
|
||||
while(pointsEnumerator.MoveNext())points.Add(pointsEnumerator.Current);
|
||||
return points.ToArray();
|
||||
}
|
||||
public SKPoint[] SKPoints
|
||||
{
|
||||
get{return points;}
|
||||
private set{;}
|
||||
}
|
||||
public double GetXExtent()
|
||||
{
|
||||
if(null==points)return 0.00;
|
||||
double xExtents=points.Max(x=>x.X);
|
||||
return xExtents;
|
||||
}
|
||||
public double GetXExtentMin()
|
||||
{
|
||||
if(null==points)return 0.00;
|
||||
double xExtents=points.Min(x=>x.X);
|
||||
return xExtents;
|
||||
}
|
||||
public double GetYExtent()
|
||||
{
|
||||
if(null==points)return 0.00;
|
||||
double yExtents=points.Max(x=>x.Y);
|
||||
return yExtents;
|
||||
}
|
||||
public double GetYExtentMin()
|
||||
{
|
||||
if(null==points)return 0.00;
|
||||
double yExtents=points.Min(x=>x.Y);
|
||||
return yExtents;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
DataDisplay/Renderers/CircleRenderer.cs
Normal file
41
DataDisplay/Renderers/CircleRenderer.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
//using SkiaSharp;
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Text;
|
||||
|
||||
//namespace DataDisplay.Renderers
|
||||
//{
|
||||
// class CircleRenderer : IRenderer
|
||||
// {
|
||||
// public void PaintSurface(SKSurface surface, SKImageInfo info)
|
||||
// {
|
||||
// SKCanvas canvas = surface.Canvas; // récupère le canvas
|
||||
// canvas.Clear();
|
||||
|
||||
// // opérations de dessin, par exemple dessiner un cercle
|
||||
// // paramètre de remplissage du cercle
|
||||
// SKPaint fillPaint = new SKPaint
|
||||
// {
|
||||
// Style = SKPaintStyle.Fill,
|
||||
// Color = FillColor
|
||||
// };
|
||||
// canvas.DrawCircle(info.Width / 2, info.Height / 2, 100, fillPaint);
|
||||
// }
|
||||
|
||||
// SKColor _fillColor = new SKColor(160, 160, 160);
|
||||
// public SKColor FillColor
|
||||
// {
|
||||
// get => _fillColor;
|
||||
// set
|
||||
// {
|
||||
// if (_fillColor != value)
|
||||
// {
|
||||
// _fillColor = value;
|
||||
// RefreshRequested?.Invoke(this, EventArgs.Empty);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// public event EventHandler RefreshRequested;
|
||||
// }
|
||||
//}
|
||||
11
DataDisplay/Renderers/IRenderer.cs
Normal file
11
DataDisplay/Renderers/IRenderer.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace DataDisplay.Renderers
|
||||
{
|
||||
public interface IRenderer
|
||||
{
|
||||
void PaintSurface(SKSurface surface, SKImageInfo info);
|
||||
event EventHandler RefreshRequested;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user