【问题标题】:New Plot Type using OxyPlot使用 OxyPlot 的新绘图类型
【发布时间】:2015-02-24 14:43:19
【问题描述】:

我正在尝试在 OxyPlot 中创建新的绘图类型。我本质上需要一个 StairStepSeries,但是任何负值都被它们的Math.Abs 值替换,当这种情况发生时,反映这种情况的线条样式已经发生(通过使用颜色和或LineStyle)。所以,突出我想要的东西

为此,我创建了两个类(我粘贴了下面使用的实际代码)。当您知道您正在使用的工具时,这在概念上很容易,而我不知道。我的问题与我对rectangle.DrawClippedLineSegments()的不当使用直接相关。我可以得到一个标准的StairStepSeries 绘图(复制内部代码),但是当我尝试使用rectangle.DrawClippedLineSegments() 直觉时,我意识到我不知道这个方法的作用或它应该如何使用,但可以'找不到任何文档。 rectangle.DrawClippedLineSegments()在做什么,这个方法应该怎么用?

感谢您的宝贵时间。


代码:

namespace OxyPlot.Series
{
    using System;
    using System.Collections.Generic;
    using OxyPlot.Series;

    /// <summary>
    /// Are we reversing positive of negative values?
    /// </summary>
    public enum ThresholdType { ReflectAbove, ReflectBelow };

    /// <summary>
    /// Class that renders absolute positive and absolute negative values 
    /// but changes the line style according to those values that changed sign. 
    /// The value at which the absolute vaue is taken can be manually set.
    /// </summary>
    public class AbsoluteStairStepSeries : StairStepSeries
    {
        /// <summary>
        /// The default color used when a value is reversed accross the threshold.
        /// </summary>
        private OxyColor defaultColorThreshold;

        #region Initialization.
        /// <summary>
        /// Default ctor.
        /// </summary>
        public AbsoluteStairStepSeries()
        {
            this.Threshold = 0.0;
            this.ThresholdType = OxyPlot.Series.ThresholdType.ReflectAbove;
            this.ColorThreshold = this.ActualColor;
            this.LineStyleThreshold = OxyPlot.LineStyle.LongDash;
        }
        #endregion // Initialization.

        /// <summary>
        /// Sets the default values.
        /// </summary>
        /// <param name="model">The model.</param>
        protected override void SetDefaultValues(PlotModel model)
        {
            base.SetDefaultValues(model);
            if (this.ColorThreshold.IsAutomatic())
                this.defaultColorThreshold = model.GetDefaultColor();
            if (this.LineStyleThreshold == LineStyle.Automatic)
                this.LineStyleThreshold = model.GetDefaultLineStyle();
        }

