Skip to content

Commit 952d5c3

Browse files
authored
Make consistent BaseValue and BaseLine across BarSeries, LinearBarSeries, and HistogramSeries (#2001)
1 parent 735188e commit 952d5c3

7 files changed

Lines changed: 269 additions & 54 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
1111
- BarSeries.LabelAngle property (#1870)
1212

1313
### Changed
14+
- Make consistent BaseValue and BaseLine across BarSeries, LinearBarSeries, and HistogramSeries
1415

1516
### Removed
1617
- Support for .NET Framework 4.0 and 4.5 (#1839)

Source/Examples/ExampleLibrary/Series/BarSeriesExamples.cs

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,8 @@ public static PlotModel StackedNoAxes()
389389
return model;
390390
}
391391

392-
[Example("Logarithmic axis")]
393-
public static PlotModel LogAxis()
392+
[Example("Logarithmic axis (Base Value)")]
393+
public static PlotModel LogAxisBaseValue()
394394
{
395395
var model = new PlotModel
396396
{
@@ -423,8 +423,42 @@ public static PlotModel LogAxis()
423423
return model;
424424
}
425425

426+
[Example("Logarithmic axis (Base Line)")]
427+
public static PlotModel LogAxisBaseLine()
428+
{
429+
var model = new PlotModel
430+
{
431+
Title = "Logarithmic axis"
432+
};
433+
434+
var l = new Legend
435+
{
436+
LegendPlacement = LegendPlacement.Outside,
437+
LegendPosition = LegendPosition.BottomCenter,
438+
LegendOrientation = LegendOrientation.Horizontal,
439+
LegendBorderThickness = 0
440+
};
441+
442+
model.Legends.Add(l);
443+
var s1 = new BarSeries { Title = "Series 1", BaseLine = 0.1, StrokeColor = OxyColors.Black, StrokeThickness = 1 };
444+
s1.Items.Add(new BarItem { Value = 25 });
445+
s1.Items.Add(new BarItem { Value = 37 });
446+
s1.Items.Add(new BarItem { Value = 18 });
447+
s1.Items.Add(new BarItem { Value = 40 });
448+
449+
var categoryAxis = new CategoryAxis { Position = AxisPosition.Left };
450+
categoryAxis.Labels.Add("Category A");
451+
categoryAxis.Labels.Add("Category B");
452+
categoryAxis.Labels.Add("Category C");
453+
categoryAxis.Labels.Add("Category D");
454+
model.Series.Add(s1);
455+
model.Axes.Add(categoryAxis);
456+
model.Axes.Add(new LogarithmicAxis { Position = AxisPosition.Bottom, MinimumPadding = 0, AbsoluteMinimum = 0 });
457+
return model;
458+
}
459+
426460
[Example("Logarithmic axis (not stacked)")]
427-
public static PlotModel LogAxis2()
461+
public static PlotModel LogAxisNotStacked()
428462
{
429463
var model = new PlotModel { Title = "Logarithmic axis" };
430464
var l = new Legend
@@ -440,19 +474,19 @@ public static PlotModel LogAxis2()
440474
new Item {Label = "Bananas", Value1 = 23, Value2 = 2, Value3 = 29}
441475
};
442476

443-
model.Series.Add(new BarSeries { Title = "2009", BaseValue = 0.1, ItemsSource = items, ValueField = "Value1" });
444-
model.Series.Add(new BarSeries { Title = "2010", BaseValue = 0.1, ItemsSource = items, ValueField = "Value2" });
445-
model.Series.Add(new BarSeries { Title = "2011", BaseValue = 0.1, ItemsSource = items, ValueField = "Value3" });
477+
model.Series.Add(new BarSeries { Title = "2009", ItemsSource = items, ValueField = "Value1" });
478+
model.Series.Add(new BarSeries { Title = "2010", ItemsSource = items, ValueField = "Value2" });
479+
model.Series.Add(new BarSeries { Title = "2011", ItemsSource = items, ValueField = "Value3" });
446480

447481
model.Axes.Add(new CategoryAxis { Position = AxisPosition.Left, ItemsSource = items, LabelField = "Label" });
448482
model.Axes.Add(new LogarithmicAxis { Position = AxisPosition.Bottom, Minimum = 1 });
449483
return model;
450484
}
451485

452486
[Example("Logarithmic axis (stacked series)")]
453-
public static PlotModel LogAxis3()
487+
public static PlotModel LogAxisStacked()
454488
{
455-
var model = LogAxis2();
489+
var model = LogAxisNotStacked();
456490
foreach (var s in model.Series.OfType<BarSeries>())
457491
{
458492
s.IsStacked = true;

Source/Examples/ExampleLibrary/Series/HistogramSeriesExamples.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ public static PlotModel ExponentialDistributionLogarithmicAxis()
3636
return CreateExponentialDistribution(true);
3737
}
3838

39+
[Example("Exponential Distribution (logarithmic,with BaseValue)")]
40+
[DocumentationExample("Series/HistogramSeries")]
41+
public static PlotModel ExponentialDistributionLogarithmicAxisWithBaseValue()
42+
{
43+
return CreateExponentialDistribution(true, baseValue: 0.1);
44+
}
45+
3946
[Example("Label Placement")]
4047
public static PlotModel HistogramLabelPlacement()
4148
{
@@ -129,7 +136,7 @@ public CustomHistogramItem(double rangeStart, double rangeEnd, double area, int
129136
public string Description { get; }
130137
}
131138

132-
public static PlotModel CreateExponentialDistribution(bool logarithmicYAxis = false, double mean = 1, int n = 10000)
139+
public static PlotModel CreateExponentialDistribution(bool logarithmicYAxis = false, double mean = 1, int n = 10000, double baseValue = 0)
133140
{
134141
var model = new PlotModel { Title = logarithmicYAxis ? "Exponential Distribution (logarithmic)" : "Exponential Distribution", Subtitle = "Uniformly distributed bins (" + n + " samples)" };
135142
model.Axes.Add(
@@ -146,6 +153,8 @@ public static PlotModel CreateExponentialDistribution(bool logarithmicYAxis = fa
146153
var binBreaks = HistogramHelpers.CreateUniformBins(0, 5, 15);
147154
chs.Items.AddRange(HistogramHelpers.Collect(SampleExps(rnd, mean, n), binBreaks, binningOptions));
148155
chs.StrokeThickness = 1;
156+
chs.BaseValue = baseValue;
157+
chs.NegativeFillColor = OxyColors.Red;
149158
model.Series.Add(chs);
150159

151160
return model;

Source/Examples/ExampleLibrary/Series/LinearBarSeriesExamples.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,22 @@ public static PlotModel WithNegativeColors()
5050
[Example("With negative colors (logaritmic)")]
5151
public static PlotModel WithNegativeColorsLogarithmic()
5252
{
53-
return CreateWithNegativeColors(true);
53+
return CreateWithNegativeColors(logarithmic: true);
5454
}
5555

56-
public static PlotModel CreateWithNegativeColors(bool logarithmic = false)
56+
[Example("With BaseValue")]
57+
public static PlotModel WithBaseValue()
58+
{
59+
return CreateWithNegativeColors(baseValue: 50);
60+
}
61+
62+
[Example("With BaseValue (Logarithmic)")]
63+
public static PlotModel WithBaseValueLogarithmic()
64+
{
65+
return CreateWithNegativeColors(logarithmic: true, baseValue: 50);
66+
}
67+
68+
public static PlotModel CreateWithNegativeColors(bool logarithmic = false, double baseValue = 0)
5769
{
5870
var model = new PlotModel { Title = logarithmic ? "LinearBarSeries with stroke (logarithmic)" : "LinearBarSeries with stroke" };
5971
model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom });
@@ -65,6 +77,7 @@ public static PlotModel CreateWithNegativeColors(bool logarithmic = false)
6577
linearBarSeries.NegativeFillColor = OxyColor.Parse("#45BF360C");
6678
linearBarSeries.NegativeStrokeColor = OxyColor.Parse("#BF360C");
6779
linearBarSeries.StrokeThickness = 1;
80+
linearBarSeries.BaseValue = baseValue;
6881
model.Series.Add(linearBarSeries);
6982

7083
return model;

Source/OxyPlot/Series/BarSeries/BarSeries.cs

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,34 @@ public BarSeries()
4141
this.LabelAngle = 0;
4242
this.StackGroup = string.Empty;
4343
this.StrokeThickness = 0;
44+
this.BaseValue = 0;
45+
this.BaseLine = double.NaN;
46+
this.ActualBaseLine = double.NaN;
4447
}
4548

4649
/// <summary>
47-
/// Gets the actual fill color.
50+
/// Gets or sets the base value. Default value is 0.
4851
/// </summary>
49-
/// <value>The actual color.</value>
50-
public OxyColor ActualFillColor => this.FillColor.GetActualColor(this.defaultFillColor);
51-
52+
/// <value>The base value.</value>
53+
public double BaseValue { get; set; }
54+
5255
/// <summary>
5356
/// Gets or sets the base value.
5457
/// </summary>
5558
/// <value>The base value.</value>
56-
public double BaseValue { get; set; }
59+
public double BaseLine { get; set; }
60+
61+
/// <summary>
62+
/// Gets or sets the actual base line.
63+
/// </summary>
64+
/// <returns>The actual base line.</returns>
65+
public double ActualBaseLine { get; protected set; }
66+
67+
/// <summary>
68+
/// Gets the actual fill color.
69+
/// </summary>
70+
/// <value>The actual color.</value>
71+
public OxyColor ActualFillColor => this.FillColor.GetActualColor(this.defaultFillColor);
5772

5873
/// <summary>
5974
/// Gets or sets the color field.
@@ -174,6 +189,40 @@ protected internal override void SetDefaultValues()
174189
}
175190
}
176191

192+
/// <summary>
193+
/// Updates the axes to include the max and min of this series.
194+
/// </summary>
195+
protected internal override void UpdateAxisMaxMin()
196+
{
197+
base.UpdateAxisMaxMin();
198+
199+
this.ComputeActualBaseLine();
200+
this.XAxis.Include(this.ActualBaseLine);
201+
}
202+
203+
/// <summary>
204+
/// Computes the actual base value..
205+
/// </summary>
206+
protected void ComputeActualBaseLine()
207+
{
208+
if (double.IsNaN(this.BaseLine))
209+
{
210+
if (this.XAxis.IsLogarithmic())
211+
{
212+
var lowestPositiveValue = this.ActualItems == null ? 1 : this.ActualItems.Select(p => p.Value).Where(v => v > 0).MinOrDefault(1);
213+
this.ActualBaseLine = Math.Max(lowestPositiveValue / 10.0, this.BaseValue);
214+
}
215+
else
216+
{
217+
this.ActualBaseLine = 0;
218+
}
219+
}
220+
else
221+
{
222+
this.ActualBaseLine = this.BaseLine;
223+
}
224+
}
225+
177226
/// <inheritdoc/>
178227
protected internal override void UpdateMaxMin()
179228
{
@@ -418,16 +467,15 @@ public override void Render(IRenderContext rc)
418467
this.Manager.SetCurrentBaseValue(stackIndex, categoryIndex, value < 0, topValue);
419468
}
420469

421-
if (this.YAxis.IsLogarithmic() && !this.YAxis.IsValidValue(baseValue))
422-
{
423-
baseValue = LogarithmicAxis.LowestValidRoundtripValue;
424-
}
470+
var clampBase = this.XAxis.IsLogarithmic() && !this.XAxis.IsValidValue(baseValue);
471+
var p1 = this.Transform(clampBase ? this.XAxis.ClipMinimum : baseValue, categoryValue);
472+
var p2 = this.Transform(topValue, categoryValue + actualBarWidth);
425473

426-
var rect = new OxyRect(this.Transform(baseValue, categoryValue), this.Transform(topValue, categoryValue + actualBarWidth));
474+
var rectangle = new OxyRect(p1, p2);
427475

428-
this.ActualBarRectangles.Add(rect);
476+
this.ActualBarRectangles.Add(rectangle);
429477

430-
this.RenderItem(rc, topValue, categoryValue, actualBarWidth, item, rect);
478+
this.RenderItem(rc, topValue, categoryValue, actualBarWidth, item, rectangle);
431479

432480
if (this.LabelFormatString != null)
433481
{

Source/OxyPlot/Series/BarSeries/LinearBarSeries.cs

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public LinearBarSeries()
4646
this.TrackerFormatString = XYAxisSeries.DefaultTrackerFormatString;
4747
this.NegativeFillColor = OxyColors.Undefined;
4848
this.NegativeStrokeColor = OxyColors.Undefined;
49+
this.BaseValue = 0;
50+
this.BaseLine = double.NaN;
51+
this.ActualBaseLine = double.NaN;
4952
}
5053

5154
/// <summary>
@@ -97,10 +100,22 @@ public OxyColor ActualColor
97100
}
98101

99102
/// <summary>
100-
/// Gets or sets the base line. This value is used for plotting to determine
101-
/// the start of the plot. A suitable value is selected automatically if it is NaN.
103+
/// Gets or sets the base value. Default value is 0.
102104
/// </summary>
103-
public double BaseLine { get; set; } = double.NaN;
105+
/// <value>The base value.</value>
106+
public double BaseValue { get; set; }
107+
108+
/// <summary>
109+
/// Gets or sets the base value.
110+
/// </summary>
111+
/// <value>The base value.</value>
112+
public double BaseLine { get; set; }
113+
114+
/// <summary>
115+
/// Gets or sets the actual base line.
116+
/// </summary>
117+
/// <returns>The actual base line.</returns>
118+
public double ActualBaseLine { get; protected set; }
104119

105120
/// <summary>
106121
/// Gets the nearest point.
@@ -162,7 +177,6 @@ public override void Render(IRenderContext rc)
162177
}
163178

164179
this.VerifyAxes();
165-
166180
this.RenderBars(rc, actualPoints);
167181
}
168182

