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>
)
}
Related
After implementing the drag and drop feature on AG Grid table, I'm looking for a way to get the current state with the updated order/index of rows. My goal is to persist the table data after changing the order, but can't find the respective state of the current order.
I'd appreciate any help or any idea.
Sandbox demo and example code below
import React from "react";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
function App() {
const [gridApi, setGridApi] = React.useState(null);
const [gridColumnApi, setGridColumnApi] = React.useState(null);
const onGridReady = (params) => {
setGridApi(params.api);
setGridColumnApi(params.columnApi);
};
const defaultColDef = {
flex: 1,
editable: true
};
const columnDefs = [
{
headerName: "Name",
field: "name",
rowDrag: true
},
{ headerName: "stop", field: "stop" },
{
headerName: "duration",
field: "duration"
}
];
const rowData = React.useMemo(
() => [
{
name: "John",
stop: 10,
duration: 5
},
{
name: "David",
stop: 15,
duration: 8
},
{
name: "Dan",
stop: 20,
duration: 6
}
],
[]
);
return (
<div>
<h1 align="center">React-App</h1>
<div>
<div className="ag-theme-alpine" style={{ height: "700px" }}>
<AgGridReact
columnDefs={columnDefs}
rowData={rowData}
defaultColDef={defaultColDef}
onGridReady={onGridReady}
rowDragManaged={true}
></AgGridReact>
</div>
</div>
</div>
);
}
export default App;
You can get the order of the rows inside the grid by iterating over them using the Grid API method forEachNode:
API for Row Nodes
const rows = [];
gridApi.forEachNodeAfterFilterAndSort((node) => rows.push(node.data));
console.log(rows);
See this implemented in the following sample.
You're currently using managed dragging by passing rowManagedDragging={true}, which means the AgGridReact component is managing the row order state.
If you want to maintain row order state outside the component, you need to use Unmanaged Dragging.
Add a handler for onRowDragMove, and use the node and overIndex or overNode properties of the event to update your local event order state, and pass it to the AgGridReact component to re-render.
Take a look at this example from the docs
I'm trying to use the documentation for adding context to my React Ag Grid app. My issue is that their code example doesn't use the way that I create the grid. This is how they add the context
var gridOptions = {
columnDefs: columnDefs,
defaultColDef: {
flex: 1,
resizable: true
},
rowData: rowData,
context: {
reportingCurrency: 'EUR'
},
};
// setup the grid after the page has finished loading
document.addEventListener('DOMContentLoaded', function() {
var gridDiv = document.querySelector('#myGrid');
new agGrid.Grid(gridDiv, gridOptions);
});
And they change the context value like this
function currencyChanged() {
var value = document.getElementById('currency').value;
gridOptions.context = {reportingCurrency: value};
gridOptions.api.refreshCells();
gridOptions.api.refreshHeader();
}
I'm using React though, so I'm creating my component like this
const [gridParams, setGridParams] = useState(null);
const onFirstDataRendered = (params) => {
setGridParams(params)
}
<AgGridReact
columnDefs={columnDefs}
defaultColDef={{
flex: 1,
resizable: true
}}
rowData={rowData}
context={{ reportingCurrency: 'EUR' }}
onFirstDataRendered={onFirstDataRendered}
>
This is how I'm changing context in my file
const currencyChange = (value) => {
gridParams.context = { reportingCurrency: value };
gridParams.api.refreshCells();
};
When I update the context like this, my cells don't see that the context has changed for them. Since the documentation doesn't show how to implement this for React, I figured I'm just missing something. Does anyone have any idea what that could be?
It looks like the way to change the context is actually different. After poking around, it looks like it needs to be changed like this
const currencyChange = (value) => {
gridParams.api.gridOptionsWrapper.gridOptions.context = { reportingCurrency: value };
gridParams.api.refreshCells();
};
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
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...