Integrated animated sparklines into new details panel

also added sortBy date after merge in value buffer
This commit is contained in:
David Kaltschmidt
2016-02-02 18:06:33 +01:00
parent a8809cadfd
commit 0a0179aeb1
3 changed files with 58 additions and 43 deletions

View File

@@ -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 (
<Sparkline data={data} first={first} last={last} min={this.props.min} />
<Sparkline data={data} first={movingFirstDate} last={movingLastDate} min={this.props.min} />
);
}

View File

@@ -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) => {
<div className="node-details-health-item">
<div className="node-details-health-item-value">{formatMetric(props.item.value, props.item)}</div>
<div className="node-details-health-item-sparkline">
<Sparkline data={props.item.samples} min={0} max={props.item.max}
first={props.item.first} last={props.item.last} interpolate="none" />
<AnimatedSparkline data={props.item.samples} max={props.item.max}
first={props.item.first} last={props.item.last} />
</div>
<div className="node-details-health-item-label">{props.item.label}</div>
</div>

View File

@@ -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 <div />;
}
const {lastX, lastY, title, data} = this.getGraphData();
return (
<div title={title}>
@@ -76,7 +81,6 @@ export default class Sparkline extends React.Component {
<circle className="sparkcircle" cx={lastX} cy={lastY} fill="#46466a"
fillOpacity="0.6" stroke="none" r={this.props.circleDiameter} />
</svg>
{lastValue}
</div>
);
}