Open hemm1 opened 9 years ago
Thank you, I am glad the library is useful for you!
I think that should be possible. Let me try around how to do it and I will come back to you. Are you embedding the Chart in a another React Component?
Great, thanks!
Yes, our whole application is written in React. I was briefly trying out something myself, but it couldn't quickly figure out a way of knowing the width of the containing component before it is actually rendered, because I guess it will need to render all it's child components before it actually can be rendered itself. I'm quite new to React, though, so there is a good chance that this is still possible.
I actually implemented something for this a couple of days ago, take a look (ES6 ahead):
let AreaGraph = React.createClass({
mixins: [React.addons.PureRenderMixin],
getInitialState() {
return {
parentWidth: 0
}
},
getDefaultProps() {
return {
width: '100%',
height: 300,
margin: { left: -1, top: 10, bottom: 0, right: 1 }
}
},
handleResize(e) {
let elem = this.getDOMNode();
let width = elem.offsetWidth;
this.setState({
parentWidth: width
});
},
componentDidMount() {
if(this.props.width === '100%') {
window.addEventListener('resize', this.handleResize);
}
this.handleResize();
},
componentWillUnmount() {
if(this.props.width === '100%') {
window.removeEventListener('resize', this.handleResize);
}
},
render() {
let { width, height, margin, xScale, yScale, xAxis, ...props } = this.props;
// Determine the right graph width to use if it's set to be responsive
if(width === '100%') {
width = this.state.parentWidth || 400;
}
// Set scale ranges
xScale && xScale.range([0, width - (margin.left + margin.right)]);
yScale && yScale.range([height - (margin.top + margin.bottom), 0]);
return (
<div className={"usage__cpu__graph "+props.className}>
<AreaChart
ref="chart"
width={width}
height={height}
margin={margin}
xScale={xScale}
yScale={yScale}
xAxis={xAxis}
{...props}
/>
</div>
);
}
})
This could definitely be improved on, most notably by debouncing the the resize event with requestAnimationFrame
to prevent janking, but overall it works well
Cool! That's how I was thinking to do it too, with resize listeners. Very nice! You could also use RxJS to get the resize event and debounce with that :) I am thinking of breaking apart the Chart components a little bit more, because I thought it would be cool if we could just render a line chart inside the brush for example (like: http://bl.ocks.org/mbostock/1667367 ) And then making a resizable version of the Chart would be very nice! In case this library manages to get 500 stars I will be able to work 1 day per week full time on it :)
Wow, cool fix, Craga89! I will definitely try this out on our when I get the time!
You have gotten a few more stars from our team at least, codesuki ;) Keep up the good work :+1:
Thanks! I Appreciate that :) Next on my list are Transitions and Zoom.
I just contributed with 1 star ;) But I think we really need something easier to handle dynamic sizes. I'd like to use 100% for width without having to have this logic in all my components. Is that possible?
Thanks! And yes, I think that is possible. Recently have been too busy with other things but this week I should have some time again to improve everything.
Thanks!
Any update on this @codesuki would love to see this feature.
Just started a project importing this library, but really wanted to use variable widths. Any update on native support for this yet? Is Craga89's example of extending some of the existing components in the library the way to go?
It's the way to go. I am still working on improving all the parts (slowly these days) but since I will probably implement it in the same or a very similar way it's just a matter of maybe removing code in the future.
awesome! thanks for the note.
hopefully I can contribute to the repo. :D
That would be great and very welcome!
Based on @Craga89 's code, I wrote a wrapper to make the charts responsive universally.
How to use
import {ResponsiveBarChart
ResponsiveLineChart,
ResponsiveAreaChart,
ResponsiveScatterPlot,
ResponsivePieChart} from './ResponsiveChart';
// the <div> tag is used to position and wrap the chart.
<div style={myPositionStyle}>
<ResponsiveLineChart data={myData} />
</div>
Code
'use strict';
import React from 'react';
import D3 from 'react-d3-components';
let {
BarChart,
LineChart,
AreaChart,
ScatterPlot,
PieChart
} = D3;
let createClass = function (chartType) {
return React.createClass({
getDefaultProps () {
return {
margin: {top: 25, bottom: 25, left: 25, right: 25}
};
},
getInitialState () {
return {
size: {w: 0, h: 0}
};
},
componentDidMount () {
this.fitToParentSize();
},
componentWillReceiveProps () {
this.fitToParentSize();
},
fitToParentSize () {
let elem = this.getDOMNode();
let w = elem.parentNode.offsetWidth;
let h = elem.parentNode.offsetHeight;
let currentSize = this.state.size;
if (w !== currentSize.w || h !== currentSize.h) {
this.setState({
size: {
w: w,
h: h
}
});
}
},
getChartClass () {
let Component;
switch (chartType) {
case 'BarChart':
Component = BarChart;
break;
case 'LineChart':
Component = LineChart;
break;
case 'AreaChart':
Component = AreaChart;
break;
case 'ScatterPlot':
Component = ScatterPlot;
break;
case 'PieChart':
Component = PieChart;
break;
default:
console.error('Invalid Chart Type name.');
break;
}
return Component;
},
render () {
// Component name has to start with CAPITAL
let Component = this.getChartClass();
let {width, height, margin, ...others} = this.props;
width = this.state.size.w || 100;
height = this.state.size.h || 100;
return (
<Component
width = {width}
height = {height}
margin = {margin}
{...others}
/>
);
}
});
};
let ResponsiveBarChart = createClass('BarChart');
let ResponsiveLineChart = createClass('LineChart');
let ResponsiveAreaChart = createClass('AreaChart');
let ResponsiveScatterPlot = createClass('ScatterPlot');
let ResponsivePieChart = createClass('PieChart');
export {
ResponsiveBarChart,
ResponsiveLineChart,
ResponsiveAreaChart,
ResponsiveScatterPlot,
ResponsivePieChart
};
export default {
ResponsiveBarChart: ResponsiveBarChart,
ResponsiveLineChart: ResponsiveLineChart,
ResponsiveAreaChart: ResponsiveAreaChart,
ResponsiveScatterPlot: ResponsiveScatterPlot,
ResponsivePieChart: ResponsivePieChart
};
That was fast! Thanks for contributing! I will integrate your code if that's OK with you. As soon as I find some time.
I'm glad that I can help! :)
@codesuki any ETA on the responsive chart? Would love to see this feature.
A little bird told me there are some contributions coming in soon and this is on the list as far as I know. Personally I am out of resources :(
Could just copy and paste the code from above until then. Let me see.
I added resizing event listener to @f15gdsy 's wrapper. 😄
import React from 'react';
import ReactDOM from 'react-dom';
import D3 from 'react-d3-components';
const {
BarChart,
LineChart,
AreaChart,
ScatterPlot,
PieChart,
} = D3;
const createClass = (chartType) => {
class Chart extends React.Component {
constructor() {
super();
this.state = { size: { w: 0, h: 0 } };
}
fitToParentSize() {
const elem = ReactDOM.findDOMNode(this);
const w = elem.parentNode.offsetWidth;
const h = elem.parentNode.offsetHeight;
const currentSize = this.state.size;
if (w !== currentSize.w || h !== currentSize.h) {
this.setState({
size: { w, h },
});
}
}
getChartClass() {
let Component;
switch (chartType) {
case 'BarChart':
Component = BarChart;
break;
case 'LineChart':
Component = LineChart;
break;
case 'AreaChart':
Component = AreaChart;
break;
case 'ScatterPlot':
Component = ScatterPlot;
break;
case 'PieChart':
Component = PieChart;
break;
default:
console.error('Invalid Chart Type name.');
break;
}
return Component;
}
componentDidMount() {
window.addEventListener('resize', ::this.fitToParentSize);
this.fitToParentSize();
}
componentWillReceiveProps() {
this.fitToParentSize();
}
componentWillUnmount() {
window.removeEventListener('resize', ::this.fitToParentSize);
}
render() {
let Component = this.getChartClass();
let { width, height, margin, ...others } = this.props;
width = this.state.size.w || 100;
height = this.state.size.h || 100;
return (
<Component
width = {width}
height = {height}
margin = {margin}
{...others}
/>
);
}
}
Chart.defaultProps = {
margin: {
top: 50,
bottom: 50,
left: 50,
right: 50,
},
};
Chart.propTypes = {
width: React.PropTypes.number,
height: React.PropTypes.number,
margin: React.PropTypes.object,
};
return Chart;
};
const ResponsiveBarChart = createClass('BarChart');
const ResponsiveLineChart = createClass('LineChart');
const ResponsiveAreaChart = createClass('AreaChart');
const ResponsiveScatterPlot = createClass('ScatterPlot');
const ResponsivePieChart = createClass('PieChart');
export {
ResponsiveBarChart,
ResponsiveLineChart,
ResponsiveAreaChart,
ResponsiveScatterPlot,
ResponsivePieChart,
};
export default {
ResponsiveBarChart,
ResponsiveLineChart,
ResponsiveAreaChart,
ResponsiveScatterPlot,
ResponsivePieChart,
};
I'll add that in the project. I hope that's ok!
😄
Note: to get this to work with React 15, you'll need to replace
const elem = ReactDOM.findDOMNode(this);
with
import ReactDOM from 'react-dom';
...
let elem = ReactDOM.findDOMNode(this);
Cool library!
We are trying to use this on our project to create a pretty straightforward bar chart, but we are having a hard time making the chart responsive. It seems to me that there is no way around specifying the width and the height of a chart when you create it. We might be missing something, though.
For now, we are trying out a workaround where we specify the width and height of the BarChart based on the viewport height and width. I am not completely satisfied with it. In my mind, it would be ideal if there was a way to make the chart stretch to 100% of its container's size. Do you have any plans to implement something like that? Or do you think it is even technically possible?