Need help finding the correct way to convert DATA point to SCREEN point (pixels) and back.

33 posts, 0 answers
  1. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 11 Oct Link to this post

    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?

  2. Martin
    Admin
    Martin avatar
    1101 posts

    Posted 12 Oct Link to this post

    Hello Nathanial,

    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
    Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
  3. UI for WPF is Visual Studio 2017 Ready
  4. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 12 Oct in reply to Martin Link to this post

    Think I tried this before and gave up. 

    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.. :) 


  5. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 12 Oct Link to this post

    P.S. I could not get the above image link to work on my end? In case you also have the same "Oh, no! 
    It seems we've lost this page" error message, here is an imgur link:

     
  6. Petar Marchev
    Admin
    Petar Marchev avatar
    968 posts

    Posted 13 Oct Link to this post

    Hello Nathanial,

    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
    Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
  7. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 13 Oct in reply to Petar Marchev Link to this post

    That attachment does not work, just like the image attachment. Can you email it tome perhaps? 

    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?
  8. Petar Marchev
    Admin
    Petar Marchev avatar
    968 posts

    Posted 13 Oct Link to this post

    Hello Nathanial,

    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
    Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
  9. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 13 Oct in reply to Petar Marchev Link to this post

    That example works great. However the big difference I see is that the code examples in the documentation and this example are using the code-behind or the input handlers for the chart.

    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!
  10. Petar Marchev
    Admin
    Petar Marchev avatar
    968 posts

    Posted 13 Oct Link to this post

    Hi,

    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
    Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
  11. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 13 Oct in reply to Petar Marchev Link to this post

    I got it to work in one spot of code, but then after the Series was cleared on it, it stops working. So I must have to set my object after a series is applied. 
  12. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 13 Oct in reply to Nathanial Link to this post

    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!




  13. Petar Marchev
    Admin
    Petar Marchev avatar
    968 posts

    Posted 14 Oct Link to this post

    Hi Nathanial,

    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
    Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
  14. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 14 Oct in reply to Nathanial Link to this post

    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!

  15. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 24 Oct in reply to Nathanial Link to this post

    I am still getting a problem. Been wrestling with it for days. It turns out, there is one view that it does not work on, my CombinedAudiogramView (a left ear graph and a right ear graph combined into one)

    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! 

  16. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 24 Oct in reply to Nathanial Link to this post

    Darn, it's happening on my non-combined chart too. Hmm. What is going on here? What am I missing?

    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);
                        }
     
                       
     
     
                    }
                }
     
     
            }
  17. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 25 Oct Link to this post

    Don't worry, it's just a bad codebase, not Telerik's fault. 

    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.
  18. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 25 Oct in reply to Nathanial Link to this post

    While debugging I somehow got some behavior to happen..but in the end putting the cod ein UIUpdated doesn't work. Even if I do 1 line of code:

    ((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.
  19. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 25 Oct Link to this post

    Again having the problem where I can convert one way (from data to screen pixels) but not back (returns datatuple of null, null)

    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......
  20. Martin
    Admin
    Martin avatar
    1101 posts

    Posted 27 Oct Link to this post

    Hello Nathanial,

    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
    Do you need help with upgrading your WPF project? Try the Telerik API Analyzer and share your thoughts!
  21. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 31 Oct Link to this post

    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"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 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"
            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
                    });
                }
            }
        }
    }
  22. Martin
    Admin
    Martin avatar
    1101 posts

    Posted 02 Nov Link to this post

    Hello Nathanial,

    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);
    }));
    This will schedule calling of the AdjustPoints() method for later, which in the common case is one layout pass later.

    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
    Do you need help with upgrading your WPF project? Try the Telerik API Analyzer and share your thoughts!
  23. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 02 Nov in reply to Martin Link to this post

    Thank you so much. I had a feeling it had to be "Updated" but had to idea how to let it handle itself properly. I am going to try and fix it again and let you know if it works. So happy you responded and found my code snippets helpful!
  24. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 02 Nov Link to this post

    It does not work.I get the same error. Did you test your fix? :/ 
    The fix makes sense, but I just copied it to my sample project and...same error. 

    Please advise.
  25. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 02 Nov Link to this post

    I have added a null check and now it has the original behavior I had: It works, but only sometimes. And only after the initial viewing of the graph which doesn't help me. There has to be a better way to make this work. Also when I flip the graph orientation it does not work. 

    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);
                }));
  26. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 03 Nov Link to this post

    Well if I can't get this to work I've failed my boss over a 3-month headache trying to get this damn point adjusting to work. IT doesn't seem like it should be this complicated. 

    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. 
  27. Martin
    Admin
    Martin avatar
    1101 posts

    Posted 04 Nov Link to this post

    Hello Nathanial,

    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
    Do you need help with upgrading your WPF project? Try the Telerik API Analyzer and share your thoughts!
  28. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 04 Nov in reply to Martin Link to this post

    I put your exact code in my same example project I gave you. It returns null right away. If I catch the error and continue, it works after flipping the graph and flipping it again. I beg you to send me a .zip file of it working.
  29. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 04 Nov Link to this post

    The Loaded event does not work. Same behavior.

    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. :( 
  30. Nathanial
    Nathanial avatar
    25 posts
    Member since:
    Sep 2016

    Posted 04 Nov Link to this post

    This works if I do this work around.....I hope it will work in my huge complicated project now :) 

    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
  31. Martin
    Admin
    Martin avatar
    1101 posts

    Posted 04 Nov Link to this post

    Hi Nathanial,

    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
    Do you need help with upgrading your WPF project? Try the Telerik API Analyzer and share your thoughts!
Back to Top
UI for WPF is Visual Studio 2017 Ready