Skip to content

Instantly share code, notes, and snippets.

@jon-a-nygaard
Last active June 3, 2019 23:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jon-a-nygaard/7d0253b2c73ae634d5804d6794a67c0c to your computer and use it in GitHub Desktop.
Save jon-a-nygaard/7d0253b2c73ae634d5804d6794a67c0c to your computer and use it in GitHub Desktop.
Example of rendering React components in a Highcharts formatter function.

Example of rendering React components in a Highcharts formatter function.

The index.js file shows an example of creating a React app with Highcharts using the offical wrapper. It also demonstrates a possible solution to render React components in a Highcharts formatter function.

The helper function named formatter takes a react component and render it to a string which is returned to the Highcharts formatter. It also takes the arguments and context in the Highcharts formatter and passes them into the component as props named arguments and context

import React from 'react'
import { render } from 'react-dom'
import Highcharts from 'highcharts/highstock'
import HighchartsReact from 'highcharts-react-official'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
// Used in the helper function named formatter
import { renderToString } from 'react-dom/server'
// Axis formatter which renders labels with links.
class AxisFormatter extends React.Component {
renderLabel = (match, ctx) => {
const msg = (
match.params.id === ctx.value.toString() ?
'You clicked me' :
'Click me!'
)
const text = `${ctx.value}: ${msg}`
return <Link to={`/${ctx.value}`}>{text}</Link>
}
render() {
const ctx = this.props.context
const fn = ({ match }) => this.renderLabel(match, ctx)
return <Router>
<div>
<Route path="/:id" render={fn} />
<Route exact path="/" render={fn} />
</div>
</Router>
}
}
// Helper to render components in a formatter function.
const formatter = (Component) => {
return function () {
const args = arguments
const ctx = this
const uniqueId = () => Math.random().toString(36).substring(2, 9)
const id = 'highcharts-formatter-' + uniqueId()
const axis = ctx.axis;
const chart = axis.chart;
const addEvent = Highcharts.addEvent;
// When issue with fireEvent is fixed, remove the following block.
if (!chart.removeFormatterCleanUp) {
chart.formatterCleanUp = [];
chart.removeFormatterCleanUp = addEvent(chart, 'beforeRedraw', function () {
this.formatterCleanUp.forEach(function (removeEvent) {
removeEvent();
});
this.formatterCleanUp = [];
})
}
const removeEvent = addEvent(axis, 'afterRender', function () {
const el = document.getElementById(id);
if (el) {
// Render the component as an element with events handlers and so on.
render(<Component arguments={args} context={ctx} />, el);
el.removeAttribute('id');
}
// When issue with fireEvent is fixed, remove next line, and uncomment the second next line.
chart.formatterCleanUp.push(removeEvent)
//removeEvent()
});
return '<span id="' + id + '">' +
// Render content to have correct positioning
renderToString(<Component arguments={args} context={ctx} />) +
'</span>'
}
}
const options = {
title: {
text: 'My stock chart'
},
series: [{
data: [1, 2, 3]
}],
xAxis: {
labels: {
useHTML: true,
// Passing the component into the helper function
formatter: formatter(AxisFormatter)
}
}
}
const App = () => <div>
<HighchartsReact
highcharts={Highcharts}
constructorType={'stockChart'}
options={options}
/>
</div>
render(<App />, document.getElementById('root'))
@boazadato
Copy link

@jon-a-nygaard nice solution, thanks for this 👏
However, this renders <div data-reactroot=""><a href="/0">0: Click me!</a></div> which clicking on generate a server page load...

@jon-a-nygaard
Copy link
Author

Hi @boazadato, thanks for your feedback.

I will look into the issue and come back to you as soon as I know some more.

@jon-a-nygaard
Copy link
Author

Hi @boazadato,

I have updated the gist with a fix to the formatter function.
Now it renders the component correctly and the events are not lost.

Unfortunately I discovered an issue with Highcharts.fireEvent causing removeEvent to not work properly. As a result the final formatter function is more complicated then necessary. I will see if we can have this issue fixed in the near future, and make an update to the gist again.

Hope it helps!

@boazadato
Copy link

boazadato commented May 28, 2018

@jon-a-nygaard thanks, it is now working 💪

Another (hacky) alternative, for reference -

added a ref='chartName' to the chart and after mount hooked an event to the labels click.

componentDidMount(){
        let ctxComponent = this;
        let chart = this.refs.chartName && this.refs.chartName.getChart();
        if (chart){
            chart.xAxis[0].labelGroup.element.childNodes.forEach(function(label)
            {
                label.onclick = function(){
                    setTimeout(()=>ctxComponent.setState({pendingRedirect:`/${this.textContent}`}), 1)
                }
            });
        }
    }

also, added some styling to make the labels look like links (.highcharts-x-axis-link-style .highcharts-xaxis-labels text tspan)

@jon-a-nygaard
Copy link
Author

Nice, that's an interesting workaround 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment