<NME> BarSeries.cs
<BEF> ﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="BarSeries.cs" company="OxyPlot">
//   Copyright (c) 2014 OxyPlot contributors
// </copyright>
// <summary>
//   Represents a series for clustered or stacked bar charts.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace OxyPlot.Series
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Represents a series for clustered or stacked bar charts.
    /// </summary>
    public class BarSeries : BarSeriesBase<BarItem>, IStackableSeries
    {
        /// <summary>
        /// The default tracker format string
        /// </summary>
        public new const string DefaultTrackerFormatString = "{0}\n{1}: {2}";

        /// <summary>
        /// The default fill color.
        /// </summary>
        private OxyColor defaultFillColor;

        /// <summary>
        /// Initializes a new instance of the <see cref="BarSeries" /> class.
        /// </summary>
        public BarSeries()
        {
            this.FillColor = OxyColors.Automatic;
            this.NegativeFillColor = OxyColors.Undefined;
            this.TrackerFormatString = DefaultTrackerFormatString;
            this.LabelMargin = 2;
            this.StackGroup = string.Empty;
            this.StrokeThickness = 0;
        }

        /// <summary>
        /// Gets the actual fill color.
        /// </summary>
        /// <value>The actual color.</value>
        public OxyColor ActualFillColor => this.FillColor.GetActualColor(this.defaultFillColor);

        /// <summary>
        /// Gets or sets the base value.
        /// </summary>
        /// <value>The base value.</value>
        public double BaseValue { get; set; }

        /// <summary>
        /// Gets or sets the color field.
        /// </summary>
        public string ColorField { get; set; }

        /// <summary>
        /// Gets or sets the color of the interior of the bars.
        /// </summary>
        /// <value>The color.</value>
        public OxyColor FillColor { get; set; }

        /// <inheritdoc/>
        public bool IsStacked { get; set; }

        /// <inheritdoc/>
        public bool OverlapsStack { get; set; }

        /// <summary>
        /// Gets or sets the label format string.
        /// </summary>
        /// <value>The label format string.</value>
        public string LabelFormatString { get; set; }

        /// <summary>
        /// Gets or sets the label margins.
        /// </summary>
        public double LabelMargin { get; set; }

        /// <summary>
        /// Gets or sets label placements.
        /// </summary>
        public LabelPlacement LabelPlacement { get; set; }

        /// <summary>
        /// Gets or sets the color of the interior of the bars when the value is negative.
        /// </summary>
        /// <value>The color.</value>
        public OxyColor NegativeFillColor { get; set; }

        /// <inheritdoc/>
        public string StackGroup { get; set; }

        /// <summary>
        /// Gets or sets the value field.
        /// </summary>
        public string ValueField { get; set; }

        /// <summary>
        /// Gets or sets the actual rectangles for the bars.
        /// </summary>
        protected IList<OxyRect> ActualBarRectangles { get; set; }

        /// <inheritdoc/>
        public override TrackerHitResult GetNearestPoint(ScreenPoint point, bool interpolate)
        {
            if (this.ActualBarRectangles == null || this.ValidItems.Count == 0)
            {
                return null;
            }

            var i = 0;
            foreach (var rectangle in this.ActualBarRectangles)
            {
                if (rectangle.Contains(point))
                {
                    // get the item corresponding to this bar/column rectangle
                    var item = this.ValidItems[i];
                    var categoryIndex = item.GetCategoryIndex(i);
                    var dp = new DataPoint(categoryIndex, this.ValidItems[i].Value);

                    // get the item that the bar/column is bound to, or the item from the Items collection
                    var boundItem = this.GetItem(this.ValidItemsIndexInversion[i]);

                    return new TrackerHitResult
                    {
                        Series = this,
                        DataPoint = dp,
                        Position = point,
                        Item = boundItem,
                        Index = i,
                        Text = this.GetTrackerText(item, boundItem, categoryIndex)
                    };
                }

                i++;
            }

            return null;
        }

        /// <inheritdoc/>
        public override void RenderLegend(IRenderContext rc, OxyRect legendBox)
        {
            var xmid = (legendBox.Left + legendBox.Right) / 2;
            var ymid = (legendBox.Top + legendBox.Bottom) / 2;
            var height = (legendBox.Bottom - legendBox.Top) * 0.8;
            var width = height;
            rc.DrawRectangle(
                new OxyRect(xmid - (0.5 * width), ymid - (0.5 * height), width, height),
                this.GetSelectableColor(this.ActualFillColor),
                this.StrokeColor,
                this.StrokeThickness,
                this.EdgeRenderingMode);
        }

        /// <inheritdoc/>
        protected internal override void SetDefaultValues()
        {
            if (this.FillColor.IsAutomatic())
            {
                this.defaultFillColor = this.PlotModel.GetDefaultColor();
            }
        }

        /// <inheritdoc/>
        protected internal override void UpdateMaxMin()
        {
            base.UpdateMaxMin();

            if (this.ValidItems.Count == 0)
            {
                return;
            }

            var categoryAxis = this.GetCategoryAxis();

            double minValue = double.MaxValue, maxValue = double.MinValue;
            if (this.IsStacked)
            {
                var labels = this.GetCategoryAxis().ActualLabels;
                for (var i = 0; i < labels.Count; i++)
                {
                    var j = 0;
                    var values = this.ValidItems.Where(item => item.GetCategoryIndex(j++) == i)
                        .Select(item => item.Value).Concat(new[] { 0d }).ToList();
                    var minTemp = values.Where(v => v <= 0).Sum();
                    var maxTemp = values.Where(v => v >= 0).Sum();

                    var stackIndex = this.Manager.GetStackIndex(this.StackGroup);
                    var stackedMinValue = this.Manager.GetCurrentMinValue(stackIndex, i);
                    if (!double.IsNaN(stackedMinValue))
                    {
                        minTemp += stackedMinValue;
                    }

                    this.Manager.SetCurrentMinValue(stackIndex, i, minTemp);

                    var stackedMaxValue = this.Manager.GetCurrentMaxValue(stackIndex, i);
                    if (!this.OverlapsStack && !double.IsNaN(stackedMaxValue))
                    {
                        maxTemp += stackedMaxValue;
                    }

                    this.Manager.SetCurrentMaxValue(stackIndex, i, maxTemp);

                    minValue = Math.Min(minValue, minTemp + this.BaseValue);
                    maxValue = Math.Max(maxValue, maxTemp + this.BaseValue);
                }
            }
            else
            {
                var values = this.ValidItems.Select(item => item.Value).Concat(new[] { 0d }).ToList();
                minValue = values.Min();
                maxValue = values.Max();
                if (this.BaseValue < minValue)
                {
                    minValue = this.BaseValue;
                }

                if (this.BaseValue > maxValue)
                {
                    maxValue = this.BaseValue;
                }
            }

            this.MinX = minValue;
            this.MaxX = maxValue;
        }

        /// <inheritdoc/>
        protected virtual string GetTrackerText(BarItem barItem, object item, int categoryIndex)
        {
            var categoryAxis = this.GetCategoryAxis();
            var valueAxis = this.XAxis;

            return StringHelper.Format(
                this.ActualCulture,
                this.TrackerFormatString,
                item,
                this.Title,
                categoryAxis.FormatValue(categoryIndex),
                valueAxis.GetValue(barItem.Value));
        }

        /// <inheritdoc/>
        protected override bool IsValid(BarItem item)
        {
            return this.XAxis.IsValidValue(item.Value);
        }

        /// <summary>
        /// Renders the bar/column item.
        /// </summary>
        /// <param name="rc">The render context.</param>
        /// <param name="barValue">The end value of the bar.</param>
        /// <param name="categoryValue">The category value.</param>
        /// <param name="actualBarWidth">The actual width of the bar.</param>
        /// <param name="item">The item.</param>
        /// <param name="rect">The rectangle of the bar.</param>
        protected virtual void RenderItem(
            IRenderContext rc,
            double barValue,
            double categoryValue,
            double actualBarWidth,
            BarItem item,
            OxyRect rect)
        {
            // Get the color of the item
            var actualFillColor = item.Color;
            if (actualFillColor.IsAutomatic())
            {
                actualFillColor = this.ActualFillColor;
                if (item.Value < 0 && !this.NegativeFillColor.IsUndefined())
                {
                    actualFillColor = this.NegativeFillColor;
                }
            }

            rc.DrawRectangle(
                rect,
                this.GetSelectableFillColor(actualFillColor),
                this.StrokeColor,
                this.StrokeThickness,
                this.EdgeRenderingMode.GetActual(EdgeRenderingMode.PreferSharpness));
        }

        /// <summary>
        /// Renders the item label.
        /// </summary>
        /// <param name="rc">The render context</param>
        /// <param name="item">The item.</param>
        /// <param name="baseValue">The bar item base value.</param>
        /// <param name="topValue">The bar item top value.</param>
        /// <param name="categoryValue">The bar item category value.</param>
        /// <param name="categoryEndValue">The bar item category end value.</param>
        protected void RenderLabel(
            IRenderContext rc,
            BarItem item,
            double baseValue,
            double topValue,
            double categoryValue,
            double categoryEndValue)
        {
            var s = StringHelper.Format(this.ActualCulture, this.LabelFormatString, item, item.Value);
            HorizontalAlignment ha;
            ScreenPoint pt;
            var y = (categoryEndValue + categoryValue) / 2;
            var sign = Math.Sign(item.Value);
            var marginVector = new ScreenVector(this.LabelMargin, 0) * sign;

            switch (this.LabelPlacement)
            {
                case LabelPlacement.Inside:
                    pt = this.Transform(topValue, y);
                    marginVector = -marginVector;
                    ha = (HorizontalAlignment)sign;
                    break;
                case LabelPlacement.Outside:
                    pt = this.Transform(topValue, y);
                    ha = (HorizontalAlignment)(-sign);
                    break;
                case LabelPlacement.Middle:
                    pt = this.Transform((topValue + baseValue) / 2, y);
                    marginVector = new ScreenVector(0, 0);
                    ha = HorizontalAlignment.Center;
                    break;
                case LabelPlacement.Base:
                    pt = this.Transform(baseValue, y);
                    ha = (HorizontalAlignment)(-sign);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            var va = VerticalAlignment.Middle;
            this.Orientate(ref ha, ref va);

            pt += this.Orientate(marginVector);

            rc.DrawText(
                pt,
                s,
                this.ActualTextColor,
                this.ActualFont,
                this.ActualFontSize,
                this.ActualFontWeight,
                0,
                ha,
                va);
        }

        /// <inheritdoc/>
        public override void Render(IRenderContext rc)
        {
            this.ActualBarRectangles = new List<OxyRect>();

            if (this.ValidItems.Count == 0)
            {
                return;
            }

            var actualBarWidth = this.GetActualBarWidth();
            var stackIndex = this.IsStacked ? this.Manager.GetStackIndex(this.StackGroup) : 0;
            for (var i = 0; i < this.ValidItems.Count; i++)
            {
                var item = this.ValidItems[i];
                var categoryIndex = this.ValidItems[i].GetCategoryIndex(i);

                var value = item.Value;

                // Get base- and topValue
                var baseValue = double.NaN;
                if (this.IsStacked && !this.OverlapsStack)
                {
                    baseValue = this.Manager.GetCurrentBaseValue(stackIndex, categoryIndex, value < 0);
                }

                if (double.IsNaN(baseValue))
                {
                    baseValue = this.BaseValue;
                }

                var topValue = this.IsStacked ? baseValue + value : value;

                // Calculate offset
                double categoryValue;
                if (this.IsStacked)
                {
                    categoryValue = this.Manager.GetCategoryValue(categoryIndex, stackIndex, actualBarWidth);
                }
                else
                {
                    categoryValue = categoryIndex - 0.5 + this.Manager.GetCurrentBarOffset(categoryIndex);
                }

                if (this.IsStacked)
                {
                    this.Manager.SetCurrentBaseValue(stackIndex, categoryIndex, value < 0, topValue);
                }

                var rect = new OxyRect(this.Transform(baseValue, categoryValue), this.Transform(topValue, categoryValue + actualBarWidth));

                this.ActualBarRectangles.Add(rect);

                this.RenderItem(rc, topValue, categoryValue, actualBarWidth, item, rect);

                if (this.LabelFormatString != null)
                {
                    this.RenderLabel(
                        rc,
                        item,
                        baseValue,
                        topValue,
                        categoryValue,
                        categoryValue + actualBarWidth);
                }

                if (!this.IsStacked)
                {
                    this.Manager.IncreaseCurrentBarOffset(categoryIndex, actualBarWidth);
                }
            }
        }

        /// <inheritdoc/>
        protected override bool UpdateFromDataFields()
        {
            if (this.ValueField == null)
            {
                return false;
            }

            // Using reflection to add items by value and color (optional)
            var filler = new ListBuilder<BarItem>();
            filler.Add(this.ValueField, double.NaN);
            filler.Add(this.ColorField, OxyColors.Automatic);
            filler.Fill(
                this.ItemsSourceItems,
                this.ItemsSource,
                args => new BarItem(Convert.ToDouble(args[0])) { Color = (OxyColor)args[1] });

            return true;
        }
    }
}

