3

When creating a stacked responsive bar from nivo library, Can I make the bar of each stack have a border radius on their top and not bottom? Responsive bar border radius applies it to the bottom radius as well thank you

vertigo
  • 125
  • 8

1 Answers1

1

You can create your own Bar Component. The following code is a copy of the original Bar Component with slight modifications to the border radius to only apply the border radius to the top.

// This is a copy of https://github.com/plouc/nivo/blob/master/packages/bar/src/BarItem.tsx with some modifications to the borderRadius
// Borderradius will be applied to the Top only!

import { createElement, MouseEvent, useCallback, useMemo } from 'react';
import { animated, to } from '@react-spring/web';
import { useTheme } from '@nivo/core';
import { useTooltip } from '@nivo/tooltip';
import { BarDatum, BarItemProps } from '@nivo/bar/dist/types/types';

export const BarItem = <RawDatum extends BarDatum>({
  bar: { data, ...bar },

  style: {
    borderColor,
    color,
    height,
    labelColor,
    labelOpacity,
    labelX,
    labelY,
    transform,
    width
  },

  borderRadius,
  borderWidth,

  label,
  shouldRenderLabel,

  isInteractive,
  onClick,
  onMouseEnter,
  onMouseLeave,

  tooltip,

  isFocusable,
  ariaLabel,
  ariaLabelledBy,
  ariaDescribedBy
}: BarItemProps<RawDatum>) => {
  const theme = useTheme();
  const { showTooltipFromEvent, showTooltipAt, hideTooltip } = useTooltip();

  const renderTooltip = useMemo(
    () => () => createElement(tooltip, { ...bar, ...data }),
    [tooltip, bar, data]
  );

  const handleClick = useCallback(
    (event: MouseEvent<SVGRectElement>) => {
      onClick?.({ color: bar.color, ...data }, event);
    },
    [bar, data, onClick]
  );
  const handleTooltip = useCallback(
    (event: MouseEvent<SVGRectElement>) =>
      showTooltipFromEvent(renderTooltip(), event),
    [showTooltipFromEvent, renderTooltip]
  );
  const handleMouseEnter = useCallback(
    (event: MouseEvent<SVGRectElement>) => {
      onMouseEnter?.(data, event);
      showTooltipFromEvent(renderTooltip(), event);
    },
    [data, onMouseEnter, showTooltipFromEvent, renderTooltip]
  );
  const handleMouseLeave = useCallback(
    (event: MouseEvent<SVGRectElement>) => {
      onMouseLeave?.(data, event);
      hideTooltip();
    },
    [data, hideTooltip, onMouseLeave]
  );

  // extra handlers to allow keyboard navigation
  const handleFocus = useCallback(() => {
    showTooltipAt(renderTooltip(), [bar.absX + bar.width / 2, bar.absY]);
  }, [showTooltipAt, renderTooltip, bar]);
  const handleBlur = useCallback(() => {
    hideTooltip();
  }, [hideTooltip]);

  return (
    <animated.g transform={transform}>
      <defs>
        <clipPath id={`round-corner-${label}`}>
          <animated.rect
            x="0"
            y="0"
            rx={borderRadius}
            ry={borderRadius}
            width={to(width, (value) => Math.max(value, 0))}
            height={to(height, (value) => Math.max(value + borderRadius, 0))}
          />
        </clipPath>
      </defs>

      <animated.rect
        clipPath={`url(#round-corner-${label})`}
        width={to(width, (value) => Math.max(value, 0))}
        height={to(height, (value) => Math.max(value, 0))}
        // rx={borderRadius}
        // ry={borderRadius}
        fill={data.fill ?? color}
        strokeWidth={borderWidth}
        stroke={borderColor}
        focusable={isFocusable}
        tabIndex={isFocusable ? 0 : undefined}
        aria-label={ariaLabel ? ariaLabel(data) : undefined}
        aria-labelledby={ariaLabelledBy ? ariaLabelledBy(data) : undefined}
        aria-describedby={ariaDescribedBy ? ariaDescribedBy(data) : undefined}
        onMouseEnter={isInteractive ? handleMouseEnter : undefined}
        onMouseMove={isInteractive ? handleTooltip : undefined}
        onMouseLeave={isInteractive ? handleMouseLeave : undefined}
        onClick={isInteractive ? handleClick : undefined}
        onFocus={isInteractive && isFocusable ? handleFocus : undefined}
        onBlur={isInteractive && isFocusable ? handleBlur : undefined}
      />

      {shouldRenderLabel && (
        <animated.text
          x={labelX}
          y={labelY}
          textAnchor="middle"
          dominantBaseline="central"
          fillOpacity={labelOpacity}
          style={{
            ...theme.labels.text,
            pointerEvents: 'none',
            fill: labelColor
          }}
        >
          {label}
        </animated.text>
      )}
    </animated.g>
  );
};

You can then use it in your ResponsiveBar:

<ResponsiveBar
   ...
   barComponent={BarItem}
   borderRadius={20}
Lukas Kirner
  • 3,989
  • 6
  • 23
  • 30