Hi Inhyuk,
The problem is somewhat complex as you generally have to align the two axis ranges so that their relative aspects toward zero match. Generally there are nine cases depending of the primary and secondary axis ranges relative position against zero. The following code shows a complete implementation, which also allow to align two ranges to any value (not just zero) and have this value visually matched on the PrimaryX and PrimaryY axes:
enum ValuePosition
{
AboveRange,
BelowRange,
ContainedInRange
}
public partial class Form1 : Form
{
NChart m_Chart;
NBarSeries m_Bar1;
NBarSeries m_Bar2;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
m_Chart = nChartControl1.Charts[0];
// add a bar series
m_Bar1 = (NBarSeries)m_Chart.Series.Add(SeriesType.Bar);
m_Bar1.MultiBarMode = MultiBarMode.Series;
m_Bar1.DataLabelStyle.Visible = false;
// add another bar series
m_Bar2 = (NBarSeries)m_Chart.Series.Add(SeriesType.Bar);
m_Bar2.MultiBarMode = MultiBarMode.Clustered;
m_Bar2.DataLabelStyle.Visible = false;
m_Bar2.DisplayOnAxis(StandardAxis.PrimaryY, false);
m_Bar2.DisplayOnAxis(StandardAxis.SecondaryY, true);
m_Chart.Axis(StandardAxis.SecondaryY).Visible = true;
// turn off rounding to preserve ranges relative disposition towards zero
NStandardScaleConfigurator primaryYScale = m_Chart.Axis(StandardAxis.PrimaryY).ScaleConfigurator as NStandardScaleConfigurator;
primaryYScale.RoundToTickMax = false;
primaryYScale.RoundToTickMin = false;
NStandardScaleConfigurator secondaryYScale = m_Chart.Axis(StandardAxis.SecondaryY).ScaleConfigurator as NStandardScaleConfigurator;
secondaryYScale.RoundToTickMax = false;
secondaryYScale.RoundToTickMin = false;
NStyleSheet.CreatePredefinedStyleSheet(PredefinedStyleSheet.Nevron).Apply(nChartControl1.Document);
}
private NRange1DD GetSeriesMinMax(NBarSeries series)
{
return new NRange1DD((double)series.Values[series.Values.FindMinValue()],
(double)series.Values[series.Values.FindMaxValue()]);
}
/// <summary>
///
/// </summary>
/// <param name="pivotValue"></param>
/// <param name="range1"></param>
/// <param name="range2"></param>
private void AlignRangeAspectsToPivot(double pivotValue, ref NRange1DD range1, ref NRange1DD range2)
{
// extend ranges to contain pivot
range1 = ExtendRangeToContainValue(range1, pivotValue);
range2 = ExtendRangeToContainValue(range2, pivotValue);
ValuePosition range1Disposition = GetValueDisposition(range1, pivotValue);
ValuePosition range2Disposition = GetValueDisposition(range2, pivotValue);
// examine all possible cases of range1 and range2 vs pivotValue
switch (range1Disposition)
{
case ValuePosition.AboveRange:
switch (range2Disposition)
{
case ValuePosition.AboveRange:
// both above range => do nothing
break;
case ValuePosition.BelowRange:
{
// need to extend both ranges
range1.End = pivotValue + range1.GetLength();
range2.Begin = pivotValue - range2.GetLength();
}
break;
case ValuePosition.ContainedInRange:
{
// need to extend range1 end by aspect of range2
double aspect = GetRangeAspect(range2, pivotValue);
range1 = ExtendRangeWithAspect(range1, aspect, pivotValue);
}
break;
}
break;
case ValuePosition.BelowRange:
switch (range2Disposition)
{
case ValuePosition.AboveRange:
{
range1.Begin = pivotValue - range1.GetLength();
range2.End = pivotValue + range2.GetLength();
}
break;
case ValuePosition.BelowRange:
// both below range => do nothing
break;
case ValuePosition.ContainedInRange:
{
// need to extend range1 end by aspect of range2
double aspect = GetRangeAspect(range2, pivotValue);
range1 = ExtendRangeWithAspect(range1, aspect, pivotValue);
}
break;
}
break;
case ValuePosition.ContainedInRange:
switch (range2Disposition)
{
case ValuePosition.AboveRange:
{
// need to extend range2 end by aspect of range1
double aspect = GetRangeAspect(range1, pivotValue);
range2 = ExtendRangeWithAspect(range2, aspect, pivotValue);
}
break;
case ValuePosition.BelowRange:
{
// need to extend range2 end by aspect of range1
double aspect = GetRangeAspect(range1, pivotValue);
range2 = ExtendRangeWithAspect(range2, aspect, pivotValue);
}
break;
case ValuePosition.ContainedInRange:
{
// both contain pivot, find the most balanced aspect
double aspect1 = GetRangeAspect(range1, pivotValue);
double aspect2 = GetRangeAspect(range2, pivotValue);
if (Math.Abs(1.0 - aspect1) < Math.Abs(1.0 - aspect2))
{
range2 = ExtendRangeWithAspect(range2, aspect1, pivotValue);
}
else
{
range1 = ExtendRangeWithAspect(range1, aspect2, pivotValue);
}
}
break;
}
break;
}
}
/// <summary>
///
/// </summary>
/// <param name="range"></param>
/// <param name="value"></param>
/// <returns></returns>
private static NRange1DD ExtendRangeToContainValue(NRange1DD range, double value)
{
// extend range2 to contain 0
if (!range.Contains(value))
{
if (range.Begin > value)
{
range.Begin = value;
}
else if (range.End < value)
{
range.End = value;
}
}
return range;
}
/// <summary>
///
/// </summary>
/// <param name="range"></param>
/// <param name="pivotValue"></param>
/// <returns></returns>
private static double GetRangeAspect(NRange1DD range, double pivotValue)
{
Debug.Assert(range.Contains(pivotValue));
double diffBegin = pivotValue - range.Begin;
double diffEnd = range.End - pivotValue;
return diffBegin / diffEnd;
}
/// <summary>
/// Extends the range in such a way that its aspect towards pivot becomes the specified one
/// </summary>
/// <param name="range"></param>
/// <param name="aspect"></param>
/// <param name="pivotValue"></param>
/// <returns></returns>
private static NRange1DD ExtendRangeWithAspect(NRange1DD range, double aspect, double pivotValue)
{
Debug.Assert(aspect != 0);
Debug.Assert(range.Contains(pivotValue));
double diffBegin = pivotValue - range.Begin;
double diffEnd = range.End - pivotValue;
double newDiffBegin = diffBegin;
double newDiffEnd = diffEnd;
if (diffBegin == 0)
{
// extend begin
newDiffBegin = aspect * diffEnd;
}
else if (diffEnd == 0)
{
// extend end
newDiffEnd = diffBegin / aspect;
}
else
{
double aspect1 = diffBegin / diffEnd;
if (aspect > aspect1)
{
newDiffEnd = diffBegin / aspect;
}
else
{
newDiffBegin = aspect * diffEnd;
}
}
return new NRange1DD(pivotValue - newDiffBegin, pivotValue + newDiffEnd);
}
/// <summary>
///
/// </summary>
/// <param name="range"></param>
/// <param name="value"></param>
/// <returns></returns>
private static ValuePosition GetValueDisposition(NRange1DD range, double value)
{
// determine begin value disposition
if (value <= range.Begin)
{
return ValuePosition.BelowRange;
}
else if (value >= range.End)
{
return ValuePosition.AboveRange;
}
else
{
return ValuePosition.ContainedInRange;
}
}
/// <summary>
/// Inflates the range with the specified factor
/// </summary>
/// <param name="factor"></param>
/// <returns></returns>
private static NRange1DD InflateRangeByFactor(NRange1DD range, double factor)
{
return new NRange1DD(range.Begin * factor, range.End * factor);
}
private void button1_Click(object sender, EventArgs e)
{
nChartControl1.ShowEditor();
}
private void button2_Click(object sender, EventArgs e)
{
Random random = new Random();
int caseRange1 = random.Next(3);
int caseRange2 = random.Next(3);
switch (caseRange1)
{
case 0:
// above
m_Bar1.Values.FillRandomRange(random, 5, 0, 100);
break;
case 1:
// below
m_Bar1.Values.FillRandomRange(random, 5, -100, 0);
break;
case 2:
m_Bar1.Values.FillRandomRange(random, 5, -100, 100);
break;
default:
Debug.Assert(false);
break;
}
switch (caseRange2)
{
case 0:
m_Bar2.Values.FillRandomRange(random, 5, 0, 500);
break;
case 1:
m_Bar2.Values.FillRandomRange(random, 5, -500, 0);
break;
case 2:
m_Bar2.Values.FillRandomRange(random, 5, -500, 200);
break;
default:
Debug.Assert(false);
break;
}
// pivot value 0
double pivotValue = 0.0;
// get series min/max values and form a normalized range
NRange1DD range1 = GetSeriesMinMax(m_Bar1);
NRange1DD range2 = GetSeriesMinMax(m_Bar2);
AlignRangeAspectsToPivot(pivotValue, ref range1, ref range2);
range1 = InflateRangeByFactor(range1, 1.1);
range2 = InflateRangeByFactor(range2, 1.1);
m_Chart.Axis(StandardAxis.PrimaryY).View = new NRangeAxisView(range1, true, true);
m_Chart.Axis(StandardAxis.SecondaryY).View = new NRangeAxisView(range2, true, true);
nChartControl1.Refresh();
}
}
Hope this helps - I've commented the code a bit, but if you have any questions let me know...
Best regards,
Bob