From 0a0179aeb11b0c63cf1320235da9982589bb8842 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Tue, 2 Feb 2016 18:06:33 +0100 Subject: [PATCH] Integrated animated sparklines into new details panel also added sortBy date after merge in value buffer --- .../scripts/components/animated-sparkline.js | 85 +++++++++++-------- .../node-details/node-details-health-item.js | 6 +- client/app/scripts/components/sparkline.js | 10 ++- 3 files changed, 58 insertions(+), 43 deletions(-) diff --git a/client/app/scripts/components/animated-sparkline.js b/client/app/scripts/components/animated-sparkline.js index 456f7105c..dde9b42b2 100644 --- a/client/app/scripts/components/animated-sparkline.js +++ b/client/app/scripts/components/animated-sparkline.js @@ -7,6 +7,7 @@ import Sparkline from './sparkline'; const makeOrderedMap = OrderedMap; const parseDate = d3.time.format.iso.parse; +const sortDate = (v, d) => d; export default class AnimatedSparkline extends React.Component { @@ -16,8 +17,8 @@ export default class AnimatedSparkline extends React.Component { this.tickTimer = null; this.state = { buffer: makeOrderedMap(), - first: null, - last: null + movingFirst: null, + movingLast: null }; } @@ -44,79 +45,89 @@ export default class AnimatedSparkline extends React.Component { // merge new samples into buffer let buffer = this.state.buffer; const nextSamples = makeOrderedMap(props.data.map(d => [d.date, d.value])); - buffer = buffer.merge(nextSamples); + buffer = buffer.merge(nextSamples).sortBy(sortDate); const state = {}; // set first/last marker of sliding window if (buffer.size > 0) { const bufferKeys = buffer.keySeq(); - if (this.state.first === null) { - state.first = bufferKeys.first(); + if (this.state.movingFirst === null) { + state.movingFirst = bufferKeys.first(); } - if (this.state.last === null) { - state.last = bufferKeys.last(); + if (this.state.movingLast === null) { + state.movingLast = bufferKeys.last(); } } // remove old values from buffer - const first = this.state.first ? this.state.first : state.first; - state.buffer = buffer.skipWhile((v, d) => d < first); + const movingFirst = this.state.movingFirst ? this.state.movingFirst : state.movingFirst; + state.buffer = buffer.filter((v, d) => d >= movingFirst); return state; } tick() { - if (this.state.last < this.state.buffer.keySeq().last()) { - const dates = this.state.buffer.keySeq(); - let firstIndex = dates.indexOf(this.state.first); - if (firstIndex > -1 && firstIndex < dates.size - 1) { + const { buffer } = this.state; + let { movingFirst, movingLast } = this.state; + const bufferKeys = buffer.keySeq(); + + if (movingLast < bufferKeys.last()) { + let firstIndex = bufferKeys.indexOf(movingFirst); + if (firstIndex > -1 && firstIndex < bufferKeys.size - 1) { firstIndex++; } else { firstIndex = 0; } - const first = dates.get(firstIndex); + movingFirst = bufferKeys.get(firstIndex); - let lastIndex = dates.indexOf(this.state.last); + let lastIndex = bufferKeys.indexOf(movingLast); if (lastIndex > -1) { lastIndex++; } else { - lastIndex = dates.length - 1; + lastIndex = bufferKeys.length - 1; } - const last = dates.get(lastIndex); + movingLast = bufferKeys.get(lastIndex); this.tickTimer = setTimeout(() => { this.tickTimer = null; - this.setState({first, last}); + this.setState({movingFirst, movingLast}); }, 900); } } getGraphData() { - let first = this.state.first; - if (this.props.first && this.props.first < this.state.first) { - // first prop date is way before buffer, keeping it - first = this.props.first; - } - let last = this.state.last; - if (this.props.last && this.props.last > this.state.buffer.keySeq().last()) { - // prop last is after buffer values, need to shift dates - const skip = parseDate(this.props.last) - parseDate(this.state.buffer.keySeq().last()); - last -= skip; - first -= skip; - } - const dateFilter = d => d.date >= first && d.date <= last; - const data = this.state.buffer.map((v, k) => { - return {value: v, date: k}; - }).toIndexedSeq().toJS().filter(dateFilter); + const firstDate = parseDate(this.props.first); + const lastDate = parseDate(this.props.last); + const { buffer } = this.state; + let movingFirstDate = parseDate(this.state.movingFirst); + let movingLastDate = parseDate(this.state.movingLast); + const lastBufferDate = parseDate(buffer.keySeq().last()); - return {first, last, data}; + if (firstDate && movingFirstDate && firstDate < movingFirstDate) { + // first prop date is way before buffer, keeping it + movingFirstDate = firstDate; + } + if (lastDate && lastBufferDate && lastDate > lastBufferDate) { + // prop last is after buffer values, need to shift dates + const skip = lastDate - lastBufferDate; + movingLastDate -= skip; + movingFirstDate -= skip; + } + const dateFilter = d => d.date >= movingFirstDate && d.date <= movingLastDate; + const data = this.state.buffer + .map((v, k) => ({value: v, date: +parseDate(k)})) + .toIndexedSeq() + .toJS() + .filter(dateFilter); + + return {movingFirstDate, movingLastDate, data}; } render() { - const {data, first, last} = this.getGraphData(); + const {data, movingFirstDate, movingLastDate} = this.getGraphData(); return ( - + ); } diff --git a/client/app/scripts/components/node-details/node-details-health-item.js b/client/app/scripts/components/node-details/node-details-health-item.js index 7ddeba23f..e16d374ec 100644 --- a/client/app/scripts/components/node-details/node-details-health-item.js +++ b/client/app/scripts/components/node-details/node-details-health-item.js @@ -1,6 +1,6 @@ import React from 'react'; -import Sparkline from '../sparkline'; +import AnimatedSparkline from '../animated-sparkline'; import { formatMetric } from '../../utils/string-utils'; export default (props) => { @@ -8,8 +8,8 @@ export default (props) => {
{formatMetric(props.item.value, props.item)}
- +
{props.item.label}
diff --git a/client/app/scripts/components/sparkline.js b/client/app/scripts/components/sparkline.js index 5b79625ef..2dca2cc1d 100644 --- a/client/app/scripts/components/sparkline.js +++ b/client/app/scripts/components/sparkline.js @@ -62,11 +62,16 @@ export default class Sparkline extends React.Component { ', max: ' + d3.round(d3.max(data, d => d.value), 2) + ', mean: ' + d3.round(d3.mean(data, d => d.value), 2); - return {title, lastValue, lastX, lastY, data}; + return {title, lastX, lastY, data}; } render() { - const {lastValue, lastX, lastY, title, data} = this.getGraphData(); + // Do nothing if no data or data w/o date are passed in. + if (this.props.data.length === 0 || this.props.data[0].date === undefined) { + return
; + } + + const {lastX, lastY, title, data} = this.getGraphData(); return (
@@ -76,7 +81,6 @@ export default class Sparkline extends React.Component { - {lastValue}
); }