Measurement Studio for .NET Languages

cancel
Showing results for 
Search instead for 
Did you mean: 

Different custom colors for a WPF line plot

Dear Sir,

 

I am trying to create a WPF graph with different colors for different points of time , example  Black 0-10,red 11-20, green 21-30...

 

I saw the nifty video and download the range masking cursor but got stuck. 

 

Thank you

 

George MAsiello

0 Kudos
Message 1 of 6
(6,235 Views)

The key aspect of the example is adjusting a gradient brush to match the desired data range. Below is an adaptation of the example for the color ranges in your question:


    XAML
    <ni:Graph x:Name="graph" RenderMode="Vector">
        <ni:Graph.Axes>
            <ni:AxisDouble x:Name="xAxis"
                           Adjuster="FitExactly"
                           Orientation="Horizontal"
                           RangeChanged="OnRangeChanged" />
        </ni:Graph.Axes>
        <ni:Graph.Plots>
            <ni:Plot>
                <ni:LinePlotRenderer x:Name="renderer" />
            </ni:Plot>
        </ni:Graph.Plots>
    </ni:Graph>

    Code
    public partial class MainWindow : Window {
        // The color ranges we want to show on the plot.
        private readonly Tuple<Color, double>[] _colorRanges = new[] {
            Tuple.Create( Colors.Black, 10.0 ),
            Tuple.Create( Colors.Red, 20.0 ),
            Tuple.Create( Colors.Green, 30.0 ),
        };

        public MainWindow( ) {
            this.InitializeComponent( );
            this.UpdatePlotGradient( );
        }

        private void OnRangeChanged( object sender, EventArgs e ) {
            this.UpdatePlotGradient( );
        }

        private void UpdatePlotGradient( ) {
            var gradient = renderer.Stroke as GradientBrush;
            if( gradient == null ) {
                // Initialize the plot renderer with a gradient brush holding the range colors.
                var gradientStops = new GradientStopCollection( );
                for( int i = 0; i < _colorRanges.Length; ++i ) {
                    Color color = _colorRanges[i].Item1;
                    gradientStops.Add( new GradientStop { Color = color } );
                    if( i > 0 )
                        gradientStops.Add( new GradientStop { Color = color } );
                }

                renderer.Stroke = gradient = new LinearGradientBrush( gradientStops, 0.0 );
            }

            var dataMapper = xAxis.GetDataMapper( );
            for( int i = 0; i < _colorRanges.Length; ++i ) {
                // Get the position of the color range along the axis.
                double range = _colorRanges[i].Item2;
                double offset = dataMapper.Map( range );

                // Update adjacent ranges with the new offset.
                int stopIndex = 2 * i;
                gradient.GradientStops[stopIndex].Offset = offset;
                if( i + 1 < _colorRanges.Length )
                    gradient.GradientStops[stopIndex + 1].Offset = offset;
            }
        }
    }


Note that this does depend on the data being fully rendered without clipping. To get this to work with arbitrary subsets of data, a custom renderer would probably be more appropraite.

~ Paul H
0 Kudos
Message 2 of 6
(6,216 Views)

Paul,

 

Thank you this is great.  That solves one set of graphs where the data color is dependant of time on the horz axis.  The other part is a replacement of the old CW3dgraph.  I got 3 wpf graphs forming the inside of a 3 sided cube, this works great, but I would like to create the custom redender to color map.  The Data is either XY, xz, or YZ and I need another array to set the color map based on time.  I then have a routine to draw the 3d mesh inside the "cube" walls.  Can you help on the custom render for the color map?

 

If you are as fluent in vb as c# it is apprecaite but I can translate.

 

Thank you for your help.  I suspect from the survey on the new measurement studio many would like this solution to replace the old cwgraph3d com object.

 

George

0 Kudos
Message 3 of 6
(6,197 Views)

Paul,

 

Thank you this is great.  That solves one set of graphs where the data color is dependant of time on the horz axis.  The other part is a replacement of the old CW3dgraph.  I got 3 wpf graphs forming the inside of a 3 sided cube, this works great, but I would like to create the custom redender to color map.  The Data is either XY, xz, or YZ and I need another array to set the color map based on time.  I then have a routine to draw the 3d mesh inside the "cube" walls.  Can you help on the custom render for the color map?

 

