diff --git a/assets/package-lock.json b/assets/package-lock.json index ef6f271a6d8992a2eebd90a475ea5ca7a08976e2..88678c909e6b6956523f2b17ddc7afa185f8528d 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -8727,6 +8727,11 @@ "yallist": "2.1.2" } }, + "luxon": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.2.0.tgz", + "integrity": "sha512-OFqqtLlSo7OpjFuTCJf+LYtyX1xTc5tp0X3f1ynLafjAM2qjaO6LVe0EkdfdoH1hSeS+Jo9FeAs244MEZ7dcww==" + }, "magic-string": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.23.2.tgz", diff --git a/assets/src/scripts/components/hydrograph/legend.js b/assets/src/scripts/components/hydrograph/legend.js index e34d3039186713156d61d30a4acbda0fa55a5ba9..f4fcaa95ef576c31d7fc08703d5fb2403832e238 100644 --- a/assets/src/scripts/components/hydrograph/legend.js +++ b/assets/src/scripts/components/hydrograph/legend.js @@ -5,10 +5,10 @@ const { createSelector } = require('reselect'); const { CIRCLE_RADIUS } = require('./layout'); const { defineLineMarker, defineTextOnlyMarker, defineRectangleMarker} = require('./markers'); const { currentVariableLineSegmentsSelector, HASH_ID, MASK_DESC} = require('./drawingData'); -const { currentVariableTimeSeriesSelector } = require('./timeSeries'); const { USWDS_MEDIUM_SCREEN } = require('../../config'); const { getMethods } = require('../../selectors/timeSeriesSelector'); +const { getCurrentVariableMedianMetadata} = require('../../selectors/medianStatisticsSelector'); const { mediaQuery } = require('../../utils'); const TS_LABEL = { @@ -79,7 +79,7 @@ const createLegendMarkers = function(displayItems) { } if (displayItems.median) { - for (const [index, stats] of displayItems.median.entries()) { + for (const [index, stats] of Object.values(displayItems.median).entries()) { // Get the unique non-null years, in chronological order const years = []; if (stats.beginYear) { @@ -90,7 +90,7 @@ const createLegendMarkers = function(displayItems) { } const dateText = years.join(' - '); - const descriptionText = stats.description ? `${stats.description} ` : ''; + const descriptionText = stats.methodDescription ? `${stats.methodDescription} ` : ''; const classes = `median-data-series median-step median-step-${index % 6}`; const label = `${descriptionText}${dateText}`; @@ -197,21 +197,15 @@ const uniqueClassesSelector = memoize(tsKey => createSelector( */ const legendDisplaySelector = createSelector( (state) => state.timeSeriesState.showSeries, - currentVariableTimeSeriesSelector('median'), + getCurrentVariableMedianMetadata, getMethods, uniqueClassesSelector('current'), uniqueClassesSelector('compare'), - (showSeries, medianTSs, methods, currentClasses, compareClasses) => { + (showSeries, medianSeries, methods, currentClasses, compareClasses) => { return { current: showSeries.current ? currentClasses : undefined, compare: showSeries.compare ? compareClasses : undefined, - median: showSeries.median ? Object.values(medianTSs).map(medianTS => { - return { - beginYear: medianTS ? medianTS.metadata.beginYear : undefined, - endYear: medianTS ? medianTS.metadata.endYear : undefined, - description: medianTS && medianTS.method ? methods[medianTS.method].methodDescription : '' - }; - }) : undefined + median: showSeries.median ? medianSeries : undefined }; } ); diff --git a/assets/src/scripts/selectors/medianStatisticsSelector.js b/assets/src/scripts/selectors/medianStatisticsSelector.js index 8c57c2398d8bb53f9c010e556fa15583ac08b747..08a8d3979fbfa2f89a48f6df476edea701d5b6af 100644 --- a/assets/src/scripts/selectors/medianStatisticsSelector.js +++ b/assets/src/scripts/selectors/medianStatisticsSelector.js @@ -1,9 +1,11 @@ + const memoize = require('fast-memoize'); const find = require('lodash/find'); -const reduce = require('lodash/reduce') +const reduce = require('lodash/reduce'); +const { DateTime } = require('luxon'); const { createSelector } = require('reselect'); -const { getCurrentParmCd, getRequestTimeRange } = require('./timeSeriesSelector'); +const { getCurrentParmCd, getRequestTimeRange, getIanaTimeZone } = require('./timeSeriesSelector'); /* * Selectors that return properties from the state @@ -18,35 +20,42 @@ export const getMedianStatisticsByParmCd = memoize(parmCd => createSelector( stats => stats[parmCd] || null )); +/* + * @ return {Object} where keys are TsID and the properties are the median data. + */ export const getCurrentVariableMedianStatistics = createSelector( getCurrentParmCd, getMedianStatistics, (parmCd, stats) => stats[parmCd] || null ); +/* + * @ return {Object} where keys are tsID and the properties are date (universal) and value + */ export const getCurrentVariableMedianStatPointsInDateRange = createSelector( getCurrentVariableMedianStatistics, getRequestTimeRange('current'), - (stats, timeRange) => { + getIanaTimeZone, + (stats, timeRange, ianaTimeZone) => { if (!stats || !timeRange) { return {}; } - const startDate = new Date(timeRange.start).setFullYear(timeRange.start.getFullYear(), timeRange.start.getMonth(), timeRange.start.getDate()); - const endDate = new Date(timeRange.end).setFullYear(timeRange.end.getFullYear(), timeRange.end.getMonth(), timeRange.end.getDate()); - let nextDate = new Date(startDate); + + let nextDateTime = DateTime.fromMillis(timeRange.start, {zone: ianaTimeZone}).startOf('day'); + const endTime = DateTime.fromMillis(timeRange.end, {zone: ianaTimeZone}).endOf('day').valueOf(); let datesOfInterest = []; - while (nextDate <= endDate) { + while (nextDateTime.valueOf() <= endTime) { datesOfInterest.push({ - year: nextDate.getFullYear(), - month: (nextDate.getMonth() + 1).toString(), - day: nextDate.getDate().toString() + year: nextDateTime.year, + month: nextDateTime.month.toString(), + day: nextDateTime.day.toString() }); - nextDate.setDate(nextDate.getDate() + 1); + nextDateTime = nextDateTime.plus({days: 1}); } datesOfInterest.push({ - year: nextDate.getFullYear(), - month: (nextDate.getMonth() + 1).toString(), - day: nextDate.getDate().toString() + year: nextDateTime.year, + month: nextDateTime.month.toString(), + day: nextDateTime.day.toString() }); return reduce(stats, function (result, tsData, tsId) { result[tsId] = datesOfInterest @@ -54,7 +63,12 @@ export const getCurrentVariableMedianStatPointsInDateRange = createSelector( let stat = find(tsData, {'month_nu': date.month, 'day_nu': date.day}); return { value: stat ? stat.p50_va: null, - date: new Date(parseInt(date.year), parseInt(date.month) - 1, parseInt(date.day)) + date: DateTime.fromObject({ + year: date.year, + month: parseInt(date.month), + day: parseInt(date.day), + zone: ianaTimeZone + }).startOf('day').valueOf() }; }) .filter((point) => { @@ -63,4 +77,21 @@ export const getCurrentVariableMedianStatPointsInDateRange = createSelector( return result; }, {}); } +); + +/* + * @Return an object where the key is tsID and properties are meta data for that tsId + */ +export const getCurrentVariableMedianMetadata = createSelector( + getCurrentVariableMedianStatistics, + (stats) => { + return reduce(stats, (result, tsData, tsId) => { + result[tsId] = { + beginYear: tsData[0].begin_yr, + endYear: tsData[0].end_yr, + methodDescription: tsData[0].loc_web_ds + }; + return result; + }, {}); + } ); \ No newline at end of file diff --git a/assets/src/scripts/selectors/medianStatisticsSelector.spec.js b/assets/src/scripts/selectors/medianStatisticsSelector.spec.js index 9b5bc2a31d9d48d18d590d40121b6d12c0697884..7ebf38859889d01b7bb5ec5b841ebc03f3b5936d 100644 --- a/assets/src/scripts/selectors/medianStatisticsSelector.spec.js +++ b/assets/src/scripts/selectors/medianStatisticsSelector.spec.js @@ -1,7 +1,7 @@ const { getMedianStatistics, getMedianStatisticsByParmCd, getCurrentVariableMedianStatistics, getCurrentVariableMedianStatPointsInDateRange} = require('./medianStatisticsSelector'); -describe('medianStatisticsSelector', () => { +fdescribe('medianStatisticsSelector', () => { describe('getMedianStatistics', () => { it('Return empty object if median is not in the statisticsData in the state', () => { expect(getMedianStatistics({ @@ -107,7 +107,7 @@ describe('medianStatisticsSelector', () => { }); }); - fdescribe('getCurrentVariableMedianStatsPointInDateRange', () => { + describe('getCurrentVariableMedianStatsPointInDateRange', () => { const TEST_VARS = { '45807042': { variableCode: { @@ -126,7 +126,7 @@ describe('medianStatisticsSelector', () => { queryInfo: { 'current:P7D': { notes: { - requestDT: new Date('2017-03-01 11:15'), + requestDT: new Date('2017-03-01 11:15').getTime(), 'filter:timeRange': { mode: 'PERIOD', periodDays: 7, @@ -197,14 +197,14 @@ describe('medianStatisticsSelector', () => { it('Return the expected data points', () => { let result = getCurrentVariableMedianStatPointsInDateRange(TEST_STATE); - expect(result['1234'].length).toBe(8); + expect(result['1234'].length).toBe(9); expect(result['1234'][0]).toEqual({ value: '42', - date: new Date('2017-02-22 00:00') + date: new Date('2017-02-22 00:00').getTime() }); expect(result['1234'][7]).toEqual({ value: '39', - date: new Date('2017-03-01 00:00') + date: new Date('2017-03-01 00:00').getTime() }); }); diff --git a/assets/src/scripts/utils.js b/assets/src/scripts/utils.js index 504f4fbbbde81e879f431df74e78e6349f092fd9..3d110a3d454f4c0c7850019396e81460665418da 100644 --- a/assets/src/scripts/utils.js +++ b/assets/src/scripts/utils.js @@ -159,10 +159,10 @@ export const mediaQuery = function (minWidth) { /** * Calculate the start time of a time range based on a time-delta string and the end time * - * @param period -- ISO duration for date range of the time series - * @param endTime -- the end time - * @param ianaTimeZone -- Internet Assigned Numbers Authority designation for a time zone - * @returns {int} + * @param {String} period -- ISO duration for date range of the time series + * @param {Number} endTime -- the end time as universal time + * @param {String} ianaTimeZone -- Internet Assigned Numbers Authority designation for a time zone + * @returns {Number} the start time as universal time */ export const calcStartTime = function (period, endTime, ianaTimeZone) { let startTime = new DateTime.fromMillis(endTime, {zone: ianaTimeZone}); @@ -218,4 +218,4 @@ export const parseRDB = function(rdbData) { } } return recordData; -} +};