highcharts-react icon indicating copy to clipboard operation
highcharts-react copied to clipboard

Difficult to use React components with Highcharts formatters.

Open jon-a-nygaard opened this issue 7 years ago • 57 comments

I have received feedback from a user asking how they can render React components in a Highcharts formatter function. This proved to be quite difficult and we should try to somehow make it more user friendly.

I made one example where I am rendering a component with a react router in xAxis.labels.formatter using a helper function named formatter: Gist The solution was complicated by a bug in Highcharts.fireEvent, and that the Highcharts formatters expects a string in return.

One way I believe this can perhaps be simplified is that the Highcharts formatters can be modified to handle dom elements as return values. Still this will not solve the full issue, as the Highcharts formatters will still not be able to handle a React component. So in my opinion there will likely be a need for some sort of helper as middleware.

jon-a-nygaard avatar May 28 '18 11:05 jon-a-nygaard

Any update on this? Being able to render dom nodes inside formatters would make a huge difference. Right now it's really frustrating to properly add event handlers to elements inside a formatter function.

Anwardo avatar May 06 '19 08:05 Anwardo

Hello,

Unfortunately, there is currently no progress in this topic. As the autor of this issue rightly noticed, the implementation would be complicated and would require some changes in the Highcharts core.

However, I think that I can suggest a slightly different approach. React v16 provided portals:

Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.

which allow us to add React components for example to xAxis labels. Please check the following example:

Live demo: https://codesandbox.io/s/1o5y7r31k3 Docs: https://reactjs.org/docs/portals.html

Best regards!

ppotaczek avatar May 08 '19 16:05 ppotaczek

Hi, thanks for your detailed response, provided examples and insights! I considered using Portals but didn't figure out how to actually bring them into the chart yet. Very interesting solution.

I managed to utilize renderToString while binding a listener to a generated id for my use case for now, but might look into using your provided solution at a later stage.

Anwardo avatar May 09 '19 07:05 Anwardo

Related issue in highcharts repository https://github.com/highcharts/highcharts/issues/7755

jon-a-nygaard avatar May 10 '19 07:05 jon-a-nygaard

I want to +1 on this feature. Currently, I'm trying to add React Router Links on my heatmap labels in order to load more detailed charts though since strings are the only accepted returns for formatters, I can't get to make router links work. Mimicking links using the <a> tags on labels would make the page reload which is different from the intended SPA behavior.

ksdelacruz avatar Jun 06 '19 07:06 ksdelacruz

Hi @ksdelacruz, Did you try to use the solution with portals?

ppotaczek avatar Jun 06 '19 10:06 ppotaczek

@ppotaczek I can't get to work the solution (or at least I can see how to incorporate it) on my development, because unlike the given example that access the ticks/label of x-Axis, I want to access the actual labels (points) of the series. Anyway, I just utilized the "onClick" event of the chart to save the point/label and just re-render a button with <Link> tag outside the heatmap.

ksdelacruz avatar Jun 09 '19 09:06 ksdelacruz

@ksdelacruz,

By portals you can add React components to all HTML chart's elements. Please check the example with data labels: https://codesandbox.io/s/highcharts-react-demo-v9dns

Best regards!

ppotaczek avatar Jun 10 '19 14:06 ppotaczek

@ppotaczek Thank you very much for your help!

ksdelacruz avatar Jun 11 '19 05:06 ksdelacruz

Don't know if this is any help, but we're currently using renderToStaticMarkup to enable using JSX in the formatting function:

import { renderToStaticMarkup } from 'react-dom/server'

