You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

在SciChart中如何为非均匀数据添加均匀间隔的PointMarkers?

实现SciChart中均匀间隔的PointMarkers(类似虚线间隔效果)

嘿,这个需求确实很实用——在多系列重叠的图表里,用固定间隔的标记来区分数据,比单纯靠颜色和虚线辨识度高多了。默认的PointMarker是绑定数据点的,所以缩放时会跟着数据点散开,要实现你要的「像虚线那样不管缩放都保持屏幕间隔一致」的效果,我们可以通过自定义渲染逻辑来实现。

下面给你分享一个WPF平台上的具体实现思路和代码,其他平台(iOS/Android)的思路是一致的,只是API细节略有不同:

核心思路

脱离数据点的绑定,基于屏幕坐标来计算标记的位置:先绘制默认的折线,然后根据当前视口下折线的屏幕起点和终点,计算出均匀间隔的屏幕坐标点,再把这些点映射回数据坐标系(确保标记落在折线上),最后绘制PointMarker。

自定义RenderableSeries实现

创建一个继承自FastLineRenderableSeries的子类,重写渲染方法来添加均匀标记:

using SciChart.Charting.Model.DataSeries;
using SciChart.Charting.Visuals.PointMarkers;
using SciChart.Charting.Visuals.RenderableSeries;
using SciChart.Core.Extensions;
using SciChart.Drawing.Common;
using System.Windows;
using System.Windows.Media;

public class UniformPointMarkerLineSeries : FastLineRenderableSeries
{
    // 可配置的屏幕间隔(单位:像素)
    public double MarkerScreenSpacing { get; set; } = 60;

    // 自定义PointMarker样式
    public IPointMarker PointMarker { get; set; } = new EllipsePointMarker 
    { 
        Width = 10, 
        Height = 10, 
        Fill = Colors.Yellow, 
        Stroke = Colors.Blue, 
        StrokeThickness = 1 
    };

    protected override void Render(IRenderContext2D renderContext, IRenderPassData renderPassData)
    {
        // 先绘制默认的折线
        base.Render(renderContext, renderPassData);

        // 如果没有设置PointMarker或间隔无效,直接返回
        if (PointMarker == null || MarkerScreenSpacing <= 0 || renderPassData.PointSeries.Count < 2)
            return;

        var transformProvider = renderPassData.TransformProvider;
        var pointSeries = renderPassData.PointSeries;

        // 获取折线首尾点的屏幕坐标
        var firstScreenPoint = transformProvider.GetPointByDataPoint(pointSeries.XValues[0], pointSeries.YValues[0]);
        var lastScreenPoint = transformProvider.GetPointByDataPoint(pointSeries.XValues.Last(), pointSeries.YValues.Last());

        // 计算折线在屏幕上的总长度
        var lineScreenLength = Math.Sqrt(
            Math.Pow(lastScreenPoint.X - firstScreenPoint.X, 2) + 
            Math.Pow(lastScreenPoint.Y - firstScreenPoint.Y, 2));

        if (lineScreenLength <= 0) return;

        // 计算需要绘制的标记数量
        int markerCount = (int)(lineScreenLength / MarkerScreenSpacing) + 1;

        // 遍历每个标记位置,绘制PointMarker
        for (int i = 0; i < markerCount; i++)
        {
            // 计算当前标记在屏幕上的插值位置
            var t = (double)i / (markerCount - 1);
            var interpolatedScreenX = firstScreenPoint.X + t * (lastScreenPoint.X - firstScreenPoint.X);
            var interpolatedScreenY = firstScreenPoint.Y + t * (lastScreenPoint.Y - firstScreenPoint.Y);

            // 把屏幕坐标转换回数据坐标(确保标记严格落在折线上)
            var dataPoint = transformProvider.GetDataPointByPoint(new Point(interpolatedScreenX, interpolatedScreenY));
            var finalScreenPoint = transformProvider.GetPointByDataPoint(dataPoint.X, dataPoint.Y);

            // 绘制标记
            PointMarker.Draw(renderContext, finalScreenPoint.X, finalScreenPoint.Y);
        }
    }
}

在XAML中使用自定义系列

把这个自定义系列加到SciChartSurface里,配置参数即可:

<Window x:Class="SciChartUniformMarkerDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="http://schemas.abtsoftware.co.uk/scichart"
        xmlns:local="clr-namespace:SciChartUniformMarkerDemo"
        Title="Uniform PointMarker Demo" Height="450" Width="800">
    <Grid>
        <s:SciChartSurface>
            <s:SciChartSurface.XAxes>
                <s:NumericAxis AutoRange="Always"/>
            </s:SciChartSurface.XAxes>
            <s:SciChartSurface.YAxes>
                <s:NumericAxis AutoRange="Always"/>
            </s:SciChartSurface.YAxes>
            <s:SciChartSurface.RenderableSeries>
                <local:UniformPointMarkerLineSeries 
                    MarkerScreenSpacing="60"
                    Stroke="DarkBlue"
                    StrokeThickness="2"
                    StrokeDashArray="5,3">
                    <local:UniformPointMarkerLineSeries.DataSeries>
                        <s:XyDataSeries x:TypeArguments="double,double" SeriesName="Demo Series"/>
                    </local:UniformPointMarkerLineSeries.DataSeries>
                </local:UniformPointMarkerLineSeries>
            </s:SciChartSurface.RenderableSeries>
            <s:SciChartSurface.ChartModifier>
                <s:ZoomPanModifier/>
            </s:SciChartSurface.ChartModifier>
        </s:SciChartSurface>
    </Grid>
</Window>

额外说明

  • 这个实现是基于折线首尾点的线性插值,适合直线或近似直线的曲线。如果是复杂的曲线(比如正弦曲线),想要沿着曲线路径均匀分布标记,需要更复杂的逻辑:比如先获取曲线的PathGeometry,然后用GetPointAtFractionLength方法来获取路径上的均匀点。
  • 你可以通过调整MarkerScreenSpacing参数来控制标记的密度,数值越小标记越密集。
  • 其他平台(iOS/Android)的实现思路相同:继承对应平台的FastLineRenderableSeries,重写渲染方法,利用平台的绘图API来计算屏幕坐标并绘制标记。

这样实现后,不管你怎么缩放图表,标记在屏幕上的间隔都会保持一致,和虚线的效果完全匹配,完美解决多系列重叠的区分问题!

内容的提问来源于stack exchange,提问作者The Don

火山引擎 最新活动