How to invert Y (Vertical) axis

May 19, 2009 at 10:38 PM

First of, I stumbled across D3 today & am very impressed.

Secondly, I need to invert the vertical axis so that downwards onscreen is positive on the graph. i.e. so that 0,0 is at the top left of the graph instead of bottom left.

I've tried a DataTransform, but that inverts the data not the Y axis. :-(

A regular transform will invert everything, including labels (I think).

Is there a solution?

Thank you

May 20, 2009 at 10:42 AM

I have been working with this code for about a month and continue to be impressed.

It does take awhile to get your head into the data transform options.  I do not claim to fully understand it, but I'll give what I have...

Your chart has several coordinate spaces:  data, viewport, and screen.

You can add as many X and Y axes as you like.  Each has its own data transform for going from data to viewport and vice versa.  You control this by setting the Axis' ConvertToDouble and ConvertFromDouble properties (set them to a lambda).  They are Func<double, double>, so to invert the y axis you might do something like plotter.MainVerticalAxis.ConvertToDouble = s => -s + 1000; plotter.MainVerticalAxis.ConvertFromDouble = t => -t - 1000

When you create a LineGraph (using plotter.AddLineGraph(...), it has a DataTransform property that transforms the data into viewport coordinates.  There is a DelegateDataTransform class that has a constructor that takes two Func<double, double>, one to go from viewport to data, and one to go from data to viewport.

plotter also has a DataTransform property, which is applied to all LineGraphs added to the chart (I think plotter just sets this property on the viewport).

I think what makes this hard conceptually is the disconnect between the axes and the line graph transforms.  It might be easier to work with if adding a line graph included associating the line graph with a vertical axis and horizontal axis, and the data transforms of the axes were used for the data...   but once you understand the independent operation of these guys, it really works well and is quite powerful.

You can even use the transforms to apply map coordinate geometry transforms.

May 20, 2009 at 3:10 PM

Here's a code sample for you to play with...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Microsoft.Research.DynamicDataDisplay;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using Microsoft.Research.DynamicDataDisplay.Charts;

namespace ChartTesterApp
{
    /// <summary>
    /// Interaction logic for Window2.xaml
    /// </summary>
    public partial class Window2 : Window
    {
        public Window2()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Window2_Loaded);
        }

        void Window2_Loaded(object sender, RoutedEventArgs e)
        {
            ChartPlotter plotter = new ChartPlotter();
            EnumerableDataSource<Point> eds = new EnumerableDataSource<Point>(
                Enumerable.Range(-20, 41).Select(s => new Point
                {
                    X = s,
                    Y = s * s
                }
                ).ToList());

            // here's another chance to transform data when being read from source
            //    in this example, we just pass it on through
            eds.XMapping = s => s.X;
            eds.YMapping = s => s.Y;

            double yMin, yMax;
            double M, B; // y = M * x + B

            GetMinAndMaxForEDS(eds, out yMin, out yMax);
            Get_M_and_B(yMin, yMax, out M, out B);

            Func<double, double> ConvertToDouble = s => -(M * s + B);
            Func<double, double> ConvertFromDouble = t => -((t - B) / M);
            Func<Point, Point> DataToViewport = s => new Point(s.X, ConvertToDouble(s.Y));
            Func<Point, Point> ViewportToData = t => new Point(t.X, ConvertFromDouble(t.Y));

            Brush brush = new SolidColorBrush(Colors.Blue);
            Pen linePen = new Pen(brush, 2.0);
            PenDescription desc = new PenDescription("f(x) = x");
            LineGraph lg = plotter.AddLineGraph(eds, linePen, desc);

            lg.DataTransform = new DelegateDataTransform(DataToViewport, ViewportToData);
            ((VerticalAxis)plotter.MainVerticalAxis).ConvertFromDouble = ConvertFromDouble;
            ((VerticalAxis)plotter.MainVerticalAxis).ConvertToDouble = ConvertToDouble;

            this.Content = plotter;
            plotter.FitToView();
        }

        private void GetMinAndMaxForEDS(EnumerableDataSource<Point> eds, out double minData, out double maxData)
        {
            minData = eds.GetPoints().Min(s => s.Y);
            maxData = eds.GetPoints().Max(s => s.Y);
        }

        private double smaller(double d1, double d2)
        {
            if (d1 <= d2) return d1;
            else return d2;
        }

        private double bigger(double d1, double d2)
        {
            if (d1 >= d2) return d1;
            else return d2;
        }

        private void Get_M_and_B(double min, double max, out double M, out double B)
        {
            // @min, 0
            // @max, 1

            // y = m x + b

            // 0 = m * min + b
            // 1 = m * max + b

            // b = -m  min
            // b = 1 = m * max

            // m * min + m * max = 1
            // m = 1 / (min + max)

            // b = -m * min
            // b = -min / (min + max)

            M = 1 / (min + max);
            B = -min / (min + max);
        }
    }
}

