Skip to content

Dynamic border radius for StackedColumnSeries #830

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Serproger opened this issue Jul 26, 2022 · 10 comments
Closed

Dynamic border radius for StackedColumnSeries #830

Serproger opened this issue Jul 26, 2022 · 10 comments
Labels
charts Charts component solved Solved the query using existing solutions

Comments

@Serproger
Copy link

Serproger commented Jul 26, 2022

We want to implement a plot according to attached design. Your library syncfusion_flutter_charts almost allows to do it, but we have a question.

We need to add border radius only to a top series of data in each column (and this series can be different for each column). As we can see, currently class StackedColumnSeries allows to add border radius only as a double, therefore it can only be set simultaneously for all the records belonging to one series. We don't have any builder function that allows to set border radius depending on column. Could you please elaborate on this problem and tell us how such a result can be achieved?
image

@Serproger Serproger changed the title Dynamic border radius for series Dynamic border radius for StackedColumnSeries Jul 26, 2022
@Serproger
Copy link
Author

Serproger commented Jul 26, 2022

Here is the sample of code we have written to build a plot

      SfCartesianChart(
          plotAreaBorderWidth: 0,
          margin: EdgeInsets.zero,
          primaryXAxis: CategoryAxis(
              axisLine: const AxisLine(color: Color(0xFFE5E5E5)),
              labelStyle:
                  const TextStyle(color: Color(0xFF898989), fontSize: 12),
              majorTickLines: const MajorTickLines(width: 0),
              majorGridLines: const MajorGridLines(width: 0)),
          primaryYAxis: NumericAxis(
              axisLine: const AxisLine(color: Color(0xFFE5E5E5)),
              labelStyle:
                  const TextStyle(color: Color(0xFF898989), fontSize: 12),
              majorTickLines: const MajorTickLines(width: 0),
              majorGridLines: const MajorGridLines(
                  dashArray: [2, 3], color: Color(0xFFDADADA))),
          palette: [newWordsColor, repeatedWordsColor, learnedWordsColor],
          legend: Legend(
              isVisible: true,
              alignment: ChartAlignment.near,
              position: LegendPosition.bottom,
              orientation: LegendItemOrientation.vertical,
              itemPadding: 0,
              offset: Offset.zero,
              legendItemBuilder: (text, series, point, seriesIndex) {
                return Padding(
                    padding: const EdgeInsets.symmetric(vertical: 6),
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: [
                        Container(
                            width: 17,
                            height: 17,
                            decoration: BoxDecoration(
                                color: series.color,
                                borderRadius: BorderRadius.circular(10))),
                        const SizedBox(width: 8),
                        Text(series.legendItemText,
                            style: Theme.of(context).textTheme.bodyMedium)
                      ],
                    ));
              }),
          series: <ChartSeries>[
            StackedColumnSeries<WordsChartGroupData, String>(
                dataSource: chartData,
                animationDuration: 0,
                color: newWordsColor,
                spacing: 0.1,
                borderRadius: const BorderRadius.all(Radius.circular(10)),
                dataLabelSettings: const DataLabelSettings(
                    isVisible: true,
                    showZeroValue: false,
                    labelIntersectAction: LabelIntersectAction.shift,
                    overflowMode: OverflowMode.hide,
                    textStyle: TextStyle(color: Colors.white, fontSize: 10),
                    labelAlignment: ChartDataLabelAlignment.middle),
                xValueMapper: (data, _) => data.title,
                yValueMapper: (data, _) => data.newWords),
            StackedColumnSeries<WordsChartGroupData, String>(
                dataSource: chartData,
                animationDuration: 0,
                color: repeatedWordsColor,
                spacing: 0.1,
                borderRadius: const BorderRadius.all(Radius.circular(10)),
                dataLabelSettings: const DataLabelSettings(
                    isVisible: true,
                    showZeroValue: false,
                    labelIntersectAction: LabelIntersectAction.shift,
                    overflowMode: OverflowMode.none,
                    textStyle: TextStyle(
                        color: ColorResources.primaryText, fontSize: 10),
                    labelAlignment: ChartDataLabelAlignment.middle),
                xValueMapper: (data, _) => data.title,
                yValueMapper: (data, _) => data.repeatedWords),
            StackedColumnSeries<WordsChartGroupData, String>(
                dataSource: chartData,
                animationDuration: 0,
                color: learnedWordsColor,
                spacing: 0.1,
                borderRadius: const BorderRadius.all(Radius.circular(10)),
                dataLabelSettings: const DataLabelSettings(
                    isVisible: true,
                    showZeroValue: false,
                    labelIntersectAction: LabelIntersectAction.shift,
                    overflowMode: OverflowMode.none,
                    textStyle: TextStyle(color: Colors.white, fontSize: 10),
                    labelAlignment: ChartDataLabelAlignment.middle),
                xValueMapper: (data, _) => data.title,
                yValueMapper: (data, _) => data.learnedWords),
          ])