        /// <summary>
        /// Renders the LineSeries on the specified rendering context.
        /// </summary>
        /// <param name="rc">The rendering context.</param>
        /// <param name="model">The owner plot model.</param>
        public override void Render(IRenderContext rc, PlotModel model)
        {
            if (this.ActualPoints.Count == 0)
                return;

            // Set defaults.
            this.VerifyAxes();
            OxyRect clippingRect = this.GetClippingRect();
            double[] dashArray = this.ActualDashArray;
            double[] verticalLineDashArray = this.VerticalLineStyle.GetDashArray();
            LineStyle lineStyle = this.ActualLineStyle;
            double verticalStrokeThickness = double.IsNaN(this.VerticalStrokeThickness) ?
                this.StrokeThickness : this.VerticalStrokeThickness;
            OxyColor actualColor = this.GetSelectableColor(this.ActualColor);

            // Perform thresholding on clipping rectangle. 
            //double threshold = this.YAxis.Transform(this.Threshold);
            //switch (ThresholdType)
            //{
            //  // reflect any values below the threshold above the threshold. 
            //  case ThresholdType.ReflectAbove:
            //      //if (clippingRect.Bottom < threshold)
            //          clippingRect.Bottom = threshold;
            //      break;
            //  case ThresholdType.ReflectBelow:
            //      break;
            //  default:
            //      break;
            //}

            // Perform the render action.
            Action<IList<ScreenPoint>, IList<ScreenPoint>> renderPoints = (lpts, mpts) =>
            {
                // Clip the line segments with the clipping rectangle.
                if (this.StrokeThickness > 0 && lineStyle != LineStyle.None)
                {
                    if (!verticalStrokeThickness.Equals(this.StrokeThickness) || 
                         this.VerticalLineStyle != lineStyle)
                    {
                        // TODO: change to array
                        List<ScreenPoint> hlptsOk = new List<ScreenPoint>();
                        List<ScreenPoint> vlptsOk = new List<ScreenPoint>();
                        List<ScreenPoint> hlptsFlip = new List<ScreenPoint>();
                        List<ScreenPoint> vlptsFlip = new List<ScreenPoint>();
                        double threshold = this.YAxis.Transform(this.Threshold);
                        for (int i = 0; i + 2 < lpts.Count; i += 2)
                        {
                            switch (ThresholdType)
                            {
                                case ThresholdType.ReflectAbove:
                                    clippingRect.Bottom = threshold;
                                    if (lpts[i].Y < threshold)
                                        hlptsFlip.Add(new ScreenPoint(lpts[i].X, threshold - lpts[i].Y));
                                    else
                                        hlptsOk.Add(lpts[i]);

                                    if (lpts[i + 1].Y < threshold)
                                    {
                                        ScreenPoint tmp = new ScreenPoint(
                                            lpts[i + 1].X, threshold - lpts[i + 1].Y);
                                        hlptsFlip.Add(tmp);
                                        vlptsFlip.Add(tmp);
                                    }
                                    else
                                    {
                                        hlptsOk.Add(lpts[i + 1]);
                                        vlptsOk.Add(lpts[i + 1]);
                                    }

                                    if (lpts[i + 2].Y < threshold)
                                        vlptsFlip.Add(new ScreenPoint(lpts[i + 2].X, threshold - lpts[i + 2].Y));
                                    else
                                        vlptsOk.Add(lpts[i + 2]);
                                    break;
                                case ThresholdType.ReflectBelow:
                                    break;
                                default:
                                    break;
                            }
                        }

                        //for (int i = 0; i + 2 < lpts.Count; i += 2)
                        //{
                        //  hlpts.Add(lpts[i]);
                        //  hlpts.Add(lpts[i + 1]);
                        //  vlpts.Add(lpts[i + 1]);
                        //  vlpts.Add(lpts[i + 2]);
                        //}

                        rc.DrawClippedLineSegments(
                             clippingRect,
                             hlptsOk, 
                             actualColor,
                             this.StrokeThickness,
                             dashArray,
                             this.LineJoin,
                             false);
                        rc.DrawClippedLineSegments(
                             clippingRect,
                             hlptsFlip,
                             OxyColor.FromRgb(255, 0, 0),
                             this.StrokeThickness,
                             dashArray,
                             this.LineJoin,
                             false);
                        rc.DrawClippedLineSegments(
                             clippingRect,
                             vlptsOk,
                             actualColor,
                             verticalStrokeThickness,
                             verticalLineDashArray,
                             this.LineJoin,
                             false);
                        rc.DrawClippedLineSegments(
                             clippingRect,
                             vlptsFlip,
                             OxyColor.FromRgb(255, 0, 0),
                             verticalStrokeThickness,
                             verticalLineDashArray,
                             this.LineJoin,
                             false);
                    }
                    else
                    {
                        rc.DrawClippedLine(
                             clippingRect,
                             lpts,
                             0,
                             actualColor,
                             this.StrokeThickness,
                             dashArray,
                             this.LineJoin,
                             false);
                    }
                }

                if (this.MarkerType != MarkerType.None)
                {
                    rc.DrawMarkers(
                         clippingRect,
                         mpts,
                         this.MarkerType,
                         this.MarkerOutline,
                         new[] { this.MarkerSize },
                         this.MarkerFill,
                         this.MarkerStroke,
                         this.MarkerStrokeThickness);
                }
            };

            // Transform all points to screen coordinates
            // Render the line when invalid points occur.
            var linePoints = new List<ScreenPoint>();
            var markerPoints = new List<ScreenPoint>();
            double previousY = double.NaN;
            foreach (var point in this.ActualPoints)
            {
                if (!this.IsValidPoint(point))
                {
                    renderPoints(linePoints, markerPoints);
                    linePoints.Clear();
                    markerPoints.Clear();
                    previousY = double.NaN;
                    continue;
                }

                var transformedPoint = this.Transform(point);
                if (!double.IsNaN(previousY))
                {
                    // Horizontal line from the previous point to the current x-coordinate
                    linePoints.Add(new ScreenPoint(transformedPoint.X, previousY));
                }

                linePoints.Add(transformedPoint);
                markerPoints.Add(transformedPoint);
                previousY = transformedPoint.Y;
            }

            renderPoints(linePoints, markerPoints);
            if (this.LabelFormatString != null)
            {
                // Render point labels (not optimized for performance).
                this.RenderPointLabels(rc, clippingRect);
            }
        }

