diff --git a/.github/workflows/autodeploy.yml b/.github/workflows/autodeploy.yml
new file mode 100644
index 00000000..8f28cc84
--- /dev/null
+++ b/.github/workflows/autodeploy.yml
@@ -0,0 +1,28 @@
+# This is a basic deployment workflow triggered by pushes to the alpha branch.
+
+name: Auto-Deploy
+
+# Controls when the action will run. Workflow runs when the alpha branch receives a push event
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - alpha
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ downloadcode:
+ runs-on: self-hosted
+ steps:
+ - shell: bash
+ run: |
+ rm -Rf ./coriolis
+ rm -Rf ./coriolis-data
+ git clone https://github.com/alex-williams/coriolis.git --single-branch --branch alpha
+ git clone https://github.com/alex-williams/coriolis-data.git --single-branch --branch alpha
+ cd coriolis-data
+ npm install
+ cd ../coriolis
+ npm install
+ npm run build
+ sudo -u www-data cp -r ./build/* /var/www/newdisk/coriolis.brighter-applications.co.uk/
\ No newline at end of file
diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx
index 3f2637c8..e5f54030 100644
--- a/src/app/components/AvailableModulesMenu.jsx
+++ b/src/app/components/AvailableModulesMenu.jsx
@@ -39,13 +39,17 @@ const GRPCAT = {
'ml': 'lasers',
'c': 'projectiles',
'mc': 'projectiles',
+ 'advmc': 'projectiles',
'axmc': 'experimental',
+ 'axmce': 'experimental',
+ 'ntp': 'experimental',
'fc': 'projectiles',
'rfl': 'experimental',
'pa': 'projectiles',
'rg': 'projectiles',
'mr': 'ordnance',
'axmr': 'experimental',
+ 'axmre': 'experimental',
'rcpl': 'experimental',
'dtl': 'experimental',
'tbsc': 'experimental',
@@ -104,7 +108,7 @@ const CATEGORIES = {
// Hardpoints
'lasers': ['pl', 'ul', 'bl'],
- 'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
+ 'projectiles': ['mc', 'advmc', 'c', 'fc', 'pa', 'rg'],
'ordnance': ['mr', 'tp', 'nl'],
// Utilities
'sb': ['sb'],
@@ -113,7 +117,7 @@ const CATEGORIES = {
'defence': ['ch', 'po', 'ec'],
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
// Experimental
- 'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
+ 'experimental': ['axmc', 'axmce', 'axmr', 'axmre', 'ntp','rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
'weapon stabilizers': ['ews'],
// Guardian
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'],
diff --git a/src/app/components/LineChart.jsx b/src/app/components/LineChart.jsx
index b4113ee6..76078aa6 100644
--- a/src/app/components/LineChart.jsx
+++ b/src/app/components/LineChart.jsx
@@ -1,281 +1,281 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import ContainerDimensions from 'react-container-dimensions';
-import * as d3 from 'd3';
-import TranslatedComponent from './TranslatedComponent';
-
-const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
-
-/**
- * Line Chart
- */
-export default class LineChart extends TranslatedComponent {
-
- static defaultProps = {
- code: '',
- xMin: 0,
- yMin: 0,
- points: 20,
- colors: ['#ff8c0d'],
- aspect: 0.5
- };
-
- static propTypes = {
- func: PropTypes.func.isRequired,
- xLabel: PropTypes.string.isRequired,
- xMin: PropTypes.number,
- xMax: PropTypes.number.isRequired,
- xUnit: PropTypes.string.isRequired,
- xMark: PropTypes.number,
- yLabel: PropTypes.string.isRequired,
- yMin: PropTypes.number,
- yMax: PropTypes.number.isRequired,
- yUnit: PropTypes.string,
- series: PropTypes.array,
- colors: PropTypes.array,
- points: PropTypes.number,
- aspect: PropTypes.number,
- code: PropTypes.string,
- };
-
- /**
- * Constructor
- * @param {Object} props React Component properties
- * @param {Object} context React Component context
- */
- constructor(props, context) {
- super(props);
-
- this._updateDimensions = this._updateDimensions.bind(this);
- this._updateSeries = this._updateSeries.bind(this);
- this._tooltip = this._tooltip.bind(this);
- this._showTip = this._showTip.bind(this);
- this._hideTip = this._hideTip.bind(this);
- this._moveTip = this._moveTip.bind(this);
-
- const series = props.series;
-
- let xScale = d3.scaleLinear();
- let yScale = d3.scaleLinear();
- let xAxisScale = d3.scaleLinear();
-
- this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
- this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
-
- this.state = {
- xScale,
- xAxisScale,
- yScale,
- tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
- };
- }
-
- /**
- * Update tooltip content
- * @param {number} xPos x coordinate
- * @param {number} width current container width
- */
- _tooltip(xPos, width) {
- let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
- let { xScale, yScale } = this.state;
- let { formats, translate } = this.context.language;
- let x0 = xScale.invert(xPos),
- y0 = func(x0),
- tips = this.tipContainer,
- yTotal = 0,
- flip = (xPos / width > 0.50),
- tipWidth = 0,
- tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
-
-
- xPos = xScale(x0); // Clamp xPos
-
- tips.selectAll('text.text-tip.y').text(function(d, i) {
- let yVal = series ? y0[series[i]] : y0;
- yTotal += yVal;
- return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
- }).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
-
- tips.selectAll('text').each(function() {
- if (this.getBBox().width > tipWidth) {
- tipWidth = Math.ceil(this.getBBox().width);
- }
- });
-
- let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2));
-
- tipWidth += 8;
- tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')');
- tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
- tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
- tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start');
- this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0));
- }
-
- /**
- * Update dimensions based on properties and scale
- * @param {Object} props React Component properties
- * @param {number} scale size ratio / scale
- * @param {number} width current width of the container
- * @returns {Object} calculated dimensions
- */
- _updateDimensions(props, scale, width) {
- const { xMax, xMin, yMin, yMax } = props;
- const innerWidth = width - MARGIN.left - MARGIN.right;
- const outerHeight = Math.round(width * props.aspect);
- const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
-
- this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
- this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true);
- this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility
- return { innerWidth, outerHeight, innerHeight };
- }
-
- /**
- * Show tooltip
- * @param {SyntheticEvent} e Event
- */
- _showTip(e) {
- e.preventDefault();
- this.tipContainer.style('display', null);
- this.markersContainer.style('display', null);
- this._moveTip(e);
- }
-
- /**
- * Move and update tooltip
- * @param {SyntheticEvent} e Event
- * @param {number} width current container width
- */
- _moveTip(e, width) {
- let clientX = e.touches ? e.touches[0].clientX : e.clientX;
- this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width);
- }
-
- /**
- * Hide tooltip
- * @param {SyntheticEvent} e Event
- */
- _hideTip(e) {
- e.preventDefault();
- this.tipContainer.style('display', 'none');
- this.markersContainer.style('display', 'none');
- }
-
- /**
- * Update series generated from props
- * @param {Object} props React Component properties
- * @param {Object} state React Component state
- */
- _updateSeries(props, state) {
- let { func, xMin, xMax, series, points } = props;
- let delta = (xMax - xMin) / points;
- let seriesData = new Array(points);
-
- if (delta) {
- seriesData = new Array(points);
- for (let i = 0, x = xMin; i < points; i++) {
- seriesData[i] = [x, func(x)];
- x += delta;
- }
- seriesData[points - 1] = [xMax, func(xMax)];
- } else {
- let yVal = func(xMin);
- seriesData = [[0, yVal], [1, yVal]];
- }
-
- const markerElems = [];
- const detailElems = [];
- const seriesLines = [];
- for (let i = 0, l = series ? series.length : 1; i < l; i++) {
- const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]);
- seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor));
- detailElems.push();
- markerElems.push();
- }
-
- const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8));
-
- this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight });
- }
-
- /**
- * Update dimensions and series data based on props and context.
- */
- componentWillMount() {
- this._updateSeries(this.props, this.state);
- }
-
- /**
- * Update state based on property and context changes
- * @param {Object} nextProps Incoming/Next properties
- * @param {Object} nextContext Incoming/Next conext
- */
- componentWillReceiveProps(nextProps, nextContext) {
- const props = this.props;
-
- if (props.code != nextProps.code) {
- this._updateSeries(nextProps, this.state);
- }
- }
-
- /**
- * Render the chart
- * @return {React.Component} Chart SVG
- */
- render() {
- return (
-
- { ({ width, height }) => {
- const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height);
- const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
- const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
- const lines = seriesLines.map((line, i) => ).reverse();
-
- const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
- const xmark = xMark ? : '';
- return (
-
-
-
- );
- }}
-
- );
- }
-}
+import React from 'react';
+import PropTypes from 'prop-types';
+import ContainerDimensions from 'react-container-dimensions';
+import * as d3 from 'd3';
+import TranslatedComponent from './TranslatedComponent';
+
+const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
+
+/**
+ * Line Chart
+ */
+export default class LineChart extends TranslatedComponent {
+
+ static defaultProps = {
+ code: '',
+ xMin: 0,
+ yMin: 0,
+ points: 20,
+ colors: ['#ff8c0d'],
+ aspect: 0.5
+ };
+
+ static propTypes = {
+ func: PropTypes.func.isRequired,
+ xLabel: PropTypes.string.isRequired,
+ xMin: PropTypes.number,
+ xMax: PropTypes.number.isRequired,
+ xUnit: PropTypes.string.isRequired,
+ xMark: PropTypes.number,
+ yLabel: PropTypes.string.isRequired,
+ yMin: PropTypes.number,
+ yMax: PropTypes.number.isRequired,
+ yUnit: PropTypes.string,
+ series: PropTypes.array,
+ colors: PropTypes.array,
+ points: PropTypes.number,
+ aspect: PropTypes.number,
+ code: PropTypes.string,
+ };
+
+ /**
+ * Constructor
+ * @param {Object} props React Component properties
+ * @param {Object} context React Component context
+ */
+ constructor(props, context) {
+ super(props);
+
+ this._updateDimensions = this._updateDimensions.bind(this);
+ this._updateSeries = this._updateSeries.bind(this);
+ this._tooltip = this._tooltip.bind(this);
+ this._showTip = this._showTip.bind(this);
+ this._hideTip = this._hideTip.bind(this);
+ this._moveTip = this._moveTip.bind(this);
+
+ const series = props.series;
+
+ let xScale = d3.scaleLinear();
+ let yScale = d3.scaleLinear();
+ let xAxisScale = d3.scaleLinear();
+
+ this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
+ this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
+
+ this.state = {
+ xScale,
+ xAxisScale,
+ yScale,
+ tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
+ };
+ }
+
+ /**
+ * Update tooltip content
+ * @param {number} xPos x coordinate
+ * @param {number} width current container width
+ */
+ _tooltip(xPos, width) {
+ let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
+ let { xScale, yScale } = this.state;
+ let { formats, translate } = this.context.language;
+ let x0 = xScale.invert(xPos),
+ y0 = func(x0),
+ tips = this.tipContainer,
+ yTotal = 0,
+ flip = (xPos / width > 0.50),
+ tipWidth = 0,
+ tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
+
+
+ xPos = xScale(x0); // Clamp xPos
+
+ tips.selectAll('text.text-tip.y').text(function(d, i) {
+ let yVal = series ? y0[series[i]] : y0;
+ yTotal += yVal;
+ return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
+ }).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
+
+ tips.selectAll('text').each(function() {
+ if (this.getBBox().width > tipWidth) {
+ tipWidth = Math.ceil(this.getBBox().width);
+ }
+ });
+
+ let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2));
+
+ tipWidth += 8;
+ tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')');
+ tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
+ tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
+ tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start');
+ this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0));
+ }
+
+ /**
+ * Update dimensions based on properties and scale
+ * @param {Object} props React Component properties
+ * @param {number} scale size ratio / scale
+ * @param {number} width current width of the container
+ * @returns {Object} calculated dimensions
+ */
+ _updateDimensions(props, scale, width) {
+ const { xMax, xMin, yMin, yMax } = props;
+ const innerWidth = width - MARGIN.left - MARGIN.right;
+ const outerHeight = Math.round(width * props.aspect);
+ const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
+
+ this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
+ this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true);
+ this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility
+ return { innerWidth, outerHeight, innerHeight };
+ }
+
+ /**
+ * Show tooltip
+ * @param {SyntheticEvent} e Event
+ */
+ _showTip(e) {
+ e.preventDefault();
+ this.tipContainer.style('display', null);
+ this.markersContainer.style('display', null);
+ this._moveTip(e);
+ }
+
+ /**
+ * Move and update tooltip
+ * @param {SyntheticEvent} e Event
+ * @param {number} width current container width
+ */
+ _moveTip(e, width) {
+ let clientX = e.touches ? e.touches[0].clientX : e.clientX;
+ this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width);
+ }
+
+ /**
+ * Hide tooltip
+ * @param {SyntheticEvent} e Event
+ */
+ _hideTip(e) {
+ e.preventDefault();
+ this.tipContainer.style('display', 'none');
+ this.markersContainer.style('display', 'none');
+ }
+
+ /**
+ * Update series generated from props
+ * @param {Object} props React Component properties
+ * @param {Object} state React Component state
+ */
+ _updateSeries(props, state) {
+ let { func, xMin, xMax, series, points } = props;
+ let delta = (xMax - xMin) / points;
+ let seriesData = new Array(points);
+
+ if (delta) {
+ seriesData = new Array(points);
+ for (let i = 0, x = xMin; i < points; i++) {
+ seriesData[i] = [x, func(x)];
+ x += delta;
+ }
+ seriesData[points - 1] = [xMax, func(xMax)];
+ } else {
+ let yVal = func(xMin);
+ seriesData = [[0, yVal], [1, yVal]];
+ }
+
+ const markerElems = [];
+ const detailElems = [];
+ const seriesLines = [];
+ for (let i = 0, l = series ? series.length : 1; i < l; i++) {
+ const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]);
+ seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor));
+ detailElems.push();
+ markerElems.push();
+ }
+
+ const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8));
+
+ this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight });
+ }
+
+ /**
+ * Update dimensions and series data based on props and context.
+ */
+ componentWillMount() {
+ this._updateSeries(this.props, this.state);
+ }
+
+ /**
+ * Update state based on property and context changes
+ * @param {Object} nextProps Incoming/Next properties
+ * @param {Object} nextContext Incoming/Next conext
+ */
+ componentWillReceiveProps(nextProps, nextContext) {
+ const props = this.props;
+
+ if (props.code != nextProps.code) {
+ this._updateSeries(nextProps, this.state);
+ }
+ }
+
+ /**
+ * Render the chart
+ * @return {React.Component} Chart SVG
+ */
+ render() {
+ return (
+
+ { ({ width, height }) => {
+ const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height);
+ const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
+ const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
+ const lines = seriesLines.map((line, i) => ).reverse();
+
+ const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
+ const xmark = xMark ? : '';
+ return (
+
+
+
+ );
+ }}
+
+ );
+ }
+}
diff --git a/src/app/components/PieChart.jsx b/src/app/components/PieChart.jsx
index 1c9ccac8..6aa29fd4 100644
--- a/src/app/components/PieChart.jsx
+++ b/src/app/components/PieChart.jsx
@@ -1,92 +1,92 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import ContainerDimensions from 'react-container-dimensions';
-import * as d3 from 'd3';
-
-const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
-const LABEL_COLOUR = '#000000';
-
-/**
- * A pie chart
- */
-export default class PieChart extends Component {
-
- static propTypes = {
- data : PropTypes.array.isRequired
- };
-
- /**
- * Constructor
- * @param {Object} props React Component properties
- * @param {Object} context React Component context
- */
- constructor(props, context) {
- super(props);
-
- this.pie = d3.pie().value((d) => d.value);
- this.colors = CORIOLIS_COLOURS;
- this.arc = d3.arc();
- this.arc.innerRadius(0);
- }
-
-
- /**
- * Generate a slice of the pie chart
- * @param {Object} d the data for this slice
- * @param {number} i the index of this slice
- * @param {number} width the current width of the parent container
- * @returns {Object} the SVG for the slice
- */
- sliceGenerator(d, i, width) {
- if (!d || d.value == 0) {
- // Ignore 0 values
- return null;
- }
-
- const { data } = this.props;
-
- // Push the labels further out from the centre of the slice
- let [labelX, labelY] = this.arc.centroid(d);
- const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
-
- // Put the keys in a line with equal spacing
- const nonZeroItems = data.filter(d => d.value != 0).length;
- const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1;
- const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5);
- const keyTranslate = `translate(${keyX}, ${width * 0.45})`;
-
- return (
-
-
- {d.value}
- {d.data.label}
-
- );
- }
-
- /**
- * Render the component
- * @returns {object} Markup
- */
- render() {
- return (
-
- { ({ width }) => {
- const pie = this.pie(this.props.data),
- translate = `translate(${width / 2}, ${width * 0.4})`;
-
- this.arc.outerRadius(width * 0.4);
- return (
-
-
-
- );
- }}
-
- );
- }
-}
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import ContainerDimensions from 'react-container-dimensions';
+import * as d3 from 'd3';
+
+const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
+const LABEL_COLOUR = '#000000';
+
+/**
+ * A pie chart
+ */
+export default class PieChart extends Component {
+
+ static propTypes = {
+ data : PropTypes.array.isRequired
+ };
+
+ /**
+ * Constructor
+ * @param {Object} props React Component properties
+ * @param {Object} context React Component context
+ */
+ constructor(props, context) {
+ super(props);
+
+ this.pie = d3.pie().value((d) => d.value);
+ this.colors = CORIOLIS_COLOURS;
+ this.arc = d3.arc();
+ this.arc.innerRadius(0);
+ }
+
+
+ /**
+ * Generate a slice of the pie chart
+ * @param {Object} d the data for this slice
+ * @param {number} i the index of this slice
+ * @param {number} width the current width of the parent container
+ * @returns {Object} the SVG for the slice
+ */
+ sliceGenerator(d, i, width) {
+ if (!d || d.value == 0) {
+ // Ignore 0 values
+ return null;
+ }
+
+ const { data } = this.props;
+
+ // Push the labels further out from the centre of the slice
+ let [labelX, labelY] = this.arc.centroid(d);
+ const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
+
+ // Put the keys in a line with equal spacing
+ const nonZeroItems = data.filter(d => d.value != 0).length;
+ const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1;
+ const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5);
+ const keyTranslate = `translate(${keyX}, ${width * 0.45})`;
+
+ return (
+
+
+ {d.value}
+ {d.data.label}
+
+ );
+ }
+
+ /**
+ * Render the component
+ * @returns {object} Markup
+ */
+ render() {
+ return (
+
+ { ({ width }) => {
+ const pie = this.pie(this.props.data),
+ translate = `translate(${width / 2}, ${width * 0.4})`;
+
+ this.arc.outerRadius(width * 0.4);
+ return (
+
+
+
+ );
+ }}
+
+ );
+ }
+}
diff --git a/src/app/components/Slider.jsx b/src/app/components/Slider.jsx
index d36ca968..0e5c5696 100644
--- a/src/app/components/Slider.jsx
+++ b/src/app/components/Slider.jsx
@@ -1,386 +1,386 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-const MARGIN_LR = 8; // Left/ Right margin
-
-/**
- * Horizontal Slider
- */
-export default class Slider extends React.Component {
-
- static defaultProps = {
- axis: false,
- min: 0,
- max: 1,
- scale: 1 // SVG render scale
- };
-
- static propTypes = {
- axis: PropTypes.bool,
- axisUnit: PropTypes.string,// units (T, M, etc.)
- max: PropTypes.number,
- min: PropTypes.number,
- onChange: PropTypes.func.isRequired,// function which determins percent value
- onResize: PropTypes.func,
- percent: PropTypes.number.isRequired,// value of slider
- scale: PropTypes.number
- };
-
- /**
- * Constructor
- * @param {Object} props React Component properties
- */
- constructor(props) {
- super(props);
- this._down = this._down.bind(this);
- this._move = this._move.bind(this);
- this._up = this._up.bind(this);
- this._keyup = this._keyup.bind(this);
- this._keydown = this._keydown.bind(this);
- this._touchstart = this._touchstart.bind(this);
- this._touchend = this._touchend.bind(this);
-
- this._updatePercent = this._updatePercent.bind(this);
- this._updateDimensions = this._updateDimensions.bind(this);
-
- this.state = { width: 0 };
- }
-
- /**
- * On Mouse/Touch down handler
- * @param {SyntheticEvent} event Event
- */
- _down(event) {
- let rect = event.currentTarget.getBoundingClientRect();
- this.left = rect.left;
- this.width = rect.width;
- this._move(event);
- this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
- }
-
- /**
- * Update the slider percentage on move
- * @param {SyntheticEvent} event Event
- */
- _move(event) {
- if(this.width !== null && this.left != null) {
- let clientX = event.touches ? event.touches[0].clientX : event.clientX;
- event.preventDefault();
- this._updatePercent(clientX - this.left, this.width);
- }
- }
-
- /**
- * On Mouse/Touch up handler
- * @param {Event} event DOM Event
- */
- _up(event) {
- this.sliderInputBox.sliderVal.focus();
- clearTimeout(this.touchStartTimer);
- event.preventDefault();
- this.left = null;
- this.width = null;
- }
-
-
- /**
- * Key up handler for keyboard.
- * display the number field then set focus to it
- * when "Enter" key is pressed
- * @param {Event} event Keyboard event
- */
- _keyup(event) {
- switch (event.key) {
- case 'Enter':
- event.preventDefault();
- this.sliderInputBox._setDisplay('block');
- return;
- default:
- return;
- }
- }
- /**
- * Key down handler
- * increment slider position by +/- 1 when right/left arrow key is pressed or held
- * @param {Event} event Keyboard even
- */
- _keydown(event) {
- let newVal = this.props.percent * this.props.max;
- switch (event.key) {
- case 'ArrowRight':
- newVal += 1;
- if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max);
- return;
- case 'ArrowLeft':
- newVal -= 1;
- if (newVal >= 0) this.props.onChange(newVal / this.props.max);
- return;
- default:
- return;
- }
- }
-
- /**
- * Touch start handler
- * @param {Event} event DOM Event
- *
- */
- _touchstart(event) {
- this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
- }
-
- /**
- * Touch end handler
- * @param {Event} event DOM Event
- *
- */
- _touchend(event) {
- this.sliderInputBox.sliderVal.focus();
- clearTimeout(this.touchStartTimer);
- }
-
- /**
- * Determine if the user is still dragging
- * @param {SyntheticEvent} event Event
- */
- _enter(event) {
- if(event.buttons !== 1) {
- this.left = null;
- this.width = null;
- }
- }
-
- /**
- * Update the slider percentage
- * @param {number} pos Slider drag position
- * @param {number} width Slider width
- * @param {Event} event DOM Event
- */
- _updatePercent(pos, width) {
- this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
- }
-
- /**
- * Update dimenions from rendered DOM
- */
- _updateDimensions() {
- this.setState({
- outerWidth: this.node.getBoundingClientRect().width
- });
- }
-
- /**
- * Add listeners when about to mount
- */
- componentWillMount() {
- if (this.props.onResize) {
- this.resizeListener = this.props.onResize(this._updateDimensions);
- }
- }
-
- /**
- * Trigger DOM updates on mount
- */
- componentDidMount() {
- this._updateDimensions();
- }
-
- /**
- * Remove listeners on unmount
- */
- componentWillUnmount() {
- if (this.resizeListener) {
- this.resizeListener.remove();
- }
- }
-
- /**
- * Render the slider
- * @return {React.Component} The slider
- */
- render() {
- let outerWidth = this.state.outerWidth;
- let { axis, axisUnit, min, max, scale } = this.props;
- let style = {
- width: '100%',
- height: axis ? '2.5em' : '1.5em',
- boxSizing: 'border-box'
- };
- if (!outerWidth) {
- return