class WordsChartGroupData {
  WordsChartGroupData(
      this.title, this.newWords, this.repeatedWords, this.learnedWords);
  final String title;
  final int newWords;
  final int repeatedWords;
  final int learnedWords;
}

@sfHariHaraSudhan
Copy link
Contributor

Hi @Serproger,

To implement the border radius for the last stacked column alone, we have added the borderRadius property inside the StackedColumnSeries to the last series alone. Here we have added the border radius only to the top left and top right of the stacked column. In this way, you can achieve your expected output by applying the border radius to the last series alone.

Kindly review this code snippet:

StackedColumnSeries<ChartSampleData, num>(
  dataSource: [
    ChartSampleData(10, 05),
    ChartSampleData(20, 10),
    ChartSampleData(30, 15),
  ],
  borderRadius: const BorderRadius.only(
    topLeft: Radius.circular(10),
    topRight: Radius.circular(10),
  ),
  xValueMapper: (data, _) => data.x,
  yValueMapper: (data, _) => data.y,
),

Snapshot:

image

Also, we have attached sample below for your reference.

Regards,
Hari Hara Sudhan. K

473877.zip

@pandazed
Copy link

pandazed commented Aug 15, 2023

I have two problems with it

  1. If you have big and small values in the last series, rounded edges will look different.
    For example
StackedColumnSeries<ChartSampleData, num>(
            dataSource: [
              ChartSampleData(10, 20),
              ChartSampleData(20, 1),  // rounded edges will look different here
              ChartSampleData(30, 60),
            ],
            borderRadius: const BorderRadius.only(
              topLeft: Radius.circular(10),
              topRight: Radius.circular(10),
            ),
            xValueMapper: (data, _) => data.x,
            yValueMapper: (data, _) => data.y,
          ),
  1. It doesn't works if you have zero value in last series in one of the column.
    See my comment in your code:
StackedColumnSeries<ChartSampleData, num>(
              dataSource: [
                ChartSampleData(10, 20),
                ChartSampleData(20, 30),
                ChartSampleData(30, 40),
              ],
              xValueMapper: (data, _) => data.x,
              yValueMapper: (data, _) => data.y),
          StackedColumnSeries<ChartSampleData, num>(
              dataSource: [
                ChartSampleData(10, 0),
                ChartSampleData(20, 50),
                ChartSampleData(30, 70),
              ],
              xValueMapper: (data, _) => data.x,
              yValueMapper: (data, _) => data.y),
          StackedColumnSeries<ChartSampleData, num>(
            dataSource: [
              ChartSampleData(10, 05),
              ChartSampleData(20, 0),  
              //if you have zero value here, column "20" will not be rounded. It will not round value from previous series
              ChartSampleData(30, 15),
            ],
            borderRadius: const BorderRadius.only(
              topLeft: Radius.circular(10),
              topRight: Radius.circular(10),
            ),
            xValueMapper: (data, _) => data.x,
            yValueMapper: (data, _) => data.y,
          ),

In Serproger's example, if he will use borderRadius on green series, all others will be without rounded edges. If he will use borderRadius on gray series and green series, there will be ugly artifacts in the last column

Suggest we need ability to make border radius on whole column, not for one of series

@woolseym
Copy link

woolseym commented Jul 2, 2024

