Skip to content

Commit

Permalink
Merge pull request #975 from Elias0127/error_bar
Browse files Browse the repository at this point in the history
Error bar
  • Loading branch information
huss authored Aug 17, 2023
2 parents a4add6e + 2d3edc9 commit e6e9162
Show file tree
Hide file tree
Showing 24 changed files with 829 additions and 210 deletions.
10 changes: 8 additions & 2 deletions src/client/app/actions/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export function changeChartToRender(chartType: t.ChartTypes): t.ChangeChartToRen
export function toggleAreaNormalization(): t.ToggleAreaNormalizationAction {
return { type: ActionType.ToggleAreaNormalization };
}
export function toggleShowMinMax(): t.ToggleShowMinMaxAction {
return { type: ActionType.ToggleShowMinMax }
}

export function changeBarStacking(): t.ChangeBarStackingAction {
return { type: ActionType.ChangeBarStacking };
Expand Down Expand Up @@ -213,6 +216,7 @@ export interface LinkOptions {
sliderRange?: TimeInterval;
toggleAreaNormalization?: boolean;
areaUnit?: string;
toggleMinMax?: boolean;
toggleBarStacking?: boolean;
comparePeriod?: ComparePeriod;
compareSortingOrder?: SortingOrder;
Expand All @@ -231,7 +235,7 @@ export function changeOptionsFromLink(options: LinkOptions) {
const dispatchSecond: Array<Thunk | t.ChangeChartToRenderAction | t.ChangeBarStackingAction |
t.ChangeGraphZoomAction | t.ChangeCompareSortingOrderAction | t.ToggleOptionsVisibility |
m.UpdateSelectedMapAction | t.UpdateLineGraphRate | t.ToggleAreaNormalizationAction |
t.UpdateSelectedAreaUnitAction> = [];
t.UpdateSelectedAreaUnitAction | t.ToggleShowMinMaxAction> = [];
/* eslint-enable @typescript-eslint/indent */

if (options.meterIDs) {
Expand Down Expand Up @@ -266,7 +270,9 @@ export function changeOptionsFromLink(options: LinkOptions) {
}
if (options.areaUnit) {
dispatchSecond.push(updateSelectedAreaUnit(options.areaUnit as AreaUnitType));

}
if (options.toggleMinMax) {
dispatchSecond.push(toggleShowMinMax());
}
if (options.toggleBarStacking) {
dispatchSecond.push(changeBarStacking());
Expand Down
42 changes: 42 additions & 0 deletions src/client/app/components/ErrorBarComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import * as React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { State } from '../types/redux/state';
import { toggleShowMinMax } from '../actions/graph';
import translate from '../utils/translate';
import TooltipMarkerComponent from './TooltipMarkerComponent';

/**
* React Component rendering an Error Bar checkbox for toggle operation.
* @returns Error Bar checkbox with tooltip and label
*/
export default function ErrorBarComponent() {
const dispatch = useDispatch();
const graphState = useSelector((state: State) => state.graph);

/**
* Dispatches an action to toggle visibility of min/max lines on checkbox interaction
*/
const handleToggleShowMinMax = () => {
dispatch(toggleShowMinMax());
}

return (
<div className='checkbox'>
<input
type='checkbox'
style={{ marginRight: '10px' }}
onChange={() => handleToggleShowMinMax()}
checked={graphState.showMinMax}
id='errorBar'
/>
<label htmlFor='errorBar'>
{translate('error.bar')}
</label>
<TooltipMarkerComponent page='home' helpTextId='help.home.error.bar' />
</div>
);
}
6 changes: 4 additions & 2 deletions src/client/app/components/ExportComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export default function ExportComponent() {
const adminState = useSelector((state: State) => state.admin);
// readings state
const readingsState = useSelector((state: State) => state.readings);
// error bar state
const errorBarState = useSelector((state: State) => state.graph.showMinMax);
// Time range of graphic
const timeInterval = graphState.timeInterval;

Expand Down Expand Up @@ -84,7 +86,7 @@ export default function ExportComponent() {
const sortedReadings = _.sortBy(readings, item => item.startTimestamp, 'asc');
// Identifier for current meter.
const meterIdentifier = metersState[meterId].identifier;
graphExport(sortedReadings, meterIdentifier, unitLabel, unitIdentifier, chartName, scaling);
graphExport(sortedReadings, meterIdentifier, unitLabel, unitIdentifier, chartName, scaling, errorBarState);
}
}
}
Expand Down Expand Up @@ -324,4 +326,4 @@ export default function ExportComponent() {
</div> : ''}
</>
);
}
}
6 changes: 6 additions & 0 deletions src/client/app/components/RouteComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface RouteProps {
role: UserRole;
renderOnce: boolean;
areaNormalization: boolean;
minMax: boolean;
changeOptionsFromLink(options: LinkOptions): Promise<any[]>;
clearCurrentUser(): any;
changeRenderOnce(): any;
Expand Down Expand Up @@ -214,6 +215,11 @@ export default class RouteComponent extends React.Component<RouteProps> {
case 'areaUnit':
options.areaUnit = info;
break;
case 'minMax':
if (this.props.minMax.toString() !== info) {
options.toggleMinMax = true;
}
break;
case 'comparePeriod':
options.comparePeriod = validateComparePeriod(info);
break;
Expand Down
1 change: 1 addition & 0 deletions src/client/app/components/TooltipHelpComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export default class TooltipHelpComponent extends React.Component<TooltipHelpPro
'help.home.chart.select': { link: `${HELP_URL}/graphType.html` },
'help.home.compare.interval.tip': { link: `${HELP_URL}/compareGraphic.html#usage` },
'help.home.compare.sort.tip': { link: `${HELP_URL}/compareGraphic.html#usage` },
'help.home.error.bar': { link: `${HELP_URL}/errorBar.html#usage` },
'help.home.export.graph.data': { link: `${HELP_URL}/export.html` },
'help.home.hide.or.show.options': { link: `${HELP_URL}/hideOptions.html` },
'help.home.map.interval.tip': { link: `${HELP_URL}/mapGraphic.html#usage` },
Expand Down
10 changes: 7 additions & 3 deletions src/client/app/components/UIOptionsComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import MapChartSelectComponent from './MapChartSelectComponent';
import ReactTooltip from 'react-tooltip';
import GraphicRateMenuComponent from './GraphicRateMenuComponent';
import AreaUnitSelectComponent from './AreaUnitSelectComponent';
import ErrorBarComponent from './ErrorBarComponent';

const Slider = createSliderWithTooltip(sliderWithoutTooltips);

Expand Down Expand Up @@ -88,8 +89,11 @@ class UIOptionsComponent extends React.Component<UIOptionsPropsWithIntl, UIOptio
<ChartSelectComponent />
<ChartDataSelectComponent />
<GraphicRateMenuComponent />
<AreaUnitSelectComponent/>

<AreaUnitSelectComponent />
{/* Controls error bar, specifically for the line chart. */}
{this.props.chartToRender === ChartTypes.line &&
<ErrorBarComponent />
}
{/* Controls specific to the bar chart. */}
{this.props.chartToRender === ChartTypes.bar &&
<div>
Expand Down Expand Up @@ -327,4 +331,4 @@ class UIOptionsComponent extends React.Component<UIOptionsPropsWithIntl, UIOptio
}
}

export default injectIntl(UIOptionsComponent);
export default injectIntl(UIOptionsComponent);
1 change: 1 addition & 0 deletions src/client/app/containers/ChartLinkContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ function mapStateToProps(state: State) {
linkText += `&unitID=${unitID.toString()}`;
linkText += `&rate=${state.graph.lineGraphRate.label.toString()},${state.graph.lineGraphRate.rate.toString()}`;
linkText += `&areaUnit=${state.graph.selectedAreaUnit}&areaNormalization=${state.graph.areaNormalization}`;
linkText += `&minMax=${state.graph.showMinMax}`;
return {
linkText,
chartType
Expand Down
26 changes: 24 additions & 2 deletions src/client/app/containers/LineChartContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ function mapStateToProps(state: State) {
// Create two arrays for the x and y values. Fill the array with the data from the line readings
const xData: string[] = [];
const yData: number[] = [];
// Create two arrays to store the min and max values of y-axis data points
const yMinData: number[] = [];
const yMaxData: number[] = [];
const hoverText: string[] = [];
const readings = _.values(readingsData.readings);
// The scaling is the factor to change the reading by. It divides by the area while will be 1 if no scaling by area.
// const scaling = currentSelectedRate.rate / meterArea;
readings.forEach(reading => {
// As usual, we want to interpret the readings in UTC. We lose the timezone as this as the start/endTimestamp
// are equivalent to Unix timestamp in milliseconds.
Expand All @@ -81,7 +83,20 @@ function mapStateToProps(state: State) {
xData.push(timeReading.format('YYYY-MM-DD HH:mm:ss'));
const readingValue = reading.reading * scaling;
yData.push(readingValue);
hoverText.push(`<b> ${timeReading.format('ddd, ll LTS')} </b> <br> ${label}: ${readingValue.toPrecision(6)} ${unitLabel}`);
// All hover have the date, meter name and value.
const hoverStart = `<b> ${timeReading.format('ddd, ll LTS')} </b> <br> ${label}: ${readingValue.toPrecision(6)} ${unitLabel}`;
if (state.graph.showMinMax && reading.max != null) {
// We want to show min/max. Note if the data is raw for this meter then all the min/max values are null.
// In this case we still push the min/max but plotly will not show them. This is a little extra work
// but makes the code cleaner.
const minValue = reading.min * scaling;
yMinData.push(minValue);
const maxValue = reading.max * scaling;
yMaxData.push(maxValue);
hoverText.push(`${hoverStart} <br> ${translate('min')}: ${minValue.toPrecision(6)} <br> ${translate('max')}: ${maxValue.toPrecision(6)}`);
} else {
hoverText.push(hoverStart);
}
});

/*
Expand All @@ -103,6 +118,13 @@ function mapStateToProps(state: State) {
name: label,
x: xData,
y: yData,
// only show error bars if enabled and there is data
error_y: state.graph.showMinMax && yMaxData.length > 0 ? {
type: 'data',
symmetric: false,
array: yMaxData.map((maxValue, index) => (maxValue - yData[index])),
arrayminus: yData.map((value, index) => (value - yMinData[index]))
} : undefined,
text: hoverText,
hoverinfo: 'text',
type: 'scatter',
Expand Down
3 changes: 2 additions & 1 deletion src/client/app/containers/RouteContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function mapStateToProps(state: State) {
role,
// true if the chartlink rendering has been done.
renderOnce: state.graph.renderOnce,
areaNormalization: state.graph.areaNormalization
areaNormalization: state.graph.areaNormalization,
minMax: state.graph.showMinMax
};
}

Expand Down
10 changes: 8 additions & 2 deletions src/client/app/reducers/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ const defaultState: GraphState = {
areaNormalization: false,
hotlinked: false,
optionsVisibility: true,
lineGraphRate: {label: 'hour', rate: 1},
renderOnce: false
lineGraphRate: { label: 'hour', rate: 1 },
renderOnce: false,
showMinMax: false
};

export default function graph(state = defaultState, action: GraphAction) {
Expand Down Expand Up @@ -93,6 +94,11 @@ export default function graph(state = defaultState, action: GraphAction) {
...state,
areaNormalization: !state.areaNormalization
};
case ActionType.ToggleShowMinMax:
return {
...state,
showMinMax: !state.showMinMax
};
case ActionType.ChangeBarStacking:
return {
...state,
Expand Down
Loading

0 comments on commit e6e9162

Please sign in to comment.