0
votes

I'm using Teechart(.Net 2009) in a project, and I found something weird happening when I draw a box with specific double points.

This is my xaml code to reproduce the problem.

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <WindowsFormsHost x:Name="chartHost"/>
        <WindowsFormsHost x:Name="chartHost2" Grid.Column="1"/>
    </Grid>
</Window>

This is the behind code for the above xaml file.

public partial class MainWindow : Window
{
    public MainWindow()
    {
    InitializeComponent();

        SetChart(new[] { 0.5685, 0.7141, 0.7301, 0.748, 0.7847, 1.2127 }, chartHost);
        SetChart(new[] { 0.5686, 0.7141, 0.7301, 0.748, 0.7847, 1.2127 }, chartHost2);
    }

    private void SetChart(double[] values, WindowsFormsHost host)
    {
        var chart = new TChart();
        var box = new Box(chart.Chart);
        box.Add(values);
        box.ExtrOut.HorizSize = 0;
        box.ExtrOut.VertSize = 0;
        box.MildOut.HorizSize = 0;
        box.MildOut.VertSize = 0;

        chart.Axes.Left.Maximum = 1.2;
        chart.Axes.Left.Minimum = 0.5;

        host.Child = chart;
    }
}

The result looks like this. (Please click the link to see the captured image. I currently cannot attach image due to the reputation limit.)

http://www.flickr.com/photos/99238307@N06/9341426974/

Surprisingly, the only difference between two charts is that the first double value of each chart's data. The first double value of the left looks-ok-chart is 0.5685 and another one uses 0.5686 which sounds not that big difference. The 0.0001 made the right chart weird. I didn't try to use UseCustomValues property of the Box series and I don't want to use that.

Anybody knows how to draw the chart correctly with both two data set?

1

1 Answers

1
votes

This is as designed. The solution here is using custom values. You can see the difference by dropping ListBox and TChart components in a Form and using this code:

public Form1()
{
  InitializeComponent();
  InitializeChart();
}

private void InitializeChart()
{
  bool automatic = true;

  SetChart(new[] { 0.5685, 0.7141, 0.7301, 0.748, 0.7847, 1.2127 }, automatic);
  SetChart(new[] { 0.5686, 0.7141, 0.7301, 0.748, 0.7847, 1.2127 }, automatic);

  tChart1.Axes.Left.SetMinMax(0.55, 1.25);
}

private void SetChart(double[] values, bool auto)
{
  var box = new Steema.TeeChart.Styles.Box(tChart1.Chart);
  box.Add(tChart1.Series.Count, values);

  if (auto)
  {
    box.ReconstructFromData();

    listBox1.Items.Add("Series: " + box.Title.ToString());
    listBox1.Items.Add("Median: " + box.Median.ToString());
    listBox1.Items.Add("Quartile1: " + box.Quartile1.ToString());
    listBox1.Items.Add("Quartile3: " + box.Quartile3.ToString());
    listBox1.Items.Add("InnerFence1: " + box.InnerFence1.ToString());
    listBox1.Items.Add("InnerFence3: " + box.InnerFence3.ToString());
    listBox1.Items.Add("OuterFence1: " + box.OuterFence1.ToString());
    listBox1.Items.Add("OuterFence3: " + box.OuterFence3.ToString());
    listBox1.Items.Add("AdjacentPoint1: " + box.AdjacentPoint1.ToString());
    listBox1.Items.Add("AdjacentPoint3: " + box.AdjacentPoint3.ToString());
    listBox1.Items.Add("-------------------------");
  }
  else
  {
    box.UseCustomValues = !auto;

    box.Median = 0.73905;
    box.OuterFence1 = 0.0357;
    box.OuterFence3 = 1.5337;

    box.InnerFence1 = 0.3567;
    box.InnerFence3 = 1.2127;

    box.Quartile1 = 0.6777;
    box.Quartile3 = 0.8917;

    box.AdjacentPoint1 = box.YValues[0];
    box.AdjacentPoint3 = 1.2127;

    box.Median = 0.73905;
  }
}

With automatic variable being true you'll see automatically calculated values as in this image:

enter image description here

Setting it to false it will use manual custom values. To see the difference you should have a look at how ReconstructFromData() method is implemented (you can check it with a reflector tool):

/// <summary>
/// Reconstructs the box plot from series data
/// </summary>
public void ReconstructFromData()
{
  int N = SampleValues.Count;
  if (N > 0)
  {
    double InvN = 1.0 / N;
    /* calculate median */
    int med = N / 2;
    if ((N % 2) == 0) median = 0.5 * (SampleValues[med - 1] + SampleValues[med]);
    else median = SampleValues[med];

    /* calculate Q1 && Q3 */
            quartile1 = N > 1 ? Percentile(SampleValues, 0.25) : SampleValues[0];
            quartile3 = N > 1 ? Percentile(SampleValues, 0.75) : SampleValues[0];

    /* calculate IQR */
    double iqr = quartile3 - quartile1;
    innerFence1 = quartile1 - whiskerLength * iqr;
    innerFence3 = quartile3 + whiskerLength * iqr;

    /* find adjacent points */
    int i;
    for (i = 0; i <= med; i++) if (SampleValues[i] > innerFence1) break;
    adjacentPoint1 = SampleValues[i];

    for (i = med; i < N; i++) if (SampleValues[i] > innerFence3) break;
    adjacentPoint3 = SampleValues[i - 1];

    /* calculate outer fences */
    outerFence1 = quartile1 - 2 * whiskerLength * iqr;
    outerFence3 = quartile3 + 2 * whiskerLength * iqr;
  }
}

What makes a difference here is adjacentPoint3. In the first box-plot, innerFence3 coincides with the last value in the series while in the second box-plot it's slightly smaller. Therefore, this code:

for (i = med; i < N; i++) if (SampleValues[i] > innerFence3) break;
adjacentPoint3 = SampleValues[i - 1];

breaks one step before than in the first series and penultimate value is being used instead of the last. Hence the difference in AdjacentPoint3 and what's being painted.

To get data calculated automatically you can do something like this:

box.ReconstructFromData();

box.UseCustomValues = true;

box.Median = box.Median;
box.OuterFence1 = box.OuterFence1;
box.OuterFence3 = box.OuterFence3;

box.InnerFence1 = box.InnerFence1;
box.InnerFence3 = box.InnerFence3;

box.Quartile1 = box.Quartile1;
box.Quartile3 = box.Quartile3;

box.AdjacentPoint1 = box.AdjacentPoint1;
box.AdjacentPoint3 = box.YValues[box.Count-1];

box.Median = box.Median;