Rewrite vertical bar chart to use recharts

This commit is contained in:
Cmdr McDonald
2017-03-21 14:46:35 +00:00
parent eb042b2778
commit 3f18987007
4 changed files with 71 additions and 97 deletions

View File

@@ -90,16 +90,17 @@
}, },
"dependencies": { "dependencies": {
"babel-polyfill": "*", "babel-polyfill": "*",
"classnames": "^2.2.0",
"browserify-zlib": "ipfs/browserify-zlib", "browserify-zlib": "ipfs/browserify-zlib",
"classnames": "^2.2.0",
"coriolis-data": "EDCD/coriolis-data", "coriolis-data": "EDCD/coriolis-data",
"d3": "4.6.0", "d3": "4.6.0",
"fbemitter": "^2.0.0", "fbemitter": "^2.0.0",
"lodash": "^4.15.0", "lodash": "^4.15.0",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"react": "^15.0.1", "react": "^15.0.1",
"react-dom": "^15.0.1", "react-dom": "^15.0.1",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"recharts": "^0.21.2",
"superagent": "^1.4.0" "superagent": "^1.4.0"
} }
} }

View File

@@ -241,14 +241,19 @@ export default class Coriolis extends React.Component {
/** /**
* Show the term tip * Show the term tip
* @param {string} term Term or Phrase * @param {string} term Term or Phrase
* @param {Object} opts Options - dontCap, orientation (n,e,s,w) * @param {Object} opts Options - dontCap, orientation (n,e,s,w) (can also be the event if no options supplied)
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
* @param {SyntheticEvent} e2 Alternative location for synthetic event from charts (where 'Event' is actually a chart index)
*/ */
_termtip(term, opts, event) { _termtip(term, opts, event, e2) {
if (opts && opts.nativeEvent) { // Opts is a SyntheticEvent if (opts && opts.nativeEvent) { // Opts is the SyntheticEvent
event = opts; event = opts;
opts = { cap: true }; opts = { cap: true };
} }
if (e2 instanceof Object && e2.nativeEvent) { // E2 is the SyntheticEvent
event = e2;
}
this._tooltip( this._tooltip(
<div className={'cen' + (opts.cap ? ' cap' : '')}>{this.state.language.translate(term)}</div>, <div className={'cen' + (opts.cap ? ' cap' : '')}>{this.state.language.translate(term)}</div>,
event.currentTarget.getBoundingClientRect(), event.currentTarget.getBoundingClientRect(),

View File

@@ -165,7 +165,7 @@ export default class Defence extends TranslatedComponent {
</div> </div>
<div className='group quarter'> <div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2> <h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
<VerticalBarChart data={shieldDamageTakenData} yMax={100} /> <VerticalBarChart data={shieldDamageTakenData} yMax={140} />
</div> </div>
<div className='group quarter'> <div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2> <h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>

View File

@@ -1,12 +1,11 @@
import React, { Component } from 'react';
import Measure from 'react-measure';
import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import React, { PropTypes } from 'react';
import Measure from 'react-measure';
import { BarChart, Bar, XAxis, YAxis } from 'recharts';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D']; const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000'; const LABEL_COLOUR = '#000000';
const AXIS_COLOUR = '#C06400';
const margin = { top: 10, right: 0, bottom: 0, left: 55 };
const ASPECT = 1; const ASPECT = 1;
@@ -20,8 +19,8 @@ const merge = function(one, two) {
export default class VerticalBarChart extends TranslatedComponent { export default class VerticalBarChart extends TranslatedComponent {
static propTypes = { static propTypes = {
data : React.PropTypes.array.isRequired, data : PropTypes.array.isRequired,
yMax : React.PropTypes.number yMax : PropTypes.number
}; };
/** /**
@@ -32,6 +31,8 @@ export default class VerticalBarChart extends TranslatedComponent {
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this._termtip = this._termtip.bind(this);
this.state = { this.state = {
dimensions: { dimensions: {
width: 300, width: 300,
@@ -41,97 +42,64 @@ export default class VerticalBarChart extends TranslatedComponent {
} }
/** /**
* Render the graph * Render the bar chart
* @param {Object} props React Component properties * @returns {Object} the markup
*/
_renderGraph(props) {
let { width, height } = this.state.dimensions;
const { tooltip, termtip } = this.context;
width = width - margin.left - margin.right,
height = width * ASPECT - margin.top - margin.bottom;
// X axis is a band scale with values being 'label'
this.x = d3.scaleBand();
this.x.domain(this.props.data.map(d => d.label)).padding(0.2);
this.xAxis = d3.axisBottom(this.x).tickValues(this.props.data.map(d => d.label));
this.x.range([0, width]);
// Y axis is a numeric scale with values being 'value'
this.y = d3.scaleLinear();
if (props.yMax) {
// Fixed maximum value (unless we go off the scale)
const localMax = d3.max(this.props.data, d => d.value);
this.y.domain([0, localMax > props.yMax ? localMax : props.yMax]);
} else {
this.y.domain([0, d3.max(this.props.data, d => d.value)]);
}
this.yAxis = d3.axisLeft(this.y);
this.y.range([height, 0]);
let svg = d3.select(this.svg).select('g');
svg.selectAll('rect').remove();
svg.selectAll('text').remove();
svg.select('.x.axis').remove();
svg.select('.y.axis').remove();
svg.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(0, ${height})`)
.call(this.xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(this.yAxis)
.attr('fill', CORIOLIS_COLOURS[0]);
svg.selectAll('rect.bar')
.data(props.data)
.enter().append('rect')
.attr('class', 'bar')
.attr('x', d => this.x(d.label))
.attr('width', this.x.bandwidth())
.attr('y', d => this.y(d.value))
.attr('height', d => height - this.y(d.value))
.attr('fill', CORIOLIS_COLOURS[0]);
svg.selectAll('text.bar')
.data(props.data)
.enter().append('text')
.attr('class', 'bar')
.attr('text-anchor', 'middle')
.attr('x', 100)
.attr('y', 100)
.attr('stroke-width', '0px')
.attr('fill', LABEL_COLOUR)
.attr('x', d => this.x(d.label) + this.x.bandwidth() / 2)
.attr('y', d => this.y(d.value) + 15)
.text(d => d.value);
}
/**
* Render the component
* @returns {object} Markup
*/ */
render() { render() {
const { width } = this.state.dimensions; const { width, height } = this.state.dimensions;
const translate = `translate(${margin.left}, ${margin.top})`; const { tooltip, termtip } = this.context;
const height = width * ASPECT; // Calculate maximum for Y
let dataMax = Math.max(...this.props.data.map(d => d.value));
this._renderGraph(this.props); if (dataMax == -Infinity) dataMax = 0;
let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0;
const localMax = Math.max(dataMax, yMax);
return ( return (
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}> <Measure whitelist={['width', 'top']} onMeasure={ (dimensions) => this.setState({ dimensions }) }>
<div width={width} height={height}> <div width='100%'>
{ this.x ? <BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
<svg ref={ref => this.svg = ref} width={width} height={height}> <XAxis interval={0} fontSize='0.8em' stroke={AXIS_COLOUR} dataKey='label' />
<g transform={translate}></g> <YAxis interval={'preserveStart'} tickCount={11} fontSize='0.8em' stroke={AXIS_COLOUR} type='number' domain={[0, localMax]}/>
</svg> : null } <Bar dataKey='value' label={<ValueLabel />} fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/>
</BarChart>
</div> </div>
</Measure> </Measure>
); );
} }
/**
* Generate a term tip
* @param {Object} d the data
* @param {number} i the index
* @param {Object} e the event
* @returns {Object} termtip markup
*/
_termtip(d, i, e) {
if (this.props.data[i].tooltip) {
return this.context.termtip(this.props.data[i].tooltip, e);
} else {
return null;
}
}
} }
/**
* A label that displays the value within the bar of the chart
*/
const ValueLabel = React.createClass({
propTypes: {
x: PropTypes.number,
y: PropTypes.number,
payload: PropTypes.object,
value: PropTypes.number
},
render() {
const { x, y, payload, value } = this.props;
return (
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20}>{value}</text>
);
}
});