// ...
  return (
    <HighchartsReact
      options={{
        tooltip: {
          formatter() {
            return renderToStaticMarkup(
              <span style={{ color: 'red' }}>
                {/* ... */}

stovmascript avatar Jun 20 '19 10:06 stovmascript

@ppotaczek I would like to ask for additional help. I tried converting the implementation of your given solution into React Hooks but there's a problem and I tried looking for solution but can't find any. The portals do work but only on the first row of the heatmap. Hovering on the other rows would release an error

Uncaught TypeError: b.onMouseOver is not a function at HTMLDivElement.e

Upon further checking/printing, it seems that the dataLabel attributes of points belonging to second row onward are missing.

P.S. Printing the points using chart.series[0].points logs 50 points but expanding the array on the console would show only 10 points.

ksdelacruz avatar Jun 23 '19 14:06 ksdelacruz

Hi @ksdelacruz,

Could you reproduce that issue in some online code editor? You can edit the live example that I provided previously.

ppotaczek avatar Jun 24 '19 10:06 ppotaczek

@ppotaczek Here's the example of the code that I've been talking to. https://codesandbox.io/s/highcharts-react-demo-w4fix

Kindly refer to the console for further clarifications. Thank you very much.

ksdelacruz avatar Jun 25 '19 02:06 ksdelacruz

Hi @ksdelacruz, Thank you for the live demo!

The problem is that the chart re-renders after adding components from the portal, please check the order of functions in this example: https://codesandbox.io/s/highcharts-react-demo-tqzrr

You can prevent re-rendering, for example by setting allowChartUpdate option to false:

<HighchartsReact
    ...
    allowChartUpdate={false}
/>

Live example: https://codesandbox.io/s/highcharts-react-demo-nvve6

Best regards!

ppotaczek avatar Jun 25 '19 18:06 ppotaczek

@ppotaczek Again, thank you very much for assisting me!

ksdelacruz avatar Jun 26 '19 01:06 ksdelacruz

I tried both portals and jsx to string solutions. I decided to go with a jsx to string solution similar to @stovmascript 's suggestion. This works with either renderToString() or renderToStaticMarkup().

import { renderToString } from 'react-dom/server';
import { Tooltip } from './CustomTooltip';

// ...
  return (
    <HighchartsReact
      options={{
        tooltip: {
          formatter: function() { // you can't use an arrow function here if you want to access the tooltip data off of 'this'
            const { x, y, point, series } = this;
            return renderToString(<Tooltip {...{ x, y, point, series }} />);
          },
          {/* ... */}

cstoddart avatar Sep 18 '20 16:09 cstoddart

Both renderToString and renderToStaticMarkup work quite well. Obviously, they strip out all event handlers from components, and, thus, for tooltips with actions - usage of event bubbling is the only option.

However, since Highcharts v9.0.0, rendering of React components in the tooltip can be quite straightforward using React portals (thank you for the idea, @ppotaczek). In the previous version, tooltip DOM content was completely rerendered on each refresh regardless of whether it has changed or not (which was making the implementation using portals quite complicated).

I published POC for v9.0.0 using React portals here: https://gist.github.com/dankremniov/a9a6b969e63dfc4f0f83e6f82b82eb4f. @ppotaczek, I would love to hear your feedback and any suggestions.

dankremniov avatar Feb 07 '21 15:02 dankremniov

Hi @dankremniov,

Thanks for your comments.

Your code looks really well! The only thing I can see to improve is chart updating. You don't need to merge options, it is enough to apply only the new ones:

  chart.update(
    tooltip: {
      formatter,
      useHTML: true
    }
  );

Best regards!

ppotaczek avatar Feb 08 '21 16:02 ppotaczek

@ppotaczek, thank you 👍 I can add the link to the codesandbox to the corresponding FAQ section of the readme if you think it is worth it.

dankremniov avatar Feb 08 '21 21:02 dankremniov

Yes, it is worth it 👍 good idea!

ppotaczek avatar Feb 09 '21 09:02 ppotaczek

I was trying to add custom React component on x-axis labels, but I noticed the example sandbox (https://github.com/highcharts/highcharts-react#how-to-add-react-component-to-a-charts-element) is using highcharts-react v2 and does not work with v3. Does someone know how could I make it work with v3?

(I've checked @dankremniov's solution but it looks a lot more complex and I am not sure how to adapt it to labels instead of tooltips.)

plotka avatar Mar 10 '21 15:03 plotka

Hi @plotka,

I just checked the example and it also works for version 3.0.0, maybe you didn't update React version?

Live example: https://codesandbox.io/s/highcharts-react-demo-forked-qk6cb?file=/demo.jsx

ppotaczek avatar Mar 11 '21 10:03 ppotaczek

Hi @ppotaczek. You are right, the React version needed to be updated. However, I tried to convert it into a functional component and for some reason, the labels are not being rendered. Do you think you could help? Thanks.

Live example: https://codesandbox.io/s/highcharts-react-demo-forked-sh3qt?file=/demo.jsx

plotka avatar Mar 17 '21 13:03 plotka

Hello @plotka, I converted the example into a functional component with small improvements here: https://codesandbox.io/s/highcharts-react-demo-forked-e1v82?file=/demo.jsx

ppotaczek avatar Mar 17 '21 17:03 ppotaczek

Should I be seeing those custom labels in the above demo @ppotaczek? This is what renders for me in the linked code sandbox. ~Can't see the "React" word being rendered anywhere~ Screen Shot 2021-03-22 at 16 09 30

Edit: Ah, I misunderstood. I was under the impression that this will render inside the tooltip shown when you hover over some data point. Is that possible to achieve?

maciej-gurban avatar Mar 22 '21 14:03 maciej-gurban

Hello @maciej-gurban, I recommend you to check the example from @dankremniov Please let me know in case of any questions.

ppotaczek avatar Mar 23 '21 11:03 ppotaczek

I've reached out to him and got help in adjusting that solution to our use case, thanks. Do you think you could incorporate part of his solution into highcharts-react documentation, or even making it part of the library?

maciej-gurban avatar Mar 23 '21 11:03 maciej-gurban

Ok, great! Every case of using portals is slightly different, so adding something like this to the library is not a good idea, but I will add an example with tooltip to the docs.

ppotaczek avatar Mar 23 '21 16:03 ppotaczek

I agree that adding the examples to the documentation would be great, especially the one with the functional components. Thanks a lot @ppotaczek for the help!

plotka avatar Mar 23 '21 17:03 plotka

What would be the main benefit of using createPortal instead of ReactDOMServer.renderToStaticMarkup? Is ReactDOMServer increasing the bundle size? Does ReactDOMServer.renderToStaticMarkup has any performance downsides compared to just returning a plain HTML string?

@dankremniov any idea?

alvarotrigo avatar Jun 03 '21 12:06 alvarotrigo