src/components/app-controllers/DualAppController/DualAppController.js
/************************************************************************
* DualAppController.js - Two-variable application controller
*
* This controller represents a portal that allows the user to compare
* and display two variables at once. It has dropdowns to select a model,
* experiment, and two seperate variables.
*
* Its children are DualDataController, which coordinates graphs comparing
* the two selected variables, and DualMapController, which coordinates a map
* displaying one variable as scalar colours and the other as isolines.
*
* The main variable is internally referred to as "variable," the variable
* being compared to it is internally referred to as "comparand."
* Timestamps and available datasets are based on what's available for
* the main variable; if the user selects parameters for which the
* comparand lacks data, it won't be displayed.
************************************************************************/
import PropTypes from 'prop-types';
import React from 'react';
import { Grid, Row, Col, Panel, ControlLabel } from 'react-bootstrap';
import DualDataController from '../../data-controllers/DualDataController/DualDataController';
import {
modelSelectorLabel,
emissionScenarioSelectorLabel,
variable1SelectorLabel,
variable2SelectorLabel,
datasetFilterPanelLabel,
} from '../../guidance-content/info/InformationItems';
import g from '../../../core/geo';
import DualMapController from '../../map-controllers/DualMapController';
import { FullWidthCol, HalfWidthCol } from '../../layout/rb-derived-components';
import FilteredDatasetsSummary from '../../data-presentation/FilteredDatasetsSummary';
import FlowArrow from '../../data-presentation/FlowArrow';
import UnfilteredDatasetsSummary from '../../data-presentation/UnfilteredDatasetsSummary';
import {
EmissionsScenarioSelector,
ModelSelector, VariableSelector
} from 'pcic-react-components';
import { getMetadata } from '../../../data-services/ce-backend';
import {
ensemble_name,
} from '../common';
import { setNamedState } from '../../../core/react-component-utils';
import withAsyncMetadata from '../../../HOCs/withAsyncMetadata';
import {
findModelNamed, findScenarioIncluding, findVariableMatching,
representativeValue, constraintsFor, filterMetaBy,
} from '../../../core/selectors';
class DualAppControllerDisplay extends React.Component {
// This is a pure (state-free), controlled component that renders the
// entire content of DualAppController, including the controls.
// It is wrapped by `withAsyncMetadata` to inject the asynchronously fetched
// metadata that it needs.
static propTypes = {
ensemble_name: PropTypes.string,
model: PropTypes.object,
scenario: PropTypes.object,
variable: PropTypes.object,
comparand: PropTypes.object,
area: PropTypes.object,
onChangeModel: PropTypes.func,
onChangeScenario: PropTypes.func,
onChangeVariable: PropTypes.func,
onChangeComparand: PropTypes.func,
onChangeArea: PropTypes.func,
meta: PropTypes.array,
};
replaceInvalidModel = findModelNamed('PCIC12');
replaceInvalidScenario = findScenarioIncluding('rcp85');
replaceInvalidVariable = findVariableMatching(opt => !opt.isDisabled);
representativeValue = (...args) => representativeValue(...args)(this.props);
constraintsFor = (...args) => constraintsFor(...args)(this.props);
filterMetaBy = (...args) =>
filterMetaBy(...args)(this.props)(this.props.meta);
render() {
const filteredMetaVariable =
this.filterMetaBy('model', 'scenario', 'variable');
const filteredMetaComparand =
this.filterMetaBy('model', 'scenario', 'comparand');
const model_id = this.representativeValue('model', 'model_id');
const experiment = this.representativeValue('scenario', 'experiment');
const variable_id = this.representativeValue('variable', 'variable_id');
const comparand_id = this.representativeValue('comparand', 'variable_id');
return (
<Grid fluid>
<Row>
<FullWidthCol>
<UnfilteredDatasetsSummary meta={this.props.meta} />
</FullWidthCol>
</Row>
<Row>
<FullWidthCol>
<FlowArrow pullUp />
</FullWidthCol>
</Row>
<Row>
<FullWidthCol>
<Panel>
<Panel.Heading>
<Panel.Title>{datasetFilterPanelLabel}</Panel.Title>
</Panel.Heading>
<Panel.Body>
<Row>
<Col lg={2} md={2}>
<ControlLabel>{modelSelectorLabel}</ControlLabel>
<ModelSelector
bases={this.props.meta}
value={this.props.model}
onChange={this.props.onChangeModel}
replaceInvalidValue={this.replaceInvalidModel}
/>
</Col>
<Col lg={2} md={2}>
<ControlLabel>{emissionScenarioSelectorLabel}</ControlLabel>
<EmissionsScenarioSelector
bases={this.props.meta}
constraint={this.constraintsFor('model')}
value={this.props.scenario}
onChange={this.props.onChangeScenario}
replaceInvalidValue={this.replaceInvalidScenario}
/>
</Col>
<Col lg={3} md={3}>
<ControlLabel>{variable1SelectorLabel}</ControlLabel>
<VariableSelector
bases={this.props.meta}
constraint={this.constraintsFor('model', 'scenario')}
value={this.props.variable}
onChange={this.props.onChangeVariable}
replaceInvalidValue={this.replaceInvalidVariable}
/>
</Col>
<Col lg={3} md={3}>
<ControlLabel>{variable2SelectorLabel}</ControlLabel>
<VariableSelector
bases={this.props.meta}
constraint={this.constraintsFor('model', 'scenario')}
value={this.props.comparand}
onChange={this.props.onChangeComparand}
replaceInvalidValue={this.replaceInvalidVariable}
/>
</Col>
</Row>
</Panel.Body>
</Panel>
</FullWidthCol>
</Row>
<Row>
<FullWidthCol>
<FlowArrow pullUp />
</FullWidthCol>
</Row>
<Row>
<FullWidthCol>
<FilteredDatasetsSummary
model_id={model_id}
experiment={experiment}
variable_id={variable_id}
comparand_id={comparand_id}
meta={filteredMetaVariable}
comparandMeta={filteredMetaComparand}
dual
/>
</FullWidthCol>
</Row>
<Row>
<HalfWidthCol>
<FlowArrow pullUp />
</HalfWidthCol>
<HalfWidthCol>
<FlowArrow pullUp />
</HalfWidthCol>
</Row>
<Row>
<HalfWidthCol>
<DualMapController
model_id={model_id}
experiment={experiment}
variable_id={variable_id}
meta={filteredMetaVariable}
comparand_id={comparand_id}
comparandMeta={filteredMetaComparand}
area={this.props.area}
onSetArea={this.props.onChangeArea}
/>
</HalfWidthCol>
<HalfWidthCol>
<DualDataController
ensemble_name={this.props.ensemble_name}
model_id={model_id}
experiment={experiment}
variable_id={variable_id}
meta={filteredMetaVariable}
comparand_id={comparand_id}
comparandMeta={
// TODO: Is this conditional necessary?
this.props.comparand ?
filteredMetaComparand :
filteredMetaVariable
}
area={g.geojson(this.props.area).toWKT()}
/>
</HalfWidthCol>
</Row>
</Grid>
);
}
}
// Inject asynchronously fetched metadata into controlled component.
const WmdDualAppControllerDisplay = withAsyncMetadata(DualAppControllerDisplay);
export default class DualAppController extends React.Component {
// This manages the state of selectors and renders the display component.
state = {
model: undefined,
scenario: undefined,
variable: undefined,
comparand: undefined,
area: undefined, // geojson object
};
// TODO: https://github.com/pacificclimate/climate-explorer-frontend/issues/122
handleChangeArea = setNamedState(this, 'area');
handleChangeModel = setNamedState(this, 'model');
handleChangeScenario = setNamedState(this, 'scenario');
handleChangeVariable = setNamedState(this, 'variable');
handleChangeComparand = setNamedState(this, 'comparand');
render() {
return (
<WmdDualAppControllerDisplay
ensemble_name={ensemble_name(this.props)}
{...this.state}
onChangeArea={this.handleChangeArea}
onChangeModel={this.handleChangeModel}
onChangeScenario={this.handleChangeScenario}
onChangeVariable={this.handleChangeVariable}
onChangeComparand={this.handleChangeComparand}
/>
);
}
}