diff --git a/package.json b/package.json
index f4fc62d03376f21e44cad58a260620e6342c8e9f..ee8d434dc4f10302c49de6b9c95fe50fd49d55ae 100644
--- a/package.json
+++ b/package.json
@@ -56,8 +56,11 @@
"dependencies": {
"bpl": "git+https://7f8bbb4b0a383ad905fee5b0f7cbc0c22533b556:x-oauth-basic@github.com/Blocp/bpl.git",
"dom-to-image": "^2.5.2",
+ "highcharts": "^5.0.9",
+ "highcharts-3d": "^0.1.2",
"react": "^15.3.2",
"react-dom": "^15.3.2",
+ "react-highcharts": "^11.5.0",
"react-redux": "^4.4.5",
"react-router": "^3.0.0",
"react-router-redux": "^4.0.7",
diff --git a/src/components/TurkHit/features.js b/src/components/TurkHit/features.js
new file mode 100644
index 0000000000000000000000000000000000000000..aa8c5b830de210de979bf1cf316ea07aa5697eb5
--- /dev/null
+++ b/src/components/TurkHit/features.js
@@ -0,0 +1,9 @@
+const featuresDict = {
+ 1: 'Door',
+ 2: 'Window',
+ 3: 'Building Point',
+ 4: 'Roof Point',
+
+};
+
+export default featuresDict;
diff --git a/src/components/TurkHit/index.js b/src/components/TurkHit/index.js
index 09416740f824e51b47485205372d7e90811cd556..27abed2d3a01f147321721dee14e78a3352a9c99 100644
--- a/src/components/TurkHit/index.js
+++ b/src/components/TurkHit/index.js
@@ -1,21 +1,270 @@
import React, { PropTypes, Component } from 'react';
+import Highcharts from 'highcharts';
+import ReactHighcharts from 'react-highcharts';
+import Highcharts3D from 'highcharts-3d';
import defaultForm from './defaultForm';
import './styles.css';
+import featuresDict from './features';
import turkHitPropTypes from '../../containers/Dimensions/propTypes';
import documentsPropType from '../../containers/Documents/propTypes';
import turkStatus from './turkStatus';
+// Need to call this to load the 3D highcharts module
+Highcharts3D(ReactHighcharts.Highcharts);
class TurkHit extends Component {
constructor(props) {
super(props);
- // TODO not being used
this.state = {
- error: false,
+ displayChart: false,
+ chart: null,
+ chartDrag: {
+ dragging: false,
+ posX: null,
+ posY: null,
+ alpha: null,
+ beta: null,
+ sensitivity: 5,
+ },
};
}
+ componentDidUpdate() {
+ // Create the chart when the component updates with turk data
+ const hit = this.props.hit;
+ if (!hit.loading &&
+ (hit.hitData.length > 0) &&
+ hit.hitData[0].dimensions &&
+ !this.state.chart) {
+ const curHit = this.props.hit.hitData[0];
+
+ // Calculate the minLat to get a measurement
+ let minLat = null;
+ let minLong = null;
+ let maxFoot = null;
+
+ const data = curHit.dimensions.points.reduce((acc, val) => {
+ // Convert latitude and longitude to feet
+ const DEG_TO_FT = 364488.888889;
+ const latitudeFeet = (val.latitude * DEG_TO_FT);
+ const longitudeFeet = (val.longitude * DEG_TO_FT);
+ if ((minLat === null) || (latitudeFeet < minLat)) {
+ minLat = latitudeFeet;
+ }
+ if ((minLong === null) || (longitudeFeet < minLong)) {
+ minLong = longitudeFeet;
+ }
+ if ((maxFoot === null) || (val.corresponding_height > maxFoot)) {
+ maxFoot = val.corresponding_height;
+ }
+
+ const point = [latitudeFeet, val.corresponding_height, longitudeFeet];
+
+ if (featuresDict[val.feature] === 'Building Point') {
+ acc.buildingPoints.push(point);
+ } else if (featuresDict[val.feature] === 'Roof Point') {
+ acc.roofPoints.push(point);
+ }
+ return acc;
+ }, { buildingPoints: [], roofPoints: [] });
+
+ // Now clean the data we've create and add new series to support
+ // the visualization
+
+ const buildingPointColor = '#5882FA';
+ const roofPointColor = '#2E2E2E';
+ const basePoints = [];
+ // Points that will visually connect the building points
+ // to the base points
+ const buildingBaseConnectPoints = [];
+ const buildingPoints = data.buildingPoints.map((val) => {
+ const newLat = val[0] - minLat;
+ const newLong = val[2] - minLong;
+
+ if (newLat > maxFoot) {
+ maxFoot = newLat;
+ }
+ if (newLong > maxFoot) {
+ maxFoot = newLong;
+ }
+ const newBuildingPoint = [newLat, val[1], newLong];
+ // A point with height of 0
+ const basePoint = [newLat, 0, newLong];
+ // Now connect the base points and the building point
+ buildingBaseConnectPoints.push({
+ color: buildingPointColor,
+ data: [newBuildingPoint, basePoint],
+ stickyTracking: false,
+ enableMouseTracking: false,
+ });
+
+ basePoints.push([newLat, 0, newLong]);
+ return [newLat, val[1], newLong];
+ });
+ // Add the first point so it connects as a closed shape
+ if (buildingPoints.length > 0) {
+ buildingPoints.push(buildingPoints[0]);
+ basePoints.push(basePoints[0]);
+ }
+
+ const roofPoints = data.roofPoints.map((val) => {
+ const newLat = val[0] - minLat;
+ const newLong = val[2] - minLong;
+ if (newLat > maxFoot) {
+ maxFoot = newLat;
+ }
+ if (newLong > maxFoot) {
+ maxFoot = newLong;
+ }
+ return [newLat, val[1], newLong];
+ });
+ // Add the first point so it connects as a closed shape
+ if (roofPoints.length > 0) {
+ roofPoints.push(roofPoints[0]);
+ }
+
+ Highcharts.getOptions().colors = Highcharts.getOptions().colors.map(color => (
+ {
+ radialGradient: {
+ cx: 0.4,
+ cy: 0.3,
+ r: 0.5,
+ },
+ stops: [
+ [0, color],
+ [1, Highcharts.Color(color).brighten(-0.2).get('rgb')],
+ ],
+ }
+ ));
+ const chart = new Highcharts.Chart({
+ chart: {
+ renderTo: 'chart-container',
+ // height: '100%',
+ margin: 100,
+ marginLeft: 300,
+ marginRight: 400,
+ marginTop: 100,
+ zoomType: 'xyz',
+ type: 'scatter',
+ options3d: {
+ enabled: true,
+ alpha: 30,
+ beta: 30,
+ depth: 250,
+ viewDistance: 5,
+ fitToPlot: false,
+ frame: {
+ bottom: { size: 1, color: 'rgba(0,0,0,0.02)' },
+ back: { size: 1, color: 'rgba(0,0,0,0.04)' },
+ side: { size: 1, color: 'rgba(0,0,0,0.06)' },
+ },
+ },
+ },
+ title: {
+ text: 'Roof and Building Points',
+ },
+ subtitle: {
+ text: 'Click and drag the plot area to rotate in space',
+ },
+ plotOptions: {
+ scatter: {
+ marker: {
+ symbol: 'circle',
+ },
+ lineWidth: 2,
+ width: 10,
+ height: 10,
+ depth: 10,
+ },
+ },
+ yAxis: {
+ title: 'height',
+ min: 0,
+ max: maxFoot,
+ },
+ xAxis: {
+ gridLineWidth: 1,
+ min: 0,
+ max: maxFoot,
+ },
+ zAxis: {
+ showFirstLabel: false,
+ min: 0,
+ max: maxFoot,
+ },
+ legend: {
+ enabled: false,
+ },
+ series: [
+ {
+ name: 'Building Points',
+ color: buildingPointColor,
+ data: buildingPoints,
+ stickyTracking: false,
+ },
+ {
+ name: 'Base Points',
+ color: buildingPointColor,
+ data: basePoints,
+ showInLegend: false,
+ enableMouseTracking: false,
+ },
+ {
+ name: 'Roof Points',
+ color: roofPointColor,
+ data: roofPoints,
+ stickyTracking: false,
+ lineWidth: 0,
+ },
+ ...buildingBaseConnectPoints,
+ ],
+ });
+ // We need to set state AFTER the component updates because
+ // the chart requires that the
it is targetting
+ // is already rendered
+ /* eslint-disable react/no-did-update-set-state */
+ this.setState({ chart });
+
+ chart.container.onmousedown = (event) => {
+ this.setState({
+ chartDrag: {
+ ...this.state.chartDrag,
+ dragging: true,
+ posX: event.pageX,
+ posY: event.pageY,
+ alpha: this.state.chart.options.chart.options3d.alpha,
+ beta: this.state.chart.options.chart.options3d.beta,
+ },
+ });
+ };
+ chart.container.onmousemove = (event) => {
+ const { dragging, beta, alpha, posX, posY, sensitivity } = this.state.chartDrag;
+ if (dragging) {
+ const newBeta = beta + ((posX - event.pageX) / sensitivity);
+ this.state.chart.options.chart.options3d.beta = newBeta;
+
+ const newAlpha = alpha + ((event.pageY - posY) / sensitivity);
+ this.state.chart.options.chart.options3d.alpha = newAlpha;
+
+ this.state.chart.redraw(false);
+ }
+ };
+ chart.container.onmouseup = () => {
+ this.setState({
+ chartDrag: {
+ ...this.state.chartDrag,
+ dragging: false,
+ },
+ });
+ };
+ }
+ // Resize the chart
+ if (this.state.chart) {
+ this.state.chart.reflow();
+ }
+ }
+
handleHitDecision = (hit, approve) => {
const { hitDecision } = this.props;
let responseMessage = null;
@@ -29,6 +278,10 @@ class TurkHit extends Component {
hitDecision(hit, approve, responseMessage);
}
+ toggleDisplayChart = () => {
+ this.setState({ displayChart: !this.state.displayChart });
+ }
+
renderDefinitions = () => (
Mechanical Turk
@@ -66,7 +319,7 @@ class TurkHit extends Component {
);
}
- return (
);
+ return (
);
}
renderCreateHitButton = () => {
@@ -127,6 +380,36 @@ class TurkHit extends Component {
})
)
+ renderChartToggleButton = () => (
+
+
+
+ )
+
+ renderChart = () => {
+ let chartDiv = (
+
+ );
+ if (this.state.displayChart) {
+ chartDiv = (
+
+ );
+ }
+
+ return chartDiv;
+ }
+
render() {
const { hit } = this.props;
const status = hit.status;
@@ -136,7 +419,7 @@ class TurkHit extends Component {
return (
{this.renderDefinitions()}
-
Loading...
+
Loading... Please be patient and do not refresh the page...
);
} else if (hit.error && hit.error.response.status !== 404) {
@@ -180,6 +463,8 @@ class TurkHit extends Component {
{currStatus.downloadLink && this.renderDownloadLink(curHit.csv_document_key)}
{currStatus.fileActions && this.renderHitActions(curHit)}
+ {this.state.chart && this.renderChartToggleButton()}
+ {this.renderChart()}
diff --git a/src/components/Utilities/index.js b/src/components/Utilities/index.js
index 2fe2a8c7279aefa6d5d932acc0cfc5b58897d8aa..af3dd011d8ac37d1ecb3c1b43430cd114905362b 100644
--- a/src/components/Utilities/index.js
+++ b/src/components/Utilities/index.js
@@ -146,7 +146,6 @@ class Utilities extends Component {
const data = res.data;
const disaggregateData = this.parseDisaggregateData(data.disaggregate_csv_output);
setDisaggregateData(disaggregateData);
-
this.resetErrorMessage();
}
updateLoadingUploadState(false);
diff --git a/src/components/UtilityAccount/index.js b/src/components/UtilityAccount/index.js
index e8003ccadb3039ab44eacb45df490d3ae1f159a7..8a521131b0ccfa78afe62d1b44c4636d6d0bf4e3 100644
--- a/src/components/UtilityAccount/index.js
+++ b/src/components/UtilityAccount/index.js
@@ -157,7 +157,7 @@ class UtilityAccount extends Component {
let img = (

-
Upload
Disaggregation
+ Upload Disaggregation
);
if (this.state.convertingFile || this.state.loadingUpload) {
@@ -172,7 +172,7 @@ class UtilityAccount extends Component {
);
}
return (
-
+
{
- const { scrape, disaggregate } = this.state.documentURLs;
+ renderFetchBtn = () => (
+
+ )
+
+ renderDeleteAccountBtn = () => (
+
+ )
+
+ renderDisaggregateButtons = () => {
+ const scrapeKey = this.state.documentKeys.scrape;
+ const disaggregateKey = this.state.documentKeys.disaggregate;
+ const scrapeUrl = this.state.documentURLs.scrape;
+ const disaggregateUrl = this.state.documentURLs.disaggregate;
+
let scrapeVisibility = 'hidden';
- if (scrape !== undefined && scrape !== '') {
+ if (scrapeUrl !== undefined && scrapeUrl !== '') {
scrapeVisibility = 'visible';
}
let disaggregateVisibility = 'hidden';
- if (disaggregate !== undefined && disaggregate !== '') {
+ if (disaggregateUrl !== undefined && disaggregateUrl !== '') {
disaggregateVisibility = 'visible';
}
- let chartToggle = (
);
- let chartDownload = (
);
+ let chartToggle = (
);
+ let chartDownload = (
);
if (this.state.disaggregateData.length > 0) {
chartToggle = (