May 20, 2009 at 3:30 PM

Thank you. Your example was a great help. Do you happen to have an example with multiple Y axes?

For anyone reading this thread: the key is to set both LineGraph.DataTransform AND ChartPlotter.VerticalAxis.ConvertFromDouble/ConvertToDouble. Note that the axis converters can't be set in XAML.

May 20, 2009 at 3:55 PM

Multiple Y Axis Sample: left side is Log10, right is Linear, X Axis is time

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Research.DynamicDataDisplay;
using Microsoft.Research.DynamicDataDisplay.Charts;
using Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using Microsoft.Research.DynamicDataDisplay.PointMarkers;

namespace ChartTesterApp
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        VerticalAxis yAxisLeft;
        VerticalAxis yAxisRight;
        HorizontalDateTimeAxis xAxis;
        double tick1, tick2;

        public Window1()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Window1_Loaded);
        }

        void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            ChartPlotter plotter = CreatePlotter();
            ConfigureYAxisLeft(plotter);
            ConfigureYAxisRight(plotter);
            ConfigureXAxis(plotter);
            AddChartGraphs(plotter);
            GetXAxisExtents(plotter);
            double buf = (tick2 - tick1) / 20;
            plotter.Viewport.Visible = new DataRect(new Point(tick1 - buf, -.05), new Point(tick2 + buf, 1.05));
            this.Content = plotter;
        }

        private void GetXAxisExtents(ChartPlotter plotter)
        {
            tick1 = double.MaxValue;
            tick2 = double.MinValue;
            foreach (LineGraph lg in plotter.Children.OfType<LineGraph>())
            {
                tick1 = smaller(tick1, lg.DataSource.GetPoints().Min(s => s.X));
                tick2 = bigger(tick2, lg.DataSource.GetPoints().Max(s => s.X));
            }
        }

        private double smaller(double d1, double d2)
        {
            if (d1 <= d2) return d1;
            else return d2;
        }

        private double bigger(double d1, double d2)
        {
            if (d1 >= d2) return d1;
            else return d2;
        }

        private void AddChartGraphs(ChartPlotter plotter)
        {
            CreatePowGraphOnLeftYAxis(plotter);
            CreateLinearGraphOnRightYAxis(plotter);
        }

        private void CreatePowGraphOnLeftYAxis(ChartPlotter plotter)
        {
            EnumerableDataSource<TPoint> edsPow = new EnumerableDataSource<TPoint>(
                 Enumerable.Range(1, 2000).Select(s =>
                     new TPoint
                     {
                         X = DateTime.Now.AddYears(-20).AddDays(s),
                         Y = Math.Pow(10, s / 700.0)
                     }
                     ).ToList());
            edsPow.SetXMapping(s => xAxis.ConvertToDouble(s.X));
            edsPow.SetYMapping(s => s.Y);

            double minData, maxData, M, B;
            GetMinAndMaxForEDS(edsPow, out minData, out maxData);
            Get_M_and_B(Math.Floor(Math.Log10(minData)), Math.Ceiling(Math.Log10(maxData)), out M, out B);

            Func<double, double> ConvertToDouble = s => M * Math.Log10(s) + B;
            Func<double, double> ConvertFromDouble = t => Math.Pow(10.0, (t - B) / M);
            Func<Point, Point> DataToViewport = s => new Point(s.X, ConvertToDouble(s.Y));
            Func<Point, Point> ViewportToData = t => new Point(t.X, ConvertFromDouble(t.Y));

            Brush brushPow = new SolidColorBrush(Colors.Green);
            Pen linePenPow = new Pen(brushPow, 2.0);
            PenDescription descPow = new PenDescription("f(x) = 10 ^ x");
            LineGraph lg = plotter.AddLineGraph(edsPow, linePenPow, descPow);

            lg.DataTransform = new DelegateDataTransform(DataToViewport, ViewportToData);
            yAxisLeft.ConvertFromDouble = ConvertFromDouble;
            yAxisLeft.ConvertToDouble = ConvertToDouble;
        }

        private void GetMinAndMaxForEDS(EnumerableDataSource<TPoint> eds, out double minData, out double maxData)
        {
            minData = eds.GetPoints().Min(s => s.Y);
            maxData = eds.GetPoints().Max(s => s.Y);
        }

        private void GetMinAndMaxForLineGraph(LineGraph lg, out double minData, out double maxData)
        {
            minData = lg.DataSource.GetPoints().Min(s => s.Y);
            maxData = lg.DataSource.GetPoints().Max(s => s.Y);
        }

        private void Get_M_and_B(double min, double max, out double M, out double B)
        {
            // @min, 0
            // @max, 1

            // y = m x + b

            // 0 = m * min + b
            // 1 = m * max + b

            // b = -m  min
            // b = 1 = m * max

            // m * min + m * max = 1
            // m = 1 / (min + max)
           
            // b = -m * min
            // b = -min / (min + max)

            M = 1 / (min + max);
            B = -min / (min + max);
        }

        private void CreateLinearGraphOnRightYAxis(ChartPlotter plotter)
        {
            EnumerableDataSource<TPoint> edsLinear = new EnumerableDataSource<TPoint>(
                Enumerable.Range(1, 2000).Select(s =>
                    new TPoint
                    {
                        X = DateTime.Now.AddYears(-20).AddDays(s),
                        Y = s
                    }
                    ).ToList());
            edsLinear.SetXMapping(s => xAxis.ConvertToDouble(s.X));
            edsLinear.SetYMapping(s => s.Y);

            double minData, maxData, M, B;
            GetMinAndMaxForEDS(edsLinear, out minData, out maxData);
            Get_M_and_B(minData, maxData, out M, out B);

            Func<double, double> ConvertToDouble = s => M * s + B;
            Func<double, double> ConvertFromDouble = t => (t - B) / M;
            Func<Point, Point> DataToViewport = s => new Point(s.X, ConvertToDouble(s.Y));
            Func<Point, Point> ViewportToData = t => new Point(t.X, ConvertFromDouble(t.Y));

            Brush brushLinear = new SolidColorBrush(Colors.Blue);
            Pen linePenLinear = new Pen(brushLinear, 2.0);
            PenDescription descLinear = new PenDescription("f(x) = x");
            LineGraph lg = plotter.AddLineGraph(edsLinear, linePenLinear, descLinear);

            lg.DataTransform = new DelegateDataTransform(DataToViewport, ViewportToData);
            yAxisRight.ConvertFromDouble = ConvertFromDouble;
            yAxisRight.ConvertToDouble = ConvertToDouble;
        }

        private void ConfigureXAxis(ChartPlotter plotter)
        {
            xAxis = new HorizontalDateTimeAxis();
            plotter.MainHorizontalAxis = xAxis;
        }

        private void ConfigureYAxisLeft(ChartPlotter plotter)
        {
            yAxisLeft = new VerticalAxis
            {
                TicksProvider = new LogarithmNumericTicksProvider(10),
                LabelProvider = new UnroundingLabelProvider(),
                ConvertFromDouble = t => Math.Pow(10.0, t),
                ConvertToDouble = s => Math.Log10(s)
            };
            plotter.MainVerticalAxis = yAxisLeft;
            plotter.AxisGrid.DrawVerticalMinorTicks = true;
        }

        private void ConfigureYAxisRight(ChartPlotter plotter)
        {
            yAxisRight = new VerticalAxis()
            {
                Placement = AxisPlacement.Right
            };
            plotter.Children.Add(yAxisRight);
            yAxisRight.ConvertFromDouble = s => (s + .5) * 500.0;
            yAxisRight.ConvertToDouble = s => s / 500.0 - .5;
        }

        private static ChartPlotter CreatePlotter()
        {
            ChartPlotter plotter = new ChartPlotter();
            return plotter;
        }
    }

    public class TPoint
    {
        public TPoint() { }
        public TPoint(DateTime x, double y)
        {
            _X = x;
            _Y = y;
        }

        private DateTime _X;
        public DateTime X
        {
            get { return _X; }
            set { _X = value; }
        }

        private double _Y;
        public double Y
        {
            get { return _Y; }
            set { _Y = value; }
        }
    }
}

