I want to create a char using c3 in React to, later on, be updated each second.
I am trying to follow the example provided by
Updating C3 charts on props change with React, but the first step, which is to create the chart, is not happening.
This is my fiddle:
https://jsfiddle.net/69z2wepo/227446/
import c3 from 'c3';
import React from "react";
import ReactDOM from "react-dom";
class Hello extends React.Component {
renderChart() {
this.chart = c3.generate({
bindto:"#chart1",
data: {
columns: [
['data1', 30, 200, 100, 400, 150, 250],
['data2', 50, 20, 10, 40, 15, 25]
]
}
});
}
render() {
this.renderChart()
return <div id="chart1"></div>;
}
}
ReactDOM.render(
<Hello />,
document.getElementById('container')
);
I installed c3 with npm and am importing it in the component.
Thanks for the help.
In your example, it looks like the chart is generated before the selector div is even rendered, so the chart has no where to go. Instead of calling this.renderChart() in render(), you can call it componentDidMount. In that case, render will be called on the initial load, your <div id="chart1"></div> will be rendered and then renderChart will run, adding the SVG to the div.
As for updating the data, you can move the column data itself to state, then call setState with some new data and use componentDidUpdate to rerender the chart. That might look something like this:
class Chart extends React.Component {
constructor(props) {
super(props);
this.state = {
column1: ['data1', 30, 200, 100, 400, 150, 250],
column2: ['data2', 50, 20, 10, 40, 15, 25],
};
this.changeData = this.changeData.bind(this);
}
renderChart() {
c3.generate({
bindto: "#chart1",
data: {
columns: [this.state.column1, this.state.column2],
},
});
}
componentDidMount() {
this.renderChart();
}
componentDidUpdate() {
this.renderChart();
}
// Changes data to something arbitrary on button click
changeData() {
this.setState({
column1: ['data1', 70, 120, 30, 300, 230, 300],
column2: ['data2', 100, 120, 50, 140, 150, 80],
});
}
render() {
return (
<div>
<div id="chart1"></div>
<button onClick={this.changeData}>Change</button>
</div>
);
}
}
React Lifecycle methods are key here. Here's a handy chart linked from the docs: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
Related
When hovering over a line in billboardjs you can see a marker which follows the mouse (a tall vertical line). Is there a function for putting a marker on the x-line which can be used without triggering an automatic marker via onmousemove/hovering over data-points?
var chart = bb.generate({
data: {
columns: [
["data1", 30, 200, 100, 400, 150, 250],
["data2", 50, 20, 10, 40, 15, 25]
],
type: "line", // for ESM specify as: line()
},
bindto: "#lineChart"
});
https://naver.github.io/billboard.js/demo/#Chart.LineChart
So to exemplify. I use an onclick (in the data object) in the chart which defocuses the view and I still want the marker to remain.
So the code would look something like:
var chart = bb.generate({
data: {
columns: [
["data1", 30, 200, 100, 400, 150, 250],
["data2", 50, 20, 10, 40, 15, 25]
],
type: "line", // for ESM specify as: line()
onclick: function (d) {
focusElsewhere()
showMarker(d.x)
}
},
bindto: "#lineChart"
});
So the question is if there is a function for this, or an obvious fix?
I have looked through https://naver.github.io/billboard.js/release/latest/doc/Chart.html but I may of course have missed something.
I found that using xgrids did the trick. I don't think that the documentation gives a good example of how to use it. But basically you can use the "value" field to give which point the line should be on and add a class to show different kinds of lines.
var chart = bb.generate({
data: {
columns: [
["data1", 30, 200, 100, 400, 150, 250],
["data2", 50, 20, 10, 40, 15, 25]
],
type: "line", // for ESM specify as: line()
onclick: function (d) {
focusElsewhere()
this.xgrids.add({ value: d.x, class: "hover-line" }); //showMarker(d.x)
}
},
bindto: "#lineChart"
});
To remove the line or reset the billboard for continued use so to say, you can use
xgrids․remove({}) and add an object with some parameters of what kind of lines you want to remove.
I have my main App.js component rendering a scatterplot with D3, which in turn renders a barplot, also with D3
When a point on the scatterpoint is clicked, based on the value of the point, one of 2 barplots is generated (on first click), or updated with new data (on subsequent clicks).
With the simplified code below, instead of updating the data on the original chart, a new bar plot is being appended to the div element with each click. How do I alter the componentDidUpdate() code in the barplot.js section so that the original barplot chart is updated?
//Data is input here
const scatterPlotData = [
[5, 20], [48, 90], [250, 50], [100, 33], [330, 95],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88]
];
const bardata1 = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
const bardata2 = [ 1, 20, 33, 5, 7, 25, 22, 10, 1, 23,31, 2, 5, 40, 28, 27, 6, 8, 3, 5 ];
class ScatterPlot extends Component {
createScatterPlot() {
//d3 code here, relevant part which determines which barplot data to select
.on("click", function(d) {
//If x value of scatterplot point is >= 200, choose bar plot data above
if (d[0] >= 200) {
component.setState({ bardata: bardata2, event: d3.event });
} else {
component.setState({ bardata: bardata1, event: d3.event });
}
})
}
render() {
return (
<div>
{
//BarPlot class is called
this.state.bardata && this.state.event &&
<BarPlot bardata={this.state.bardata}/>
}
</div>
)
}
}
class BarPlot extends Component {
constructor(props){
super(props)
this.createBarPlot = this.createBarPlot.bind(this)
}
componentDidMount() {
this.createBarPlot()
}
componentDidUpdate(prevProps) {
if(JSON.stringify(this.props.bardata) !== JSON.stringify(prevProps.bardata)) {
this.setState({bardata: null});
this.createBarPlot()
}
}
createBarPlot() {
//d3 code here
}
render() {
return (
<div>
</div>
)
}
}
//Main app which calls the ScatterPlot class
class App extends Component {
constructor(props) {
super(props)
this.state = {
pointdata: pointdata1
}
}
render() {
return (
<div className="wrapper">
<header className="App-header">
Scatter Plot via React
</header>
<div id = 'plot-1'>
<ScatterPlot pointdata={this.state.pointdata} />
</div>
<div id="barplot"></div>
</div>
);
}
I've tried making a radar chart using react-chartjs (https://github.com/reactjs/react-chartjs). It renders, but there are no colors.
What am I missing? I pretty much copied a large chunk of the example at https://reactcommunity.org/react-chartjs/index.html. (I simplified the data to one dataset.)
import React, {PropTypes} from 'react';
import {Grid} from 'react-bootstrap';
const {Radar} = require("react-chartjs");
const ReactDOM = require('react-dom');
function rand(min, max, num) {
var rtn = [];
while (rtn.length < num) {
rtn.push((Math.random() * (max - min)) + min);
}
return rtn;
}
var chartData = {
labels: ["Eating", "Drinking", "Sleeping", "Designing", "Coding", "Cycling", "Running"],
datasets: [
{
label: "My First dataset",
backgroundColor: "rgba(179,181,198,0.2)",
borderColor: "red",
pointBackgroundColor: "rgba(179,181,198,1)",
pointBorderColor: "#fff",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(179,181,198,1)",
data: [65, 59, 90, 81, 56, 55, 40]
}
]
};
var chartOptions = {
scale: {
reverse: true,
ticks: {
beginAtZero: true
}
}
};
function TasteGraph({rating}) {
//loop through and output one slider and one value per component
return (
<div>
<Radar data={chartData} options={chartOptions}/>
</div>
);
}
TasteGraph.propTypes = {
rating: PropTypes.array
};
TasteGraph.defaultProps = {
rating: []
};
export default TasteGraph;
There doesn't seem to be any imports missing or clear error. I tried surrounding the chartOptions and ChartData with "[" and "]" based on another related SO question.
Replace backgroundColor with fillColor. Propably your borderColor should be also replaced with strokeColor.
See in this jsfiddle. (It uses chart.js without react wrapper - but your properties gave same output as in your screenshot)
well i have a application when this use a dynamic tooltip, this have to change the design depending of the values on the graph, but add inner html into the content of the tooltip is being very hard to refactor because i have a lot of design for the tooltip into many conditions and variables
i tried importing a component but the content function expect only a string with the html
so i wondering if is possible to add jsx syntax or add a react component to render the html of tooltip into the content of c3
data: {
columns: [
['data1', 300, 350, 300, 0, 0, 0],
['data2', 130, 100, 140, 200, 150, 50]
],
types: {
data1: 'area',
data2: 'area-spline'
}
},
axis: {
y: {
padding: {bottom: 0},
min: 0
},
x: {
padding: {left: 0},
min: 0,
show: false
}
},
tooltip: {
contents: function () {
// call a function that return a react component
}
}
You can use ReactDomServer.renderToString to render this https://www.npmjs.com/package/react-dom#react-domserver
However i am not sure if this a good solution, it was not designed for this purpose. (if you check console, you can see there still is an error).
Here is working fiddle of using React component in c3js tooltip.
http://jsfiddle.net/uqh2679x/
But still you can achieve same thing using template literal:
Working fiddle: http://jsfiddle.net/z7evqpnL/
tooltip: {
contents: (data) => `
<div class="tooltip">
<div class="title">${customText}</div>
<div>Data1: ${data[0].value}</div>
<div>Data2: ${data[1].value}</div>
</div>`
}
}
When you use `` you can put variables inside the string like above ${variable}. Also you can break this string into multiple lines so it is more readable (it will also preserve all line breaks if you i.e. log it)
I'm seeing an awkward bug using a third party library inside of a react component. I was able to reproduce a contrived demo for this post.
Let me start by explaining that I am using c3js - which is a charting library and rendering it in componentDidMount() and removing it in componentWillUnmount() with the correct calls to this.chart.destroy().
The bug occurs when filtering the components themselves, essentially what happens is the components are filtered correctly but the actual chart that sits inside the component stays the same as the first chart, which is very strange behaviour. Basically it's the wrong chart inside the wrong component!
You can see what I mean by clicking on the Remove all charts except chart 3 button, I have labeled the charts with a chartid and the filtering will correctly remove the other charts.
I am fairly certain it isn't my code because the filtering works correctly and updates the view. You can verify because I have labeled the charts and it is visible in the view. There is no console errors and I have verified my code works.
So my question is - can we work around this limitation using c3js, or is this really a problem with my code and the way I am rendering the charts.
Related demo: https://jsfiddle.net/69z2wepo/38614/
Related code:
var data = [
{
chartid: 1,
columns: [
['x', 0, 1, 2, 3, 4, 5, 6],
['data1', 130, 300, 330, 400, 300, 400, 500],
['data2', 390, 230, 200, 150, 100, 130, 210],
['data3', 290, 430, 300, 160, 210, 170, 190],
['data4', 190, 330, 200, 260, 190, 250, 320]
]
},
{
chartid: 2,
columns: [
['x', 0, 1, 2, 3, 4, 5, 6],
['data1', 130, 300, 330, 400, 300, 400, 500],
['data2', 390, 230, 200, 150, 100, 130, 210],
['data3', 290, 430, 300, 160, 210, 170, 190]
]
},
{
chartid: 3,
columns: [
['x', 0, 1, 2, 3, 4, 5, 6],
['data1', 130, 300, 330, 400, 300, 400, 500],
['data2', 390, 230, 200, 150, 100, 130, 210]
]
}
];
var Button = React.createClass({
handleDelete: function (id) {
this.props.handleDelete(id);
},
render: function() {
return (
<button onClick={this.handleDelete.bind(null, 3)}>
Remove all charts except chart 3
</button>
)
}
});
var Chart = React.createClass({
componentDidMount() {
this.chart = c3.generate({
bindto: '.chart-' + this.props.data.chartid,
data: {
columns: this.props.data.columns
}
});
},
componentWillUnmount() {
this.chart.destroy();
},
render: function() {
return (
<div>
<h4>{"chart-" + this.props.data.chartid}</h4>
<div className={"chart-" + this.props.data.chartid}>
</div>
</div>
)
}
});
var Child = React.createClass({
renderCharts: function(data) {
return data.map(function(metrics, i) {
return (
<Chart key={i} data={metrics} />
)
});
},
handleDelete: function(id) {
this.props.handleDelete(id);
},
render: function() {
return (
<div>
<Button handleDelete={this.handleDelete} />
{this.renderCharts(this.props.data)}
</div>
)
}
})
var App = React.createClass({
getInitialState: function() {
return {
initialData: this.props.data
}
},
handleDelete: function(id) {
var _filterFunc = function(data) {
if (data.chartid == id) return true;
return false;
};
var _filterCharts = Array.prototype.filter.call(this.state.initialData, _filterFunc);
this.setState({
initialData: _filterCharts
})
},
render: function() {
return (
<div>
<Child handleDelete={this.handleDelete} data={this.state.initialData} />
</div>
);
}
});
ReactDOM.render(
<App data={data} />,
document.getElementById('container')
);
The problem is the way you are setting the key on your chart. It's causing the renderer to get confused about which chart you're trying to keep.
Try this:
<Chart key={data[i].chartid} data={metrics} />
instead of <Chart key={i} data={metrics} />
Take a look at how React handles keys. Remember that you're uniquely identifying a child with a key for the lifecycle of the component. So since chart 1 is uniquely identified by key "1", you can't render chart 3 with key "1." My solution above ensures that the chart is uniquely identified by its chart id instead of by its rendering order.