Spacing for stacked bars with stackgroupkey

1 Answer 58 Views
ChartView
Tim
Top achievements
Rank 1
Tim asked on 02 Oct 2023, 04:21 PM
I have a chart with 2 stacked columns in each category but they are right beside each other.  Using your example they show up together but I would like for there to be some space at the location indicated in red on the picture.  It looks like the older chart had an overlap percentage that would have made this happen and the other frameworks have a space property but I have only found a gaplength in Winforms.  Can someone point me to the Space property for a barseries/CategoricalAxis?

1 Answer, 1 is accepted

Sort by
0
Accepted
Dess | Tech Support Engineer, Principal
Telerik team
answered on 05 Oct 2023, 06:41 AM

Hi, Tim,

RadChartView allows you to specify only the distance between bar groups as percent by the CategoricalAxis.GapLength. However, if you want to adjust the spacing between the stacked bar groups, I have prepared a sample code snippet for your reference which uses a custom renderer

        public RadForm1()
        {
            InitializeComponent(); new RadControlSpyForm().Show();

            this.radChartView1.CreateRenderer += new ChartViewCreateRendererEventHandler(radChartView1_CreateRenderer);

            BarSeries barSeries = new BarSeries("Performance", "RepresentativeName");
            barSeries.Name = "Q1";
            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.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.DataPoints.Add(new CategoricalDataPoint(177, "Harley"));
            barSeries3.DataPoints.Add(new CategoricalDataPoint(128, "White"));
            barSeries3.DataPoints.Add(new CategoricalDataPoint(143, "Smith"));
            barSeries3.DataPoints.Add(new CategoricalDataPoint(111, "Jones"));
            barSeries3.DataPoints.Add(new CategoricalDataPoint(118, "Marshall"));
            this.radChartView1.Series.Add(barSeries3);
            BarSeries barSeries4 = new BarSeries("Performance", "RepresentativeName");
            barSeries4.Name = "Q4";
            barSeries4.DataPoints.Add(new CategoricalDataPoint(153, "Harley"));
            barSeries4.DataPoints.Add(new CategoricalDataPoint(141, "White"));
            barSeries4.DataPoints.Add(new CategoricalDataPoint(130, "Smith"));
            barSeries4.DataPoints.Add(new CategoricalDataPoint(88, "Jones"));
            barSeries4.DataPoints.Add(new CategoricalDataPoint(109, "Marshall"));
            this.radChartView1.Series.Add(barSeries4);

            barSeries.CombineMode = ChartSeriesCombineMode.Stack;
            barSeries2.CombineMode = ChartSeriesCombineMode.Stack;
            barSeries3.CombineMode = ChartSeriesCombineMode.Stack;
            barSeries4.CombineMode = ChartSeriesCombineMode.Stack;

            barSeries.StackGroupKey = 0;
            barSeries2.StackGroupKey = 0;
            barSeries3.StackGroupKey = 1;
            barSeries4.StackGroupKey = 1;
         
        }

        void radChartView1_CreateRenderer(object sender, ChartViewCreateRendererEventArgs e)
        {
            RenderParameter param = new RenderParameter();
            param.BarOffset = 5;
            e.Renderer = new CustomCartesianRenderer(param, e.Area as CartesianArea);
        }

        public class RenderParameter
        {
            public int BarOffset { get; set; }

            public RenderParameter()
            {
            }
        }

        public class CustomCartesianRenderer : CartesianRenderer
        {
            internal RenderParameter RenderParameter { get; set; }

            public CustomCartesianRenderer(CartesianArea area) : base(area)
            {
            }
            public CustomCartesianRenderer(RenderParameter renderParameter, CartesianArea area) : base(area)
            {
                RenderParameter = renderParameter ?? throw new ArgumentNullException(nameof(renderParameter));
            }

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

                for (int i = 0; i < this.DrawParts.Count; i++)
                {
                    BarSeriesDrawPart linePart = this.DrawParts[i] as BarSeriesDrawPart;
                    if (linePart != null)
                    {
                        this.DrawParts[i] = new CustomBarSeriesDrawPart((BarSeries)linePart.Element, this);
                    }
                }
            }
        }

        public class CustomBarSeriesDrawPart : BarSeriesDrawPart
        {
            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;
            }
            public CustomBarSeriesDrawPart(BarSeries series, IChartRenderer renderer) : base(series, renderer)
            {
            }

            public override void DrawSeriesParts()
            {
                CustomCartesianRenderer customRenderer = this.Renderer as CustomCartesianRenderer;
                RenderParameter param = customRenderer.RenderParameter;
                int xOffset = 0;
                Graphics graphics = ((ChartRenderer)this.Renderer).Graphics;
                RadGdiGraphics radGraphics = new RadGdiGraphics(graphics);

                for (int j = 0; j < this.Element.DataPoints.Count; j++)
                { 

                    RadRect slot = this.Element.DataPoints[j].LayoutSlot;
                    if (this.Element.Name=="Q3" ||this.Element.Name=="Q4") {
                        xOffset = param.BarOffset;
                    }
                    RectangleF barBounds = new RectangleF((float)(this.OffsetX + slot.X+xOffset ), (float)(this.OffsetY + slot.Y), (float)slot.Width, (float)slot.Height);
                    bool isAreaVertical = ((BarSeries)this.Element).View.GetArea<CartesianArea>().Orientation == System.Windows.Forms.Orientation.Vertical;

                    DataPointElement childElement = (DataPointElement)this.Element.Children[j];

                    if (isAreaVertical)
                    {
                        float realHeight = barBounds.Height * childElement.HeightAspectRatio;
                        barBounds.Y += barBounds.Height - realHeight;
                        barBounds.Height = realHeight;
                    }
                    else
                    {
                        float realWidth = barBounds.Width * childElement.HeightAspectRatio;
                        barBounds.Width = realWidth;
                    }

                    barBounds = AdjustBarDataPointBounds(childElement, barBounds);

                    if (isAreaVertical)
                    {
                        barBounds.Width = Math.Max(barBounds.Width, 1f);
                    }
                    else
                    {
                        barBounds.Height = Math.Max(barBounds.Height, 1f);
                    }

                    RadImageShape background = childElement.BackgroundShape;

                    if (background != null)
                    {
                        background.Paint(graphics, barBounds);
                    }

                    Telerik.WinControls.Primitives.FillPrimitiveImpl fill = new Telerik.WinControls.Primitives.FillPrimitiveImpl(childElement, null);
                    fill.PaintFill(radGraphics, 0, new SizeF(1, 1), barBounds);

                    Telerik.WinControls.Primitives.BorderPrimitiveImpl border = new Telerik.WinControls.Primitives.BorderPrimitiveImpl(childElement, null);
                    border.PaintBorder(radGraphics, 0, new SizeF(1, 1), barBounds);

                    if (childElement.Image != null)
                    {
                        GraphicsState state = graphics.Save();
                        Region clipRegion = graphics.Clip;

                        graphics.SetClip(barBounds, CombineMode.Intersect);

                        Telerik.WinControls.Primitives.ImagePrimitiveImpl pointImage = new Telerik.WinControls.Primitives.ImagePrimitiveImpl(childElement);
                        pointImage.PaintImage(radGraphics, childElement.Image, barBounds, childElement.ImageLayout, childElement.ImageAlignment, childElement.ImageOpacity, false);

                        graphics.Clip = clipRegion;
                        graphics.Restore(state);
                    }
                }

            }
        }

The achieved result is illustrated in the below screenshot. Note that this is just a sample approach and it may not cover all possible cases. Feel free to modify and further extend it in a way that fits the custom requirements you have:

I hope this information helps.

Regards,
Dess | Tech Support Engineer, Principal
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

Tim
Top achievements
Rank 1
commented on 05 Oct 2023, 01:36 PM

Thank you Dess that is what I wanted it to look like.  I was hoping for property I could set but this does adjust the spacing the way I needed.

 

 

Tags
ChartView
Asked by
Tim
Top achievements
Rank 1
Answers by
Dess | Tech Support Engineer, Principal
Telerik team
Share this question
or