Skip to content

Round Stacked Bars OR Provide Access to formattedGraphicalItems via hook, i.e. useGraphicalItems #6698

@iChaosren

Description

@iChaosren

Is your feature request related to a problem? Please describe.
Yes. We recently tried updating/upgrading from v2 to v3.
A long standing bug that we'd experienced and made a fix for, was the way in which the charts rounded bar graphs, especially stacked bar graphs.
It was only on a per "cell" basis, and did not take neighboring bar charts into account.

Our solution on v2:
A clipping mask, that perfectly fits the bars, and rounds the corners of all bars at once.

{stacked ? <Customized component={BarClippingMask} id={id} /> : null}
const BarClippingMask = (props) => {

  const { formattedGraphicalItems } = props;
  const barProps: any[] = formattedGraphicalItems.filter(item => item.props.key.startsWith('bar-')).map(item => item.props);
  var individualBars: any[] = [];

  const dataLength = max(barProps.map(bar => bar?.data?.length ?? 0));

  for (let i = 0; i < dataLength; i++)
    individualBars[i] = barProps.filter(bar => !!bar.data).map(bar => bar.data[i]);

  return (
    <defs>
      <clipPath id={`clip-all-bars-${props.id}`}>
        {
          individualBars.map((bar, i) => {
            const minY = min(bar.map(d => d.y));
            const minX = min(bar.map(d => d.x));

            const posH = sum(bar.map(d => d.height >= 0 ? d.height : 0));
            const negH = sum(bar.map(d => d.height < 0 ? d.height : 0));

            const maxW: number = max(bar.map(d => d.width))!;

            const topRadius = calculateRadius(posH, maxW);
            const botRadius = calculateRadius(negH, maxW);

            if (posH > 0) {
              return (
                <path key={i} style={{ transform: `translate(${minX}px, ${minY}px)` }} d={getRoundedRectanglePath(maxW, Math.abs(negH) + posH, topRadius, topRadius, botRadius, botRadius)} />
              );
            } else {
              return (
                <path key={i} style={{ transform: `translate(${minX}px, ${minY}px)` }} d={getRoundedRectanglePath(maxW, negH, botRadius, botRadius, topRadius, topRadius)} />
              );
            }
          })
        }
      </clipPath>
    </defs>
  );
};

export default BarClippingMask;

Describe the solution you'd like
Either:
a: Provide access to the graphical information on final bar outputs. Maybe through a useFormattedGraphicalItems or useComputedGraphicalItems hook?
b: Add a setting to round the corners of all bars in stack.

Describe alternatives you've considered

"Why not simply round the top bar?"
We tried that, but we have several entries, sometimes 5 or more, that can be only a few pixels high. So this causes other issues/complaints.

Using the values from useAppSelector to get the current graphical items:

const isPanorama = useIsPanorama();
const chartState = useAppSelector((state: any) => {
  // Attempt One:
  const bars = state.graphicalItems.cartesianItems.filter(item => item.type === 'bar').map(item => selectBarRectangles(state, props.xAxisId, props.yAxisId, isPanorama, item.id, []));
  // Attempt Two:
  return selectAllVisibleBars(state, props.xAxisId, props.yAxisId, isPanorama);
});
console.log('chartState', chartState);

Two Major Issues here,

  1. This code doesn't work. The entries inside of graphical items do not actually contain the data it's supposed to. All the props on graphicalItems.cartesianItems[0] are props that I was providing and already had access to,
  2. It's a hack. Clearly this was not even remotely the intended way. The fact that props on these entries are incomplete or missing confirms it...

Additional context
There are 3 items in this stack:
Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions