pacificclimate/climate-explorer-frontend

View on GitHub
src/components/data-controllers/MotiDataController/MotiDataController.js

Summary

Maintainability
D
1 day
Test Coverage
// DEPRECATED: DO NOT MAINTAIN THIS COMPONENT.
//
// This component is not currently in use and it has not been kept up to date
// with changes made to other data controllers. This component is being kept
// for the sole purpose of keeping a record of what it was intended to do.
//
// If and when a MotiDataController is actually needed, it should be created
// by cloning/interbreeding/genetically modifying one or more of the other data
// controllers, which are much better structured.

/************************************************************************
 * MotiDataController.js - controller to display summarized numerical data
 *
 * This DataController is intended to be used with an ensemble that
 * contains a small number of datasets, each of which is the average
 * of many runs. It does not offer the user a way to select or
 * distinguish between individual runs or indicate a time of year
 * they are interested in; it should be used with an ensemble that
 * features only one dataset for each combination of model, variable,
 * and emission scenario.
 *
 * It receives a model, variable, and emissions scenario from its parent,
 * MotiApp. Based on the type of variable, it loads relevant data and passes
 * it to viewer component children:
 *
 * Multi-year mean:
 *  - Annual cycle DataGraph (monthly resolution only)
 *
 * Non multi-year mean:
 *  - A freeform timeseries Datagraph showing data at whatever resolution
 *    is available.
 *
 * In both cases, a stats DataTable summarizing the dataset is also
 * generated.
 *
 * IMPORTANT: This component has fallen into disuse and has not been
 * kept up to date with changes to other similar components. See
 * https://github.com/pacificclimate/climate-explorer-frontend/issues/218
 ************************************************************************/
import PropTypes from "prop-types";

import React from "react";
import createReactClass from "create-react-class";
import { Panel, Button } from "react-bootstrap";

import { parseBootstrapTableData, validateStatsData } from "../../../core/util";
import DataGraph from "../../graphs/DataGraph/DataGraph";
import DataTable from "../../DataTable/DataTable";
import DataControllerMixin from "../../DataControllerMixin";
import {
  timeseriesToAnnualCycleGraph,
  timeseriesToTimeseriesGraph,
} from "../../../core/chart-generators";
import { getStats } from "../../../data-services/ce-backend";
import _ from "lodash";

import AnnualCycleGraph from "../../graphs/AnnualCycleGraph";
import TimeSeriesGraph from "../../graphs/TimeSeriesGraph";
import {
  graphsPanelLabel,
  exportStatsTableDataLabel,
  statsTableLabel,
} from "../../guidance-content/info/LabelWithInfoItems";

import styles from "../DataController.module.css";

