Nevron Forum

Showing a range of bars on an NStockSeries

https://www.nevron.com/Forum/Topic6391.aspx

By David Cobbold - Friday, May 11, 2012

Hi,

I'm having a bit of trouble with NStockSeries and i'm hoping someone can help. I'm trying to figure out how to automatically focus the x axis on a specific range of bars in the chart. This is how I'm populating the chart:

NCartesianChart cartChart = (NCartesianChart)_chart.Charts[0];
NStockSeries stockSeries = (NStockSeries)cartChart.Series[0];

for (int i = stockSeries.XValues.Count; i < _marketData.Bars.Length; ++i)
{
MarketData.Bar bar = _marketData.Bars[i];
stockSeries.AddDataPoint(new NStockDataPoint(bar.Open, bar.High, bar.Low, bar.Close, bar.DateTime));
}


.. And I then try to set the paging view on the x axis to the last 4 months worth of data (_marketData.Bars is already sorted by date).

DateTime endDate = _marketData.Bars.Last().DateTime;
DateTime viewBegin = endDate.AddMonths(-4);
NNumericAxisPagingView dateAxis = new NNumericAxisPagingView(new NRange1DD(viewBegin.ToOADate(), endDate.ToOADate()));
dateAxis.Enabled = true;
cartChart.Axis(StandardAxis.PrimaryX).PagingView = dateAxis;


When I run this the view seems to have scrolled to some date much further in the future than any bar of market data I have, although it's difficult to tell the exact date. I've also tried using the NDateTimeAxisPagingView with similar results.

Another point is when I change the logic to attempt to keep the first 4 months of data in focus, it tends to scroll to somewhere roughy in the middle.

One other question I have is is it possible to discover which bars are in the view after the user zooms + scrolls? This is so that I can set the y axis paging view to the min and max value of the visible bars, in order to zoom the y axis in on what is visible.

Thanks for any help
David.
By Nevron Support - Monday, May 14, 2012

Hi David,

Most likely the stock series is not instructed to use the x values:

stockSeries.UseXValues = true;

We just tested the control with the following code and it was working properly:

  private void Form1_Load(object sender, EventArgs e)
  {
   NCartesianChart cartChart = (NCartesianChart)nChartControl1.Charts[0];
   cartChart.BoundsMode = BoundsMode.Stretch;
   NStockSeries stockSeries = new NStockSeries();
   stockSeries.UseXValues = true;
   stockSeries.DataLabelStyle.Visible = false;

   GenerateData(stockSeries, 10, 200);

   cartChart.Series.Add(stockSeries);
   cartChart.Axis(StandardAxis.PrimaryX).ScaleConfigurator = new NRangeTimelineScaleConfigurator();

   DateTime endDate = DateTime.FromOADate((double)stockSeries.XValues[stockSeries.XValues.Count -1]);
   DateTime viewBegin = endDate.AddMonths(-4);
   NNumericAxisPagingView dateAxis = new NNumericAxisPagingView(new NRange1DD(viewBegin.ToOADate(), endDate.ToOADate()));
   dateAxis.Enabled = true;
   cartChart.Axis(StandardAxis.PrimaryX).PagingView = dateAxis;
  }

  private void GenerateData(NStockSeries s, double dPrevClose, int nCount)
  {
   DateTime now = DateTime.Now;
   double open, high, low, close;

   s.ClearDataPoints();

   Random rand = new Random();

   for (int nIndex = 0; nIndex < nCount; nIndex++)
   {
    open = dPrevClose;

    if (dPrevClose < 25 || rand.NextDouble() > 0.5)
    {
     // upward price change
     close = open + (2 + (rand.NextDouble() * 20));
     high = close + (rand.NextDouble() * 10);
     low = open - (rand.NextDouble() * 10);
    }
    else
    {
     // downward price change
     close = open - (2 + (rand.NextDouble() * 20));
     high = open + (rand.NextDouble() * 10);
     low = close - (rand.NextDouble() * 10);
    }

    if (low < 1)
    {
     low = 1;
    }

    dPrevClose = close;

    s.OpenValues.Add(open);
    s.HighValues.Add(high);
    s.LowValues.Add(low);
    s.CloseValues.Add(close);
    s.XValues.Add(now.ToOADate());

    // advance to next working day
    now += new TimeSpan(1, 0, 0, 0);
   }
  }