        #region Properties.
        /// <summary>
        /// The value, positive or negative at which any values are reversed 
        /// accross the threshold.
        /// </summary>
        public double Threshold { get; set; }

        /// <summary>
        /// Hold the thresholding type.
        /// </summary>
        public ThresholdType    ThresholdType { get; set; }

        /// <summary>
        /// Gets or sets the color for the part of the 
        /// line that is above/below the threshold.
        /// </summary>
        public OxyColor ColorThreshold { get; set; }

        /// <summary>
        /// Gets the actual threshold color.
        /// </summary>
        /// <value>The actual color.</value>
        public OxyColor ActualColorThreshold
        {
            get { return this.ColorThreshold.GetActualColor(this.defaultColorThreshold); }
        }

        /// <summary>
        /// Gets or sets the line style for the part of the 
        /// line that is above/below the threshold.
        /// </summary>
        /// <value>The line style.</value>
        public LineStyle LineStyleThreshold { get; set; }

        /// <summary>
        /// Gets the actual line style for the part of the 
        /// line that is above/below the threshold.
        /// </summary>
        /// <value>The line style.</value>
        public LineStyle ActualLineStyleThreshold
        {
            get
            {
                return this.LineStyleThreshold != LineStyle.Automatic ?
                    this.LineStyleThreshold : LineStyle.Solid;
            }
        }
        #endregion // Properties.
    }
}

和 WPF 类

namespace OxyPlot.Wpf
{
    using System.Windows;
    using System.Windows.Media;
    using OxyPlot.Series;

    /// <summary>
    /// The WPF wrapper for OxyPlot.AbsoluteStairStepSeries.
    /// </summary>
    public class AbsoluteStairStepSeries : StairStepSeries
    {
        /// <summary>
        /// Default ctor.
        /// </summary>
        public AbsoluteStairStepSeries()
        {
            this.InternalSeries = new OxyPlot.Series.AbsoluteStairStepSeries();
        }

        /// <summary>
        /// Creates the internal series.
        /// </summary>
        /// <returns>
        /// The internal series.
        /// </returns>
        public override OxyPlot.Series.Series CreateModel()
        {
            this.SynchronizeProperties(this.InternalSeries);
            return this.InternalSeries;
        }

        /// <summary>
        /// Synchronizes the properties.
        /// </summary>
        /// <param name="series">The series.</param>
        protected override void SynchronizeProperties(OxyPlot.Series.Series series)
        {
            base.SynchronizeProperties(series);
            var s = series as OxyPlot.Series.AbsoluteStairStepSeries;
            s.Threshold = this.Threshold;
            s.ColorThreshold = this.ColorThreshold.ToOxyColor();
        }

        /// <summary>
        /// Identifies the <see cref="Threshold"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ThresholdProperty = DependencyProperty.Register(
            "Threshold", typeof(double), typeof(AbsoluteStairStepSeries), 
                new UIPropertyMetadata(0.0, AppearanceChanged));

