Hi folks, I appreciate the help!
We used to use VisiBlox and there was a very helpful method called GetRenderPosition() and GetRealPosition() I think it was.
This is helpful for converting a value such as 1000 (Hertz) into a screen position such as 123.456 (pixels) and back again.
Say I need to adjust a ScatterPointSeries by 10 pixels. I can't do this because there is no way to translate 10 pixels into X units on the HorizontalAxis.
Specifically, this is impossible to do because I amusing a LogarithmicAxis for the X-Axis. So on the left side of the graph, 10 pixels on the screen corresponds to about 125Hz on my graph. On the right side of the graph, 10 pixels on screen corresponds to more like 2000Hz.
Anyways, I found a method called RadChart.DefaultView.ChartArea.AxisX.ConvertPhysicalUnitsToData()
This looks like what I need, except for these problems:
-I am using RadCartesianChart not RadChart
-I tried hacking it together with a temporary RadChart variable and noticed that the AxisX property has no IsLogarithmic.
So is there a method to do this? If not, that is incredibly disappointing. The VisiBlox method was so easy and helpful.
Right now I have typed in dozens of data points and their approximate screen positions using screenshots of my pc, and then typing all these values into a Ti-83 graphing calculator and finding the best fit, and converting that function into c# code. It works, but now that the graph size has changed due to changes in my program, it is useless and there is no way to fix it again and make it future-proof.
tl;dr - How, with RadCartesianChart, do I convert from a graph value to screen position and back again?
32 Answers, 1 is accepted
To convert screen coordinates to axis coordinates and vise versa, you can use the RadChartView Conversion API. Basically, the chart exposes couple methods which you can use - ConvertPointToData() and ConvertDataToPoint().
I hope this helps.
Regards,
Martin
Telerik by Progress
ConvertDataToPoint works great!
ConvertPointToData usually returns null! Why does this happen?
To properly test this, I converted a real graph Data to a screen Point. Works great.
I immediately pass the same exact values returned (screen Point) expecting to get a DataTuple with the Graph/Data X and Y. Both values are null. :(
I've attached a screenshot of debugging values and code.
//1000Hz and 0dB
var dataPosition = new DataTuple(1000.0, 0.0);
//Convert from graph data X/Y (Hz and dB) into screen X/Y (pixels)
var screenPosition = radCartesianChart.ConvertDataToPoint(dataPosition);
//Convert right back to see if it works..from screen X/Y (pixels) back to Graph X/Y (Hz and dB)
DataTuple dataPoint = radCartesianChart.ConvertPointToData(screenPosition);
if (dataPoint.FirstValue == null)
{
var ohNo = "Help! I'm null and I can't get up!";
}
And the radCArtesianChart, the only "weird" thing is the Logarithmic HorizontalAxis. It's the same object though why would it work one way and not the other? Do I have X and Y mixed up? Thank you SO MUCH for the help.. :)
It seems we've lost this page" error message, here is an imgur link:
I created a small project based on your description and code snippet. I was unable to reproduce the issue you get on your side. I am attaching the project so you can test on your machine. Do let us know whether the issue is reproduced or not.
In the attached project, the code you provided is executed in the SizeChanged handler and the values are output in three text blocks in the upper left corner. On my machine, I never go inside the "ohNo" if clause.
I will ask that you modify the project to reproduce the issue and send it back to us for investigation. Thank you for your cooperation.
Regards,
Petar Marchev
Telerik by Progress
I also set up a for loop to go through X=0 -> x=8000 and Y=-10 -> y=120 (all the values in my graph) and no combination returns anything but null.... This is bizarre. So I am using it correctly though? Is it the fact that I have Logarithmic axis for HorizontalAxis? Can you try that?
Indeed it appears that there are temporary issues and downloads don't work. Essentially what I tried is a very basic chart in xaml and just a few lines of code behind. I will copy past it here for your reference. Even though downloading does not work for public forums, I am still able to download attachments so you can send a small project that reproduces the issue on your side. Indeed what you are observing is odd.
<
Grid
>
<
telerik:RadCartesianChart
x:Name
=
"chart1"
Margin
=
"40"
>
<
telerik:RadCartesianChart.HorizontalAxis
>
<
telerik:LogarithmicAxis
/>
</
telerik:RadCartesianChart.HorizontalAxis
>
<
telerik:RadCartesianChart.VerticalAxis
>
<
telerik:LinearAxis
/>
</
telerik:RadCartesianChart.VerticalAxis
>
<
telerik:RadCartesianChart.Series
>
<
telerik:ScatterPointSeries
>
<
telerik:ScatterPointSeries.DataPoints
>
<
telerik:ScatterDataPoint
XValue
=
"1"
YValue
=
"1"
/>
<
telerik:ScatterDataPoint
XValue
=
"10"
YValue
=
"2"
/>
<
telerik:ScatterDataPoint
XValue
=
"100"
YValue
=
"3"
/>
<
telerik:ScatterDataPoint
XValue
=
"500"
YValue
=
"4"
/>
<
telerik:ScatterDataPoint
XValue
=
"700"
YValue
=
"5"
/>
<
telerik:ScatterDataPoint
XValue
=
"888"
YValue
=
"6"
/>
<
telerik:ScatterDataPoint
XValue
=
"900"
YValue
=
"7"
/>
</
telerik:ScatterPointSeries.DataPoints
>
</
telerik:ScatterPointSeries
>
</
telerik:RadCartesianChart.Series
>
<
telerik:RadCartesianChart.Behaviors
>
<
telerik:ChartPanAndZoomBehavior
DragMode
=
"Pan"
/>
</
telerik:RadCartesianChart.Behaviors
>
</
telerik:RadCartesianChart
>
<
Border
Background
=
"White"
BorderBrush
=
"Gray"
BorderThickness
=
"1"
Margin
=
"2"
HorizontalAlignment
=
"Left"
VerticalAlignment
=
"Top"
>
<
StackPanel
>
<
TextBlock
x:Name
=
"tb1"
/>
<
TextBlock
x:Name
=
"tb2"
/>
<
TextBlock
x:Name
=
"tb3"
/>
</
StackPanel
>
</
Border
>
</
Grid
>
public
MainWindow()
{
InitializeComponent();
this
.chart1.SizeChanged +=
this
.chart1_SizeChanged;
}
void
chart1_SizeChanged(
object
sender, SizeChangedEventArgs e)
{
var radCartesianChart =
this
.chart1;
var dataPosition =
new
DataTuple(1000.0, 0.0);
//Convert from graph data X/Y (Hz and dB) into screen X/Y (pixels)
var screenPosition = radCartesianChart.ConvertDataToPoint(dataPosition);
//Convert right back to see if it works..from screen X/Y (pixels) back to Graph X/Y (Hz and dB)
DataTuple dataPoint = radCartesianChart.ConvertPointToData(screenPosition);
this
.tb1.Text =
"["
+ dataPosition.FirstValue +
", "
+ dataPosition.SecondValue +
"]"
;
this
.tb2.Text =
"("
+ screenPosition.X +
", "
+ screenPosition.Y +
")"
;
this
.tb3.Text =
"["
+ dataPoint.FirstValue +
", "
+ dataPoint.SecondValue +
"]"
;
if
(dataPoint.FirstValue ==
null
)
{
var ohNo =
"Help! I'm null and I can't get up!"
;
this
.tb3.Text = ohNo;
}
}
Do give it a try and see the results on your side. Once again, would be best if you create a small project that reproduces this so that we can investigate.
Regards,
Petar Marchev
Telerik by Progress
That, and I might have different options set.
My last question is, what's the proper way to pass the chart object to a view model, and can I use this method correctly there? Will keep you updated, thanks a bunch, glad to know it at least works in some code path!
It is irrelevant how the event handler was attached - xaml or code behind should give the same results. This cannot be the cause of the issue. We hope you are able to locate the differences and find the origins of this.
In general, the MVVM pattern says that you shouldn't touch UI elements in the view model. Perhaps if this is strictly UI logic (and it sounds to be) you should have this logic inside the code behind file.
Let us know of your findings.
Regards,
Petar Marchev
Telerik by Progress
Alright so the point in code where I want to use it, the chart.Series.Clear() has been executed and that's why it's broken. At a certain point in code I can get it to work, but the AddSeries() functions it's passing through are interfering.
I need to perform this conversion action for adjusting connecting lines etc. in my "ChartSeriesBuilder"
Well, I can't build it correctly unless I can call the PointToData and vice versa functions,
So I am trying to use a "Dummy" series with only a blank point and the correct axes. Does not work either. Hmm.
At the point in AddSeries where I get the conversion to work, it has 4 series (I am assuming these are the default axes from xaml? I don't know what they are) the function is
public void AddSeries(RadCartesianChart chart, object seriesDataProvider)
in a class that inherits ISeriesProvider
Thanks for the help, all, I don't know what I'm missing but it still does not work after manually adding series with c# code. I will post my solution when it's done. You all have been helpful!
I was not able to fully understand the requirements and I will not be giving any exact suggestions. I think that you expect the conversion api to do something it will not do. I will explain below.
The only way the conversion api will work properly is if the chart is fully rendered. Before that - the chart does not know the actual physical coordinates it will be displayed in, does not know the layout slots of its elements, thus it does not know how to map data to position. So when you see the chart properly displayed, with arranged axes, labels, series, grid, datapoint visuals​, etc, the chart can easily determine where certain data is and calculate its position. But if the chart was just rebound and it has not fully rendered - it has no idea where the plot area will result to be and where the values of the axis will be. I hope I was able to explain this well.
I suspect that you can use the UIUpdated event of the chart - it is thrown whenever the UI is updated. This includes operations like - after zoom, after pan, after resize, after rebind and so on. If you clear the series, the UIUpdated event will still be called, because all series are gone and now the chart looks different. Still, I think you may have success with it in order to get your scenario to work.
If you need further assistance, I will ask that you describe the requirements in a little more details, because right now we are a bit confused. Let us know how it goes.
Regards,
Petar Marchev
Telerik by Progress
Ugh! Sorry folks.
I was in fact adding series to the chart before trying to do calculations, but it only worked one way. Not sure what to tell you about that. But I have given up on that code path and here is my solution below.
Basically here was the failed implementation:
-AddSeries() called
-Series.Clear() is called
-Run my ChartBuilder() (which makes sists of series and annotations)
-Add annotations
-Add series
-continue (then some lower level code displays the chart etc.)
Well I was afraid to move the series clearing code around but found that it works fine if I do this:
-AddSeries() called
-Run my ChartBuilder() (which makes sists of series and annotations)
-Series.Clear() is called
-Add annotations
-Add series
-continue (then some lower level code displays the chart etc.)
So I feel dumb now but thanks for the help :) Still is indeed a mystery why this did not work:
-AddSeries() called
-Series.Clear() is called
-Attempt to add series needed (axis and stuff)
-Run my ChartBuilder() (which makes sists of series and annotations)
-at this point during chart builder it only converts one way and not the other-- bizarre!!
-Add annotations
-Add series
-continue (then some lower level code displays the chart etc.)
It seems like you said, I need it to actually *draw* to the screen first but still doesn't explain the 1-way action.
Thanks again folks have a nice day!
Here is the broken behavior, even with code moved into UIUpdated() of RadCArtesianChart:
-convert from graph data to screen pixels- WORKS! :)
-convert from screen pixels to data coords: -BROKE (returns a DataTuple of {null, null })
I think it is being confused on how to convert back to graph data but I don';t know why yet. I'm going to post the reason if I find it. If you have any clues for me let me know :)
Otherwise, for my other charts it is working fine, and UIUpdated() is the best spot, so thanks for that tip!
Note: data.AdjustmentInPixelsX; and data.AdjustmentInPixelsY are both 0.0, so no actual adjustment is taking place...
Otherwise, if there is a way to say "Move graph point by this many pixels.." with a Telerik method please let me know..
using System;
using System.Linq;
using System.Windows.Controls;
using GrasonStadler.GsiSuiteCore.Graphing.Audiometry;
using GrasonStadler.GsiSuiteCore.Source.Graphing;
using Telerik.Charting;
using Telerik.Windows.Controls.ChartView;
namespace GrasonStadler.GsiSuiteCore.Views.Audiometry
{
/// <
summary
>
/// Interaction logic for AudiogramChart.xaml
/// </
summary
>
public partial class AudiogramChart : UserControl
{
public AudiogramChart()
{
InitializeComponent();
//My RadCartesianChart is called "TheChart"
TheChart.UIUpdated += AdjustExtraPointMovements;
}
private void AdjustExtraPointMovements(object sender, EventArgs e)
{
foreach (var scatterDataSeries in TheChart.Series.OfType<
ScatterPointSeries
>())
{
foreach (var scatterDataPoint in scatterDataSeries.DataPoints)
{
//Convert to AudiometryScatterPoint
if (scatterDataPoint as AudiometryScatterPoint != null)
{
var data = (AudiometryScatterPoint)scatterDataPoint;
//Convert to Screen Pixels
var pointInPixels = TheChart.ConvertDataToPoint(new DataTuple(data.XValue, data.YValue));
//Adjust by Screen Pixels
pointInPixels.X += data.AdjustmentInPixelsX;
pointInPixels.Y += data.AdjustmentInPixelsY;
//Convert to Graph Data
var pointInGraphData = TheChart.ConvertPointToData(pointInPixels);
//Apply new X/Y to originating point
data.XValue = (double)(pointInGraphData.FirstValue);
data.YValue = (double)(pointInGraphData.SecondValue);
}
}
}
}
If anyone stumbles on this issue just be aware that calling and Update too many times in your code or i the wrong places will make it difficult to work with. Hopefully I can help clean up this project's code at my job for the next release! :)
Using UIUpdated and proper null checks (for the weird 1-way conversion problem) seems to work o.k. Can we close this issue? Sorry for the mass confusion. Ughh.
((ScatterPointSeries)TheChart.Series[6]).DataPoints[0].XValue = 5;
It does not change my graph at all. This is a huge disappointment and I'm going to have to try again in somewhere other than the code-behind. Unless there's a way to force update the chart after the fact? I hate this. It doesn't make sense at all.
I am trying to make a sample project for you. I hope it works and shows you that I'm not crazy; it really is behaving unexpectedly (I have X and Y axis, there is enough data to convert from data to render..but not back? impossible)
Also, wish I could delete posts, I am getting more and more embarrassed to reply. More-so talking to myself at this point. Will reply with a sample project if I ever get it to work......
Thank you for sharing your thoughts on the issue. If you find what is causing the reported behavior on your side, please do not hesitate to share it here.
Regards,
Martin
Telerik by Progress
Ok, I'm not going crazy. First try, it does not work with this sample project. Exception is thrown when I try to use the DataTuple values returned, because it is {null, null} :D
It is converting one way, just fine (data to pixels) but when I try to go the other way (pixels to data) it returns nothing, so odd. Let me know if I'm doing something wrong.
Also you can comment this line (so that no adjustments are made and it's a straight conversion back and forth)
//screenPositionInPixels.X += 10.0;
Thanks!!! :)
P.S. How do I send you my project in .zip format? This post won't let me attach it....
App.config
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
configuration
>
<
startup
>
<
supportedRuntime
version
=
"v4.0"
sku
=
".NETFramework,Version=v4.5"
/>
</
startup
>
</
configuration
>
App.xaml
<
Application
x:Class
=
"TelerikChartTest.App"
StartupUri
=
"MainWindow.xaml"
>
<
Application.Resources
>
</
Application.Resources
>
</
Application
>
App.xaml.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace TelerikChartTest
{
/// <
summary
>
/// Interaction logic for App.xaml
/// </
summary
>
public partial class App : Application
{
}
}
MainWindow.xaml
<
Window
x:Class
=
"TelerikChartTest.MainWindow"
xmlns:telerik
=
"http://schemas.telerik.com/2008/xaml/presentation"
Title
=
"MainWindow"
Height
=
"600"
Width
=
"700"
DataContext
=
"{Binding RelativeSource={RelativeSource Self}}"
>
<
Window.Resources
>
<
Style
x:Key
=
"CollapsedStyle"
TargetType
=
"FrameworkElement"
>
<
Setter
Property
=
"Visibility"
Value
=
"Collapsed"
/>
</
Style
>
<
Style
TargetType
=
"Label"
>
<
Setter
Property
=
"BorderThickness"
Value
=
"1"
/>
<
Setter
Property
=
"BorderBrush"
Value
=
"Gray"
/>
<
Setter
Property
=
"Foreground"
Value
=
"WhiteSmoke"
/>
<
Setter
Property
=
"Height"
Value
=
"35"
/>
<!--<Setter Property="Width" Value="60"/>-->
<!--<Setter Property="HorizontalAlignment" Value="Stretch"></Setter>-->
</
Style
>
</
Window.Resources
>
<
Grid
>
<
Grid.ColumnDefinitions
>
<
ColumnDefinition
/>
<
ColumnDefinition
/>
</
Grid.ColumnDefinitions
>
<
Grid.RowDefinitions
>
<
RowDefinition
Height
=
"40"
/>
<
RowDefinition
/>
<
RowDefinition
Height
=
"Auto"
/>
</
Grid.RowDefinitions
>
<
Label
x:Name
=
"LeftLabelLeft"
Background
=
"Blue"
Content
=
"LEFT"
Grid.Column
=
"0"
Grid.Row
=
"0"
HorizontalContentAlignment
=
"Center"
VerticalContentAlignment
=
"Center"
Visibility
=
"Visible"
Width
=
"{Binding ElementName=Left1Chart, Path=Width}"
/>
<
Label
x:Name
=
"LeftLabelRight"
Background
=
"Red"
Content
=
"RIGHT"
Grid.Column
=
"0"
Grid.Row
=
"0"
HorizontalContentAlignment
=
"Center"
VerticalContentAlignment
=
"Center"
Visibility
=
"Hidden"
Width
=
"{Binding ElementName=Left1Chart, Path=Width}"
/>
<
telerik:RadCartesianChart
x:Name
=
"Left1Chart"
Grid.Column
=
"0"
Grid.Row
=
"1"
BorderBrush
=
"Black"
BorderThickness
=
"2"
Margin
=
"25"
Visibility
=
"Visible"
/>
<
telerik:RadCartesianChart
x:Name
=
"Left2Chart"
Grid.Column
=
"0"
Grid.Row
=
"1"
BorderBrush
=
"Black"
BorderThickness
=
"2"
Margin
=
"25"
Visibility
=
"Hidden"
/>
<
Label
x:Name
=
"RightLabelLeft"
Background
=
"Blue"
Content
=
"LEFT"
Grid.Column
=
"1"
Grid.Row
=
"0"
HorizontalContentAlignment
=
"Center"
VerticalContentAlignment
=
"Center"
Visibility
=
"Hidden"
Width
=
"{Binding ElementName=Right1Chart, Path=Width}"
/>
<
Label
x:Name
=
"RightLabelRight"
Background
=
"Red"
Content
=
"RIGHT"
Grid.Column
=
"1"
Grid.Row
=
"0"
HorizontalContentAlignment
=
"Center"
VerticalContentAlignment
=
"Center"
Visibility
=
"Visible"
Width
=
"{Binding ElementName=Right1Chart, Path=Width}"
/>
<
telerik:RadCartesianChart
x:Name
=
"Right1Chart"
Grid.Column
=
"1"
Grid.Row
=
"1"
BorderBrush
=
"Black"
BorderThickness
=
"2"
Margin
=
"25"
Visibility
=
"Visible"
/>
<
telerik:RadCartesianChart
x:Name
=
"Right2Chart"
Grid.Column
=
"1"
Grid.Row
=
"1"
BorderBrush
=
"Black"
BorderThickness
=
"2"
Margin
=
"25"
Visibility
=
"Hidden"
/>
<
Button
Grid.Column
=
"0"
Grid.ColumnSpan
=
"2"
Grid.Row
=
"2"
x:Name
=
"SwitchButton"
Content
=
"Switch"
Click
=
"SwitchButton_OnClick"
Width
=
"40"
Height
=
"25"
Margin
=
"10"
/>
</
Grid
>
</
Window
>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using Telerik.Charting;
using Telerik.Windows.Controls;
using Telerik.Windows.Controls.ChartView;
namespace TelerikChartTest
{
/// <
summary
>
/// Interaction logic for MainWindow.xaml
/// </
summary
>
public partial class MainWindow : Window
{
public Visibility FirstVisibility { get; set; }
public Visibility SecondVisibility { get; set; }
private bool isFirstVisible = true;
public MainWindow()
{
FirstVisibility = Visibility.Visible;
SecondVisibility = Visibility.Hidden;
InitializeComponent();
ReloadCharts();
}
private void SwitchButton_OnClick(object sender, RoutedEventArgs e)
{
isFirstVisible = !isFirstVisible;
ReloadCharts();
if (isFirstVisible)
{
Left1Chart.Visibility = Visibility.Visible;
Left2Chart.Visibility = Visibility.Hidden;
Right1Chart.Visibility = Visibility.Visible;
Right2Chart.Visibility = Visibility.Hidden;
LeftLabelLeft.Visibility = Visibility.Visible;
LeftLabelRight.Visibility = Visibility.Hidden;
RightLabelLeft.Visibility = Visibility.Hidden;
RightLabelRight.Visibility = Visibility.Visible;
}
else
{
Left1Chart.Visibility = Visibility.Hidden;
Left2Chart.Visibility = Visibility.Visible;
Right1Chart.Visibility = Visibility.Hidden;
Right2Chart.Visibility = Visibility.Visible;
LeftLabelLeft.Visibility = Visibility.Hidden;
LeftLabelRight.Visibility = Visibility.Visible;
RightLabelLeft.Visibility = Visibility.Visible;
RightLabelRight.Visibility = Visibility.Hidden;
}
}
private void ReloadCharts()
{
Left1Chart.Series.Clear();
Left1Chart.Annotations.Clear();
Left2Chart.Series.Clear();
Left2Chart.Annotations.Clear();
Right1Chart.Series.Clear();
Right1Chart.Annotations.Clear();
Right2Chart.Series.Clear();
Right2Chart.Annotations.Clear();
SetAxes(Left1Chart);
SetAxes(Left2Chart);
SetAxes(Right1Chart);
SetAxes(Right2Chart);
SetAnnotations(Left1Chart);
SetAnnotations(Left2Chart);
SetAnnotations(Right1Chart);
SetAnnotations(Right2Chart);
var left1Series = new ScatterPointSeries();
left1Series.DataPoints.Add(new ScatterDataPoint{XValue = 500, YValue = 25});
left1Series.DataPoints.Add(new ScatterDataPoint{XValue = 1000, YValue = 20});
left1Series.DataPoints.Add(new ScatterDataPoint{XValue = 2000, YValue = 15});
Left1Chart.Series.Add(left1Series);
var left2Series = new ScatterPointSeries();
left2Series.DataPoints.Add(new ScatterDataPoint { XValue = 500, YValue = 30 });
left2Series.DataPoints.Add(new ScatterDataPoint { XValue = 1000, YValue = 35 });
left2Series.DataPoints.Add(new ScatterDataPoint { XValue = 2000, YValue = 40 });
Left2Chart.Series.Add(left2Series);
var right1Series = new ScatterPointSeries();
right1Series.DataPoints.Add(new ScatterDataPoint { XValue = 500, YValue = 30 });
right1Series.DataPoints.Add(new ScatterDataPoint { XValue = 1000, YValue = 35 });
right1Series.DataPoints.Add(new ScatterDataPoint { XValue = 2000, YValue = 40 });
Right1Chart.Series.Add(right1Series);
var right2Series = new ScatterPointSeries();
right2Series.DataPoints.Add(new ScatterDataPoint { XValue = 500, YValue = 25 });
right2Series.DataPoints.Add(new ScatterDataPoint { XValue = 1000, YValue = 20 });
right2Series.DataPoints.Add(new ScatterDataPoint { XValue = 2000, YValue = 15 });
Right2Chart.Series.Add(right2Series);
//Adjust the points by 10 pixels
AdjustPoints(left1Series);
}
private void AdjustPoints(ScatterPointSeries series)
{
foreach (var scatterDataPoint in series.DataPoints)
{
//convert graph data to screen pixels
var dataTuple = new DataTuple(scatterDataPoint.XValue, scatterDataPoint.YValue ?? 0.0);
var screenPositionInPixels = Left1Chart.ConvertDataToPoint(dataTuple);
//Adjust pixels by 10
screenPositionInPixels.X += 10.0;
//Convert screen pixels back to graph data
dataTuple = Left1Chart.ConvertPointToData(screenPositionInPixels);
//Will crash if there is a DataTuple consisting of {null, null}
var newScatterDataPoint = new ScatterDataPoint() { XValue = (double)dataTuple.FirstValue, YValue = (double)dataTuple.SecondValue };
series.DataPoints.Add(newScatterDataPoint);
}
}
private void SetAxes(RadCartesianChart chart)
{
chart.HorizontalAxis = new LogarithmicAxis
{
VerticalLocation = AxisVerticalLocation.Top,
LogarithmBase = 2,
Minimum = 125,
Maximum = 9400,
Background = Brushes.Transparent,
ExponentStep = 1
};
chart.VerticalAxis = new LinearAxis
{
HorizontalLocation = AxisHorizontalLocation.Left,
Minimum = -20,
Maximum = 125,
MajorStep = 10,
IsInverse = true
};
}
private void SetAnnotations(RadCartesianChart chart)
{
var topTicks = new List<
double
> {250, 500, 1000, 2000, 4000, 8000};
var bottomTicks = new List<
double
> { 750, 1500, 3000, 6000, 12000 };
var dashArray = new DoubleCollection{1, 2};
var XAxis = chart.HorizontalAxis;
var YAxis = chart.VerticalAxis as LinearAxis;
//var topAxis = new LogarithmicAxis
//{
// Minimum = 125,
// Maximum = 9400,
// VerticalLocation = AxisVerticalLocation.Top,
// LogarithmBase = 2,
// ExponentStep = 1,
//};
foreach (var tick in topTicks)
{
//chart.Annotations.Add(new CartesianCustomAnnotation
//{
// ClipToPlotArea = false,
// HorizontalAxis = XAxis,
// VerticalAxis = YAxis,
// HorizontalValue = tick,
// VerticalValue = -25,
// Content = tick <
1000
? tick.ToString(CultureInfo.InvariantCulture) : String.Format("{0}K", tick / 1000),
//});
chart.Annotations.Add(new CartesianGridLineAnnotation
{
Axis
=
XAxis
,
Value
=
tick
,
//
DashArray
=
dashArray
,
Stroke
=
Brushes
.Gray,
StrokeThickness
=
1
});
}
foreach (var tick in bottomTicks)
{
chart.Annotations.Add(new CartesianCustomAnnotation
{
ClipToPlotArea
=
false
,
HorizontalAxis
=
XAxis
,
VerticalAxis
=
YAxis
,
HorizontalValue
=
tick
,
VerticalValue
=
123
,
HorizontalAlignment
= HorizontalAlignment.Center,
Content
=
tick
< 1000 ? tick.ToString(CultureInfo.InvariantCulture) : String.Format("{0}K", tick / 1000),
});
chart.Annotations.Add(new CartesianGridLineAnnotation
{
Axis
=
XAxis
,
Value
=
tick
,
DashArray
=
dashArray
,
Stroke
=
Brushes
.Gray,
StrokeThickness
=
1
});
}
var
leftTicks
=
new
List<double> {-20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120};
foreach (var tick in leftTicks)
{
//chart.Annotations.Add(new CartesianCustomAnnotation
//{
// ClipToPlotArea = false,
// HorizontalAxis = XAxis,
// VerticalAxis = YAxis,
// VerticalValue = tick,
// Content = tick.ToString(CultureInfo.InvariantCulture),
//});
chart.Annotations.Add(new CartesianGridLineAnnotation
{
Axis = YAxis,
Value = tick,
DashArray = dashArray,
Stroke = Brushes.Gray,
StrokeThickness = 1
});
}
}
}
}
The forums allow you to attach only images. If you want to send a zip you will need to open a new support ticket and send it there. Anyway, thank you for the code snippets they were quite useful for reproducing the error. No, your not going crazy - there is an error. It happens because the UI of the new axes set to the chart is not yet updated. So, basically, when you try to convert the point to data, the axis says "no, you can't do that because I am not ready yet" and it returns null. To avoid the error you will need to wait one additional layout pass before call the ConvertPointToData() method. In other words you will need to wait for the axes to be loaded. In your case you can do that using a Dispatcher.
Dispatcher.BeginInvoke(
new
Action(() =>
{
AdjustPoints(left1Series);
}));
I hope this helps.
As a side note, while I was testing your code I hit another issue which you will need to fix. The DataPoints collection in the AdjustPoints() method is modified in the loop that iterates through it. To resolve this you can save the new data points in a separate list and then merge it with the DataPoints collection of the series.
private
void
AdjustPoints(ScatterPointSeries series)
{
var points =
new
List<ScatterDataPoint>();
foreach
(var scatterDataPoint
in
series.DataPoints)
{
//convert graph data to screen pixels
var dataTuple =
new
DataTuple(scatterDataPoint.XValue, scatterDataPoint.YValue ?? 0.0);
var screenPositionInPixels = Left1Chart.ConvertDataToPoint(dataTuple);
//Adjust pixels by 10
// screenPositionInPixels.X += 10.0;
//Convert screen pixels back to graph data
dataTuple = Left1Chart.ConvertPointToData(screenPositionInPixels);
var newScatterDataPoint =
new
ScatterDataPoint() { XValue = (
double
)dataTuple.FirstValue, YValue = (
double
)dataTuple.SecondValue };
points.Add(newScatterDataPoint);
//series.DataPoints.Add(newScatterDataPoint);
}
foreach
(var item
in
points)
{
series.DataPoints.Add(item);
}
}
Regards,
Martin
Telerik by Progress
The fix makes sense, but I just copied it to my sample project and...same error.
Please advise.
It's really cool when it DOES work,but it needs to work all the time; it can't be flaky. I even tried using Invoke Priorit but it does not seem to help:
Application.Current.Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
{
AdjustPoints(left1Series);
}));
Also, if the graph isn't ready to convert points...why can it convert one-way? This seems like a bug in Telerik.
I can create a function using a graphing calculator using enough of these 1-way function calls.I probably will end up doing that instead of using Telerilk's point adjusting which makes me so upset.
I tested the approach suggested in my last reply and it works as expected. However, it is not guaranteed that it will work in more complex scenarios. This is because the dispatcher only schedules an action for a point in time which is not defined. It is not necessary that point will be after the axis is loaded, but in the general case, the action should be executed when this happens. It seems that your scenario is a bit more complex and the dispatcher approach doesn't work always.
As for the other way conversion (data-to-point), well, it works just as expected - at least on my side. If the axis is not loaded (when the exception is thrown in the point-to-data conversion), the ConvertDataToPoint() method returns Point(0,0). Which could mean two things - the data is located at position 0,0 or the axis is not yet measured and the correct point cannot be calculated. In this case, the returned result is not null because we work with the Point struct, which is not null-able and when the result cannot be calculated an empty point is returned (Point(0,0)).
In order for the conversion to be possible both axes of the chart should be measured and arranged. So, they should know about their size and location in space. Otherwise, the chart won't know where exactly to find the coordinates.
To resolve, this on your side, you will need to ensure that the axes are fully loaded. Without your working project, I can't be sure where this will happen. But I guess the chart's UIUpdated event should work for you. You can also try the Loaded event of the newly created axis.
Regards,
Martin
Telerik by Progress
UIUpdated loops forever (if I update the UI it will call UIUpdated, then repeat forever)
I tried this before and made a work around using temporary X/Y "adjustment in pixels" type values, then cleared them to 0 so it would not keep triggering UIUpdated.
When I did that, it never actually changed my graph. Using my debugger, the values did change on chart.Series.DataPoints, but one comment above suggested I should not modify this? But Add() to it instead? I hope I can remove the old series and add the new one.
The entire point of this, is really simple. I just want to move a ScatterDataPoint by a pixel amount. :(
If so then I would love to write up a new thread. Just a very simple tutorial on:
-How to add UIUpdated handler
-How to avoid infinite loop in UIUpdater
-How to properly change the data series so it actually updates the chart
I am glad to hear that you managed to achieve the desired behavior in your project.
Indeed, if you update the UI of the chart in the UIUpdated event it is expected for the event to be called again and to enter an endless recursion. You can use the event only to retrieve the converted coordinates. If you want to use UIUpdated to also update the UI you will need to implement some kind of suspension mechanism that allows updates from the event handler to be executed only when necessary.
I attached three different projects demonstrating the three approaches I suggested.
- Using Dispatcher
- Using the axis Loaded event
- Using UIUpdated, along with logic that suspends the UI updates in the handler. This project contains two approaches - using a boolean flag and unsubscribing for the UIUpdated event after the AdjustPoints() method is called.
Regards,
Martin
Telerik by Progress
I cannot get it to work in my "real project" which is going to take a long time to fix up.
what it boils down to is, so far none of these work in my "real project."
UIUpdated doesn't work. I wish it did. But even there, where it should be ready to convert, does not work all the time.
Are there any ways of forcing the graph to update so I can properly do this? Or a method to say "just finished drawing!" and then update, and force re-draw? Thanks :) I will be trying over and over to get one of these to work.
Hands down one of the most mysterious code problems in my life. I absolutely hate this scenario.
Sorry to hear that the suggested approaches do not work on your side.
About the axes, as any other UI element the WPF framework is taking care of its rendering. So you can't easily force it to be rendered. You can use the Measure(), Arrange() and UpdateLayout() to force measuring the axis, and in this case, the method could work. But, there are too many elements that depends on the axis' proper layout, so I can't guarantee that this will work properly, or if it will work at all.
If you decide, you can isolate the issue in a new project (or use your original one if not too big) and open a new support ticket from your telerik.com account. This way you can attach it and ask for help there.
Regards,
Martin
Telerik by Progress
My end conclusion is this:
In the big project, there are too many updates and refresh going on to my chart...that we need to figure out on our side. I can get it to work sometimes, but a mystery is happening that is "breaking" the chart if that makes sense. We will figure it out eventually.
You may close this thread if you want. Have to give up for this release and fix it next go around. Thanks for all the tips, it was very helpful and once we fix the program up a bit we can get it working I'm sure. :)