@@ -204,27 +218,31 @@ protected internal override void UpdateAxisMaxMin()
204218
{
205219
base.UpdateAxisMaxMin();
206220

207-
this.YAxis.Include(this.GetBaseLineOrAutomaticValue(this.YAxis));
221+
this.ComputeActualBaseLine();
222+
this.YAxis.Include(this.ActualBaseLine);
208223
}
209224

210225
/// <summary>
211-
/// Gets the base line value. If the value of the property is NaN, a sensible value is returned.
226+
/// Computes the actual base value..
212227
/// </summary>
213-
/// <param name="axis">The axis for which to return a sensible value for.</param>
214-
/// <returns>The base line property or a sensible default.</returns>
215-
protected double GetBaseLineOrAutomaticValue(Axis axis)
228+
protected void ComputeActualBaseLine()
216229
{
217230
if (double.IsNaN(this.BaseLine))
218231
{
219-
if (axis.IsLogarithmic())
232+
if (this.YAxis.IsLogarithmic())
233+
{
234+
var lowestPositiveY = this.ActualPoints == null ? 1 : this.ActualPoints.Select(p => p.Y).Where(y => y > 0).MinOrDefault(1);
235+
this.ActualBaseLine = Math.Max(lowestPositiveY / 10.0, this.BaseValue);
236+
}
237+
else
220238
{
221-
var lowestPostiveY = this.ActualPoints == null ? 1 : this.ActualPoints.Select(p => p.Y).Where(y => y > 0).Min();
222-
return Math.Sqrt(lowestPostiveY);
239+
this.ActualBaseLine = 0;
223240
}
224-
return 0;
225241
}
226-
227-
return this.BaseLine;
242+
else
243+
{
244+
this.ActualBaseLine = this.BaseLine;
245+
}
228246
}
229247

