SciChart(3.1.4549版本)
目录
- BindToDataSeriesSetView.xaml
- NumericAxis
- FastLineRenderableSeries
- SciChartSurface
- HighSpeedRenderSurface
- ViewConverter
- IRenderContext2D
- IRenderPassData
摘要
- BindToDataSeriesSetView.xaml由NumericAxis(坐标轴), FastLineRenderableSeries(曲线), SciChartSurface(Chart区域)构成;
- SciChartSurface的DoDrawingLoop用到IRenderSurface2D(HighSpeedRenderSurfaceu实现, 用来获取IRenderContext2D, 类似Winform中的Graphics)和ISciChartRenderer(ViewConverter实现, 以IRenderContext2D为参数进行绘制);
- ViewConverter的RenderLoop中会调用NumericAxis和FastLineRenderableSeries的OnDraw方法, 而IRenderContext2D和IRenderPassData是OnDraw的两个参数
BindToDataSeriesSetView.xaml
1 | <UserControl x:Class="Abt.Controls.SciChart.Example.Examples.IWantTo.UseSciChartWithMvvm.BindToDataSeriesSetView" |
程序仅绘制了一条曲线和XY坐标轴
工具: ILSpy
dll: Abt.Controls.SciChart.Wpf.dll(3.1.4549版本)
程序中是
绘制1个FastLineRenderableSeries
绘制2个NumericAxis
先看NumericAxis
NumericAxis
public class NumericAxis : AxisBase
public abstract class AxisBase : ContentControl, INotifyPropertyChanged, IXmlSerializable, IDrawable, IAxisParams, IHitTestable, ISuspendable, IInvalidatableElement, IAxis
看IDrawable
1 | namespace Abt.Controls.SciChart.Visuals |
回看AxisBase类中有OnDraw方法, 有计算ticks, 绘制坐标网格, 绘制坐标轴; NumericAxis使用OnDraw进行绘制
1 | /// <summary> |
再看FastLineRenderableSeries
FastLineRenderableSeries
public class FastLineRenderableSeries : BaseRenderableSeries
FastLineRenderableSeries中发现方法InternalDraw1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65/// <summary>
/// Draws the series using the <see cref="T:Abt.Controls.SciChart.Rendering.Common.IRenderContext2D" /> and the <see cref="T:Abt.Controls.SciChart.Visuals.RenderableSeries.IRenderPassData" /> passed in
/// </summary>
/// <param name="renderContext">The render context. This is a graphics object which has methods to draw lines, quads and polygons to the screen</param>
/// <param name="renderPassData">The render pass data. Contains a resampled <see cref="T:Abt.Controls.SciChart.Model.DataSeries.IPointSeries" />, the <see cref="T:Abt.Controls.SciChart.IndexRange" /> of points on the screen
/// and the current YAxis and XAxis <see cref="T:Abt.Controls.SciChart.Numerics.CoordinateCalculators.ICoordinateCalculator`1" /> to convert data-points to screen points</param>
protected override void InternalDraw(IRenderContext2D renderContext, IRenderPassData renderPassData)
{
Func<double, double, Color> func = null;
IPointSeries pointSeries = base.CurrentRenderPassData.PointSeries;
LinesEnumerable linesEnumerable = new LinesEnumerable(pointSeries, renderPassData.XCoordinateCalculator, renderPassData.YCoordinateCalculator, this.IsDigitalLine);
Func<Color, IPen2D> func2 = null;
FastLineRenderableSeries.ToolbarCollection toolbarCollection = new FastLineRenderableSeries.ToolbarCollection();
toolbarCollection.uriInstance = new DeploymentInvoker(renderContext, base.AntiAliasing, (float)base.StrokeThickness, base.Opacity);
try
{
if (base.SeriesColor.A != 0)
{
if (base.PaletteProvider != null)
{
IEnumerable<Point> arg_98_1 = linesEnumerable;
bool arg_98_2 = base.DrawNaNAs == LineDrawMode.ClosedLines;
if (func == null)
{
func = new Func<double, double, Color>(this.DisconnectSelection);
}
Func<double, double, Color> arg_98_3 = func;
if (func2 == null)
{
func2 = new Func<Color, IPen2D>(toolbarCollection.ViewReference);
}
renderContext.DrawLinesReusablePens(arg_98_1, arg_98_2, arg_98_3, func2);
}
else
{
this.DisconnectSelection(renderContext, base.SeriesColor, linesEnumerable, base.DrawNaNAs == LineDrawMode.ClosedLines, (this.StrokeDashArray != null) ? this.StrokeDashArray.ToArray<double>() : null);
}
}
}
finally
{
if (toolbarCollection.uriInstance != null)
{
((IDisposable)toolbarCollection.uriInstance).Dispose();
}
}
IPointMarker pointMarker = base.GetPointMarker();
if (pointMarker != null)
{
if (this.IsDigitalLine)
{
linesEnumerable = new LinesEnumerable(pointSeries, renderPassData.XCoordinateCalculator, renderPassData.YCoordinateCalculator, false);
}
using (IPen2D pen2D = renderContext.CreatePen(base.SeriesColor, base.AntiAliasing, (float)base.StrokeThickness, base.Opacity, null, PenLineCap.Round))
{
IPointMarker arg_156_0 = pointMarker;
IEnumerable<Point> arg_14E_0 = linesEnumerable;
if (FastLineRenderableSeries.CS$<>9__CachedAnonymousMethodDelegate4 == null)
{
FastLineRenderableSeries.CS$<>9__CachedAnonymousMethodDelegate4 = new Func<Point, bool>(FastLineRenderableSeries.DisconnectSelection);
}
arg_156_0.Draw(renderContext, arg_14E_0.Where(FastLineRenderableSeries.CS$<>9__CachedAnonymousMethodDelegate4), pen2D, null);
}
}
}
搜索DrawLinesReusablePens, 发现只有RenderContextBase实现IRenderContext2D接口, 然后DrawLinesReusablePens内部foreach遍历调用DrawLineSegment
后者调用DrawLine
那么谁调用了InternalDraw
protected override void InternalDraw 是虚方法,那么在父类BaseRenderableSeries中找, 发现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void IDrawable.ProcessContext(IRenderContext2D uriInstance, IRenderPassData windowCache)
{
this.CurrentRenderPassData = windowCache;
if (this.IsValidForDrawing)
{
if (this.uriInstance != uriInstance.ViewportSize)
{
this.OnParentSurfaceViewportSizeChanged();
this.uriInstance = uriInstance.ViewportSize;
}
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
this.InternalDraw(uriInstance, windowCache);
stopwatch.Stop();
SciChartDebugLogger.Instance.WriteLine("{0} DrawTime: {1}ms", new object[]
{
base.GetType().Name,
stopwatch.ElapsedMilliseconds
});
}
}
而且没有找到OnDraw方法,初步推断IDrawable.ProcessContext就是OnDraw
结束了FastLineRenderableSeries和NumericAxis, 再来看
SciChartSurface
它是如何调用FastLineRenderableSeries和NumericAxis的OnDraw
SciChartSurface的父类是SciChartSurfaceBase (SciChartSurfaceBase 继承Control,base指Control),里面有一个虚方法1
2
3
4/// <summary>
/// The inner drawing loop. Called once per frame. Do your drawing here.
/// </summary>
protected abstract void DoDrawingLoop();
由于是protected,该方法在public virtual void InvalidateElement()中被用到,终于出现public了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/// <summary>
/// Asynchronously requests that the element redraws itself plus children.
/// Will be ignored if the element is ISuspendable and currently IsSuspended (within a SuspendUpdates/ResumeUpdates call)
/// </summary>
public virtual void InvalidateElement()
{
if (this.IsSuspended)
{
SciChartDebugLogger.Instance.WriteLine("SciChartSurface.IsSuspended=true. Ignoring InvalidateElement() call", new object[0]);
return;
}
if (!OptionsList.SearchOptions() && this.RenderPriority != RenderPriority.Immediate)
{
if (this.uriInstance != null)
{
this.uriInstance.InvalidateElement();
}
return;
}
this.Services.GetService<ProjectSettings>().SelectClient(new Action(this.DoDrawingLoop), DispatcherPriority.Normal);
}
InvalidateElement在SciChartSurfaceBase中的OnSciChartSurfaceLoaded中被调用, 后者在OnLoad中被调用1
2
3
4
5
6
7/// <summary>
/// Forces initialization of the SciChartSurface in the case it is being used to render off-screen (on server)
/// </summary>
public void OnLoad()
{
this.OnSciChartSurfaceLoaded();
}
回到SciChartSurface看DoDrawingLoop1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29protected override void DoDrawingLoop()
{
if (this.windowCache)
{
return;
}
try
{
using (IRenderContext2D renderContext = this.uriInstance.GetRenderContext()) // private IRenderSurface2D uriInstance;
{
try
{
RendererErrorCode rendererErrorCode = this.uriInstance.RenderLoop(renderContext); // private ISciChartRenderer uriInstance;
if (!GroupSettings.keywordsMap.Equals(rendererErrorCode) && base.DebugWhyDoesntSciChartRender)
{
Console.WriteLine(" SciChartSurface didn't render, " + rendererErrorCode);
}
}
catch (Exception caught)
{
base.OnRenderFault(caught);
}
}
}
catch (Exception caught2)
{
base.OnRenderFault(caught2);
}
}
搜索GetRenderContext和RenderLoop, 可知
private IRenderSurface2D uriInstance可以认为是HighSpeedRenderSurface
private ISciChartRenderer uriInstance;是ViewConverter
HighSpeedRenderSurface
1 | namespace Abt.Controls.SciChart.Rendering.HighSpeedRasterizer |
protected WriteableBitmap RenderWriteableBitmap;
WriteableBitmap
https://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap(v=vs.110).aspx
WriteableBitmap使用的双缓冲技术
1 | Use the WriteableBitmap class to update and render a bitmap on a per-frame basis. This is useful for generating algorithmic content, such as a fractal image, and for data visualization, such as a music visualizer. |
ViewReference是扩展方法, 经常出现这个ViewReference, 不是特别清楚意义
ViewConverter
internal class ViewConverter : ISciChartRenderer
看ViewConverter的RenderLoop方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public RendererErrorCode RenderLoop(IRenderContext2D renderContext)
{
//...忽略
//
using (IUpdateSuspender updateSuspender = this.uriInstance.SuspendUpdates())
{
updateSuspender.ResumeTargetOnDispose = false;
SciChartDebugLogger.Instance.WriteLine("Beginning Render Loop ... ", new object[0]);
Size size = this.ViewReference(this.uriInstance);
if (this.ViewReference(this.uriInstance, size, out result))
{
RenderPassInfo renderPassInfo = ViewConverter.ViewReference(this.uriInstance, size);
renderContext.Clear();
//绘制坐标轴
ViewConverter.ViewReference(this.uriInstance, renderPassInfo, renderContext);
//绘制renderableSeries
ViewConverter.SelectClient(this.uriInstance, renderPassInfo, renderContext);
//更新Annotations
ViewConverter.ViewReference(this.uriInstance, renderPassInfo);
this.uriInstance.OnSciChartRendered();
}
}
return result;
}
绘制坐标轴, 出现OnDraw1
2
3
4
5
6
7
8
9
10
11
12
13
14internal static void ViewReference(ISciChartSurface uriInstance, RenderPassInfo windowCache, IRenderContext2D lastKeywords)
{
foreach (IAxis current in uriInstance.XAxes)
{
current.ValidateAxis();
current.OnDraw(lastKeywords, null);
}
foreach (IAxis current2 in uriInstance.YAxes)
{
current2.ValidateAxis();
current2.OnDraw(lastKeywords, null);
}
lastKeywords.Layers.Flush();
}
遍历绘制renderableSeries1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30internal static void SelectClient(ISciChartSurface uriInstance, RenderPassInfo windowCache, IRenderContext2D lastKeywords)
{
if (windowCache.RenderableSeries == null)
{
return;
}
List<int> list = new List<int>();
for (int i = 0; i < windowCache.RenderableSeries.Length; i++)
{
IRenderableSeries renderableSeries = windowCache.RenderableSeries[i];
if (renderableSeries != null)
{
renderableSeries.XAxis = uriInstance.XAxes.GetAxisById(renderableSeries.XAxisId, true);
renderableSeries.YAxis = uriInstance.YAxes.GetAxisById(renderableSeries.YAxisId, true);
if (windowCache.RenderableSeries[i].IsSelected)
{
list.Add(i);
}
else
{
ViewConverter.ViewReference(uriInstance, windowCache, lastKeywords, i);
}
}
}
foreach (int current in list)
{
ViewConverter.ViewReference(uriInstance, windowCache, lastKeywords, current);
}
uriInstance.Services.GetService<IEventAggregator>().Publish<SciChartRenderedMessage>(new SciChartRenderedMessage(uriInstance, lastKeywords));
}
1 | private static void ViewReference(ISciChartSurface uriInstance, RenderPassInfo windowCache, IRenderContext2D lastKeywords, int categoryDisposed) |
最后看一下IRenderContext2D和IRenderPassData
IRenderContext2D
相当于Winform中的Graphics1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63namespace Abt.Controls.SciChart.Rendering.Common
{
//删掉了一些
/// <summary>
/// Defines the interface to a 2D RenderContext, allowing drawing, blitting and creation of pens and brushes on the <see cref="T:Abt.Controls.SciChart.Rendering.Common.RenderSurfaceBase" />
/// </summary>
/// <remarks>The <see cref="T:Abt.Controls.SciChart.Rendering.Common.IRenderContext2D" /> is a graphics context valid for the current render pass. Any class which implements <see cref="T:Abt.Controls.SciChart.Visuals.IDrawable" /> has an OnDraw method
/// in which an <see cref="T:Abt.Controls.SciChart.Rendering.Common.IRenderContext2D" /> is passed in. Use this to draw penned lines, fills, rectangles, ellipses and blit graphics to the screen.</remarks>
public interface IRenderContext2D : IDisposable
{
/// <summary>
/// Gets a collection of <see cref="T:Abt.Controls.SciChart.Rendering.Common.RenderOperationLayers" />, which allow rendering operations to be posted to a layered queue for later
/// execution in order (and correct Z-ordering).
/// </summary>
/// <example>
/// <code title="RenderOperationLayers Example" description="Demonstrates how to enqueue operations to the RenderOperationLayers collection and later flush to ensure rendering operations get processed in the correct Z-order" lang="C#">
/// RenderOperationLayers layers = renderContext.Layers;
///
/// // Enqueue some operations in the layers in any order
/// layers[RenderLayer.AxisMajorGridlines].Enqueue(() => renderContext.DrawLine(/* .. */));
/// layers[RenderLayer.AxisBands].Enqueue(() => renderContext.DrawRectangle(/* .. */));
/// layers[RenderLayer.AxisMinorGridlines].Enqueue(() => renderContext.DrawLine(/* .. */));
///
/// // Processes all layers by executing enqueued operations in order of adding,
/// // and in Z-order of layers
/// layers.Flush();</code>
/// </example>
RenderOperationLayers Layers
{
get;
}
/// <summary>
/// Gets the current size of the viewport.
/// </summary>
Size ViewportSize
{
get;
}
IBrush2D CreateBrush(Color color, double opacity = 1.0, bool? alphaBlend = null);
IPen2D CreatePen(Color color, bool antiAliasing, float strokeThickness, double opacity = 1.0, double[] strokeDashArray = null, PenLineCap strokeEndLineCap = PenLineCap.Round);
ISprite2D CreateSprite(FrameworkElement fe);
void Clear();
void DrawSprite(Point destPoint, ISprite2D srcSprite, Rect srcRect);
void FillRectangle(IBrush2D brush, Point pt1, Point pt2, double gradientRotationAngle);
void FillPolygon(IBrush2D brush, IEnumerable<Point> points);
void FillArea(IBrush2D brush, IEnumerable<Tuple<Point, Point>> lines, double gradientRotationAngle = 0.0, bool isVerticalChart = false);
void DrawQuad(IPen2D pen, Point pt1, Point pt2);
void DrawEllipse(double width, double height, Point center, IBrush2D brush, IPen2D pen);
void DrawLine(IPen2D pen, Point pt1, Point pt2);
}
IRenderPassData
这个接口将原始数据变换到Chart所在的视窗上1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55using Abt.Controls.SciChart.Model.DataSeries;
using Abt.Controls.SciChart.Numerics.CoordinateCalculators;
using System;
namespace Abt.Controls.SciChart.Visuals.RenderableSeries
{
/// <summary>
/// Defines the interface to <see cref="T:Abt.Controls.SciChart.Visuals.RenderableSeries.RenderPassData" />, the data used in a single render pass by <see cref="T:Abt.Controls.SciChart.Visuals.RenderableSeries.BaseRenderableSeries" /> derived types
/// </summary>
public interface IRenderPassData
{
/// <summary>
/// Gets the integer indices of the X-Data array that are currently in range.
/// </summary>
/// <returns>The indices to the X-Data that are currently in range</returns>
/// <example>If the input X-data is 0...100 in steps of 1, the VisibleRange is 10, 30 then the PointRange will be 10, 30</example>
/// <remarks></remarks>
IndexRange PointRange
{
get;
}
/// <summary>
/// Gets the current point series.
/// </summary>
IPointSeries PointSeries
{
get;
}
/// <summary>
/// Gets a value, indicating whether current chart is vertical
/// </summary>
bool IsVerticalChart
{
get;
}
/// <summary>
/// Gets the current Y coordinate calculator.
/// </summary>
ICoordinateCalculator<double> YCoordinateCalculator
{
get;
}
/// <summary>
/// Gets the current X coordinate calculator.
/// </summary>
ICoordinateCalculator<double> XCoordinateCalculator
{
get;
}
}
}