07-15-2014 01:39 PM
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
07-16-2014 10:22 AM
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.
07-18-2014 08:59 AM
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
07-18-2014 08:59 AM
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
07-18-2014 09:46 AM
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 );
}
}
07-18-2014 10:23 AM
Paul,
thank you, Translation not necessary, the quick reply most appreicated. I will translate and try it.
George