>;
+ } else if (!timeInterval.getIsBounded()) {
+ return <>
{`${translate('please.set.the.date.range')}`}
>;
+ } else {
+ // adding information to the shifted data so that it can be plotted on the same graph with current data
+ const updateDataNew = dataNew.map(item => ({
+ ...item,
+ name: 'Shifted ' + item.name,
+ line: { ...item.line, color: '#1AA5F0' },
+ xaxis: 'x2',
+ text: Array.isArray(item.text)
+ ? item.text.map(text => text.replace(' ', ' Shifted '))
+ : item.text?.replace(' ', ' Shifted ')
+ }));
+
+ return (
+ <>
+
+ {
+ // This event emits an object that contains values indicating changes in the user's graph, such as zooming.
+ if (e['xaxis.range[0]'] && e['xaxis.range[1]']) {
+ // The event signals changes in the user's interaction with the graph.
+ // this will automatically trigger a refetch due to updating a query arg.
+ const startTS = utc(e['xaxis.range[0]']);
+ const endTS = utc(e['xaxis.range[1]']);
+ const workingTimeInterval = new TimeInterval(startTS, endTS);
+ dispatch(updateSliderRange(workingTimeInterval));
+ }
+ else if (e['xaxis.range']) {
+ // this case is when the slider knobs are dragged.
+ const range = e['xaxis.range']!;
+ const startTS = range && range[0];
+ const endTS = range && range[1];
+ dispatch(updateSliderRange(new TimeInterval(utc(startTS), utc(endTS))));
+ }
+ }, 500, { leading: false, trailing: true })
+ }
+ />
+ >
+
+ );
+
+ }
+}
diff --git a/src/client/app/components/CompareLineControlsComponent.tsx b/src/client/app/components/CompareLineControlsComponent.tsx
new file mode 100644
index 000000000..53c797a28
--- /dev/null
+++ b/src/client/app/components/CompareLineControlsComponent.tsx
@@ -0,0 +1,235 @@
+/* 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 { Input } from 'reactstrap';
+import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
+// eslint-disable-next-line max-len
+import { selectGraphState, selectQueryTimeInterval, selectShiftAmount, selectShiftTimeInterval, updateShiftAmount, updateShiftTimeInterval } from '../redux/slices/graphSlice';
+import translate from '../utils/translate';
+import { FormattedMessage } from 'react-intl';
+import { ShiftAmount } from '../types/redux/graph';
+import DateRangePicker from '@wojtekmaj/react-daterange-picker';
+import { dateRangeToTimeInterval, timeIntervalToDateRange } from '../utils/dateRangeCompatibility';
+import { selectSelectedLanguage } from '../redux/slices/appStateSlice';
+import { Value } from '@wojtekmaj/react-daterange-picker/dist/cjs/shared/types';
+import * as moment from 'moment';
+import { TimeInterval } from '../../../common/TimeInterval';
+import { showWarnNotification } from '../utils/notifications';
+
+/**
+ * @returns compare line control page
+ */
+export default function CompareLineControlsComponent() {
+ const dispatch = useAppDispatch();
+ const shiftAmount = useAppSelector(selectShiftAmount);
+ const timeInterval = useAppSelector(selectQueryTimeInterval);
+ const locale = useAppSelector(selectSelectedLanguage);
+ const shiftInterval = useAppSelector(selectShiftTimeInterval);
+ const graphState = useAppSelector(selectGraphState);
+
+ // Hold value of shifting option (week, month, year, or custom)
+ const [shiftOption, setShiftOption] = React.useState(shiftAmount);
+ // Hold value to track whether custom data range picker should show up or not
+ const [showDatePicker, setShowDatePicker] = React.useState(false);
+ // Hold value to store the custom date range for the shift interval
+ const [customDateRange, setCustomDateRange] = React.useState(timeIntervalToDateRange(shiftInterval));
+
+ // Add this useEffect to update the shift interval when the shift option changes
+ React.useEffect(() => {
+ if (shiftOption !== ShiftAmount.custom) {
+ updateShiftInterval(shiftOption);
+ }
+ }, [shiftOption, timeInterval]);
+
+ // Update custom date range value when shift interval changes
+ React.useEffect(() => {
+ setCustomDateRange(timeIntervalToDateRange(shiftInterval));
+ }, [shiftInterval]);
+
+ // Check for leap year shifting when new interval or meter/group is chosen
+ React.useEffect(() => {
+ const startDate = timeInterval.getStartTimestamp();
+ const endDate = timeInterval.getEndTimestamp();
+ if (startDate && endDate) {
+ // Check whether shifting to (or from) leap year to non leap year or not
+ checkLeapYearFunc(startDate, endDate, shiftOption);
+ }
+ }, [graphState.threeD.meterOrGroupID, timeInterval]);
+
+ // Handle changes in shift option (week, month, year, or custom)
+ const handleShiftOptionChange = (value: string) => {
+ if (value === 'custom') {
+ setShiftOption(ShiftAmount.custom);
+ dispatch(updateShiftAmount(ShiftAmount.custom));
+ setShowDatePicker(true);
+ } else {
+ setShowDatePicker(false);
+ const newShiftOption = value as ShiftAmount;
+ setShiftOption(newShiftOption);
+ dispatch(updateShiftAmount(newShiftOption));
+
+ // notify user when original data or shift data cross leap year
+ const startDate = timeInterval.getStartTimestamp();
+ const endDate = timeInterval.getEndTimestamp();
+
+ if (startDate && endDate) {
+ // Check whether shifting to (or from) leap year to non leap year or not
+ checkLeapYearFunc(startDate, endDate, newShiftOption);
+ }
+ // Update shift interval when shift option changes
+ updateShiftInterval(newShiftOption);
+ }
+ };
+
+ // update shift data date range when shift date interval option is chosen
+ const updateShiftInterval = (shiftOption: ShiftAmount) => {
+ const startDate = timeInterval.getStartTimestamp();
+ const endDate = timeInterval.getEndTimestamp();
+ if (startDate !== null || endDate !== null) {
+ const { shiftedStart, shiftedEnd } = shiftDateFunc(startDate, endDate, shiftOption);
+ const newInterval = new TimeInterval(shiftedStart, shiftedEnd);
+ dispatch(updateShiftTimeInterval(newInterval));
+ }
+ };
+
+ // Update date when the data range picker is used in custome shifting option
+ const handleShiftDateChange = (value: Value) => {
+ setCustomDateRange(value);
+ dispatch(updateShiftTimeInterval(dateRangeToTimeInterval(value)));
+ };
+
+ return (
+ <>
+
+
+
+ {/* // TODO: Add later */}
+
+ handleShiftOptionChange(e.target.value)}
+ >
+
+
+
+
+
+
+ {/* Show date picker when custom date range is selected */}
+ {showDatePicker &&
+ }
+
+
+ >
+ );
+
+}
+
+const labelStyle: React.CSSProperties = { fontWeight: 'bold', margin: 0 };
+
+/**
+ * shifting date function to find the shifted start date and shifted end date
+ * @param originalStart start date of current graph data
+ * @param originalEnd end date of current graph data
+ * @param shiftType shifting amount in week, month, or year
+ * @returns shifted start and shifted end dates for the new data
+ */
+export function shiftDateFunc(originalStart: moment.Moment, originalEnd: moment.Moment, shiftType: ShiftAmount) {
+ let shiftedStart: moment.Moment;
+ let shiftedEnd: moment.Moment;
+
+ const originalRangeDays = originalEnd.diff(originalStart, 'days');
+
+ switch (shiftType) {
+ case 'none':
+ shiftedStart = originalStart.clone();
+ shiftedEnd = originalEnd.clone();
+ break;
+
+ case 'week':
+ shiftedStart = originalStart.clone().subtract(7, 'days');
+ shiftedEnd = originalEnd.clone().subtract(7, 'days');
+ break;
+
+ case 'month':
+ shiftedStart = originalStart.clone().subtract(1, 'months');
+ shiftedEnd = shiftedStart.clone().add(originalRangeDays, 'days');
+
+ if (shiftedEnd.isSameOrAfter(originalStart)) {
+ shiftedEnd = originalStart.clone().subtract(1, 'day');
+ } else if (originalStart.date() === 1 && originalEnd.date() === originalEnd.daysInMonth()) {
+ if (!(shiftedStart.date() === 1 && shiftedEnd.date() === shiftedEnd.daysInMonth())) {
+ shiftedEnd = shiftedStart.clone().endOf('month');
+ }
+ }
+ break;
+
+ case 'year':
+ shiftedStart = originalStart.clone().subtract(1, 'years');
+ shiftedEnd = originalEnd.clone().subtract(1, 'years');
+
+ if (originalStart.isLeapYear() && originalStart.month() === 1 && originalStart.date() === 29) {
+ shiftedStart = shiftedStart.month(2).date(1);
+ }
+ if (originalEnd.isLeapYear() && originalEnd.month() === 1 && originalEnd.date() === 29) {
+ shiftedEnd = shiftedEnd.month(1).date(28);
+ }
+ if (shiftedEnd.isSameOrAfter(originalStart)) {
+ shiftedEnd = originalStart.clone().subtract(1, 'day');
+ }
+ break;
+
+ default:
+ shiftedStart = originalStart.clone();
+ shiftedEnd = originalEnd.clone();
+ }
+
+ return { shiftedStart, shiftedEnd };
+}
+
+/**
+ * This function check whether the original date range is leap year or the shifted date range is leap year.
+ * If it is true, it warns user about shifting to (or from) a leap year to non leap year.
+ * @param startDate original data start date
+ * @param endDate original data end date
+ * @param shiftOption shifting option
+ */
+function checkLeapYearFunc(startDate: moment.Moment, endDate: moment.Moment, shiftOption: ShiftAmount) {
+ const { shiftedStart, shiftedEnd } = shiftDateFunc(startDate, endDate, shiftOption);
+ const originalIsLeapYear = startDate.isLeapYear() || endDate.isLeapYear();
+ const shiftedIsLeapYear = shiftedStart.isLeapYear() || shiftedEnd.isLeapYear();
+
+ // Check if the original date range crosses Feb 29, which causes unaligned graph
+ const originalCrossFeb29 = (
+ startDate.isLeapYear() &&
+ startDate.isBefore(moment(`${startDate.year()}-03-01`)) &&
+ endDate.isAfter(moment(`${startDate.year()}-02-28`))
+ );
+
+ // Check if the shifted date range crosses Feb 29, which causes unaligned graph
+ const shiftedCrossFeb29 = (
+ shiftedStart.isLeapYear() &&
+ shiftedStart.isBefore(moment(`${shiftedStart.year()}-03-01`)) &&
+ shiftedEnd.isAfter(moment(`${shiftedStart.year()}-02-28`))
+ );
+
+ if (originalCrossFeb29 && !shiftedIsLeapYear) {
+ showWarnNotification(translate('original.data.crosses.leap.year.to.non.leap.year'));
+ } else if (shiftedCrossFeb29 && !originalIsLeapYear) {
+ showWarnNotification(translate('shifted.data.crosses.leap.year.to.non.leap.year'));
+ }
+}
\ No newline at end of file
diff --git a/src/client/app/components/DashboardComponent.tsx b/src/client/app/components/DashboardComponent.tsx
index 92fbcf3a2..ce63cdd32 100644
--- a/src/client/app/components/DashboardComponent.tsx
+++ b/src/client/app/components/DashboardComponent.tsx
@@ -15,6 +15,7 @@ import RadarChartComponent from './RadarChartComponent';
import ThreeDComponent from './ThreeDComponent';
import UIOptionsComponent from './UIOptionsComponent';
import PlotNavComponent from './PlotNavComponent';
+import CompareLineChartComponent from './CompareLineChartComponent';
/**
* React component that controls the dashboard
@@ -39,6 +40,7 @@ export default function DashboardComponent() {
{chartToRender === ChartTypes.map && }
{chartToRender === ChartTypes.threeD && }
{chartToRender === ChartTypes.radar && }
+ {chartToRender === ChartTypes.compareLine && }
diff --git a/src/client/app/components/MoreOptionsComponent.tsx b/src/client/app/components/MoreOptionsComponent.tsx
index 4441d882c..2671c88a4 100644
--- a/src/client/app/components/MoreOptionsComponent.tsx
+++ b/src/client/app/components/MoreOptionsComponent.tsx
@@ -32,7 +32,7 @@ export default function MoreOptionsComponent() {
return (
<>
{
-
+
@@ -74,6 +74,13 @@ export default function MoreOptionsComponent() {
{chartToRender == ChartTypes.radar && }
{chartToRender == ChartTypes.radar && }
{chartToRender == ChartTypes.radar && }
+
+ {/*More UI options for compare line */}
+ {chartToRender === ChartTypes.compareLine && }
+ {chartToRender === ChartTypes.compareLine && }
+ {chartToRender === ChartTypes.compareLine && }
+ {chartToRender === ChartTypes.compareLine && }
+ {chartToRender === ChartTypes.compareLine && }
diff --git a/src/client/app/components/UIOptionsComponent.tsx b/src/client/app/components/UIOptionsComponent.tsx
index 40d2f18b8..9fa904cbe 100644
--- a/src/client/app/components/UIOptionsComponent.tsx
+++ b/src/client/app/components/UIOptionsComponent.tsx
@@ -15,6 +15,7 @@ import DateRangeComponent from './DateRangeComponent';
import MapControlsComponent from './MapControlsComponent';
import ReadingsPerDaySelectComponent from './ReadingsPerDaySelectComponent';
import MoreOptionsComponent from './MoreOptionsComponent';
+import CompareLineControlsComponent from './CompareLineControlsComponent';
/**
* @returns the UI Control panel
@@ -80,6 +81,11 @@ export default function UIOptionsComponent() {
{/* UI options for radar graphic */}
{chartToRender == ChartTypes.radar}
+ { /* Controls specific to the compare line chart */}
+ {chartToRender === ChartTypes.compareLine && }
+ {chartToRender === ChartTypes.compareLine && }
+
+
diff --git a/src/client/app/redux/selectors/chartQuerySelectors.ts b/src/client/app/redux/selectors/chartQuerySelectors.ts
index 73e68216b..b9e53bcff 100644
--- a/src/client/app/redux/selectors/chartQuerySelectors.ts
+++ b/src/client/app/redux/selectors/chartQuerySelectors.ts
@@ -14,6 +14,7 @@ import {
selectSelectedUnit, selectThreeDState
} from '../slices/graphSlice';
import { omit } from 'lodash';
+import { selectLineChartDeps } from './lineChartSelectors';
// query args that 'most' graphs share
export interface commonQueryArgs {
@@ -25,6 +26,7 @@ export interface commonQueryArgs {
// endpoint specific args
export interface LineReadingApiArgs extends commonQueryArgs { }
+export interface CompareLineReadingApiArgs extends commonQueryArgs { }
export interface BarReadingApiArgs extends commonQueryArgs { barWidthDays: number }
// ThreeD only queries a single id so extend common, but omit ids array
@@ -81,6 +83,32 @@ export const selectLineChartQueryArgs = createSelector(
}
);
+export const selectCompareLineQueryArgs = createSelector(
+ selectQueryTimeInterval,
+ selectSelectedUnit,
+ selectThreeDState,
+ selectLineChartDeps,
+ (queryTimeInterval, selectedUnit, threeD, lineChartDeps) => {
+ const args: CompareLineReadingApiArgs =
+ threeD.meterOrGroup === MeterOrGroup.meters
+ ? {
+ ids: [threeD.meterOrGroupID!],
+ timeInterval: queryTimeInterval.toString(),
+ graphicUnitId: selectedUnit,
+ meterOrGroup: threeD.meterOrGroup!
+ }
+ : {
+ ids: [threeD.meterOrGroupID!],
+ timeInterval: queryTimeInterval.toString(),
+ graphicUnitId: selectedUnit,
+ meterOrGroup: threeD.meterOrGroup!
+ };
+ const shouldSkipQuery = !threeD.meterOrGroupID || !queryTimeInterval.getIsBounded();
+ const argsDeps = threeD.meterOrGroup === MeterOrGroup.meters ? lineChartDeps.meterDeps : lineChartDeps.groupDeps;
+ return { args, shouldSkipQuery, argsDeps };
+ }
+);
+
export const selectRadarChartQueryArgs = createSelector(
selectLineChartQueryArgs,
lineChartArgs => {
@@ -185,11 +213,13 @@ export const selectAllChartQueryArgs = createSelector(
selectCompareChartQueryArgs,
selectMapChartQueryArgs,
selectThreeDQueryArgs,
- (line, bar, compare, map, threeD) => ({
+ selectCompareLineQueryArgs,
+ (line, bar, compare, map, threeD, compareLine) => ({
line,
bar,
compare,
map,
- threeD
+ threeD,
+ compareLine
})
);
diff --git a/src/client/app/redux/selectors/lineChartSelectors.ts b/src/client/app/redux/selectors/lineChartSelectors.ts
index d4ca92247..b69ec7b28 100644
--- a/src/client/app/redux/selectors/lineChartSelectors.ts
+++ b/src/client/app/redux/selectors/lineChartSelectors.ts
@@ -45,6 +45,7 @@ export const selectPlotlyMeterData = selectFromLineReadingsResult(
const yMinData: number[] = [];
const yMaxData: number[] = [];
const hoverText: string[] = [];
+
// The scaling is the factor to change the reading by. It divides by the area while will be 1 if no scaling by area.
readings.forEach(reading => {
// As usual, we want to interpret the readings in UTC. We lose the timezone as this as the start/endTimestamp
diff --git a/src/client/app/redux/slices/graphSlice.ts b/src/client/app/redux/slices/graphSlice.ts
index 91e38edd2..0ccbc0c05 100644
--- a/src/client/app/redux/slices/graphSlice.ts
+++ b/src/client/app/redux/slices/graphSlice.ts
@@ -13,7 +13,7 @@ import {
updateHistory, updateSliderRange
} from '../../redux/actions/extraActions';
import { SelectOption } from '../../types/items';
-import { ChartTypes, GraphState, LineGraphRate, MeterOrGroup, ReadingInterval } from '../../types/redux/graph';
+import { ChartTypes, GraphState, LineGraphRate, MeterOrGroup, ReadingInterval, ShiftAmount } from '../../types/redux/graph';
import { ComparePeriod, SortingOrder, calculateCompareTimeInterval, validateComparePeriod, validateSortingOrder } from '../../utils/calculateCompare';
import { AreaUnitType } from '../../utils/getAreaUnitConversion';
import { preferencesApi } from '../api/preferencesApi';
@@ -39,7 +39,9 @@ const defaultState: GraphState = {
meterOrGroup: undefined,
readingInterval: ReadingInterval.Hourly
},
- hotlinked: false
+ hotlinked: false,
+ shiftAmount: ShiftAmount.none,
+ shiftTimeInterval: TimeInterval.unbounded()
};
interface History {
@@ -88,6 +90,14 @@ export const graphSlice = createSlice({
state.current.queryTimeInterval = action.payload;
}
},
+ updateShiftTimeInterval: (state, action: PayloadAction) => {
+ if (action.payload.getIsBounded() || state.current.shiftTimeInterval.getIsBounded()) {
+ state.current.shiftTimeInterval = action.payload;
+ }
+ },
+ updateShiftAmount: (state, action: PayloadAction) => {
+ state.current.shiftAmount = action.payload;
+ },
changeSliderRange: (state, action: PayloadAction) => {
if (action.payload.getIsBounded() || state.current.rangeSliderInterval.getIsBounded()) {
state.current.rangeSliderInterval = action.payload;
@@ -387,7 +397,9 @@ export const graphSlice = createSlice({
selectHistoryIsDirty: state => state.prev.length > 0 || state.next.length > 0,
selectSliderRangeInterval: state => state.current.rangeSliderInterval,
selectPlotlySliderMin: state => state.current.rangeSliderInterval.getStartTimestamp()?.utc().toDate().toISOString(),
- selectPlotlySliderMax: state => state.current.rangeSliderInterval.getEndTimestamp()?.utc().toDate().toISOString()
+ selectPlotlySliderMax: state => state.current.rangeSliderInterval.getEndTimestamp()?.utc().toDate().toISOString(),
+ selectShiftAmount: state => state.current.shiftAmount,
+ selectShiftTimeInterval: state => state.current.shiftTimeInterval
}
});
@@ -405,7 +417,8 @@ export const {
selectThreeDMeterOrGroupID, selectThreeDReadingInterval,
selectGraphAreaNormalization, selectSliderRangeInterval,
selectDefaultGraphState, selectHistoryIsDirty,
- selectPlotlySliderMax, selectPlotlySliderMin
+ selectPlotlySliderMax, selectPlotlySliderMin,
+ selectShiftAmount, selectShiftTimeInterval
} = graphSlice.selectors;
// actionCreators exports
@@ -422,6 +435,7 @@ export const {
toggleAreaNormalization, updateThreeDMeterOrGroup,
changeCompareSortingOrder, updateThreeDMeterOrGroupID,
updateThreeDReadingInterval, updateThreeDMeterOrGroupInfo,
- updateSelectedMetersOrGroups
+ updateSelectedMetersOrGroups, updateShiftAmount,
+ updateShiftTimeInterval
} = graphSlice.actions;
diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts
index 4b0b634a1..d909af5f6 100644
--- a/src/client/app/translations/data.ts
+++ b/src/client/app/translations/data.ts
@@ -47,7 +47,7 @@ const LocaleTranslationData = {
"clipboard.copied": "Copied To Clipboard",
"clipboard.not.copied": "Failed to Copy To Clipboard",
"close": "Close",
- "compare": "Compare",
+ "compare": "Compare bar",
"compare.period": "Compare Period",
"compare.raw": "Cannot create comparison graph on raw units such as temperature",
"confirm.action": "Confirm Action",
@@ -508,7 +508,19 @@ const LocaleTranslationData = {
"week": "Week",
"yes": "yes",
"yesterday": "Yesterday",
- "you.cannot.create.a.cyclic.group": "You cannot create a cyclic group"
+ "you.cannot.create.a.cyclic.group": "You cannot create a cyclic group",
+ "compare.line": "Compare line",
+ "shift.date.interval": "Shift Date Interval",
+ "1.month": "1 month",
+ "1.year": "1 year",
+ "2.months": "2 months",
+ "1.week": "1 week",
+ "compare.line.days.enter": "Enter in days and then hit enter",
+ "please.set.the.date.range": "Please choose date range",
+ "select.shift.amount": "Select shift amount",
+ "custom.date.range": "Custom date range",
+ "shifted.data.crosses.leap.year.to.non.leap.year": "Shifted data crosses a leap year so the graph might not align appropriately",
+ "original.data.crosses.leap.year.to.non.leap.year": "Original data crosses a leap year so the graph might not align appropriately"
},
"fr": {
"3D": "3D",
@@ -1010,7 +1022,20 @@ const LocaleTranslationData = {
"week": "Semaine",
"yes": " yes\u{26A1}",
"yesterday": "Hier",
- "you.cannot.create.a.cyclic.group": "Vous ne pouvez pas créer un groupe cyclique"
+ "you.cannot.create.a.cyclic.group": "Vous ne pouvez pas créer un groupe cyclique",
+ "compare.line": "Compare line\u{26A1}",
+ "shift.date.interval": "Shift Date Interval\u{26A1}",
+ "a few seconds": "a few seconds\u{26A1}",
+ "1.month": "1 month\u{26A1}",
+ "1.year": "1 year\u{26A1}",
+ "2.months": "2 months\u{26A1}",
+ "1.week": "1 week\u{26A1}",
+ "compare.line.days.enter": "Enter in days and then hit enter\u{26A1}",
+ "please.set.the.date.range": "Please choose date range\u{26A1}",
+ "select.shift.amount": "Select shift amount\u{26A1}",
+ "custom.date.range": "Custom date range\u{26A1}",
+ "shifted.data.crosses.leap.year.to.non.leap.year": "Shifted data crosses a leap year so the graph might not align appropriately\u{26A1}",
+ "original.data.crosses.leap.year.to.non.leap.year": "Original data crosses a leap year so the graph might not align appropriately\u{26A1}"
},
"es": {
"3D": "3D",
@@ -1513,7 +1538,20 @@ const LocaleTranslationData = {
"week": "semana",
"yes": "sí",
"yesterday": "Ayer",
- "you.cannot.create.a.cyclic.group": "No se puede crear un grupo cíclico"
+ "you.cannot.create.a.cyclic.group": "No se puede crear un grupo cíclico",
+ "compare.line": "Compare line\u{26A1}",
+ "shift.date.interval": "Shift Date Interval\u{26A1}",
+ "a few seconds": "a few seconds\u{26A1}",
+ "1.month": "1 month\u{26A1}",
+ "1.year": "1 year\u{26A1}",
+ "2.months": "2 months\u{26A1}",
+ "1.week": "1 week\u{26A1}",
+ "compare.line.days.enter": "Enter in days and then hit enter\u{26A1}",
+ "please.set.the.date.range": "Please choose date range\u{26A1}",
+ "select.shift.amount": "Select shift amount\u{26A1}",
+ "custom.date.range": "Custom date range\u{26A1}",
+ "shifted.data.crosses.leap.year.to.non.leap.year": "Shifted data crosses a leap year so the graph might not align appropriately\u{26A1}",
+ "original.data.crosses.leap.year.to.non.leap.year": "Original data crosses a leap year so the graph might not align appropriately\u{26A1}"
}
}
diff --git a/src/client/app/types/redux/graph.ts b/src/client/app/types/redux/graph.ts
index 601dd51b9..b388dbb4c 100644
--- a/src/client/app/types/redux/graph.ts
+++ b/src/client/app/types/redux/graph.ts
@@ -13,7 +13,8 @@ export enum ChartTypes {
compare = 'compare',
map = 'map',
radar = 'radar',
- threeD = '3D'
+ threeD = '3D',
+ compareLine = 'compare.line'
}
// Rates that can be graphed, only relevant to line graphs.
@@ -55,6 +56,14 @@ export interface ThreeDState {
readingInterval: ReadingInterval;
}
+export enum ShiftAmount {
+ week = 'week',
+ month = 'month',
+ year = 'year',
+ custom = 'custom',
+ none = 'none'
+}
+
export interface GraphState {
areaNormalization: boolean;
selectedMeters: number[];
@@ -73,4 +82,6 @@ export interface GraphState {
threeD: ThreeDState;
queryTimeInterval: TimeInterval;
hotlinked: boolean;
+ shiftAmount: ShiftAmount;
+ shiftTimeInterval: TimeInterval;
}