        /// <summary>
        /// Identifies the <see cref="ThresholdType"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ThresholdTypeProperty = DependencyProperty.Register(
            "ThresholdType", typeof(ThresholdType), typeof(AbsoluteStairStepSeries), 
                new UIPropertyMetadata(ThresholdType.ReflectAbove, AppearanceChanged));

        /// <summary>
        /// Identifies the <see cref="ColorThreshold"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ColorThresholdProperty = DependencyProperty.Register(
            "ColorThreshold", typeof(Color), typeof(AbsoluteStairStepSeries), 
                new UIPropertyMetadata(Colors.Red, AppearanceChanged));

        /// <summary>
        /// Identifies the <see cref="LineStyleThreshold"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty LineStyleThresholdProperty = DependencyProperty.Register(
            "LineStyleThreshold", typeof(LineStyle), typeof(AbsoluteStairStepSeries), 
                new UIPropertyMetadata(LineStyle.LongDash, AppearanceChanged));

        /// <summary>
        /// Get or set the threshold value.
        /// </summary>
        public double Threshold
        {
            get { return (double)GetValue(ThresholdProperty); }
            set { SetValue(ThresholdProperty, value); }
        }

        /// <summary>
        /// Get or set the threshold type to be used.
        /// </summary>
        public ThresholdType ThresholdType
        {
            get { return (ThresholdType)GetValue(ThresholdTypeProperty); }
            set { SetValue(ThresholdTypeProperty, value); }
        }

        /// <summary>
        /// Get or set the threshold color.
        /// </summary>
        public Color ColorThreshold
        {
            get { return (Color)GetValue(ColorThresholdProperty); }
            set { SetValue(ColorThresholdProperty, value); }
        }

        /// <summary>
        /// Get or set the threshold line style.
        /// </summary>
        public LineStyle LineStyleThreshold
        {
            get { return (LineStyle)GetValue(LineStyleThresholdProperty); }
            set { SetValue(LineStyleThresholdProperty, value); }
        }
    }
}

【问题讨论】:

  • 你真的能够产生任何视觉反馈吗?我可以得到你的代码绘图,就目前而言,它完全反转了绘图。我打算探索DrawClippedLineSegments 并稍微戳一下代码以获得您想要的结果,但我想我会事先检查一下您处于哪个阶段。
  • 是的,目前我的尝试已被评论,因此这只是绘制一个正常的StairStepSeries。当我尝试弄乱剪辑等时,我对手头的每个工具应该用于什么感到困惑。任何对解决方案的帮助将不胜感激。感谢您的宝贵时间...
  • 啊,我明白了,如果有机会,我会在接下来的几天内尝试看看,干杯。
  • 感谢克里斯。谢谢。

标签: wpf plot oxyplot


【解决方案1】:

我有机会对此进行了研究,虽然我的建议可能不是理想的解决方案,但它应该会给你一些有用的提示。

首先,DrawClippedLineSegments(可以查看源代码here)及其扩展方法对应物(DrawClippedRectangleAsPolygonDrawClippedEllipse等)用于在主绘图/渲染区域绘制各种绘图图形。提供给此方法的剪切矩形表示可以绘制图形的区域,我们不希望在该区域之外绘制任何东西,因为它不会在轴范围内,看起来很奇怪,也不会有特别的好处。在您的情况下,您向它传递了一个数据点列表,以及它们计算出的渲染位置;只有剪裁矩形内的数据点才会绘制在您的绘图上。

您可以在源文件var clipping = new CohenSutherlandClipping(clippingRectangle); 的第 118 行看到剪裁计算的开始 - 这不是我特别熟悉的,但快速搜索 wikipedia 表明它是专门用于进行线剪裁,在该源文件的其他地方使用的其他算法最少。我认为您不需要更改剪切矩形,除非其中一个数据点的反转会将其置于当前绘制的区域之外。

关于实际帮助达成解决方案,我在探索您的代码时注意到了几件事。我尝试的第一件事是绘制一些数据点(都是正数),发现整个图是倒置的,主要是因为这个语句: if (lpts[i].Y &lt; threshold) 对于正值始终为真。这是 Y 轴坐标系从窗口顶部开始向窗口底部增加的结果。由于我的阈值是0,当转换为屏幕上的渲染位置时,每个正数据点的Y 位置将小于轴Y 值;本质上,您关于哪些点被翻转或不翻转的逻辑需要反转。这应该让您获得您所追求的行为(确保正确计算翻转点。)