export default createReactClass({
  displayName: "MotiDataController",

  propTypes: {
    model_id: PropTypes.string,
    variable_id: PropTypes.string,
    experiment: PropTypes.string,
    area: PropTypes.string,
    meta: PropTypes.array,
    ensemble_name: PropTypes.string,
  },

  mixins: [DataControllerMixin],

  getInitialState: function () {
    return {
      annualCycleDatasetId: "",
      longTermAverageData: undefined,
      annualCycleData: undefined,
      statsData: undefined,
      dataTableTimeOfYear: 0,
    };
  },

  /*
   * Called when MotiController is first loaded. Selects and fetches an initial
   * dataset to display in the stats table and annual cycle graph.
   */
  getData: function (props) {
    if (this.multiYearMeanSelected(props)) {
      //load Annual Cycle graph
      this.setAnnualCycleGraphNoDataMessage("Loading Data");

      var monthlyMetadata = _.find(props.meta, {
        model_id: props.model_id,
        variable_id: props.variable_id,
        experiment: props.experiment,
        timescale: "monthly",
      });

      var myTimeseriesPromise = this.getTimeseriesPromise(
        props,
        monthlyMetadata.unique_id,
      );

      myTimeseriesPromise
        .then((response) => {
          this.setState({
            annualCycleData: timeseriesToAnnualCycleGraph(
              props.meta,
              response.data,
            ),
          });
        })
        .catch((error) => {
          this.displayError(error, this.setAnnualCycleGraphNoDataMessage);
        });
    } else {
      //load Timeseries graph
      this.setTimeseriesGraphNoDataMessage("Loading Data");

      var params = _.pick(props, "model_id", "variable_id", "experiment");

      var metadata = _.find(props.meta, params);

      var myTimeseriesPromise = this.getTimeseriesPromise(
        props,
        metadata.unique_id,
      );
      myTimeseriesPromise
        .then((response) => {
          this.setState({
            timeseriesData: timeseriesToTimeseriesGraph(
              props.meta,
              response.data,
            ),
          });
        })
        .catch((error) => {
          this.displayError(error, this.setTimeseriesGraphNoDataMessage);
        });
    }

    //Load stats table
    this.setStatsTableNoDataMessage("Loading Data");
    var myStatsPromise = getStats(
      props,
      this.state.dataTableTimeOfYear,
      "yearly",
    ).then(validateStatsData);

    myStatsPromise
      .then((response) => {
        this.setState({
          statsData: parseBootstrapTableData(
            this.injectRunIntoStats(response.data),
            props.meta,
          ),
        });
      })
      .catch((error) => {
        this.displayError(error, this.setStatsTableNoDataMessage);
      });
  },

  //Remove data from the Annual Cycle graph and display a message
  setAnnualCycleGraphNoDataMessage: function (message) {
    this.setState({
      annualCycleData: {
        data: { columns: [], empty: { label: { text: message } } },
        axis: {},
      },
    });
  },

  //Remove data from the Stats Table and display a message
  setStatsTableNoDataMessage: function (message) {
    this.setState({
      statsTableOptions: { noDataText: message },
      statsData: [],
    });
  },

  //Remove data from the Timeseries Graph and display a message
  setTimeseriesGraphNoDataMessage: function (message) {
    this.setState({
      timeseriesData: {
        data: { columns: [], empty: { label: { text: message } } },
        axis: {},
      },
    });
  },

  shouldComponentUpdate: function (nextProps, nextState) {
    // This guards against re-rendering before calls to the data server alter
    // the state
    return !(
      _.isEqual(nextState.statsData, this.state.statsData) &&
      _.isEqual(nextState.annualCycleData, this.state.annualCycleData) &&
      _.isEqual(nextProps.meta, this.props.meta) &&
      _.isEqual(nextState.timeseriesData, this.state.timeseriesData) &&
      _.isEqual(nextState.statsTableOptions, this.state.statsTableOptions)
    );
  },

  render: function () {
    var statsData = this.state.statsData
      ? this.state.statsData
      : this.blankStatsData;

    return (
      <div>
        <h3>
          {this.props.model_id} {this.props.variable_id} {this.props.experiment}
        </h3>

        <Panel>
          <Panel.Heading>
            <Panel.Title>{graphsPanelLabel}</Panel.Title>
          </Panel.Heading>
          <Panel.Body>
            {this.multiYearMeanSelected() ? (
              <AnnualCycleGraph
                meta={this.props.meta}
                dataSpec={this.state.annualCycleDataSpec}
                onChangeDataset={this.updateAnnualCycleDataset}
                graphSpec={this.state.annualCycleData || this.blankGraph}
                onExportXlsx={this.exportAnnualCycle.bind(this, "xlsx")}
                onExportCsv={this.exportAnnualCycle.bind(this, "csv")}
              />
            ) : (
              <TimeSeriesGraph
                graphSpec={this.state.timeseriesData || this.blankGraph}
              />
            )}
          </Panel.Body>
        </Panel>

        <Panel>
          <Panel.Heading>
            <Panel.Title>{statsTableLabel}</Panel.Title>
          </Panel.Heading>
          <Panel.Body className={styles.data_panel}>
            <DataTable
              data={statsData}
              options={this.state.statsTableOptions}
            />
            <div style={{ marginTop: "10px" }}>
              <Button
                style={{ marginRight: "10px" }}
                onClick={this.exportDataTable.bind(this, "xlsx")}
              >
                Export To XLSX
              </Button>
              <Button onClick={this.exportDataTable.bind(this, "csv")}>
                Export To CSV
              </Button>
              {exportStatsTableDataLabel}
            </div>
          </Panel.Body>
        </Panel>
      </div>
    );
  },
});