Skip to content

Commit

Permalink
Add Sparklines to the UI based on Metrics.
Browse files Browse the repository at this point in the history
- basic sparklines rendering for load
- Graphs are normalized so they all render on the y-axis.
- Time-axis is fixed to 15-seconds, so that data fills in correctly when data is insufficient
- Move load scalar behind sparkline
- add title to sparklines, showing timespan, samples, etc
  • Loading branch information
paulbellamy authored and tomwilkie committed Nov 11, 2015
1 parent a074278 commit 510870e
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 10 deletions.
17 changes: 7 additions & 10 deletions client/app/scripts/components/node-details-table.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
const React = require('react');
const Sparkline = require('./sparkline');

const NodeDetailsTable = React.createClass({

render: function() {
const isNumeric = this.props.isNumeric;

return (
<div className="node-details-table">
<h4 className="node-details-table-title truncate" title={this.props.title}>
Expand All @@ -15,14 +14,12 @@ const NodeDetailsTable = React.createClass({
return (
<div className="node-details-table-row" key={row.key + row.value_major}>
<div className="node-details-table-row-key truncate" title={row.key}>{row.key}</div>
{isNumeric && <div className="node-details-table-row-value-scalar">{row.value_major}</div>}
{isNumeric && <div className="node-details-table-row-value-unit">{row.value_minor}</div>}
{!isNumeric && <div className="node-details-table-row-value-major truncate" title={row.value_major}>
{row.value_major}
</div>}
{!isNumeric && row.value_minor && <div className="node-details-table-row-value-minor truncate" title={row.value_minor}>
{row.value_minor}
</div>}
{ row.value_type === 'numeric' && <div className="node-details-table-row-value-scalar">{row.value_major}</div> }
{ row.value_type === 'numeric' && <div className="node-details-table-row-value-unit">{row.value_minor}</div> }
{ row.value_type === 'sparkline' && <div className="node-details-table-row-value-sparkline"><Sparkline data={row.metric.samples} min={0} max={row.metric.max} first={row.metric.first} last={row.metric.last} interpolate="none" />{row.value_major}</div> }
{ row.value_type === 'sparkline' && <div className="node-details-table-row-value-unit">{row.value_minor}</div> }
{ row.value_type !== 'numeric' && row.value_type !== 'sparkline' && <div className="node-details-table-row-value-major truncate" title={row.value_major}>{row.value_major}</div> }
{ row.value_type !== 'numeric' && row.value_type !== 'sparkline' && row.value_minor && <div className="node-details-table-row-value-minor truncate" title={row.value_minor}>{row.value_minor}</div> }
</div>
);
})}
Expand Down
129 changes: 129 additions & 0 deletions client/app/scripts/components/sparkline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Forked from: https://github.com/KyleAMathews/react-sparkline at commit a9d7c5203d8f240938b9f2288287aaf0478df013
const React = require('react');
const d3 = require('d3');

const Sparkline = React.createClass({
getDefaultProps: function() {
return {
width: 100,
height: 16,
strokeColor: '#7d7da8',
strokeWidth: '0.5px',
interpolate: 'basis',
circleDiameter: 1.75,
data: [1, 23, 5, 5, 23, 0, 0, 0, 4, 32, 3, 12, 3, 1, 24, 1, 5, 5, 24, 23] // Some semi-random data.
};
},

componentDidMount: function() {
return this.renderSparkline();
},

renderSparkline: function() {
// If the sparkline has already been rendered, remove it.
let el = this.getDOMNode();
while (el.firstChild) {
el.removeChild(el.firstChild);
}

let data = this.props.data.slice();

// Do nothing if no data is passed in.
if (data.length === 0) {
return;
}

let x = d3.scale.linear().range([2, this.props.width - 2]);
let y = d3.scale.linear().range([this.props.height - 2, 2]);

// react-sparkline allows you to pass in two types of data.
// Data tied to dates and linear data. We need to change our line and x/y
// functions depending on the type of data.

// These are objects with a date key
let line;
let lastX;
let lastY;
let title;
if (data[0].date) {
// Convert dates into D3 dates
data.forEach(d => {
d.date = d3.time.format.iso.parse(d.date);
});

line = d3.svg.line().
interpolate(this.props.interpolate).
x(d => x(d.date)).
y(d => y(d.value));

let first = this.props.first ? d3.time.format.iso.parse(this.props.first) : d3.min(data, d => d.date);
let last = this.props.last ? d3.time.format.iso.parse(this.props.last) : d3.max(data, d => d.date);
x.domain([first, last]);

y.domain([
this.props.min || d3.min(data, d => d.value),
this.props.max || d3.max(data, d => d.value)
]);

lastX = x(data[data.length - 1].date);
lastY = y(data[data.length - 1].value);
title = 'Last ' + d3.round((last - first) / 1000) + ' seconds, ' + data.length + ' samples, min: ' + d3.round(d3.min(data, d => d.value), 2) + ', max: ' + d3.round(d3.max(data, d => d.value), 2) + ', mean: ' + d3.round(d3.mean(data, d => d.value), 2);
} else {
line = d3.svg.line().
interpolate(this.props.interpolate).
x((d, i) => x(i)).
y(d => y(d));

x.domain([
this.props.first || 0,
this.props.last || data.length
]);

y.domain([
this.props.min || d3.min(data),
this.props.max || d3.max(data)
]);

lastX = x(data.length - 1);
lastY = y(data[data.length - 1]);
title = data.length + ' samples, min: ' + d3.round(d3.min(data), 2) + ', max: ' + d3.round(d3.max(data), 2) + ', mean: ' + d3.round(d3.mean(data), 2);
}

d3.select(this.getDOMNode()).attr('title', title);

let svg = d3.select(this.getDOMNode()).
append('svg').
attr('width', this.props.width).
attr('height', this.props.height).
append('g');

svg.append('path').
datum(data).
attr('class', 'sparkline').
style('fill', 'none').
style('stroke', this.props.strokeColor).
style('stroke-width', this.props.strokeWidth).
attr('d', line);

svg.append('circle').
attr('class', 'sparkcircle').
attr('cx', lastX).
attr('cy', lastY).
attr('fill', '#46466a').
attr('fill-opacity', 0.6).
attr('stroke', 'none').
attr('r', this.props.circleDiameter);
},

render: function() {
return (
<div/>
);
},

componentDidUpdate: function() {
return this.renderSparkline();
}
});

module.exports = Sparkline;
9 changes: 9 additions & 0 deletions client/app/styles/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,15 @@ h2 {
color: @text-secondary-color;
}

&-value-sparkline {
> div {
display: inline-block;
}
span {
margin-left: 1em;
}
}

}

}
Expand Down

0 comments on commit 510870e

Please sign in to comment.