I am working on react google-chart react-google-chart and it is working fine.
What I want to do is to add click event to the labels of horizontal axis and get the label name and do what ever I want to do
I have google a lot but haven't found a correct solution
What I have done is this
import React, { Component } from 'react'
import { Chart } from "react-google-charts";
export default class manpowerGraph extends Component {
render() {
const data=[
['Year', 'Sales'],
['jan', 20],
['feb', 100],
['march', 55],
['april', 120],
['may', 200],
['june', 220],
]
const options={
// Material design options
chart: {
title: 'Manpower',
},
}
return (
<div className="manpowerChart">
<Chart
chartType="Bar"
width="100%"
height="400px"
data={data}
options={options}
legendToggle
chartEvents={[
{
eventName: "ready",
callback: ({ chartWrapper, google }) => {
const chart = chartWrapper.getChart();
google.visualization.events.addListener(chart, "onmouseover", e => {
const { row, column } = e;
console.warn("MOUSE OVER ", { row, column });
});
google.visualization.events.addListener(chart, "onmouseout", e => {
const { row, column } = e;
console.warn("MOUSE OUT ", { row, column });
});
}
}
]}
/>
</div>
)
}
}
Working code Working code I want when user click on month label it should fire any event or console
I have found this with Javascript with Javascript
Change the chart type to ColumnChart and then add the click handler from ready handler.
<Chart
chartType="ColumnChart"
width="80%"
height="400px"
data={data}
options={options}
legendToggle
chartEvents={[
{
eventName: "ready",
callback: ({ chartWrapper, google }) => {
const chart = chartWrapper.getChart();
var handler = function(e) {
console.log(e);
var parts = e.targetID.split("#");
if (parts.indexOf("label") >= 0) {
let idx = parts[parts.indexOf("label") + 1];
idx = parseInt(idx);
alert(data[idx + 1][0]);
}
};
google.visualization.events.addListener(
chartWrapper.getChart(),
"click",
handler
);
}
}
]}
/>
https://codesandbox.io/s/react-google-charts-columnchart-with-click-handler-cqe1l
Edit
To answer additional questions:
You can do limited styling of x-axis labels by setting hAxis.textStyle in options, supported styling options can be found here https://developers.google.com/docs/api/reference/rest/v1/documents#TextStyle . However, you can not set cursor using textStyle.
You can not style svg through external css. But you can add style tag inside svg tag. Again, not all css styles work, but fortunately, cursor does work.
One crude way of adding style inside svg is to grab the svg element using document.querySelector and then add style as child. This can be done from your ready handler as svg element has been created by the time ready event is fired.
Updated code now looks like:
import React from "react";
import ReactDOM from "react-dom";
import Chart from "react-google-charts";
const data = [
["Year", "Sales"],
["2004", 1000],
["2005", 1170],
["2006", 660],
["2008", 1030],
["2009", 1000],
["2010", 1170],
["2011", 660],
["2012", 1030]
];
const options = {
title: "Company Performance",
curveType: "function",
legend: { position: "bottom" },
enableInteractivity: true,
hAxis: { textStyle: { color: "blue", underline: true } }
};
class App extends React.Component {
render() {
return (
<div className="App">
<Chart
chartType="ColumnChart"
width="80%"
height="400px"
data={data}
options={options}
legendToggle
chartEvents={[
{
eventName: "ready",
callback: ({ chartWrapper, google }) => {
let svg = document.querySelector("svg");
let styles = 'text[text-anchor="middle"] { cursor: pointer; }';
var css = document.createElement("style");
if (css.styleSheet) {
css.styleSheet.cssText = styles;
} else {
css.appendChild(document.createTextNode(styles));
}
svg.appendChild(css);
const chart = chartWrapper.getChart();
var handler = function(e) {
console.log(e);
var parts = e.targetID.split("#");
if (parts.indexOf("label") >= 0) {
let idx = parts[parts.indexOf("label") + 1];
idx = parseInt(idx);
alert(data[idx + 1][0]);
}
};
google.visualization.events.addListener(
chartWrapper.getChart(),
"click",
handler
);
}
}
]}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working sandbox: https://codesandbox.io/s/react-google-charts-columnchart-with-click-handler-k6fv2
It seems that what is available at the moment for clickable tool tips in react-google-charts is limited.
However in order to configure clickable tooltips for react requires you to set the tooltip option like this:
tooltip: { isHtml: true, trigger: "selection" }
(so that it stays shown when you click), and you have setup a select chartEvent event as follows:
chartEvents={[
{
eventName: "select",
callback: ({ chartWrapper, google }) => {
var selection = chartWrapper.getChart().setAction({
id: "alertAction",
text: "Click Me!",
action: function() {
alert("Stay away Corona Virus!!");
}
});
console.warn(selection);
}
}
]}
See my codesandbox here.
And here's some google documentation on setAction() function, just the way I coded it in my example. Addtionally, there are the getAction() and removeAction() functions that tie into chart tooltips found on that same documentation page.
Hopefully this helps you some.
For making the labels clickable, In the chartEvents which is passed as props under Charts
<Chart
chartEvents = {**chartEvents**}
rootProps={{ 'data-testid': '3' }}
/>
Use can pass this as chartEvents
const chartEvents = [
{
eventName: "select",
callback({ chartWrapper }) {
console.log("Selected ", chartWrapper.getChart().Ufa.Ei);
}
}
];
This will return the label name for for the chart on which u have clicked
working example
Related
I'm running into some trouble trying to visualize data using the react version of ChartJS. I believe I'm aware of where the problem lies but after not being able to find anything online, I have no idea how to fix it. Basically, I am trying to create a doughnut chart which visualizes the amount of tasks in a specific stage (todo, in progress and completed). Here's my code:
const STATUS_BUCKETS = {
"ToDo": "ToDo",
"In Progress": "Progress",
"Completed": "Completed"
}
function DashboardCard06(props) {
const chartData = {labels: [], datasets: []};
for (const bucket in STATUS_BUCKETS) {
const filteredStatusTasks = props.task.reduce((prev, current) => {
if (current.status === STATUS_BUCKETS[bucket]) {
return prev + 1; }
else {
return prev
}
}, 0);
chartData.labels.push(bucket);
chartData.datasets.push({
data: [filteredStatusTasks],
backgroundColor: [
tailwindConfig().theme.colors.indigo[500],
tailwindConfig().theme.colors.blue[400],
tailwindConfig().theme.colors.indigo[800],
],
hoverBackgroundColor: [
tailwindConfig().theme.colors.indigo[600],
tailwindConfig().theme.colors.blue[500],
tailwindConfig().theme.colors.indigo[900],
],
hoverBorderColor: tailwindConfig().theme.colors.white,
})
}
return (
<div className="flex flex-col col-span-full sm:col-span-6 xl:col-span-4 bg-white shadow-lg rounded-sm border border-slate-200">
<header className="px-5 py-4 border-b border-slate-100">
<h2 className="font-semibold text-slate-800">Stage</h2>
</header>
{/* Chart built with Chart.js 3 */}
{/* Change the height attribute to adjust the chart height */}
<DoughnutChart data={chartData} width={389} height={260} />
</div>
);
}
The labels that I provide to ChartJS don't seem to be the problem but there is something with the datasets. For some reason, the code I wrote delivers 3 different arrays with seperated data points inside them (see screenshot).
Now, if I were to replace the data with some manually entered numbers like this:
chartData.labels.push(bucket);
chartData.datasets.push({
data: [5, 8, 13],
backgroundColor: [
tailwindConfig().theme.colors.indigo[500],
tailwindConfig().theme.colors.blue[400],
tailwindConfig().theme.colors.indigo[800],
],
hoverBackgroundColor: [
tailwindConfig().theme.colors.indigo[600],
tailwindConfig().theme.colors.blue[500],
tailwindConfig().theme.colors.indigo[900],
],
hoverBorderColor: tailwindConfig().theme.colors.white,
})
My datasets would look like this:
Even though I now have 3 copies of the same line which is not the idea, apparently this is the way ChartJS wants the datasets to be formated, yet I have no idea how to achieve that with my own code. Is there anyone that knows how I can transform my own data into 1 array structured the way ChartJS expects it?
Thank you very much for your time and effort in advance. Looking forward to getting this fixed!
Kind regards,
Bram
EDIT: Chart configuration
import React, { useRef, useEffect } from 'react';
import {
Chart, DoughnutController, ArcElement, TimeScale, Tooltip,
} from 'chart.js';
import 'chartjs-adapter-moment';
// Import utilities
import { tailwindConfig } from '../utils/Utils';
Chart.register(DoughnutController, ArcElement, TimeScale, Tooltip);
function DoughnutChart({
data,
width,
height
}) {
const canvas = useRef(null);
const legend = useRef(null);
useEffect(() => {
const ctx = canvas.current;
// eslint-disable-next-line no-unused-vars
const chart = new Chart(ctx, {
type: 'doughnut',
data: data,
options: {
cutout: '80%',
layout: {
padding: 24,
},
plugins: {
legend: {
display: false,
},
},
interaction: {
intersect: false,
mode: 'nearest',
},
animation: {
duration: 500,
},
maintainAspectRatio: false,
resizeDelay: 200,
},
plugins: [{
id: 'htmlLegend',
afterUpdate(c, args, options) {
const ul = legend.current;
if (!ul) return;
// Remove old legend items
while (ul.firstChild) {
ul.firstChild.remove();
}
// Reuse the built-in legendItems generator
const items = c.options.plugins.legend.labels.generateLabels(c);
items.forEach((item) => {
const li = document.createElement('li');
li.style.margin = tailwindConfig().theme.margin[1];
// Button element
const button = document.createElement('button');
button.classList.add('btn-xs');
button.style.backgroundColor = tailwindConfig().theme.colors.white;
button.style.borderWidth = tailwindConfig().theme.borderWidth[1];
button.style.borderColor = tailwindConfig().theme.colors.slate[200];
button.style.color = tailwindConfig().theme.colors.slate[500];
button.style.boxShadow = tailwindConfig().theme.boxShadow.md;
button.style.opacity = item.hidden ? '.3' : '';
button.onclick = () => {
c.toggleDataVisibility(item.index, !item.index);
c.update();
};
// Color box
const box = document.createElement('span');
box.style.display = 'block';
box.style.width = tailwindConfig().theme.width[2];
box.style.height = tailwindConfig().theme.height[2];
box.style.backgroundColor = item.fillStyle;
box.style.borderRadius = tailwindConfig().theme.borderRadius.sm;
box.style.marginRight = tailwindConfig().theme.margin[1];
box.style.pointerEvents = 'none';
// Label
const label = document.createElement('span');
label.style.display = 'flex';
label.style.alignItems = 'center';
const labelText = document.createTextNode(item.text);
label.appendChild(labelText);
li.appendChild(button);
button.appendChild(box);
button.appendChild(label);
ul.appendChild(li);
});
},
}],
});
return () => chart.destroy();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="grow flex flex-col justify-center">
<div>
<canvas ref={canvas} width={width} height={height}></canvas>
</div>
<div className="px-5 pt-2 pb-6">
<ul ref={legend} className="flex flex-wrap justify-center -m-1"></ul>
</div>
</div>
);
}
export default DoughnutChart;
I am new to react and trying to use Victory within a react app to make a simple scatter plot. I was working through the examples and cannot get the interactive portion of the scatter plot to work.
Here is my App.js
import ReactDOM from 'react-dom';
import { VictoryScatter } from 'victory';
import {sampleData} from './data_holder'
class App extends React.Component {
render() {
return (
<div>
<h3>Click Me</h3>
<VictoryScatter
style={{ data: { fill: "#c43a31" } }}
size={9}
labels={() => null}
events={[{
target: "data",
eventHandlers: {
onClick: () => {
return [
{
target: "data",
mutation: (props) => {
const fill = props.style && props.style.fill;
return fill === "black" ? null : { style: { fill: "black" } };
}
}, {
target: "labels",
mutation: (props) => {
return props.text === "clicked" ?
null : { text: "clicked" };
}
}
];
}
}
}]}
data={sampleData}
/>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
export default App
Sample data saved in .data_holder is here:
export const sampleData=[
{ x: 1, y: 2 },
{ x: 2, y: 3 },
{ x: 3, y: 5 },
{ x: 4, y: 4 },
{ x: 5, y: 7 }
]
The onClick event is happening (when I add a log statement in the onClick it is clearly shown), but the fill colors are not updating. Any help is much appreciated.
Use onPressIn in place of onClick will work.
It looks like the issue is caused by enabling strict mode for the component in react. I am uncertain of why, but the plot is able to update once I remove strict mode from react. I will update here once I know the specifics of interaction.
I am trying to insert a Link component inside a Tabulator table cell via a custom formatter.
Nothing is shown in the cell, as seen in the codesandbox.
Why can't the JSX be returned from a function? How can I achieve this?
const invoiceLinkFormatter = (cell, formatterParams) => { // <------ Custom formatter definition
let key = cell.getValue();
let link = `/invoices/${key}`;
return (<Link to={link}>{key}</Link>);
};
invoicesTable.current = new Tabulator(refInvoicesTable.current, {
columns: [
{
title: "Invoices",
field: "invoiceKey",
formatter: invoiceLinkFormatter // <------ Custom formatter use
},
{ title: "Dates", field: "invoiceDate" }
]
});
This approach works, but it defeats the purpose as the link leaves the react app and reloads everything.
const columns = [
{
title: "Invoice",
field: "invoiceKey",
formatter: "link",
formatterParams: { url: cell => { return "/invoices/" + cell.getValue() } }
},
{ title: "Date", field: "invoiceDate" },
];
Forgive me if I'm misunderstanding, but I believe you can use a combination of ReactDOM.render() and the onRendered parameter of Tabulator's custom formatters to help here.
import ReactDOM from "react-dom";
...
const journalLinkFormatter = (cell, formatterParams, onRendered) => {
onRendered(() => {
let key = cell.getValue();
let link = `/journals/${key}`;
ReactDOM.render(
<Link
style={{ color: "blue", fontWeight: "bold", background: "red" }}
to={link}
>
{key}
</Link>,
cell.getElement()
);
});
};
This is because the layout framework you are using parses the DOM on page load, so any elements added to the page after this point wont be parsed correctly unless they are added through the frameworks functions.
There is probably a function you can call on your layout framework to re-parse the DOM after Tabulator has formatted the cell.
As I am new to react i have been struggling to pass data from my state to the chartjs dynamically...what I need is when ever user updates the Textbox and asks for the Output, the Pie chart should update itself automatically according to the output...i have stored the output values in the state but Pie chart is not allowing me to Pass state as data...
Here's a Link to the Code Sandbox
https://codesandbox.io/s/autumn-mountain-sv1qk?fontsize=14&hidenavigation=1&theme=dark
class Buyer extends Component {
constructor(props) {
super(props);
this.state = {
income: 100000,
percent: 13,
totalTax: '',
grossPrice: '',
otherValues: '',
}
}
PieChart = (Gross,totalTax) => {
let GrossPrice = Gross ;
let TotalTax = totalTax ;
let data = [GrossPrice,TotalTax]
let Pie = {
labels: ['Total Tax', 'Gross Price'],
datasets: [{
data: data,
backgroundColor: [
'#1ca5b6',
'#89ba2b',
],
}]
}
}
handleIncome = (event) => {
let income = event.target.value
this.handleData(income, this.state.percent)
console.log(this.state)
}
handlePercent = (event) => {
let Percent = event.target.value
this.handleSliderData(this.state.income, Percent)
}
// From Slider
sliderIncome = (event) => {
this.setState({ income: event })
this.handleSliderData(event, this.state.percent)
// console.log(this.state)
}
sliderPercent = (event) => {
this.setState({ percent: event })
this.handleSliderData(this.state.income, event)
}
handleData = (income, Percent) => {
this.setState({
income: income,
percent: Percent,
totalTax: //some Calculations
grossPrice: //some Calculations
otherValues: //some Calculations
})
console.log(this.state)
}
handleSliderData = (income, Percent) => {
this.setState({
income: income,
percent: Percent,
totalTax: //some Calculations
grossPrice://some Calculations
otherValues://some Calculations
})
this.PieChart(this.state.grossPrice,this.state.totalTax)
// console.log(this.state)
}
render() {
return (
<div>
<div >
<Card s>
<PieChart data={this.PieChart} width={600} height={300} />
</Card>
</div>
</Col>
)
}
I have tried creating a function for the pie chart but was not able to get through...any help would be appreciated..thanks!!
I think there are a few problems with the code.
this.PieChart function doesn't return anything now. From giving the code a quick glance, I can see that you are trying to pass the props needed for the PieChart component from this.PieChart function. Return whatever you need as prop for the component and also call the function inside the JSX using parenthesis, passing the needed parameters into the function.
PieChart = (Gross,totalTax) => {
let GrossPrice = Gross ;
let TotalTax = totalTax ;
let data = [GrossPrice,TotalTax]
let Pie = {
labels: ['Total Tax', 'Gross Price'],
datasets: [{
data: data,
backgroundColor: [
'#1ca5b6',
'#89ba2b',
],
}]
}
}
return Pie; //or whatever data you need for the component
Also,
<PieChart data={this.PieChart(this.state.grossPrice, this.state.totalTax)} width={600} height={300} />
Also, keep in mind to use proper naming conventions. Functions should be in camel case( this.PieChart should be named this.pieChart ). Try using different names for the component and function. This shall solve a lot of problems you might run into
Update: I have updated the sandbox you have shared. Hope this helps.
https://codesandbox.io/s/friendly-mahavira-79n2s
I think i found the solution for the particular problem...i order to update the data in the piechart dynamically using state we have create a state in the constructor(props) like so...
constructor(props) {
super(props);
this.state = {
income: '',
percent: '',
totalTax: '',
grossPrice: '',
otherValues: '',
Data: {}
}
}
Then i have used componentDidMount() to mount the initial state of the PieChart like So....
componentDidMount() {
let runscore = [100,200];
this.setState(
{
Data: {
labels: [
'Purple',
'Yellow',
],
datasets: [
{
data: runscore,
backgroundColor: [
'#1ca5b6',
'#89ba2b',
]
}
]
}
});
}
then i created a function PieChart = (Gross,totalTax) that fetches data from another functions and used that to set state in Data like so...
PieChart = (Gross,totalTax) => {
let runscore = [Gross,totalTax];
this.setState({
Data: {
labels: [
'Gross Price',
'Total Tax',
],
datasets: [
{
data: runscore,
backgroundColor: [
'#1ca5b6',
'#89ba2b',
]
}
]
}
});
}
For now this is not updating the state in sync with the current state but i gets the work done...i hope this helps others...
I am using highcharts-react-official in react-redux to create a drilldown chart.
However when I click on a bar to drilldown, I also update some props which causes the component to re-render - which seems to prevent the drilldown event.
I kind of gathered from Change series data dynamically in react-highcharts without re-render of the chart that I should use shouldComponentUpdate and getChart() to prevent re-render and instead dynamically update the data.
My issue is that getChart() doesn't seem to work for the official highcharts react package. I'm getting Uncaught TypeError: this.refs.chart.getChart is not a function
Is there an alternative I'm meant to be using to get and dynamically update the chart? Or some examples that I could look at?
Just including render and shouldComponentUpdate parts here:
shouldComponentUpdate(nextProps, nextState) {
let chart = this.refs.chart.getChart();
//dynamically update data using nextProps
return false;
}
render () {
const options = {
chart: {
type: 'column',
height: 300,
events: {
drillup: (e) => {this.drilledUp(e)}
}
},
plotOptions: {
series: {
events:{
click: (e) => {this.categoryClicked(e)}
}
}
},
xAxis: {type: "category"},
yAxis: {title: {text: 'Amount Spent ($)'}},
series: [{
name: 'weekly spending',
showInLegend: false,
data: this.props.transactionChartData.series_data,
cursor: 'pointer',
events: {
click: (e)=> {this.weekSelected(e)}
}
}],
drilldown: {
series: this.props.transactionChartData.drilldown_data
}
};
return (
<HighchartsReact
highcharts={Highcharts}
options={options}
ref="chart"
/>
)
}
In highcharts-react-official v2.0.0 has been added allowChartUpdate option, which should work great in your case. By using this option you can block updating the chart with updating the component:
categoryClicked() {
this.allowChartUpdate = false;
this.setState({
...
});
}
...
<HighchartsReact
ref={"chartComponent"}
allowChartUpdate={this.allowChartUpdate}
highcharts={Highcharts}
options={...}
/>
Moreover, to get the chart instance use refs:
componentDidMount(){
const chart = this.refs.chartComponent.chart;
}
Live demo: https://codesandbox.io/s/98nl4pp5r4
// react native functional
import React, {useState, useRef,useLayoutEffect} from "react"
import HighchartsReactNative from '#highcharts/highcharts-react-native'
function chartcomponent(props){
const [options, setoptions] = useState({});
const chartRef = useRef(null);
useEffect(() => {
// create the options for your chart.
setoptions({chart:{}, yaxis:{}, xAxis:{},})// etc.
}, []);
useLayoutEffect(() => {
// new point to add, you can get new data via props, fetch, socket, etc.
var x = new Date(hr.timestamp).getTime();
var y = 10
// these are the important parts here:
var series = chartRef.current.props.options.series;
if (!Array.isArray(series)){return;}
var seriesdata = series[0].data;
seriesdata.push([x,y]);
// next line limits points in chart to 10, so new poitn replaces first point
if (seriesdata.length>10){seriesdata.splice(0,1);}
return () => {};
}, [props]);
return(
<View style={styles.charting}><HighchartsReactNative styles={{height:300, width:600}} options={options} ref={chartRef} ></HighchartsReactNative></View>
)
}