230248
/// <summary>
@@ -282,6 +300,8 @@ private int FindRectangleIndex(ScreenPoint point)
282300
/// <param name="actualPoints">The list of points that should be rendered.</param>
283301
private void RenderBars(IRenderContext rc, List<DataPoint> actualPoints)
284302
{
303+
bool clampBase = this.YAxis.IsLogarithmic() && !this.YAxis.IsValidValue(this.BaseValue);
304+
285305
var widthOffset = this.GetBarWidth(actualPoints) / 2;
286306
var widthVector = this.Orientate(new ScreenVector(widthOffset, 0));
287307

@@ -294,7 +314,7 @@ private void RenderBars(IRenderContext rc, List<DataPoint> actualPoints)
294314
}
295315

296316
var screenPoint = this.Transform(actualPoint) - widthVector;
297-
var basePoint = this.Transform(new DataPoint(actualPoint.X, this.GetBasePointY())) + widthVector;
317+
var basePoint = this.Transform(new DataPoint(actualPoint.X, clampBase ? this.YAxis.ClipMinimum : this.BaseValue)) + widthVector;
298318
var rectangle = new OxyRect(basePoint, screenPoint);
299319
this.rectangles.Add(rectangle);
300320
this.rectanglesPointIndexes.Add(pointIndex);
@@ -310,13 +330,6 @@ private void RenderBars(IRenderContext rc, List<DataPoint> actualPoints)
310330
}
311331
}
312332

313-
private double GetBasePointY()
314-
{
315-
if (this.YAxis.IsLogarithmic())
316-
return LogarithmicAxis.LowestValidRoundtripValue;
317-
return 0;
318-
}
319-
320333
/// <summary>
321334
/// Computes the bars width.
322335
/// </summary>
@@ -344,7 +357,7 @@ private double GetBarWidth(List<DataPoint> actualPoints)
344357
/// <returns>The bar colors</returns>
345358
private BarColors GetBarColors(double y)
346359
{
347-
var positive = y >= 0.0;
360+
var positive = y >= this.BaseValue;
348361
var fillColor = (positive || this.NegativeFillColor.IsUndefined()) ? this.GetSelectableFillColor(this.ActualColor) : this.NegativeFillColor;
349362
var strokeColor = (positive || this.NegativeStrokeColor.IsUndefined()) ? this.StrokeColor : this.NegativeStrokeColor;
350363

0 commit comments

Comments
 (0)