If you are as fluent in vb as c# it is apprecaite but I can translate.

 

Thank you for your help.  I suspect from the survey on the new measurement studio many would like this solution to replace the old cwgraph3d com object.

 

George

0 Kudos
Message 4 of 6
(6,196 Views)

Here's a quick mockup of the custom renderer version (sorry I did not have time to translate it to VB myself):


    public class CustomRenderer : PlotRenderer {
        private static readonly DataRequirements _dataRequirements = new DataRequirements(
            DataCulling.PreserveLines, DataDecimation.CoLinear,
            new DataDimension( DataDimensionSource.IndexData, DataDimensionScale.IndependentScale ),
            new DataDimension( DataDimensionSource.SampleData, DataDimensionScale.DependentScale ) );

        // The color ranges we want to show on the plot.
        private readonly Tuple<Color, double>[] _colorRanges = new[] {
            Tuple.Create( Colors.Black, 10.0 ),
            Tuple.Create( Colors.Red, 20.0 ),
            Tuple.Create( Colors.Green, 30.0 ),
        };

        private readonly RenderTargetOptions[] _rangeOptions;

        public CustomRenderer( ) {
            // Create render options for each range color.
            _rangeOptions = _colorRanges.Select( colorRange => new RenderTargetOptions( this,
                RenderTargetOption.CreateValue( RenderTargetOptionsProperty.Stroke, new SolidColorBrush( colorRange.Item1 ) ),
                RenderTargetOption.CreateValue( RenderTargetOptionsProperty.StrokeThickness, 1.0 ) ) )
            .ToArray( );
        }

        public override SupportedRenderModes SupportedRenderModes {
            get { return SupportedRenderModes.VectorAndRaster; }
        }

        public override DataRequirements GetDataRequirements( ) {
            return _dataRequirements;
        }

        protected override Freezable CreateInstanceCore( ) {
            return new CustomRenderer( );
        }

        protected override void RenderLegendCore( LegendRenderArgs renderArgs ) { }

        protected override void RenderGraphCore( PlotRenderArgs renderArgs ) {
            // Get the time axis for the renderer.
            Plot plot = (Plot)renderArgs.Plot;
            var timeAxis = plot.GraphParent.GetAxis( plot, Orientation.Horizontal );
            var dataMapper = (IDataMapper<double>)timeAxis.GetDataMapper( plot.GraphParent );

            int startIndex = 0;
            var xData = renderArgs.RelativeData[0];
            var yData = renderArgs.RelativeData[1];
            var renderTarget = renderArgs.RenderTarget;
            for( int i = 0; i < _colorRanges.Length - 1; ++i ) {
                // Draw all data below the current range with the associated color.
                int endIndex = startIndex;
                double range = _colorRanges[i].Item2;
                double offset = dataMapper.Map( range );
                while( endIndex < xData.Size && xData[endIndex] <= offset )
                    ++endIndex;

                RenderSegment( startIndex, endIndex, xData, yData, renderTarget, _rangeOptions[i] );

                startIndex = endIndex;
            }

            // Draw all remaining data with the last color range.
            RenderSegment( startIndex, xData.Size, xData, yData, renderTarget, _rangeOptions.Last( ) );
        }

        private static void RenderSegment( int startIndex, int endIndex, Buffer<double> xData, Buffer<double> yData, IRenderTarget renderTarget, RenderTargetOptions options ) {
            int length = endIndex - startIndex;
            if( length <= 0 )
                return;

            // Join current range with previous.
            if( startIndex > 0 ) {
                --startIndex;
                ++length;
            }

            using( Buffer<double> xSegment = xData.Slice( startIndex, length ) )
            using( Buffer<double> ySegment = yData.Slice( startIndex, length ) )
                renderTarget.DrawLines( options, xSegment, ySegment );
        }
    }

~ Paul H
0 Kudos
Message 5 of 6
(6,193 Views)

Paul,

 

thank you,  Translation not necessary,  the quick reply most appreicated.  I will translate and try it.

 

George

0 Kudos
Message 6 of 6
(6,190 Views)