vasturiano / sunburst-chart

A sunburst interactive chart web component for visualizing hierarchical data
https://vasturiano.github.io/sunburst-chart/example/flare/
MIT License
292 stars 89 forks source link

How to use this sunburst in React? #85

Closed matter13311 closed 4 months ago

matter13311 commented 2 years ago

How do I use this npm package with React? ` import React from 'react' import Sunburst from 'sunburst-chart';

return( < Sunburst/ > ) `

I get an error saying Sunburst can't be used in JSX.

vasturiano commented 2 years ago

@matter13311 this is not a React component, thus you cannot use it directly in a React environment directly. The manner to instantiate this module is specified in the docs, so you'll need to write a React wrapper around it if you want to use it in a JSX context.

react-kapsule offers a means to do that easily, but there are other ways you can achieve the same.

hallundbaek commented 2 years ago

Could you give an example with react-kapsule?

vasturiano commented 2 years ago

Something along these lines:

import fromKapsule from 'react-kapsule';
import sunburstKapsule from 'sunburst-chart';

const Sunburst = fromKapsule(sunburstKapsule);

then use it in React:

<Sunburst data={myData} />
hallundbaek commented 2 years ago

Thanks for the fast reply!

Your example was also my intuition but trying it out in yields the following typeerror:

Argument of type '(configOptions?: ConfigOptions | undefined) => SunburstChartInstance' is not assignable to parameter of type 'KapsuleClosure'.
Property 'resetProps' is missing in type 'SunburstChartGenericInstance<SunburstChartInstance>' but required in type 'KapsuleInstance'.ts(2345)
vinicius-gregorio commented 2 years ago

did you figured it out?

hallundbaek commented 2 years ago

No, I ended up not using react-kapsule. Just generated the chart and applied it to a div with an id, something to this effect:

import sunburst from "sunburst-chart";

export type SunburstLeaf = {
  name: string;
  size: number;
  color: string;
};

export type SunburstNode = {
  name: string;
  color: string;
  size: number; 
  children: (SunburstNode | SunburstLeaf)[];
};

interface SunburstElementProps {
  data: SunburstNode | undefined
}

export const SunburstElement = (props: SunburstElementProps) => {
  const { data } = props;

  useEffect(() => {
    if (data) {
      sunburst()
        .data(data)
        .label("name")
        .size("size")
        .height(200)
        .width(200)
        .color("color")(document.getElementById("chart")!);
    }
  }, [data]);

  return <div id="chart" />
}

And some extra logic to delete the children of the div when data changes.

coreymunn3 commented 5 months ago

I ended up with an implementation similar to above, but was still getting the chart duplication error.

As @hallundbaek hints here,

And some extra logic to delete the children of the div when data changes.

we need to provide a cleanup function to the useEffect. This StackOverflow says exactly what to do, and I found it to be correct.

Here's my final useEffect implementation. I used explicit props coming from the parent component and passed them to the chart.

useEffect(() => {
    if (treeData) {
      sunburstChart
        .data(treeData)
        .width(width || chartRef.current.offsetWidth)
        .height(height || chartRef.current.offsetWidth) // makes it square if no height provided
        .label(getLabel)
        .size(getSize)
        .color(getColor)
        .strokeColor(getStrokeColor)
        .nodeClassName(getNodeClassName)
        .minSliceAngle(minSliceAngle)
        .maxLevels(maxLevels)
        .excludeRoot(excludeRoot)
        .centerRadius(centerRadius)
        .radiusScaleExponent(radiusScaleExponent)
        .sort(getSortOrder)
        .showLabels(showLabels)
        .labelOrientation(labelOrientation)
        .handleNonFittingLabel(handleNonFittingLabel)
        .showTooltip(getShowTooltip)
        .tooltipTitle(getTooltipTitle)
        .tooltipContent(getTooltipContent)
        .onHover(onHover)
        .onClick(onClick)
        .transitionDuration(transitionDuration)(
        document.getElementById('sunburst')
      );
    }
    return () => {
      if (chartRef.current?.children[0]) {
        chartRef.current.removeChild(chartRef.current.children[0]);
      }
    };
  }, [treeData]);

where the component returns

return <div id='sunburst' className={classes.root} ref={chartRef} />;

@vasturiano I think we can mark this issue as closed.