bd-peanut
Version:
React chart components
303 lines (256 loc) • 9.4 kB
JSX
var React = require('react');
var ReactDOM = require('react-dom');
var _ = require('lodash');
var Maths = require('../utils/Maths');
var Legend = require('./Legend.jsx');
var Column_series = require('./Column_series.jsx');
var ClassMixin = require('../mixins/ClassMixin');
var _MAX_TICKS = 10;
var Column = React.createClass({
displayName: 'Column',
mixins: [ClassMixin],
propTypes: {
data: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.object
]).isRequired,
colors: React.PropTypes.arrayOf(React.PropTypes.string),
displayAxis: React.PropTypes.bool,
xAxisLabel: React.PropTypes.func,
yAxisLabel: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.func
]),
yValueLabel: React.PropTypes.func,
columnStyle: React.PropTypes.func,
yMax: React.PropTypes.number,
yAxisMax: React.PropTypes.number
},
getDefaultProps: function() {
return {
data: [],
colors: ['#FDC855', '#FD9457'],
tickInterval: 1,
series: null,
xAxisLabel: null,
displayAxis: true,
yValueLabel: null,
yMax: null,
columnStyle: null
};
},
getInitialState: function () {
return {
tooltipValue: {},
mouseCoordinates: [0, 0],
tooTight: false
};
},
componentDidMount: function () {
this.setState({containerSize: ReactDOM.findDOMNode(this).offsetHeight});
},
// onMouseMove: function (e) {
// var dom = this.refs.wrapper.getDOMNode();
// var main = this.refs.main.getDOMNode();
// console.dir(main);
// var ct = e.currentTarget;
// var top = e.currentTarget.offsetTop + e.currentTarget.offsetHeight;
// // var left = e.currentTarget.offsetWidth;
// var left = 0;
// // console.log(ct.offsetWidth, ct.offsetLeft, e.clientX, dom.offsetLeft);
// this.setState({
// mouseCoordinates: [e.clientX - left, e.clientY - top]
// });
// },
onColumnWrapperOver: function () {
// this.setState({
// tooltipValue: value.series,
// });
},
onColumnOver: function (value) {
this.setState({
currentValue: value,
hover: true
});
},
onColumnOut: function (e) {
if(e.currentTarget === e.target) {
this.setState({hover: false});
}
},
getComputedValues: function(data, tickInterval) {
data = data || this.props.data;
// Convert immutable to JS
if (data.size && data.toJS) {
data = data.toJS();
}
tickInterval = tickInterval || this.props.tickInterval;
var _values = _.chain(data)
.map('series')
.flatten()
.remove(null)
.value();
var extent = Maths.extent(_values);
var max = extent[1] || 0;
if(this.props.yAxisMax) {
max = this.props.yAxisMax;
}
var roundToNearest = tickInterval;
var roundMax = Math.ceil(max / roundToNearest) * roundToNearest;
var numberOfTicks = roundMax / tickInterval;
if (numberOfTicks > _MAX_TICKS) {
var nextTickInterval = tickInterval * 2;
// To avoid going over the yMax
if (this.props.yMax && roundMax > this.props.yMax) {
if (this.props.tickInterval === 1 || this.props.tickInterval % 2 === 0) {
nextTickInterval = 5;
}
else {
nextTickInterval = 1;
}
}
// Keep increasing the tickInterval until we get less than the _MAX_TICKS ticks
return this.getComputedValues(data, nextTickInterval);
}
return {
min: extent[0] || 0,
max: roundMax,
numberOfTicks: numberOfTicks
};
},
render: function () {
var classes = this.createClassName('Column');
var data = this.props.data;
// Convert immutable to JS
if (data.size && data.toJS) {
data = data.toJS();
}
var computedValues = this.getComputedValues(data, this.props.tickInterval);
return (
<div ref="main"className={classes.className}>
{this.renderLegend()}
{this.renderYAxis(computedValues)}
{this.renderColumns(data, computedValues)}
{this.renderValueBar(computedValues)}
{this.renderTooltip(this.state.tooltipValue)}
</div>
);
},
renderLegend: function () {
if (!this.props.legend) {
return null;
}
return <Legend legend={this.props.legend} colors={this.props.colors}></Legend>;
},
renderLegendItem: function (series, key) {
return <li className="Column_seriesItem" key={key} data-label={series} style={{borderColor: this.props.colors[key]}}></li>;
},
renderYAxis: function (computedValues) {
if (this.props.displayAxis) {
var range = [];
if (computedValues.numberOfTicks > 0) {
range = Maths.tickRange(computedValues.numberOfTicks, 0, computedValues.max);
}
return (
<div className="Column_yaxis">
<div className="Graph_ytitle">{this.props.yAxisLabel}</div>
{_.map(range, function(key, i) {
var lbl = key;
if (this.props.yValueLabel) {
lbl = this.props.yValueLabel(key, i);
}
var style = {
height: (100 / (range.length - 1) + '%')
};
return <div key={i} className="Column_ylabel" style={style}>{lbl !== undefined ? lbl : key}</div>;
}.bind(this))}
</div>
);
}
},
renderColumns: function (data, computedValues) {
var columns = _.map(data, (column, columnIndex) => {
var stackSize = 0;
var totalStackSize = 0;
column.series.forEach(function(val) {
totalStackSize += val;
});
var series = _.map(column.series, (vv, ii) => {
stackSize += vv;
return this.renderSeries(vv, ii, column, computedValues, stackSize, totalStackSize);
}).reverse();
// console.log(series);
// var seriesOrder = (this.props.type === 'accumulative') ? series.reverse() : series;
return (
<div key={columnIndex} className="Column_col" style={{width: 100 / data.length + '%'}}>
<div className="Column_seriesWrapper" onMouseOver={this.onColumnWrapperOver.bind(this, column)}>
{series}
</div>
<div className="Graph_xlabel Column_xlabel">{this.renderXLabel(column, columnIndex)}</div>
</div>
);
});
return (
<div className="Column_wrapper">{columns}</div>
);
},
renderXLabel: function (column, columnIndex) {
var xlabel = column.xlabel;
if (this.props.displayAxis) {
if (this.props.xAxisLabel) {
xlabel = this.props.xAxisLabel(column.xlabel, columnIndex);
}
return xlabel !== undefined ? xlabel : column.xlabel;
}
// return "\u00a0"; // Non breaking space;
},
renderSeries: function(value, seriesIndex, column_data, computedValues, stackSize, totalStackSize) {
var yPos = stackSize - value;
var yPercent = (yPos / totalStackSize) * 100;
var newValue = value;
if(this.props.type === 'stacked') {
newValue = value + yPos;
}
return <Column_series
value={newValue}
yPos={yPercent}
key={seriesIndex}
seriesIndex={seriesIndex}
containerSize={this.state.containerSize}
column_data={column_data}
columnStyle={this.props.columnStyle}
colors={this.props.colors}
computedValues={computedValues}
/>;
},
renderValueBar: function () {
var style = {
top: this.state.currentValue + '%',
opacity: 0
};
if(this.state.hover) {
style.opacity = 1;
}
return <div className="Column_valueBar" style={style}></div>;
},
renderTooltip: function (data) {
var style = {
top: this.state.mouseCoordinates[1],
left: this.state.mouseCoordinates[0],
opacity: 0
},
series;
if(this.state.hover) {
style.opacity = 1;
}
if(data) {
series = _.map(data, function (value, key) {
return this.renderSeriesItem(value, key);
}.bind(this));
}
return <div className="Column_tooltip" style={style}>
<ul>{series}</ul>
</div>;
}
});
module.exports = Column;