terriajs
Version:
Geospatial data visualization platform.
158 lines (134 loc) • 5.98 kB
JSX
;
/* global Float32Array */
/* eslint new-parens: 0 */
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import debounce from 'lodash.debounce';
import defined from 'terriajs-cesium/Source/Core/defined';
import when from 'terriajs-cesium/Source/ThirdParty/when';
import DataUri from '../../../Core/DataUri';
import ObserveModelMixin from '../../ObserveModelMixin';
import VarType from '../../../Map/VarType';
import Icon from "../../Icon.jsx";
import Styles from './chart-panel-download-button.scss';
const RUN_WORKER_DEBOUNCE = 100; // Wait 100ms for initial setup changes to be completed.
const TIME_COLUMN_DEFAULT_NAME = 'date';
const ChartPanelDownloadButton = createReactClass({
displayName: 'ChartPanelDownloadButton',
mixins: [ObserveModelMixin],
propTypes: {
chartableItems: PropTypes.array.isRequired,
errorEvent: PropTypes.object.isRequired
},
_subscription: undefined,
getInitialState: function() {
return {href: undefined};
},
/* eslint-disable-next-line camelcase */
UNSAFE_componentWillMount() {
// Changes to the graph item's catalog item results in new props being passed to this component 5 times on load...
// a debounce is a simple way to ensure it only gets run once for every batch of real changes.
this.debouncedRunWorker = debounce(this.runWorker, RUN_WORKER_DEBOUNCE);
},
componentDidMount() {
this.debouncedRunWorker(this.props.chartableItems);
},
/* eslint-disable-next-line camelcase */
UNSAFE_componentWillReceiveProps(newProps) {
this.debouncedRunWorker(newProps.chartableItems);
},
runWorker(newValue) {
const that = this;
if (window.Worker && (typeof Float32Array !== 'undefined')) {
that.setState({href: undefined});
const loadingPromises = newValue.map(item => item.load());
when.all(loadingPromises).then(() => {
const synthesized = that.synthesizeNameAndValueArrays(newValue);
// Could implement this using TaskProcessor, but requires webpack magic.
const HrefWorker = require('worker-loader!./downloadHrefWorker');
const worker = new HrefWorker;
// console.log('names and value arrays', synthesized.names, synthesized.values);
if (synthesized.values && synthesized.values.length > 0) {
worker.postMessage(synthesized);
worker.onmessage = function(event) {
// console.log('got worker message', event.data.slice(0, 60), '...');
that.setState({href: event.data});
};
}
});
}
// Currently no fallback for IE9-10 - just can't download.
},
componentWillUnmount() {
if (defined(this._subscription)) {
this._subscription.dispose();
}
},
synthesizeNameAndValueArrays(chartableItems) {
const valueArrays = [];
const names = []; // We will add the catalog item name back into the csv column name.
for (let i = chartableItems.length - 1; i >= 0; i--) {
const item = chartableItems[i];
const xColumn = getXColumn(item);
if (!names.length) {
names.push(getXColumnName(item, xColumn));
}
let columns = [xColumn];
if (item.isEnabled && defined(item.tableStructure)) {
if (!defined(columns[0])) {
continue;
}
const yColumns = item.tableStructure.columnsByType[VarType.SCALAR].filter(column=>column.isActive);
if (yColumns.length > 0) {
columns = columns.concat(yColumns);
// Use typed array if possible so we can pass by pointer to the web worker.
// Create a new array otherwise because if values are a knockout observable, they cannot be serialised for the web worker.
valueArrays.push(columns.map(column => (column.type === VarType.SCALAR ? new Float32Array(column.values) : Array.prototype.slice.call(column.values))));
yColumns.forEach(column => {
names.push(item.name + ' ' + column.name);
});
}
}
}
return {values: valueArrays, names: names};
},
render() {
if (this.state.href) {
if (DataUri.checkCompatibility()) {
return (
<a className={Styles.btnDownload}
download='chart data.csv'
href={this.state.href}>
<Icon glyph={Icon.GLYPHS.download}/>Download</a>
);
} else {
return (
<span className={Styles.btnDownload}
onClick={DataUri.checkCompatibility.bind(null, this.props.errorEvent, this.state.href)}>
<Icon glyph={Icon.GLYPHS.download}/>Download</span>
);
}
}
return null;
},
});
/**
* Gets the name for the x column - this will be 'date' if it's a time column otherwise it'll be the column's name.
*/
function getXColumnName(item, column) {
if (item.timeColumn) {
return TIME_COLUMN_DEFAULT_NAME;
} else {
return column.name;
}
}
/**
* Gets the column that will be used for the X axis of the chart.
*
* @returns {TableColumn}
*/
function getXColumn(item) {
return item.timeColumn || (item.tableStructure && item.tableStructure.columnsByType[VarType.SCALAR][0]);
}
module.exports = ChartPanelDownloadButton;