May 20, 2009 at 6:48 PM

Simple Math Error !!  Fixed Get_M_and_B method

        private void Get_M_and_B(double min, double max, out double M, out double B)
        {
            // @min, 0
            // @max, 1

            // y = m x + b

            // 0 = m * min + b
            // 1 = m * max + b

            // b = -m  min
            // b = 1 - m * max

            // -m * min = 1 - m * max
            // m * max - m * min = 1
            // m * (max - min) = 1
            // m = 1 / (max - min)

            // b = -m * min
            // b = -min / (max - min)

            M = 1 / (max - min);
            B = -min / (max - min);
        }

May 20, 2009 at 7:20 PM

Thx

Mar 2, 2011 at 2:07 PM

cmichaelgraham, could you help me with inverting linegraph with markers?

LineAndMarker<ElementMarkerPointsGraph> lg = MyChartPlotter.AddLineGraph(dataForChart, linePen,
new CircleElementPointMarker
{Size = 10, Brush = Brushes.Red, Fill = Brushes.Orange}, desc);
lg.LineGraph.DataTransform = new DelegateDataTransform(DataToViewport, ViewportToData);

lg.MarkerGraph.DataTransform = new DelegateDataTransform(DataToViewport, ViewportToData);

 

With this code linegraph is inverted, but markers stays on their original positions. Have you any idea about this? Thanks!

P.S. Sorry for my bad English.