mirror of https://github.com/linkerd/linkerd2.git
182 lines
4.5 KiB
JavaScript
182 lines
4.5 KiB
JavaScript
import _ from 'lodash';
|
|
import React from 'react';
|
|
import * as d3 from 'd3';
|
|
import './../../css/line-graph.css';
|
|
|
|
const defaultSvgWidth = 238;
|
|
const defaultSvgHeight = 72;
|
|
const margin = { top: 6, right: 6, bottom: 6, left: 0 };
|
|
|
|
export default class LineGraph extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = this.getChartDimensions();
|
|
}
|
|
|
|
componentWillMount() {
|
|
this.initializeScales();
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.svg = d3.select("." + this.props.containerClassName)
|
|
.append("svg")
|
|
.attr("width", this.state.svgWidth)
|
|
.attr("height", this.state.svgHeight)
|
|
.append("g")
|
|
.attr("transform", "translate(" + this.state.margin.left + "," + this.state.margin.top + ")");
|
|
this.xAxis = this.svg.append("g")
|
|
.attr("transform", "translate(0," + this.state.height + ")");
|
|
this.yAxis = this.svg.append("g");
|
|
|
|
this.loadingMessage = this.svg
|
|
.append("text")
|
|
.attr("transform",
|
|
"translate(" + (this.state.width / 2 - 30) + "," + (this.state.height / 2) + ")");
|
|
|
|
this.updateScales();
|
|
this.initializeGraph();
|
|
}
|
|
|
|
shouldComponentUpdate(nextProps) {
|
|
if (nextProps.lastUpdated === this.props.lastUpdated) {
|
|
// control whether react re-renders the component
|
|
// only rerender if the input data has changed
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
componentDidUpdate() {
|
|
this.updateScales();
|
|
this.updateGraph();
|
|
}
|
|
|
|
getChartDimensions() {
|
|
let svgWidth = this.props.width || defaultSvgWidth;
|
|
let svgHeight = this.props.height || defaultSvgHeight;
|
|
|
|
let width = svgWidth - margin.left - margin.right;
|
|
let height = svgHeight - margin.top - margin.bottom;
|
|
|
|
return {
|
|
svgWidth: svgWidth,
|
|
svgHeight: svgHeight,
|
|
width: width,
|
|
height: height,
|
|
margin: margin
|
|
};
|
|
}
|
|
|
|
updateScales() {
|
|
let data = this.props.data;
|
|
let ymax = d3.max(data, d => parseFloat(d.value));
|
|
let padding = 0;
|
|
if (this.state.svgHeight) {
|
|
padding = ymax / this.state.svgHeight;
|
|
}
|
|
this.xScale.domain(d3.extent(data, d => parseInt(d.timestamp)));
|
|
this.yScale.domain([0-padding, ymax+padding]);
|
|
}
|
|
|
|
initializeScales() {
|
|
this.xScale = d3.scaleLinear().range([0, this.state.width]);
|
|
this.yScale = d3.scaleLinear().range([this.state.height, 0]);
|
|
|
|
let x = this.xScale;
|
|
let y = this.yScale;
|
|
|
|
// define the line
|
|
this.line = d3.line()
|
|
.x(d => x(d.timestamp))
|
|
.y(d => y(d.value));
|
|
}
|
|
|
|
initializeGraph() {
|
|
if (_.isEmpty(this.props.data)) {
|
|
this.loadingMessage.text("---");
|
|
}
|
|
|
|
this.svg.select("path").remove();
|
|
|
|
let lineChart = this.svg.append("path")
|
|
.attr("class", "chart-line line");
|
|
|
|
lineChart
|
|
.attr("d", this.line(this.props.data));
|
|
|
|
this.svg.append("circle")
|
|
.attr("class", "flash")
|
|
.attr("flashing", "off")
|
|
.style("opacity", 0)
|
|
.attr("r", 6);
|
|
|
|
this.updateAxes();
|
|
this.flashLatestDataPoint();
|
|
}
|
|
|
|
updateGraph() {
|
|
if (_.isEmpty(this.props.data)) {
|
|
this.loadingMessage.style("opacity", 1);
|
|
} else {
|
|
this.loadingMessage.style("opacity", 0);
|
|
}
|
|
|
|
this.svg.select(".line")
|
|
.transition()
|
|
.duration(450)
|
|
.attr("d", this.line(this.props.data));
|
|
|
|
this.updateAxes();
|
|
this.flashLatestDataPoint();
|
|
}
|
|
|
|
updateAxes() {
|
|
if (this.props.showAxes) {
|
|
this.xAxis
|
|
.call(d3.axisBottom(this.xScale)); // add x axis labels
|
|
|
|
this.yAxis
|
|
.call(d3.axisLeft(this.yScale)); // add y axis labels
|
|
}
|
|
}
|
|
|
|
flashLatestDataPoint() {
|
|
if (!this.props.flashLastDatapoint) {
|
|
return;
|
|
}
|
|
|
|
let circle = this.svg.select("circle");
|
|
if (_.isEmpty(this.props.data)) {
|
|
circle.attr("flashing", "off").interrupt().style("opacity", 0);
|
|
} else {
|
|
let circleData = _.last(this.props.data);
|
|
if (circle.attr("flashing") === "off") {
|
|
circle
|
|
.attr("flashing", "on")
|
|
.transition()
|
|
.on("start", function repeat() {
|
|
d3.active(this)
|
|
.transition()
|
|
.duration(1000)
|
|
.style("opacity", 0.6)
|
|
.transition()
|
|
.duration(1000)
|
|
.style("opacity", 0)
|
|
.transition()
|
|
.on("start", repeat);
|
|
});
|
|
}
|
|
circle
|
|
.attr("cx", () => this.xScale(circleData.timestamp))
|
|
.attr("cy", () => this.yScale(circleData.value));
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div className={`line-graph ${this.props.containerClassName}`} />
|
|
);
|
|
}
|
|
}
|