import _ from 'underscore'; import { scaleLinear, scaleTime } from 'd3-scale'; import { line, area, curveLinear } from 'd3-shape'; import { extent, max } from 'd3-array'; import { timeMinute } from 'd3-time'; const d3 = { scaleLinear, scaleTime, line, area, curveLinear, extent, max, timeMinute }; const defaultColorPalette = { blue: ['#1f78d1', '#8fbce8'], orange: ['#fc9403', '#feca81'], red: ['#db3b21', '#ed9d90'], green: ['#1aaa55', '#8dd5aa'], purple: ['#6666c4', '#d1d1f0'], }; const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple']; const defaultStyleOrder = ['solid', 'dashed', 'dotted']; function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) { let usedColors = []; function pickColor(name) { let pick; if (name && defaultColorPalette[name]) { pick = name; } else { const unusedColors = _.difference(defaultColorOrder, usedColors); if (unusedColors.length > 0) { pick = unusedColors[0]; } else { usedColors = []; pick = defaultColorOrder[0]; } } usedColors.push(pick); return defaultColorPalette[pick]; } return query.result.map((timeSeries, timeSeriesNumber) => { let metricTag = ''; let lineColor = ''; let areaColor = ''; const timeSeriesScaleX = d3.scaleTime() .range([0, graphWidth - 70]); const timeSeriesScaleY = d3.scaleLinear() .range([graphHeight - graphHeightOffset, 0]); timeSeriesScaleX.domain(xDom); timeSeriesScaleX.ticks(d3.timeMinute, 60); timeSeriesScaleY.domain(yDom); const defined = d => !isNaN(d.value) && d.value != null; const lineFunction = d3.line() .defined(defined) .curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate .x(d => timeSeriesScaleX(d.time)) .y(d => timeSeriesScaleY(d.value)); const areaFunction = d3.area() .defined(defined) .curve(d3.curveLinear) .x(d => timeSeriesScaleX(d.time)) .y0(graphHeight - graphHeightOffset) .y1(d => timeSeriesScaleY(d.value)); const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]]; const seriesCustomizationData = query.series != null && _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel }); if (seriesCustomizationData) { metricTag = seriesCustomizationData.value || timeSeriesMetricLabel; [lineColor, areaColor] = pickColor(seriesCustomizationData.color); } else { metricTag = timeSeriesMetricLabel || `series ${timeSeriesNumber + 1}`; [lineColor, areaColor] = pickColor(); } if (query.track) { metricTag += ` - ${query.track}`; } return { linePath: lineFunction(timeSeries.values), areaPath: areaFunction(timeSeries.values), timeSeriesScaleX, values: timeSeries.values, lineStyle, lineColor, areaColor, metricTag, }; }); } export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) { const allValues = queries.reduce((allQueryResults, query) => allQueryResults.concat( query.result.reduce((allResults, result) => allResults.concat(result.values), []), ), []); const xDom = d3.extent(allValues, d => d.time); const yDom = [0, d3.max(allValues.map(d => d.value))]; return queries.reduce((series, query, index) => { const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length]; return series.concat( queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle), ); }, []); }