Skip to content

Commit

Permalink
Integrated animated sparklines into new details panel
Browse files Browse the repository at this point in the history
also added sortBy date after merge in value buffer
  • Loading branch information
davkal committed Feb 2, 2016
1 parent a8809ca commit 0a0179a
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 40 deletions.
79 changes: 45 additions & 34 deletions client/app/scripts/components/animated-sparkline.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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
};
}

Expand All @@ -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) {
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());

if (firstDate && movingFirstDate && firstDate < movingFirstDate) {
// first prop date is way before buffer, keeping it
first = this.props.first;
movingFirstDate = firstDate;
}
let last = this.state.last;
if (this.props.last && this.props.last > this.state.buffer.keySeq().last()) {
if (lastDate && lastBufferDate && lastDate > lastBufferDate) {
// 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 skip = lastDate - lastBufferDate;
movingLastDate -= skip;
movingFirstDate -= 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);

return {first, last, data};
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} />
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from 'react';

import Sparkline from '../sparkline';
import AnimatedSparkline from '../animated-sparkline';
import { formatMetric } from '../../utils/string-utils';

export default (props) => {
return (
<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>
Expand Down
10 changes: 7 additions & 3 deletions client/app/scripts/components/sparkline.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}>
Expand All @@ -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>
);
}
Expand Down

0 comments on commit 0a0179a

Please sign in to comment.