One other question I have is is it possible to discover which bars are in the view after the user zooms + scrolls? This is so that I can set the y axis paging view to the min and max value of the visible bars, in order to zoom the y axis in on what is visible.

You can easliy intercept the scrollbar view changed and data zoom end drag events:

 cartChart.Axis(StandardAxis.PrimaryX).ScrollBar.ViewRangeChanged += new EventHandler(ScrollBar_ViewRangeChanged);
   cartChart.Axis(StandardAxis.PrimaryX).ScrollBar.Visible = true;

   NRangeSelection rs = new NRangeSelection();
   rs.VerticalValueSnapper = new NAxisRulerMinMaxSnapper();
   cartChart.RangeSelections.Add(rs);

   nChartControl1.Controller.Tools.Add(new NSelectorTool());
   nChartControl1.Controller.Tools.Add(new NAxisScrollTool());

   NDataZoomTool dzt = new NDataZoomTool();
   dzt.EndDrag +=new EventHandler(dzt_EndDrag);
   nChartControl1.Controller.Tools.Add(dzt);

// ....

  void  dzt_EndDrag(object sender, EventArgs e)
  {
   NRange1DD range = nChartControl1.Charts[0].Axis(StandardAxis.PrimaryX).PageRange;
   textBox1.Text = "Range: Begin[" + DateTime.FromOADate(range.Begin).ToString() + "], End [" + DateTime.FromOADate(range.End).ToString() + "]";
  }

  void ScrollBar_ViewRangeChanged(object sender, EventArgs e)
  {
   NRange1DD range = nChartControl1.Charts[0].Axis(StandardAxis.PrimaryX).PageRange;
   textBox1.Text = "Range: Begin[" + DateTime.FromOADate(range.Begin).ToString() + "], End [" + DateTime.FromOADate(range.End).ToString() + "]";
  }

In order to implement a Y axis that has variable range depending on the x axis range you need to create a custom range view for the Y axis - the following code snippet shows how to achieve this for a simple line chart showing the sin function, however the code with minor modifications for the min/max calculation is the same in the case of stock chart:

  /// <summary>
  /// Configures the axis to display the range of the content that scales on it
  /// </summary>
  [Serializable]
  public partial class NCustomAxisView : NRangeAxisView
  {
   #region Constructors

   public NCustomAxisView(NChartControl chartControl)
   {
    m_ChartControl = chartControl;
   }

   #endregion

   #region Overrides

   /// <summary>
   /// Obtains the range displayed by the axis given the current content range
   /// </summary>
   /// <param name="range"></param>
   /// <param name="isZoomed"></param>
   /// <returns></returns>
   public override NRange1DD GetViewRange(NRange1DD range, ref bool isZoomed)
   {
    NChart chart = (NChart)this.ProvideReference(typeof(NChart));

// recalculate the x axis - this ensures the RulerRange is valid 

    chart.Axis(StandardAxis.PrimaryX).CalculateAxis(m_ChartControl.View.Context);

    NRange1DD xRange = chart.Axis(StandardAxis.PrimaryX).Scale.RulerRange;
    NLineSeries line = chart.Series[0] as NLineSeries;
    
    if (line.Values.Count == 0)
     return range;

    bool hasMinMax = false;
    double min = 0;
    double max = 0;

    for (int i = 0; i < line.Values.Count; i++)
    {
     if (!xRange.Contains((double)line.XValues[i]))
      continue;

     double value = (double)line.Values[i];

     if (hasMinMax)
     {      
      max = Math.Max(max, value);
      min = Math.Min(min, value);
     }
     else
     {
      max = value;
      min = value;
      hasMinMax = true;
     }
    }

    isZoomed = hasMinMax;

    if (hasMinMax)
    {
     range = new NRange1DD(min, max);
    }
    return range;
   }

   #endregion

   #region Fields

   NChartControl m_ChartControl;

   #endregion
  }

  private void Form1_Load(object sender, EventArgs e)
  {
   NCartesianChart chart = (NCartesianChart)nChartControl1.Charts[0];

   chart.Axis(StandardAxis.PrimaryY).View = new NCustomAxisView(nChartControl1);
   chart.BoundsMode = BoundsMode.Stretch;
   chart.Axis(StandardAxis.PrimaryX).ScrollBar.Visible = true;

   NLineSeries line = new NLineSeries();
   line.UseXValues = true;
   line.DataLabelStyle.Visible = false;

   for (int i = 0; i < 200; i++)
   {
    line.Values.Add(Math.Sin(2 * Math.PI * i / 200.0));
    line.XValues.Add(i);
   }

   chart.Series.Add(line);

   NRangeSelection rs = new NRangeSelection();
   rs.VerticalValueSnapper = new NAxisRulerMinMaxSnapper();
   chart.RangeSelections.Add(rs);

   nChartControl1.Controller.Tools.Add(new NSelectorTool());
   nChartControl1.Controller.Tools.Add(new NAxisScrollTool());
   nChartControl1.Controller.Tools.Add(new NDataZoomTool());
  }

