IsolineBuilder Issues

Aug 17, 2010 at 11:35 PM

ello All,

I am using d3's IsolineGraph to show a topographic map. It displays correctly, except I always get a runtime error (IsolinesUnsupportedCase Exception in IsolineBuilder::GetOutEdge()) when I use a certain data set. I made 1 change to the source code:  Exposed a private variable in IsolineGraph to allow the number of contours displayed to be changed, in my case I use 62 instead of the default hard-coded 12.

Assuming that my change is hamless as it appears, my first question is: Is it possible that the values specified in the data set (either in the grid or the values) could cause this type of error?

My second question has to do with the generated exception in IsolineBuilder. The case which generates it seems to be incorrect. Consider the following code snippet where "lt" stands for Left-Top, "rb" stands for Right-Bottom, etc:

switch (inEdge)

 {
    case Edge.Left:
                    if (value == lt)
                        value = near_one * lt + near_zero * rt;
                    else if (value == rt)
                      value = near_one * rt + near_zero * lt;
                    else
                      break;
                    throw new IsolineGenerationException(Properties.Resources.IsolinesUnsupportedCase);
                    break;
    case Edge.Top:
                 if (value == rt)
                    value = near_one * rt + near_zero * lt;
                else if (value == lt)
                   value = near_one * lt + near_zero * rt;
                else
                   throw new IsolineGenerationException(Properties.Resources.IsolinesUnsupportedCase);
              break;
    case Edge.Right:
               if (value == rb)
                   value = near_one * rb + near_zero * rt;
              else if (value == rt)
                  value = near_one * rt + near_zero * rb;
              else
                  throw new IsolineGenerationException(Properties.Resources.IsolinesUnsupportedCase);
             break;
    case Edge.Bottom:
             if (value == rb)
                  value = near_one * rb + near_zero * lb;
            else if (value == lb)
                value = near_one * lb + near_zero * rb;
            else
               throw new IsolineGenerationException(Properties.Resources.IsolinesUnsupportedCase);
            break;
   } 

The case which always generates the error is Edge.Left. Is there an error in this case? I think that there might be because it does not follow the pattern of the remaining cases. If it did, I believe it would look more like this:

case Edge.Left:

if (value == lb)
    value = near_one * lb + near_zero * lt;
else if (value == lt)
    value = near_one * lt + near_zero * lb;
else
    throw new IsolineGenerationException(Properties.Resources.IsolinesUnsupportedCase);
break;

After I changed this, the data sets which worked previously continued to work while the sets which caused errors now failed with StackOverflow Exceptions (probably due to infinite recursion). I've tried a few other things (including re-writing GetOutEdge() so that it is iterative) but it always comes down to a runtime error (or infinite loop) in the same region...but only for certain data sets.

So you can see my problem. Since I don't know enough about how the d3 code works, especially the isoline code. I can't tell if the error is caused by certain data sets or is simply exposed by them. Any help would be greatly appreciated. If someone can explain to me the isoline algorithm related to the above issue, it would also be helpful.

Aug 18, 2010 at 1:24 AM

Does look like a bug in D3 (it looks like you are using the "Recommended Download" version).

The more recent "Nightly" build has significantly different code in this area.  I'd try changing to the latest nightly version (you'll need to compile it to get a DLL).

 

            switch (inEdge)
            {
                case Edge.Left:
                    if (value == lt)
                        value = near_one * lt + near_zero * lb;
                    else if (value == lb)
                        value = near_one * lb + near_zero * lt;
                    else
                        return Edge.None;
                    // Now this is possible because of missing value
                    //throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
                    break;
                case Edge.Top:
                    if (value == rt)
                        value = near_one * rt + near_zero * lt;
                    else if (value == lt)
                        value = near_one * lt + near_zero * rt;
                    else
                        return Edge.None;
                    // Now this is possibe because of missing value
                    //throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
                    break;
                case Edge.Right:
                    if (value == rb)
                        value = near_one * rb + near_zero * rt;
                    else if (value == rt)
                        value = near_one * rt + near_zero * rb;
                    else
                        return Edge.None;
                    // Now this is possibe because of missing value
                    //throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
                    break;
                case Edge.Bottom:
                    if (value == rb)
                        value = near_one * rb + near_zero * lb;
                    else if (value == lb)
                        value = near_one * lb + near_zero * rb;
                    else
                        return Edge.None;
                    // Now this is possibe because of missing value
                    //throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
                    break;
            }

 