替代方法

我没有深入到裁剪矩形/计算转换后的数据点方法,而是选择了一条稍微懒一点的路线,这可能会受益于一些整理,但根据您的要求可能会有用。

我决定在调用实际渲染点之前执行阈值翻转/修正。

我通过这些更改(对 Render 方法)以最小的方式更改了您的 AbsoluteStairStepSeries 类,保留了您现有的大部分结构:

    public override void Render(IRenderContext rc, PlotModel model)
    {
        if (this.ActualPoints.Count == 0)
            return;

        // Set defaults.
        this.VerifyAxes();
        OxyRect clippingRect = this.GetClippingRect();
        double[] dashArray = this.ActualDashArray;
        double[] verticalLineDashArray = this.VerticalLineStyle.GetDashArray();
        LineStyle lineStyle = this.ActualLineStyle;
        double verticalStrokeThickness = double.IsNaN(this.VerticalStrokeThickness) ?
            this.StrokeThickness : this.VerticalStrokeThickness;
        OxyColor actualColor = this.GetSelectableColor(this.ActualColor);

        // Perform the render action.
        Action<IList<Tuple<bool, ScreenPoint>>, IList<Tuple<bool, ScreenPoint>>> renderPoints = (lpts, mpts) =>
        {
            // Clip the line segments with the clipping rectangle.
            if (this.StrokeThickness > 0 && lineStyle != LineStyle.None)
            {
                if (!verticalStrokeThickness.Equals(this.StrokeThickness) ||
                     this.VerticalLineStyle != lineStyle)
                {
                    // TODO: change to array
                    List<ScreenPoint> hlptsOk = new List<ScreenPoint>();
                    List<ScreenPoint> vlptsOk = new List<ScreenPoint>();
                    List<ScreenPoint> hlptsFlip = new List<ScreenPoint>();
                    List<ScreenPoint> vlptsFlip = new List<ScreenPoint>();
                    double threshold = this.YAxis.Transform(this.Threshold);

                    for (int i = 0; i + 2 < lpts.Count; i += 2)
                    {
                        hlptsOk.Add(lpts[i].Item2);
                        hlptsOk.Add(lpts[i + 1].Item2);
                        vlptsOk.Add(lpts[i + 1].Item2);
                        vlptsOk.Add(lpts[i + 2].Item2);

                        // Add flipped points so they may be overdrawn.
                        if (lpts[i].Item1 == true)
                        {
                            hlptsFlip.Add(lpts[i].Item2);
                            hlptsFlip.Add(lpts[i + 1].Item2);
                        }                            
                    }

                    rc.DrawClippedLineSegments(
                         clippingRect,
                         hlptsOk,
                         actualColor,
                         this.StrokeThickness,
                         dashArray,
                         this.LineJoin,
                         false);
                    rc.DrawClippedLineSegments(
                         clippingRect,
                         hlptsFlip,
                         OxyColor.FromRgb(255, 0, 0),
                         this.StrokeThickness,
                         dashArray,
                         this.LineJoin,
                         false);
                    rc.DrawClippedLineSegments(
                         clippingRect,
                         vlptsOk,
                         actualColor,
                         verticalStrokeThickness,
                         verticalLineDashArray,
                         this.LineJoin,
                         false);
                    rc.DrawClippedLineSegments(
                         clippingRect,
                         vlptsFlip,
                         OxyColor.FromRgb(255, 0, 0),
                         verticalStrokeThickness,
                         verticalLineDashArray,
                         this.LineJoin,
                         false);
                }
                else
                {
                    rc.DrawClippedLine(
                         clippingRect,
                         lpts.Select(x => x.Item2).ToList(),
                         0,
                         actualColor,
                         this.StrokeThickness,
                         dashArray,
                         this.LineJoin,
                         false);
                }
            }

            if (this.MarkerType != MarkerType.None)
            {
                rc.DrawMarkers(
                     clippingRect,
                     mpts.Select(x => x.Item2).ToList(),
                     this.MarkerType,
                     this.MarkerOutline,
                     new[] { this.MarkerSize },
                     this.MarkerFill,
                     this.MarkerStroke,
                     this.MarkerStrokeThickness);
            }
        };

        // Transform all points to screen coordinates
        // Render the line when invalid points occur.
        var linePoints = new List<Tuple<bool, ScreenPoint>>();
        var markerPoints = new List<Tuple<bool, ScreenPoint>>();
        double previousY = double.NaN;
        foreach (var point in this.ActualPoints)
        {
            var localPoint = point;
            bool pointAltered = false;
            // Amend/Reflect your points data here:
            if (localPoint.Y < Threshold)
            {
                localPoint.Y = Math.Abs(point.Y);
                pointAltered = true;
            }

            if (!this.IsValidPoint(localPoint))
            {
                renderPoints(linePoints, markerPoints);
                linePoints.Clear();
                markerPoints.Clear();
                previousY = double.NaN;
                continue;
            }

            var transformedPoint = this.Transform(localPoint);
            if (!double.IsNaN(previousY))
            {
                // Horizontal line from the previous point to the current x-coordinate
                linePoints.Add(new Tuple<bool, ScreenPoint>(pointAltered, new ScreenPoint(transformedPoint.X, previousY)));
            }

            linePoints.Add(new Tuple<bool, ScreenPoint>(pointAltered, transformedPoint));
            markerPoints.Add(new Tuple<bool, ScreenPoint>(pointAltered, transformedPoint));
            previousY = transformedPoint.Y;
        }

        renderPoints(linePoints, markerPoints);
        if (this.LabelFormatString != null)
        {
            // Render point labels (not optimized for performance).
            this.RenderPointLabels(rc, clippingRect);
        }
    }

