mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-09 14:45:35 +00:00
Rewrite vertical bar chart to use recharts
This commit is contained in:
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user