import _ from 'lodash'; import { metricToFormatter } from './util/Utils.js'; import React from 'react'; import * as d3 from 'd3'; import './../../css/latency-overview.css'; import './../../css/line-graph.css'; const defaultSvgWidth = 874; const defaultSvgHeight = 350; const margin = { top: 20, right: 0, bottom: 30, left: 0 }; const dataDefaults = { P50: [], P95: [], P99: [] }; export default class MultiLineGraph 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("class", "x-axis") .attr("transform", "translate(0," + this.state.height + ")"); this.yAxis = this.svg.append("g") .attr("class", "y-axis") .attr("transform", "translate(" + this.state.width + ",0)"); 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() { // update scales over all the latency values let data = _.flatten(_.values(this.props.data)); this.xScale.domain(d3.extent(data, d => parseInt(d.timestamp))); this.yScale.domain([0, d3.max(data, d => parseFloat(d.value))]); } initializeScales() { this.xScale = d3.scaleTime().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() { let d = _.isEmpty(this.props.data) ? dataDefaults : this.props.data; this.svg.append("path") .attr("class", "chart-line line-p50") .attr("d", this.line(d.P50)); this.svg.append("path") .attr("class", "chart-line line-p95") .attr("d", this.line(d.P95)); this.svg.append("path") .attr("class", "chart-line line-p99") .attr("d", this.line(d.P99)); this.updateAxes(); } updateGraph() { let d = _.isEmpty(this.props.data) ? dataDefaults : this.props.data; this.svg.select(".line-p50") .transition() .duration(450) .attr("d", this.line(d.P50)); this.svg.select(".line-p95") .transition() .duration(450) .attr("d", this.line(d.P95)); this.svg.select(".line-p99") .transition() .duration(450) .attr("d", this.line(d.P99)); this.updateAxes(); } updateAxes() { // Same as ScatterPlot.jsx if (this.props.showAxes) { let xAxis = d3.axisBottom(this.xScale) .ticks(5) .tickSize(5); this.xAxis.call(xAxis); let yAxis = d3.axisLeft(this.yScale) .ticks(4) .tickSize(this.state.width) .tickFormat(metricToFormatter["LATENCY"]); // custom axis styling: https://bl.ocks.org/mbostock/3371592 let customYAxis = g => { g.call(yAxis); g.select(".domain").remove(); g.selectAll(".tick text") .attr("x", 4) .attr("dx", -10) .attr("dy", -4); }; this.yAxis.call(customYAxis); } } renderCurrentLatencies() { return (
{ _.map(["P50", "P95", "P99"], latency => { let ts = this.props.data[latency]; let lat = metricToFormatter["LATENCY"](_.get(_.last(ts), 'value', [])); return (
Current latency ({latency})
{lat}
); }) }
); } render() { return (
Latency Overview
{this.renderCurrentLatencies()}
); } }