我使用List&lt;Tuple&lt;bool, ScreenPoint&gt;&gt;,而不是List&lt;ScreenPoint&gt; 来针对每个点存储一个布尔标志,表示该点是否已被更改;你可以使用一个小类来简化语法。

因为你是直接与点数据交互,所以你不需要担心屏幕位置(反转 Y 轴),所以从概念上来说取绝对值的计算更容易阅读:

// Amend/Reflect your points data here:
if (localPoint.Y < Threshold)
{
    localPoint.Y = Math.Abs(point.Y);
    pointAltered = true;
}

我注意到您的代码在上方反映/在下方反映,这可能是您在需要时在此处插入的逻辑,我已选择 Math.Abs,您提到这是您的初始要求。

在实际渲染线条时,我将绘制StepSeries 的原始代码留在原处,因此实际上整个系列都以绿色绘制。我只添加了一个条件语句来检查修改/反映的点,如果找到,相关的绘图点将添加到包含翻转点的现有列表中,然后用红色绘制。

Tuples 使渲染方法中的事情变得有些混乱(添加了 Item1/Item2),您可以删除修改点的双重绘制,但我认为结果就是您所追求的(或当然可以为您指明正确的方向。

示例行为:

【讨论】:

  • 克里斯,这是抽奖。非常感谢您在这里的帮助,当然没想到会返回一段工作代码!你这样做的方式比我尝试的更干净(修改剪切矩形等)。赏金正在路上。再次,谢谢;像你这样的人制作这个网站。 :]
  • 很高兴它有帮助 =D 我确信可以进行一些优化/整理,但它可能不会立即导致问题。非常感谢慷慨的赏金;祝你绘图好运 =)
  • 啊,好点,今晚晚些时候我会尝试看看,虽然我可能无法在明天之前到达它=D这将与实际上没有改变点有关传递到方法中(我们只是更改渲染过程,这就是无效无效的原因)。作为停止间隙,您可以手动定义轴oxyplot.codeplex.com/wikipage?title=Axes
  • 看起来你最好的办法是覆盖UpdateAxisMaxMin(),你可以在XYAxisSeries.cs的源代码中看到默认行为。出于您的目的,只需将 MinY 设置为 0 就足够了,但是您实际上可以计算最小的非负 Y 值,并使用它来代替。
  • 没问题,你可以在线浏览代码,虽然有点繁琐,但下载源代码大概是这样吧 =D 祝你好运!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-27
  • 1970-01-01
相关资源
最近更新 更多