Hope this helps - let us know if you meet any problems or have any questions.

 

By David Cobbold - Tuesday, May 15, 2012

Thanks for the reply, very helpful. I was setting stockSeries.UseXValues = true already so that wasn't the problem. Looks like the problem is caused by the ScaleConfigurator on the x axis - You're using a default constructor'd NRangeTimelineScaleConfigurator, whereas I'm using this (taken from one of the examples):


NValueTimelineScaleConfigurator scaleX = new NValueTimelineScaleConfigurator();
NWeekDayRule wdr = new NWeekDayRule(WeekDayBit.All);
wdr.Saturday = false;
wdr.Sunday = false;
scaleX.Calendar.Rules.Add(wdr);
scaleX.EnableCalendar = true;

cartChart.Axis(StandardAxis.PrimaryX).ScaleConfigurator = scaleX;


So since Saturday and Sunday aren't displayed, I'm guessing the chart is getting confused about how many bars are visible and is scrolling too far to the side. Is this a bug, or is there another workaround?

Thanks again.
By Nevron Support - Tuesday, May 15, 2012

Hi David,

When the chart uses calendar rules it has to know the actual X range of the axis in advance before you apply paging view - to workaround that you can calculate the control and then apply paging view:

  private void Form1_Load(object sender, EventArgs e)
  {
// Initializer chart / feed data

// those two lines will calculate the control and apply correct view ranges on the axes
   nChartControl1.Document.Calculate();
   nChartControl1.Document.RecalcLayout(nChartControl1.View.Context);

// apply paging view
   DateTime endDate = DateTime.FromOADate((double)stockSeries.XValues[stockSeries.XValues.Count - 1]);
   DateTime viewBegin = endDate.AddMonths(-1);
   NNumericAxisPagingView dateAxis = new NNumericAxisPagingView(new NRange1DD(viewBegin.ToOADate(), endDate.ToOADate()));
   dateAxis.Enabled = true;
   cartChart.Axis(StandardAxis.PrimaryX).PagingView = dateAxis;
  }

Hope this helps - let us know if you meet any problems.

By David Cobbold - Tuesday, May 15, 2012

Sorry but i'm getting the same behaviour as before with that change. Does it work for you?
By Nevron Support - Wednesday, May 16, 2012

Hi David,

Yes that works - there were several issues related to the calendar which were fixed in SP1 for 2012 - we used build number 12.5.9.12. What is the version you're currently testing with?

By David Cobbold - Thursday, May 17, 2012

Upgrading to the newer version fixed my issue - thanks a lot for the prompt help.