New to Telerik UI for WinForms? Download free 30-day trial

Summary Labels on Stacked Bars

Environment

Product Version 2018.3.911
Product RadChartView for WinForms

Description

An example demonstrating how multiple BarSeries can be customized when they are stacked by adding a summary label at the top.

Solution

A custom Cartesian renderer will be responsible for creating special label elements for the series which is positioned at the top of the stack. The custom elements will paint an additional summary text on top of the data point. The example also features a sample implementation for calculating the summary values.

Figure 1: Summary Labels

chartview-summary-labels-stacked-bars 001

Custom Renderer and Labels

public class CustomCartesianRenderer : CartesianRenderer
{
    public CustomCartesianRenderer(CartesianArea area)
        : base(area)
    { }

    protected override void InitializeSeriesLabels()
    {
        base.InitializeSeriesLabels();

        IDictionary<object, List<double?>> summaryValues = new Dictionary<object, List<double?>>();
        for (int i = 0; i < this.Area.Series.Count; i++)
        {
            BarSeries barSeries = this.Area.Series[i] as BarSeries;
            if (barSeries == null)
            {
                continue;
            }

            for (int j = 0; j < barSeries.DataPoints.Count; j++)
            {
                CategoricalDataPoint dp = (CategoricalDataPoint)barSeries.DataPoints[j];
                if (!summaryValues.ContainsKey(dp.Category))
                {
                    summaryValues.Add(dp.Category, new List<double?>() { dp.Value });
                }
                else
                {
                    summaryValues[dp.Category].Add(dp.Value);
                }
            }
        }

        string lastSeriesName = this.Area.Series[this.Area.Series.Count - 1].Name;
        for (int i = 0; i < this.DrawParts.Count; i++)
        {
            BarLabelElementDrawPart labelPart = this.DrawParts[i] as BarLabelElementDrawPart;
            if (labelPart != null && labelPart.Element.Name == lastSeriesName)
            {
                CustomBarLabelElementDrawPart customLabelPart = new CustomBarLabelElementDrawPart((BarSeries)labelPart.Element, this);
                customLabelPart.SummaryValues = summaryValues;
                this.DrawParts[i] = customLabelPart;
            }
        }
    }
}
public class CustomBarLabelElementDrawPart : BarLabelElementDrawPart
{
    private IDictionary<object, List<double?>> summaryValues;

    public CustomBarLabelElementDrawPart(BarSeries series, IChartRenderer renderer)
        : base(series, renderer)
    { }

    public IDictionary<object, List<double?>> SummaryValues
    {
        get
        {
            return this.summaryValues;
        }
        set
        {
            this.summaryValues = value;
        }
    }

    public override void Draw()
    {
        Graphics graphics = this.Renderer.Surface as Graphics;
        RadGdiGraphics radGraphics = new RadGdiGraphics(graphics);

        foreach (DataPointElement dataPointElement in this.Element.Children)
        {
            CategoricalDataPoint categoricalDataPoint = dataPointElement.DataPoint as CategoricalDataPoint;
            if (!this.summaryValues.ContainsKey(categoricalDataPoint.Category))
            {
                continue;
            }

            double? sum = this.summaryValues[categoricalDataPoint.Category].Sum();
            string summaryText = string.Format("Sum: {0}", sum);

            RadRect slot = categoricalDataPoint.LayoutSlot;
            RectangleF barBounds = new RectangleF((float)(this.OffsetX + slot.X), (float)(this.OffsetY + slot.Y), (float)slot.Width, (float)slot.Height);
            float realHeight = barBounds.Height * dataPointElement.HeightAspectRatio;
            barBounds.Y += barBounds.Height - realHeight;
            barBounds.Height = realHeight;
            barBounds = this.AdjustBarDataPointBounds(dataPointElement, barBounds);
            barBounds.Width = Math.Max(barBounds.Width, 1f);

            object state = radGraphics.SaveState();

            int horizontalTranslate = (int)(barBounds.X + barBounds.Width / 2);
            int verticalTranslate = (int)(barBounds.Y + barBounds.Height / 2);

            float angle = (float)this.Element.LabelRotationAngle % 360f;

            if (angle != 0)
            {
                radGraphics.TranslateTransform(horizontalTranslate, verticalTranslate);
                radGraphics.RotateTransform(angle);
                radGraphics.TranslateTransform(-horizontalTranslate, -verticalTranslate);
            }

            Size desiredSize = TextRenderer.MeasureText(summaryText, dataPointElement.Font);

            FillPrimitiveImpl fill = new FillPrimitiveImpl(dataPointElement, null);
            fill.PaintFill(radGraphics, 0, Size.Empty, barBounds);

            BorderPrimitiveImpl border = new BorderPrimitiveImpl(dataPointElement, null);
            border.PaintBorder(radGraphics, 0, Size.Empty, barBounds);

            using (Brush brush = new SolidBrush(dataPointElement.ForeColor))
            {
                RectangleF drawRectangle = new RectangleF();
                drawRectangle.X = barBounds.X + dataPointElement.Padding.Left + (barBounds.Width - desiredSize.Width) /2;
                drawRectangle.Y = barBounds.Y + dataPointElement.Padding.Top - desiredSize.Height;
                drawRectangle.Width = barBounds.Width - dataPointElement.Padding.Right;
                drawRectangle.Height = barBounds.Height - dataPointElement.Padding.Bottom;

                StringFormat format = new StringFormat();
                graphics.DrawString(summaryText, dataPointElement.Font, brush, drawRectangle, format);
            }

            if (angle != 0)
            {
                radGraphics.ResetTransform();
            }

            radGraphics.RestoreState(state);

        }

        base.Draw();
    }