Grant.

Aug 18, 2010 at 6:16 PM

Grant,

Thank you for your timely reply.

I have built the .dll from the 'Nightly' source like you suggested and everything appears to work. I even incorporated the same custom changes I made to the 'Stable' version without any issues.

Thanks again for all your help. It really is appreciated.

...

Justin

Aug 18, 2010 at 9:31 PM

I've spent the whole day testing this build and only came up with one issue:

On certain data sets, when I change the number of contours to specific numbers not all of the contours will be displayed. For example, for one data set I was showing 25 contours successfully, but when I tried 26, only the first and last contour were displayed. The graph works for 24, 25, 27 and 28 but not 26.

Also of interest, the underlying tracking graph continues to work even as the mouse scrolls over the missing contours.

I realize that, by changing the source code, I am playing with fire...but being able to set the number of contours is a fairly important requirement.

Any ideas as to what causes this failure or how to fix it would be very helpful.

Aug 18, 2010 at 10:24 PM

Justin,

It would be very difficult to resolve the problem based solely on your description.

However, if you can reproduce the problem with a small data set and program then someone (possibly me) could work out what is going wrong. 

Grant.

Aug 19, 2010 at 4:40 PM

Grant,

I have made a simple and dirty program which reproduces this issue. I made a new WPF project called SimpleTest:

Window1.xaml:

<Window x:Class="SimpleTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <d3:ChartPlotter>
            <d3:IsolineGraph Name="IsolineGraph"></d3:IsolineGraph>
            <d3:IsolineTrackingGraph Name="TrackingGraph"></d3:IsolineTrackingGraph>
        </d3:ChartPlotter>
    </Grid>
</Window>

Window1.xaml.cs:

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.DataSources.MultiDimensional;

namespace SimpleTest
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            Go();
        }

        public void Go()
        {
            double[,] adHeights = new double[50, 50];
            Point[,] aoPoints = new Point[50, 50];

            for (int nXIndex = 0; nXIndex < 50; nXIndex++)
            {
                int nXMax = 25 - System.Math.Abs(25 - nXIndex);
                for (int nYIndex = 0; nYIndex < 50; nYIndex++)
                {
                    int nYMax = 25 - System.Math.Abs(25 - nYIndex);                    

                    double dHeight;
                    if (nXMax <= nYMax)
                    {
                        dHeight = nXMax;
                    }
                    else
                    {
                        dHeight = nYMax;
                    }

                    Point oCurrentPoint = new Point(nXIndex, nYIndex);

                    adHeights[nXIndex, nYIndex] = dHeight;
                    aoPoints[nXIndex, nYIndex] = oCurrentPoint;
                }
            }

            WarpedDataSource2D<double> oData = new WarpedDataSource2D<double>(adHeights, aoPoints);

            this.IsolineGraph.NumberOfContours = 25;

            this.IsolineGraph.DataSource = oData;
            TrackingGraph.DataSource = oData;
        }
    }
}

You will also need to modify two files in the D3 source and rebuild it:
IsolineBuilder.cs - Change access level on 'density' member to public
IsolineGraph.cs - Add this (or similar) property:
public int NumberOfContours
{
    get
    {
        return this.IsolineBuilder.density;
    }
    set
    {
        this.IsolineBuilder.density = value;
    }
}

To reproduce the issue, change the number of contours shown to 26.
...
Justin