Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dual axis support #239

Merged
merged 26 commits into from
Apr 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions packages/app/i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2019-02-27T09:12:48.095Z\n"
"PO-Revision-Date: 2019-02-27T09:12:48.095Z\n"
"POT-Creation-Date: 2019-04-09T19:49:32.869Z\n"
"PO-Revision-Date: 2019-04-09T19:49:32.869Z\n"

msgid "Rename successful"
msgstr ""
Expand All @@ -17,6 +17,26 @@ msgstr ""
msgid "Data Visualizer"
msgstr ""

msgid "Axis 1"
msgstr ""

msgid "Axis 2"
msgstr ""

msgid "Multi-axes chart"
msgstr ""

msgid ""
"A chart can have two axes. Each axis will have its own scale. Set the axis "
"for each data selection below."
msgstr ""

msgid "Cancel"
msgstr ""

msgid "Update"
msgstr ""

msgid "Data"
msgstr ""

Expand Down Expand Up @@ -110,6 +130,9 @@ msgstr ""
msgid "Move to"
msgstr ""

msgid "Dual-axis"
msgstr ""

msgid "None selected"
msgstr ""

Expand All @@ -122,9 +145,6 @@ msgstr ""
msgid "Viewing interpretation from {{interpretationDate}}"
msgstr ""

msgid "Update"
msgstr ""

msgid "Create a new visualization by adding dimensions to the layout"
msgstr ""

Expand Down Expand Up @@ -230,6 +250,9 @@ msgstr ""
msgid "Range axis tick steps"
msgstr ""

msgid "Tick steps may extend the chart beyond 'Range axis max'"
msgstr ""

msgid "Trend line"
msgstr ""

Expand Down
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"chalk": "2.4.1",
"css-loader": "0.28.7",
"d2": "31.2.1",
"d2-charts-api": "31.0.14",
"d2-charts-api": "32.0.0",
"d2-manifest": "^1.0.0",
"data-visualizer-plugin": "github:d2-ci/data-visualizer-plugin",
"dotenv": "6.0.0",
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/actions/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
SET_UI_RIGHT_SIDEBAR_OPEN,
SET_UI_INTERPRETATION,
CLEAR_UI_INTERPRETATION,
SET_AXES,
} from '../reducers/ui';