<MSG> Fix incorrect label placement on BarSeries with non-zero BaseValue (#1726) (#1727)


<DFF> @@ -310,7 +310,7 @@ protected override bool IsValid(BarItem item)
             HorizontalAlignment ha;
             ScreenPoint pt;
             var y = (categoryEndValue + categoryValue) / 2;
-            var sign = Math.Sign(item.Value);
+            var sign = Math.Sign(item.Value - baseValue);
             var marginVector = new ScreenVector(this.LabelMargin, 0) * sign;
 
             switch (this.LabelPlacement)

{'commit': 'diff --git a/CHANGELOG.md b/CHANGELOG.md\nindex 374211f4..ea46a685 100644\n--- a/CHANGELOG.md\n+++ b/CHANGELOG.md\n@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.\n \n ### Added\n - Example for Issue #1716 showing poor tick spacing on DateTimeAxis with interval types of Weeks or Years\n+- Example for label placement on BarSeries with non-zero BaseValue (#1726)\n \n ### Changed\n \n@@ -12,6 +13,7 @@ All notable changes to this project will be documented in this file.\n \n ### Fixed\n - Zero-crossing axis bounds (#1708)\n+- Incorrect label placement on BarSeries with non-zero BaseValue (#1726)\n \n ## [2.1.0-Preview1] - 2020-10-18\n \ndiff --git a/Source/Examples/ExampleLibrary/Series/BarSeriesExamples.cs b/Source/Examples/ExampleLibrary/Series/BarSeriesExamples.cs\nindex 37f12259..dd981b5a 100644\n--- a/Source/Examples/ExampleLibrary/Series/BarSeriesExamples.cs\n+++ b/Source/Examples/ExampleLibrary/Series/BarSeriesExamples.cs\n@@ -932,6 +932,37 @@ public static PlotModel BaseValueOverlaping()\n             return model;\n         }\n \n+        [Example("BaseValue (labels)")]\n+        public static PlotModel BaseValueLabels()\n+        {\n+            var model = new PlotModel { Title = "BaseValue with Labels" };\n+\n+            foreach (var placement in new[] { LabelPlacement.Base, LabelPlacement.Inside, LabelPlacement.Middle, LabelPlacement.Outside })\n+            {\n+                var bs = new BarSeries { Title = placement.ToString(), BaseValue = -25, LabelPlacement = placement, LabelFormatString = "{0:0}" };\n+                bs.Items.Add(new BarItem { Value = -40 });\n+                bs.Items.Add(new BarItem { Value = -25 });\n+                bs.Items.Add(new BarItem { Value = -10 });\n+                bs.Items.Add(new BarItem { Value = 0 });\n+                bs.Items.Add(new BarItem { Value = 10 });\n+                model.Series.Add(bs);\n+            }\n+\n+            var categoryAxis = new CategoryAxis\n+            {\n+                Title = "Category",\n+                Position = AxisPosition.Left,\n+                StartPosition = 1,\n+                EndPosition = 0,\n+            };\n+            categoryAxis.Labels.AddRange(new[] { "A", "B", "C", "D", "E" });\n+            model.Axes.Add(categoryAxis);\n+\n+            model.Legends.Add(new Legend());\n+\n+            return model;\n+        }\n+\n         [Example("GapWidth 0%")]\n         public static PlotModel GapWidth0()\n         {\ndiff --git a/Source/OxyPlot/Series/BarSeries/BarSeries.cs b/Source/OxyPlot/Series/BarSeries/BarSeries.cs\nindex 59191d1e..ff7ced32 100644\n--- a/Source/OxyPlot/Series/BarSeries/BarSeries.cs\n+++ b/Source/OxyPlot/Series/BarSeries/BarSeries.cs\n@@ -310,7 +310,7 @@ protected override bool IsValid(BarItem item)\n             HorizontalAlignment ha;\n             ScreenPoint pt;\n             var y = (categoryEndValue + categoryValue) / 2;\n-            var sign = Math.Sign(item.Value);\n+            var sign = Math.Sign(item.Value - baseValue);\n             var marginVector = new ScreenVector(this.LabelMargin, 0) * sign;\n \n             switch (this.LabelPlacement)\n', 'commit_subject': 'Fix incorrect label placement on BarSeries with non-zero BaseValue (#1726) (#1727)', 'repo_name': 'oxyplot/oxyplot', 'committer': {'name': 'GitHub', 'email': '1505422b2465e9a84f6fdfaa161078890c593f06@github.com', 'time_sec': '1608377990', 'tz_offset': '60', 'date': {'seconds': '1608377990'}}, 'commit_tree': '2f4c8f4570f8b1f4eae1bb17a6e153ecb81eb944', 'commit_message': 'Fix incorrect label placement on BarSeries with non-zero BaseValue (#1726) (#1727)\n\n', 'language_name': 'C#', 'license': 'mit', 'hunks': '@@ -310,7 +310,7 @@ protected override bool IsValid(BarItem item)\n             HorizontalAlignment ha;\n             ScreenPoint pt;\n             var y = (categoryEndValue + categoryValue) / 2;\n-            var sign = Math.Sign(item.Value);\n+            var sign = Math.Sign(item.Value - baseValue);\n             var marginVector = new ScreenVector(this.LabelMargin, 0) * sign;\n \n             switch (this.LabelPlacement)\n', 'addition_count': 1, 'deletion_count': 1, 'src_file': 'a/Source/OxyPlot/Series/BarSeries/BarSeries.cs', 'tgt_file': 'b/Source/OxyPlot/Series/BarSeries/BarSeries.cs', 'file_extension': '.cs', 'before_file': '\ufeff// --------------------------------------------------------------------------------------------------------------------\n// <copyright file="BarSeries.cs" company="OxyPlot">\n//   Copyright (c) 2014 OxyPlot contributors\n// </copyright>\n// <summary>\n//   Represents a series for clustered or stacked bar charts.\n// </summary>\n// --------------------------------------------------------------------------------------------------------------------\n\nnamespace OxyPlot.Series\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n\n    /// <summary>\n    /// Represents a series for clustered or stacked bar charts.\n    /// </summary>\n    public class BarSeries : BarSeriesBase<BarItem>, IStackableSeries\n    {\n        /// <summary>\n        /// The default tracker format string\n        /// </summary>\n        public new const string DefaultTrackerFormatString = "{0}\\n{1}: {2}";\n\n        /// <summary>\n        /// The default fill color.\n        /// </summary>\n        private OxyColor defaultFillColor;\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref="BarSeries" /> class.\n        /// </summary>\n        public BarSeries()\n        {\n            this.FillColor = OxyColors.Automatic;\n            this.NegativeFillColor = OxyColors.Undefined;\n            this.TrackerFormatString = DefaultTrackerFormatString;\n            this.LabelMargin = 2;\n            this.StackGroup = string.Empty;\n            this.StrokeThickness = 0;\n        }\n\n        /// <summary>\n        /// Gets the actual fill color.\n        /// </summary>\n        /// <value>The actual color.</value>\n        public OxyColor ActualFillColor => this.FillColor.GetActualColor(this.defaultFillColor);\n\n        /// <summary>\n        /// Gets or sets the base value.\n        /// </summary>\n        /// <value>The base value.</value>\n        public double BaseValue { get; set; }\n\n        /// <summary>\n        /// Gets or sets the color field.\n        /// </summary>\n        public string ColorField { get; set; }\n\n        /// <summary>\n        /// Gets or sets the color of the interior of the bars.\n        /// </summary>\n        /// <value>The color.</value>\n        public OxyColor FillColor { get; set; }\n\n        /// <inheritdoc/>\n        public bool IsStacked { get; set; }\n\n        /// <inheritdoc/>\n        public bool OverlapsStack { get; set; }\n\n        /// <summary>\n        /// Gets or sets the label format string.\n        /// </summary>\n        /// <value>The label format string.</value>\n        public string LabelFormatString { get; set; }\n\n        /// <summary>\n        /// Gets or sets the label margins.\n        /// </summary>\n        public double LabelMargin { get; set; }\n\n        /// <summary>\n        /// Gets or sets label placements.\n        /// </summary>\n        public LabelPlacement LabelPlacement { get; set; }\n\n        /// <summary>\n        /// Gets or sets the color of the interior of the bars when the value is negative.\n        /// </summary>\n        /// <value>The color.</value>\n        public OxyColor NegativeFillColor { get; set; }\n\n        /// <inheritdoc/>\n        public string StackGroup { get; set; }\n\n        /// <summary>\n        /// Gets or sets the value field.\n        /// </summary>\n        public string ValueField { get; set; }\n\n        /// <summary>\n        /// Gets or sets the actual rectangles for the bars.\n        /// </summary>\n        protected IList<OxyRect> ActualBarRectangles { get; set; }\n\n        /// <inheritdoc/>\n        public override TrackerHitResult GetNearestPoint(ScreenPoint point, bool interpolate)\n        {\n            if (this.ActualBarRectangles == null || this.ValidItems.Count == 0)\n            {\n                return null;\n            }\n\n            var i = 0;\n            foreach (var rectangle in this.ActualBarRectangles)\n            {\n                if (rectangle.Contains(point))\n                {\n                    // get the item corresponding to this bar/column rectangle\n                    var item = this.ValidItems[i];\n                    var categoryIndex = item.GetCategoryIndex(i);\n                    var dp = new DataPoint(categoryIndex, this.ValidItems[i].Value);\n\n                    // get the item that the bar/column is bound to, or the item from the Items collection\n                    var boundItem = this.GetItem(this.ValidItemsIndexInversion[i]);\n\n                    return new TrackerHitResult\n                    {\n                        Series = this,\n                        DataPoint = dp,\n                        Position = point,\n                        Item = boundItem,\n                        Index = i,\n                        Text = this.GetTrackerText(item, boundItem, categoryIndex)\n                    };\n                }\n\n                i++;\n            }\n\n            return null;\n        }\n\n        /// <inheritdoc/>\n        public override void RenderLegend(IRenderContext rc, OxyRect legendBox)\n        {\n            var xmid = (legendBox.Left + legendBox.Right) / 2;\n            var ymid = (legendBox.Top + legendBox.Bottom) / 2;\n            var height = (legendBox.Bottom - legendBox.Top) * 0.8;\n            var width = height;\n            rc.DrawRectangle(\n                new OxyRect(xmid - (0.5 * width), ymid - (0.5 * height), width, height),\n                this.GetSelectableColor(this.ActualFillColor),\n                this.StrokeColor,\n                this.StrokeThickness,\n                this.EdgeRenderingMode);\n        }\n\n        /// <inheritdoc/>\n        protected internal override void SetDefaultValues()\n        {\n            if (this.FillColor.IsAutomatic())\n            {\n                this.defaultFillColor = this.PlotModel.GetDefaultColor();\n            }\n        }\n\n        /// <inheritdoc/>\n        protected internal override void UpdateMaxMin()\n        {\n            base.UpdateMaxMin();\n\n            if (this.ValidItems.Count == 0)\n            {\n                return;\n            }\n\n            var categoryAxis = this.GetCategoryAxis();\n\n            double minValue = double.MaxValue, maxValue = double.MinValue;\n            if (this.IsStacked)\n            {\n                var labels = this.GetCategoryAxis().ActualLabels;\n                for (var i = 0; i < labels.Count; i++)\n                {\n                    var j = 0;\n                    var values = this.ValidItems.Where(item => item.GetCategoryIndex(j++) == i)\n                        .Select(item => item.Value).Concat(new[] { 0d }).ToList();\n                    var minTemp = values.Where(v => v <= 0).Sum();\n                    var maxTemp = values.Where(v => v >= 0).Sum();\n\n                    var stackIndex = this.Manager.GetStackIndex(this.StackGroup);\n                    var stackedMinValue = this.Manager.GetCurrentMinValue(stackIndex, i);\n                    if (!double.IsNaN(stackedMinValue))\n                    {\n                        minTemp += stackedMinValue;\n                    }\n\n                    this.Manager.SetCurrentMinValue(stackIndex, i, minTemp);\n\n                    var stackedMaxValue = this.Manager.GetCurrentMaxValue(stackIndex, i);\n                    if (!this.OverlapsStack && !double.IsNaN(stackedMaxValue))\n                    {\n                        maxTemp += stackedMaxValue;\n                    }\n\n                    this.Manager.SetCurrentMaxValue(stackIndex, i, maxTemp);\n\n                    minValue = Math.Min(minValue, minTemp + this.BaseValue);\n                    maxValue = Math.Max(maxValue, maxTemp + this.BaseValue);\n                }\n            }\n            else\n            {\n                var values = this.ValidItems.Select(item => item.Value).Concat(new[] { 0d }).ToList();\n                minValue = values.Min();\n                maxValue = values.Max();\n                if (this.BaseValue < minValue)\n                {\n                    minValue = this.BaseValue;\n                }\n\n                if (this.BaseValue > maxValue)\n                {\n                    maxValue = this.BaseValue;\n                }\n            }\n\n            this.MinX = minValue;\n            this.MaxX = maxValue;\n        }\n\n        /// <inheritdoc/>\n        protected virtual string GetTrackerText(BarItem barItem, object item, int categoryIndex)\n        {\n            var categoryAxis = this.GetCategoryAxis();\n            var valueAxis = this.XAxis;\n\n            return StringHelper.Format(\n                this.ActualCulture,\n                this.TrackerFormatString,\n                item,\n                this.Title,\n                categoryAxis.FormatValue(categoryIndex),\n                valueAxis.GetValue(barItem.Value));\n        }\n\n        /// <inheritdoc/>\n        protected override bool IsValid(BarItem item)\n        {\n            return this.XAxis.IsValidValue(item.Value);\n        }\n\n        /// <summary>\n        /// Renders the bar/column item.\n        /// </summary>\n        /// <param name="rc">The render context.</param>\n        /// <param name="barValue">The end value of the bar.</param>\n        /// <param name="categoryValue">The category value.</param>\n        /// <param name="actualBarWidth">The actual width of the bar.</param>\n        /// <param name="item">The item.</param>\n        /// <param name="rect">The rectangle of the bar.</param>\n        protected virtual void RenderItem(\n            IRenderContext rc,\n            double barValue,\n            double categoryValue,\n            double actualBarWidth,\n            BarItem item,\n            OxyRect rect)\n        {\n            // Get the color of the item\n            var actualFillColor = item.Color;\n            if (actualFillColor.IsAutomatic())\n            {\n                actualFillColor = this.ActualFillColor;\n                if (item.Value < 0 && !this.NegativeFillColor.IsUndefined())\n                {\n                    actualFillColor = this.NegativeFillColor;\n                }\n            }\n\n            rc.DrawRectangle(\n                rect,\n                this.GetSelectableFillColor(actualFillColor),\n                this.StrokeColor,\n                this.StrokeThickness,\n                this.EdgeRenderingMode.GetActual(EdgeRenderingMode.PreferSharpness));\n        }\n\n        /// <summary>\n        /// Renders the item label.\n        /// </summary>\n        /// <param name="rc">The render context</param>\n        /// <param name="item">The item.</param>\n        /// <param name="baseValue">The bar item base value.</param>\n        /// <param name="topValue">The bar item top value.</param>\n        /// <param name="categoryValue">The bar item category value.</param>\n        /// <param name="categoryEndValue">The bar item category end value.</param>\n        protected void RenderLabel(\n            IRenderContext rc,\n            BarItem item,\n            double baseValue,\n            double topValue,\n            double categoryValue,\n            double categoryEndValue)\n        {\n            var s = StringHelper.Format(this.ActualCulture, this.LabelFormatString, item, item.Value);\n            HorizontalAlignment ha;\n            ScreenPoint pt;\n            var y = (categoryEndValue + categoryValue) / 2;\n            var sign = Math.Sign(item.Value);\n            var marginVector = new ScreenVector(this.LabelMargin, 0) * sign;\n\n            switch (this.LabelPlacement)\n            {\n                case LabelPlacement.Inside:\n                    pt = this.Transform(topValue, y);\n                    marginVector = -marginVector;\n                    ha = (HorizontalAlignment)sign;\n                    break;\n                case LabelPlacement.Outside:\n                    pt = this.Transform(topValue, y);\n                    ha = (HorizontalAlignment)(-sign);\n                    break;\n                case LabelPlacement.Middle:\n                    pt = this.Transform((topValue + baseValue) / 2, y);\n                    marginVector = new ScreenVector(0, 0);\n                    ha = HorizontalAlignment.Center;\n                    break;\n                case LabelPlacement.Base:\n                    pt = this.Transform(baseValue, y);\n                    ha = (HorizontalAlignment)(-sign);\n                    break;\n                default:\n                    throw new ArgumentOutOfRangeException();\n            }\n\n            var va = VerticalAlignment.Middle;\n            this.Orientate(ref ha, ref va);\n\n            pt += this.Orientate(marginVector);\n\n            rc.DrawText(\n                pt,\n                s,\n                this.ActualTextColor,\n                this.ActualFont,\n                this.ActualFontSize,\n                this.ActualFontWeight,\n                0,\n                ha,\n                va);\n        }\n\n        /// <inheritdoc/>\n        public override void Render(IRenderContext rc)\n        {\n            this.ActualBarRectangles = new List<OxyRect>();\n\n            if (this.ValidItems.Count == 0)\n            {\n                return;\n            }\n\n            var actualBarWidth = this.GetActualBarWidth();\n            var stackIndex = this.IsStacked ? this.Manager.GetStackIndex(this.StackGroup) : 0;\n            for (var i = 0; i < this.ValidItems.Count; i++)\n            {\n                var item = this.ValidItems[i];\n                var categoryIndex = this.ValidItems[i].GetCategoryIndex(i);\n\n                var value = item.Value;\n\n                // Get base- and topValue\n                var baseValue = double.NaN;\n                if (this.IsStacked && !this.OverlapsStack)\n                {\n                    baseValue = this.Manager.GetCurrentBaseValue(stackIndex, categoryIndex, value < 0);\n                }\n\n                if (double.IsNaN(baseValue))\n                {\n                    baseValue = this.BaseValue;\n                }\n\n                var topValue = this.IsStacked ? baseValue + value : value;\n\n                // Calculate offset\n                double categoryValue;\n                if (this.IsStacked)\n                {\n                    categoryValue = this.Manager.GetCategoryValue(categoryIndex, stackIndex, actualBarWidth);\n                }\n                else\n                {\n                    categoryValue = categoryIndex - 0.5 + this.Manager.GetCurrentBarOffset(categoryIndex);\n                }\n\n                if (this.IsStacked)\n                {\n                    this.Manager.SetCurrentBaseValue(stackIndex, categoryIndex, value < 0, topValue);\n                }\n\n                var rect = new OxyRect(this.Transform(baseValue, categoryValue), this.Transform(topValue, categoryValue + actualBarWidth));\n\n                this.ActualBarRectangles.Add(rect);\n\n                this.RenderItem(rc, topValue, categoryValue, actualBarWidth, item, rect);\n\n                if (this.LabelFormatString != null)\n                {\n                    this.RenderLabel(\n                        rc,\n                        item,\n                        baseValue,\n                        topValue,\n                        categoryValue,\n                        categoryValue + actualBarWidth);\n                }\n\n                if (!this.IsStacked)\n                {\n                    this.Manager.IncreaseCurrentBarOffset(categoryIndex, actualBarWidth);\n                }\n            }\n        }\n\n        /// <inheritdoc/>\n        protected override bool UpdateFromDataFields()\n        {\n            if (this.ValueField == null)\n            {\n                return false;\n            }\n\n            // Using reflection to add items by value and color (optional)\n            var filler = new ListBuilder<BarItem>();\n            filler.Add(this.ValueField, double.NaN);\n            filler.Add(this.ColorField, OxyColors.Automatic);\n            filler.Fill(\n                this.ItemsSourceItems,\n                this.ItemsSource,\n                args => new BarItem(Convert.ToDouble(args[0])) { Color = (OxyColor)args[1] });\n\n            return true;\n        }\n    }\n}\n', 'message': 'Fix incorrect label placement on BarSeries with non-zero BaseValue (#1726) (#1727)\n\n', 'author_name': 'GitHub', 'lang': 'cs'}