Newer
Older
const { createSelector } = require('reselect');
const { line } = require('d3-shape');
const { select } = require('d3-selection');
const { allTimeSeriesSelector } = require('./timeseries');
Bucknell, Mary S.
committed
const { Actions } = require('../../store');
const { SPARK_LINE_DIM, SMALL_SCREEN_WIDTH } = require('./layout');
const { dispatch } = require('../../lib/redux');
/**
* Returns metadata for each available timeseries.
* @param {Object} state Redux state
* @return {Array} Sorted array of [code, metadata] pairs.
*/
export const availableTimeseriesSelector = createSelector(
Naab, Daniel James
committed
state => state.series.variables,
allTimeSeriesSelector,
Naab, Daniel James
committed
state => state.currentVariableID,
(variables, timeSeries, currentVariableID) => {
if (!variables) {
return [];
}
const codes = {};
Naab, Daniel James
committed
const seriesList = Object.values(timeSeries);
for (const variableID of Object.keys(variables).sort()) {
const variable = variables[variableID];
codes[variable.variableCode.value] = {
variableID: variable.oid,
description: variable.variableDescription,
selected: currentVariableID === variableID,
currentTimeseriesCount: seriesList.filter(
Naab, Daniel James
committed
ts => ts.tsKey === 'current' && ts.variable === variableID).length,
compareTimeseriesCount: seriesList.filter(
Naab, Daniel James
committed
ts => ts.tsKey === 'compare' && ts.variable === variableID).length,
medianTimeseriesCount: seriesList.filter(
Naab, Daniel James
committed
ts => ts.tsKey === 'median' && ts.variable === variableID).length
Naab, Daniel James
committed
};
}
let sorted = [];
for (let key of Object.keys(codes).sort()) {
sorted.push([key, codes[key]]);
}
Naab, Daniel James
committed
return sorted;
}
);
/**
* Draw a sparkline in a selected SVG element
*
* @param svgSelection
* @param tsData
*/
export const addSparkLine = function(svgSelection, {seriesLineSegments, scales}) {
let spark = line()
.x(function(d) {
return scales.x(d.dateTime);
})
.y(function(d) {
return scales.y(d.value);
});
for (const lineSegment of seriesLineSegments) {
if (lineSegment.classes.dataMask === null) {
svgSelection.append('path')
.attr('d', spark(lineSegment.points))
.attr('class', 'spark-line');
/**
* Draws a table with clickable rows of timeseries parameter codes. Selecting
* a row changes the active parameter code.
* @param {Object} elem d3 selection
* @param {Object} availableTimeseries Timeseries metadata to display
* @param {Object} lineSegmentsByParmCd line segments for each parameter code
* @param {Object} timeSeriesScalesByParmCd scales for each parameter code
* @param {Object} layout layout as retrieved from the redux store
export const plotSeriesSelectTable = function (elem, {availableTimeseries, lineSegmentsByParmCd, timeSeriesScalesByParmCd, layout}) {
elem.select('#select-timeseries').remove();
// only bother to create the table if there are timeseries available
if (availableTimeseries.length > 0) {
const screenSizeCheck = layout.windowWidth <= SMALL_SCREEN_WIDTH;
Yan, Andrew N.
committed
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
let columnHeaders;
if (screenSizeCheck) {
columnHeaders = ['Parameter Code', 'Description', 'Preview'];
} else {
columnHeaders = ['Parameter Code', 'Description', 'Now', 'Last Year', 'Median', 'Preview'];
}
const table = elem
.append('table')
.attr('id', 'select-timeseries')
.classed('usa-table-borderless', true);
table.append('caption').text('Select a timeseries');
table.append('thead')
.append('tr')
.selectAll('th')
.data(columnHeaders)
.enter().append('th')
.attr('scope', 'col')
.text(d => d);
table.append('tbody')
.selectAll('tr')
.data(availableTimeseries)
.enter().append('tr')
.attr('ga-on', 'click')
.attr('ga-event-category', 'TimeseriesGraph')
.attr('ga-event-action', 'selectTimeSeries')
.classed('selected', parm => parm[1].selected)
.on('click', dispatch(function (parm) {
if (!parm[1].selected) {
return Actions.setCurrentParameterCode(parm[0], parm[1].variableID);
}
}))
.call(tr => {
let parmCdCol = tr.append('td')
.attr('scope', 'row');
parmCdCol.append('span')
.text(parm => parm[0]);
parmCdCol.append('div')
.attr('class', 'tooltip-item parameter-tooltip');
Yan, Andrew N.
committed
.text(parm => parm[1].description);
// if screen size is medium/large, place "Now", "Previous Year", and "Median Data" in the table
// under the appropriate column headers
if (!screenSizeCheck) {
tr.append('td')
.html(parm => {
const subScript = parm[1].currentTimeseriesCount > 1 ? `<sub>${parm[1].currentTimeseriesCount}</sub>` : '';
return parm[1].currentTimeseriesCount ? `<i class="fa fa-check" aria-label="Current year data available"></i>${subScript}` : '';
});
tr.append('td')
.html(parm => {
const subScript = parm[1].compareTimeseriesCount > 1 ? `<sub>${parm[1].compareTimeseriesCount}</sub>` : '';
return parm[1].compareTimeseriesCount ? `<i class="fa fa-check" aria-label="Previous year data available"></i>${subScript}` : '';
});
tr.append('td')
.html(parm => {
const subScript = parm[1].medianTimeseriesCount > 1 ? `<sub>${parm[1].medianTimeseriesCount}</sub>` : '';
return parm[1].medianTimeseriesCount ? `<i class="fa fa-check" aria-label="Median data available"></i>${subScript}` : '';
});
}
Yan, Andrew N.
committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
.append('svg')
.attr('width', SPARK_LINE_DIM.width.toString())
.attr('height', SPARK_LINE_DIM.height.toString());
});
// seems to be more straight-forward to access an element's joined
// data by iterating over a selection...
// if screen size is small, place "Now", "Previous Year", and "Median Data" in a tooltip
if (screenSizeCheck) {
table.selectAll('div.tooltip-item').each(function() {
let selection = select(this);
selection.append('sup')
.append('i')
.attr('class', 'fa fa-info-circle');
let tooltipContent = selection.append('div').attr('class', 'tooltip');
let tooltipTable = tooltipContent.append('table')
.attr('class', 'tooltip-table');
tooltipTable.append('caption').text('Available Data');
tooltipTable.append('thead')
.append('tr')
.selectAll('th')
.data(['Now', 'Last Year', 'Median'])
.enter()
.append('th')
.attr('scope', 'col')
.text(d => d);
let tableRow = tooltipTable.append('tr');
tableRow.append('td')
.html(d => d[1].currentTimeseriesCount ? '<i class="fa fa-check" aria-label="Current year data available"></i>' : '');
tableRow.append('td')
.html(d => d[1].compareTimeseriesCount ? '<i class="fa fa-check" aria-label="Previous year data available"></i>' : '');
tableRow.append('td')
.html(d => d[1].medianTimeseriesCount ? '<i class="fa fa-check" aria-label="Median data available"></i>' : '');
Yan, Andrew N.
committed
});
}
table.selectAll('tbody svg').each(function(d) {
let selection = select(this);
Yan, Andrew N.
committed
const parmCd = d[0];
const lineSegments = lineSegmentsByParmCd[parmCd] ? lineSegmentsByParmCd[parmCd] : [];
for (const seriesLineSegments of lineSegments) {
selection.call(addSparkLine, {
seriesLineSegments: seriesLineSegments,
scales: timeSeriesScalesByParmCd[parmCd]
});
}