diff --git a/package.json b/package.json index c934693c..e018f952 100644 --- a/package.json +++ b/package.json @@ -90,16 +90,17 @@ }, "dependencies": { "babel-polyfill": "*", - "classnames": "^2.2.0", "browserify-zlib": "ipfs/browserify-zlib", + "classnames": "^2.2.0", "coriolis-data": "EDCD/coriolis-data", "d3": "4.6.0", "fbemitter": "^2.0.0", "lodash": "^4.15.0", "lz-string": "^1.4.4", - "react-number-editor": "Athanasius/react-number-editor.git#miggy", "react": "^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" } } diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index 5d676b5e..c0e3ea89 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -241,14 +241,19 @@ export default class Coriolis extends React.Component { /** * Show the term tip * @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} e2 Alternative location for synthetic event from charts (where 'Event' is actually a chart index) */ - _termtip(term, opts, event) { - if (opts && opts.nativeEvent) { // Opts is a SyntheticEvent + _termtip(term, opts, event, e2) { + if (opts && opts.nativeEvent) { // Opts is the SyntheticEvent event = opts; opts = { cap: true }; } + if (e2 instanceof Object && e2.nativeEvent) { // E2 is the SyntheticEvent + event = e2; + } + this._tooltip(
{this.state.language.translate(term)}
, event.currentTarget.getBoundingClientRect(), diff --git a/src/app/components/Defence.jsx b/src/app/components/Defence.jsx index 56a1bafc..d365206c 100644 --- a/src/app/components/Defence.jsx +++ b/src/app/components/Defence.jsx @@ -165,7 +165,7 @@ export default class Defence extends TranslatedComponent {

{translate('damage taken')}(%)

- +

{translate('effective shield')}(MJ)

diff --git a/src/app/components/VerticalBarChart.jsx b/src/app/components/VerticalBarChart.jsx index 617b1b4b..b8a709cf 100644 --- a/src/app/components/VerticalBarChart.jsx +++ b/src/app/components/VerticalBarChart.jsx @@ -1,12 +1,11 @@ -import React, { Component } from 'react'; -import Measure from 'react-measure'; -import * as d3 from 'd3'; 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 LABEL_COLOUR = '#000000'; - -const margin = { top: 10, right: 0, bottom: 0, left: 55 }; +const AXIS_COLOUR = '#C06400'; const ASPECT = 1; @@ -20,8 +19,8 @@ const merge = function(one, two) { export default class VerticalBarChart extends TranslatedComponent { static propTypes = { - data : React.PropTypes.array.isRequired, - yMax : React.PropTypes.number + data : PropTypes.array.isRequired, + yMax : PropTypes.number }; /** @@ -32,6 +31,8 @@ export default class VerticalBarChart extends TranslatedComponent { constructor(props, context) { super(props); + this._termtip = this._termtip.bind(this); + this.state = { dimensions: { width: 300, @@ -41,97 +42,64 @@ export default class VerticalBarChart extends TranslatedComponent { } /** - * Render the graph - * @param {Object} props React Component properties - */ - _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 the bar chart + * @returns {Object} the markup */ render() { - const { width } = this.state.dimensions; - const translate = `translate(${margin.left}, ${margin.top})`; + const { width, height } = this.state.dimensions; + const { tooltip, termtip } = this.context; - const height = width * ASPECT; - - this._renderGraph(this.props); + // Calculate maximum for Y + let dataMax = Math.max(...this.props.data.map(d => d.value)); + if (dataMax == -Infinity) dataMax = 0; + let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0; + const localMax = Math.max(dataMax, yMax); return ( - { this.setState({ dimensions }); }}> -
- { this.x ? - this.svg = ref} width={width} height={height}> - - : null } + this.setState({ dimensions }) }> +
+ + + + } fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/> +
); } + + /** + * 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 ( + {value} + ); + } +});