export const acSetUi = value => ({
Expand Down Expand Up @@ -117,3 +118,8 @@ export const acSetUiInterpretation = value => ({
export const acClearUiInterpretation = () => ({
type: CLEAR_UI_INTERPRETATION,
});

export const acSetAxes = value => ({
type: SET_AXES,
value,
});
22 changes: 10 additions & 12 deletions packages/app/src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import {
CURRENT_AO_KEY,
} from '../api/userDataStore';

import '@dhis2/ui/defaults/reset.css';
import '@dhis2/ui/css/reset.css';

import './App.css';
import './scrollbar.css';
import { getParentGraphMapFromVisualization } from '../modules/ui';
import AxisSetup from './AxisSetup/AxisSetup';

export class App extends Component {
unlisten = null;
Expand Down Expand Up @@ -161,6 +162,7 @@ export class App extends Component {

return (
<FatalErrorBoundary>
<AxisSetup />
<div className="app flex-ct flex-dir-col">
<div className="section-headerbar">
<HeaderBar appName={i18n.t('Data Visualizer')} />
Expand Down Expand Up @@ -204,17 +206,13 @@ export class App extends Component {
}
}

const mapStateToProps = state => {
return {
settings: fromReducers.fromSettings.sGetSettings(state),
current: fromReducers.fromCurrent.sGetCurrent(state),
interpretations: fromReducers.fromVisualization.sGetInterpretations(
state
),
loadError: fromReducers.fromLoader.sGetLoadError(state),
ui: sGetUi(state),
};
};
const mapStateToProps = state => ({
settings: fromReducers.fromSettings.sGetSettings(state),
current: fromReducers.fromCurrent.sGetCurrent(state),
interpretations: fromReducers.fromVisualization.sGetInterpretations(state),
loadError: fromReducers.fromLoader.sGetLoadError(state),
ui: sGetUi(state),
});

const mapDispatchToProps = dispatch => ({
setCurrentFromUi: ui =>
Expand Down
251 changes: 251 additions & 0 deletions packages/app/src/components/AxisSetup/AxisSetup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import i18n from '@dhis2/d2-i18n';
import isEqual from 'lodash-es/isEqual';

import { withStyles } from '@material-ui/core/styles';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import Button from '@material-ui/core/Button';
import Radio from '@material-ui/core/Radio';
import Table from '@material-ui/core/Table';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import TableCell from '@material-ui/core/TableCell';
import TableBody from '@material-ui/core/TableBody';

import styles from './styles/AxisSetup.style';
import { axis1, axis2 } from './constants';
import { sGetUiActiveModalDialog, DEFAULT_UI, sGetUi } from '../../reducers/ui';
import { sGetAxisSetupItems } from '../../reducers';
import { acSetAxes, acSetUiActiveModalDialog } from '../../actions/ui';
import { acSetCurrentFromUi } from '../../actions/current';

export const AXIS_SETUP_DIALOG_ID = 'axisSetup';

class AxisSetup extends Component {
state = {
items: undefined,
};

componentDidMount() {
this.setItems(this.props.items);
}

componentDidUpdate(prevProps) {
const oldItems = prevProps.items;
const newItems = this.props.items;

if (!isEqual(oldItems, newItems)) {
this.setItems(newItems);
}
}

setItems = items => {
const itemsMap = items.reduce((itemsMap, item) => {
itemsMap[item.id] = item;
return itemsMap;
}, {});

this.setState({
items: itemsMap,
});
};

onAxisChange = (item, axis) => {
this.setState({
items: {
...this.state.items,
[item.id]: {
...item,
axis,
},
},
});
};

getAxes = () => {
const axes = Object.keys(this.state.items).reduce((map, id) => {
const axis = this.state.items[id].axis;

if (axis > 0) {
map[id] = axis;
}

return map;
}, {});

return Object.keys(axes).length > 0 ? axes : DEFAULT_UI.axes;
};

renderTable() {
const { classes } = this.props;

return (
<Table>
<colgroup>
<col className={classes.nameColumn} />
<col
className={`${classes.axisColumn} ${
classes.coloredColumn
}`}
/>
<col className={classes.axisColumn} />
</colgroup>
<TableHead>
<TableRow>
<TableCell className={classes.head} />
<TableCell
className={`${classes.tableCell} ${classes.head}`}
align="center"
>
{i18n.t('Axis 1')}
</TableCell>
<TableCell
className={`${classes.tableCell} ${classes.head}`}
align="center"
>
{i18n.t('Axis 2')}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.keys(this.state.items).map(id => {
const item = this.state.items[id];

return (
<TableRow key={`multiaxis-table-row-${id}`}>
<TableCell className={classes.tableCell}>
{item.name}
</TableCell>
<TableCell
className={classes.tableCell}
align="center"
>
<Radio
onClick={() =>
this.onAxisChange(item, axis1)
}
checked={item.axis === axis1}
className={classes.axisRadio}
/>
</TableCell>
<TableCell
className={classes.tableCell}
align="center"
>
<Radio
onClick={() =>
this.onAxisChange(item, axis2)
}
checked={item.axis === axis2}
className={classes.axisRadio}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
}

render() {
const { classes, isOpen, dialogMaxWidth, onCancelClick } = this.props;

return (
<Dialog
open={isOpen}
maxWidth={dialogMaxWidth}
onClose={onCancelClick}
disableEnforceFocus
>
<DialogTitle>{i18n.t('Multi-axes chart')}</DialogTitle>
<DialogContent className={classes.dialogContent}>
<p>
{i18n.t(
'A chart can have two axes. Each axis will have its own scale. Set the axis for each data selection below.'
)}
</p>
<div className={classes.tableContainer}>
{this.state.items && this.renderTable()}
</div>
</DialogContent>
<DialogActions className={classes.dialogActions}>
<Button
color="primary"
disableRipple
disableFocusRipple
onClick={onCancelClick}
>
{i18n.t('Cancel')}
</Button>
<Button
color="primary"
variant="contained"
onClick={() =>
this.props.onUpdateClick(
this.getAxes(),
this.props.ui
)
}
>
{i18n.t('Update')}
</Button>
</DialogActions>
</Dialog>
);
}
}

AxisSetup.propTypes = {
classes: PropTypes.object,
isOpen: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
axis: PropTypes.number.isRequired,
})
),
onUpdateClick: PropTypes.func.isRequired,
onCancelClick: PropTypes.func,
dialogMaxWidth: PropTypes.string,
};

AxisSetup.defaultProps = {
classes: {},
isOpen: false,
items: [],
onUpdateClick: Function.prototype,
onCancelClick: Function.prototype,
dialogMaxWidth: 'md',
};

const mapStateToProps = state => ({
isOpen: sGetUiActiveModalDialog(state) === AXIS_SETUP_DIALOG_ID,
items: sGetAxisSetupItems(state),
ui: sGetUi(state),
});

const mapDispatchToProps = dispatch => ({
onUpdateClick: (axes, ui) => {
dispatch(acSetAxes(axes));
dispatch(
acSetCurrentFromUi({
...ui,
axes,
})
);
dispatch(acSetUiActiveModalDialog());
},
onCancelClick: () => dispatch(acSetUiActiveModalDialog()),
});

export default connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(AxisSetup));
Loading