    private RectangleF AdjustBarDataPointBounds(DataPointElement point, RectangleF bounds)
    {
        RectangleF barBounds = bounds;

        if (point.BorderBoxStyle == BorderBoxStyle.SingleBorder || point.BorderBoxStyle == BorderBoxStyle.OuterInnerBorders)
        {
            barBounds.X += point.BorderWidth - (int)((point.BorderWidth - 1f) / 2f);
            barBounds.Width -= point.BorderWidth;
            barBounds.Y += point.BorderWidth - (int)((point.BorderWidth - 1f) / 2f);
            barBounds.Height -= point.BorderWidth;
        }
        else if (point.BorderBoxStyle == BorderBoxStyle.FourBorders)
        {
            barBounds.Y += 1;
            barBounds.Height -= 1;
            barBounds.X += 1;
            barBounds.Width -= 1;
        }

        if (((CartesianRenderer)this.Renderer).Area.Orientation == System.Windows.Forms.Orientation.Horizontal)
        {
            barBounds.X--;
        }

        return barBounds;
    }
}

Below is a sample setup with three stacked bar series. The renderer will be changed in the CreateRenderer event of the chart.

Form`s Class

public partial class RadForm1 : Telerik.WinControls.UI.RadForm
{
    public RadForm1()
        {
            InitializeComponent();

            this.radChartView1.CreateRenderer += RadChartView1_CreateRenderer;

            BarSeries barSeries = new BarSeries("Performance", "RepresentativeName");
            barSeries.Name = "Q1";
            barSeries.CombineMode = ChartSeriesCombineMode.Stack;
            barSeries.ShowLabels = true;

            barSeries.DataPoints.Add(new CategoricalDataPoint(177, "Harley"));
            barSeries.DataPoints.Add(new CategoricalDataPoint(128, "White"));
            barSeries.DataPoints.Add(new CategoricalDataPoint(143, "Smith"));
            barSeries.DataPoints.Add(new CategoricalDataPoint(111, "Jones"));
            barSeries.DataPoints.Add(new CategoricalDataPoint(118, "Marshall"));

            this.radChartView1.Series.Add(barSeries);

            BarSeries barSeries2 = new BarSeries("Performance", "RepresentativeName");
            barSeries2.Name = "Q2";
            barSeries2.CombineMode = ChartSeriesCombineMode.Stack;
            barSeries2.ShowLabels = true;

            barSeries2.DataPoints.Add(new CategoricalDataPoint(153, "Harley"));
            barSeries2.DataPoints.Add(new CategoricalDataPoint(141, "White"));
            barSeries2.DataPoints.Add(new CategoricalDataPoint(130, "Smith"));
            barSeries2.DataPoints.Add(new CategoricalDataPoint(88, "Jones"));
            barSeries2.DataPoints.Add(new CategoricalDataPoint(109, "Marshall"));
            this.radChartView1.Series.Add(barSeries2);

            BarSeries barSeries3 = new BarSeries("Performance", "RepresentativeName");
            barSeries3.Name = "Q3";
            barSeries3.CombineMode = ChartSeriesCombineMode.Stack;
            barSeries3.ShowLabels = true;

            barSeries3.DataPoints.Add(new CategoricalDataPoint(113, "Harley"));
            barSeries3.DataPoints.Add(new CategoricalDataPoint(45, "White"));
            barSeries3.DataPoints.Add(new CategoricalDataPoint(100, "Smith"));
            barSeries3.DataPoints.Add(new CategoricalDataPoint(125, "Jones"));
            barSeries3.DataPoints.Add(new CategoricalDataPoint(79, "Marshall"));
            this.radChartView1.Series.Add(barSeries3);
        }

    private void RadChartView1_CreateRenderer(object sender, ChartViewCreateRendererEventArgs e)
        {
            e.Renderer = new CustomCartesianRenderer(e.Area as CartesianArea);
        }
}