This is still an issue. I can set the radius of the top series but if there is no data for that series on that bar then the next series won't apply the radius now that it is the top one. This can be seen in the pic for the 4/26/2024 bar. Any chance this could be updated to apply to the whole bar like @pandazed suggested? Or at least reopened to be revisited.

Screenshot 2024-07-02 104455

@Scerbelo12
Copy link

Apparently they just decided to ignore this one...

@LavanyaGowtham2021 LavanyaGowtham2021 added charts Charts component open Open labels Feb 12, 2025
@ahmednoaman-git
Copy link

It would also be great to support this same functionality for custom renderers. I need to do the same thing, but with my own custom renderers.

@leeloo1
Copy link

leeloo1 commented Apr 5, 2025

we also need this functionality. And especially with a fix for the issue @Serproger @pandazed @woolseym mentioned. Unfortunately in our case last series needs the border, but series has almost no data; any chance to give an ETA for this?

e.g.:
Image

@leeloo1
Copy link

leeloo1 commented Apr 7, 2025

BTW: Since it is not supported yet out-of-the-box, I have now solved the problem with my own renderer (onCreateRenderer in StackedColumnSeries) for the time being. Perhaps it will help one or the other, even if it is more challenging and complex

@Baranibharathip
Copy link

Hi @Serproger,

We have validated your query and we would like to inform you that you can achieve your requirement by using a custom StackedColumnSeriesRenderer. By overriding the onPaint method of the segment, you can customize the corner radius of each stacked column based on your condition. Initialize the custom stacked column series in the chart using the onCreateRenderer callback to render the customized segment. Please refer to the following code snippet and demo for reference.

Custom stacked column series:

class _StackedColumnCustomSegment extends StackedColumnSegment<Datum, String> {
  _StackedColumnCustomSegment();

  @override
  void onPaint(Canvas canvas) {
    if (segmentRect == null) return;
    final int currentIndex = currentSegmentIndex;
    final int currentSeriesIndex = series.index;
    final List<Datum> dataSource = series.dataSource as List<Datum>;
    final Datum data = dataSource[currentIndex];
    // Check if all upper series have 0 y-value at this data point
    bool isTop = true;
    for (int i = currentSeriesIndex + 1; i <= 2; i++) {
      if (getYValue(data, i) != 0) {
        isTop = false;
        break;
      }
    }
    final BorderRadius borderRadius =
        isTop ? BorderRadius.circular(15) : BorderRadius.zero;
    final Rect rect = Rect.fromLTRB(
      segmentRect!.left,
      segmentRect!.top,
      segmentRect!.right,
      segmentRect!.bottom,
    );
    final RRect paintRRect = RRect.fromRectAndCorners(
      rect,
      topLeft: borderRadius.topLeft,
      topRight: borderRadius.topRight,
    );
    final Paint paint = getFillPaint();
    if (paint.color != Colors.transparent && !paintRRect.isEmpty) {
      canvas.drawRRect(paintRRect, paint);
    }
  }

  double getYValue(Datum datum, int index) {
    switch (index) {
      case 0:
        return datum.y1;
      case 1:
        return datum.y2;
      case 2:
        return datum.y3;
      default:
        return 0;
    }
  }
}

Set the custom stacked column series to chart:

StackedColumnSeries<Datum, String>(
  . . . .
  onCreateRenderer: (series) {
    return _CustomStackedColumnSeriesRenderer();
  },
),

Demo:
Image

Sample: GH_830.zip

For more details, please refer to the following User Guide link:
UG: https://help.syncfusion.com/flutter/cartesian-charts/callbacks#oncreaterenderer

Regards,
Baranibharathi P.

@Saravanan-Madhesh Saravanan-Madhesh added waiting for customer response Cannot make further progress until the customer responds. and removed open Open labels Apr 9, 2025
@LavanyaGowtham2021
Copy link
Collaborator

Please reopen this ticket, if you need further assistance with this.

@LavanyaGowtham2021 LavanyaGowtham2021 added solved Solved the query using existing solutions and removed waiting for customer response Cannot make further progress until the customer responds. labels Apr 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
charts Charts component solved Solved the query using existing solutions
Projects
None yet
Development

No branches or pull requests

10 participants