diff --git a/web/app/css/line-graph.css b/web/app/css/line-graph.css deleted file mode 100644 index f7be8aea1..000000000 --- a/web/app/css/line-graph.css +++ /dev/null @@ -1,27 +0,0 @@ -@import 'styles.css'; -@import 'scatterplot.css'; - -.chart-line { - fill: none; - stroke-width: 2px; -} - -.line { - stroke: steelblue; -} - -.line-p50 { - stroke: var(--latency-p50); -} - -.line-p95 { - stroke: var(--latency-p95); -} - -.line-p99 { - stroke: var(--latency-p99); -} - -.flash { - fill: var(--pictonblue); -} diff --git a/web/app/css/list.css b/web/app/css/list.css deleted file mode 100644 index 0664973eb..000000000 --- a/web/app/css/list.css +++ /dev/null @@ -1,32 +0,0 @@ -@import 'styles.css'; - -.summary-stat { - float: left; - width: 50%; -} - -.line-graph { - margin-top: var(--base-width); -} - -.scatterplot-display { - font-size: 12px; - - & .title { - font-weight: var(--font-weight-bold); - margin-top: var(--base-width); - } - - & .extremal-latencies { - padding-bottom: var(--base-width); - border-bottom: 1px solid #BDBDBD; - } -} - -& .border-container { - height: 151px; /* override height */ -} - -& .border-container-content { - height: 135px; /* override height */ -} diff --git a/web/app/css/scatterplot.css b/web/app/css/scatterplot.css deleted file mode 100644 index 0c78051d9..000000000 --- a/web/app/css/scatterplot.css +++ /dev/null @@ -1,40 +0,0 @@ -.grid .tick, .x-axis .tick, .y-axis .tick, .axis-label { - opacity: 0.5; - font-size: 11px; -} - -/* horizontal grid lines */ -.tick line { - stroke-width: 1px; - stroke: #777; -} - -.y-axis-label { - text-anchor: middle; -} - -/* hide the axis line */ -.x-axis path, .y-axis path { - stroke-width: 0; -} - -circle.dot { - stroke-width: 2px; - fill-opacity: 0.7; - stroke-opacity: 0.9; -} - -.overlay { - fill: none; - pointer-events: all; -} - -.overlay-tooltip { - fill: #777; - font-size: 12px; -} - -.vertical-highlight { - fill: steelblue; - opacity: 0.1; -} diff --git a/web/app/css/version.css b/web/app/css/version.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/app/js/components/LineGraph.jsx b/web/app/js/components/LineGraph.jsx deleted file mode 100644 index 376f09e58..000000000 --- a/web/app/js/components/LineGraph.jsx +++ /dev/null @@ -1,181 +0,0 @@ -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 ( -
- ); - } -} diff --git a/web/app/js/components/Namespace.jsx b/web/app/js/components/Namespace.jsx index 08394de6f..5536adfe6 100644 --- a/web/app/js/components/Namespace.jsx +++ b/web/app/js/components/Namespace.jsx @@ -9,7 +9,6 @@ import PropTypes from 'prop-types'; import React from 'react'; import { Spin } from 'antd'; import { withContext } from './util/AppContext.jsx'; -import './../../css/list.css'; import 'whatwg-fetch'; class Namespaces extends React.Component { diff --git a/web/app/js/components/ResourceDetail.jsx b/web/app/js/components/ResourceDetail.jsx index 1a292523b..d9044162a 100644 --- a/web/app/js/components/ResourceDetail.jsx +++ b/web/app/js/components/ResourceDetail.jsx @@ -9,7 +9,6 @@ import { singularResource } from './util/Utils.js'; import { Spin } from 'antd'; import withREST from './util/withREST.jsx'; import { metricsPropType, processSingleResourceRollup } from './util/MetricUtils.js'; -import './../../css/list.css'; import 'whatwg-fetch'; const getResourceFromUrl = (match, pathPrefix) => { diff --git a/web/app/js/components/ResourceList.jsx b/web/app/js/components/ResourceList.jsx index be62f7f33..b0bf839a0 100644 --- a/web/app/js/components/ResourceList.jsx +++ b/web/app/js/components/ResourceList.jsx @@ -9,7 +9,6 @@ import React from 'react'; import { Spin } from 'antd'; import withREST from './util/withREST.jsx'; import { metricsPropType, processSingleResourceRollup } from './util/MetricUtils.js'; -import './../../css/list.css'; import 'whatwg-fetch'; export class ResourceListBase extends React.Component { diff --git a/web/app/js/components/ScatterPlot.jsx b/web/app/js/components/ScatterPlot.jsx deleted file mode 100644 index 21dc046fc..000000000 --- a/web/app/js/components/ScatterPlot.jsx +++ /dev/null @@ -1,323 +0,0 @@ -import _ from 'lodash'; -import { metricToFormatter } from './util/Utils.js'; -import React from 'react'; -import * as d3 from 'd3'; -import './../../css/scatterplot.css'; - -const defaultSvgWidth = 574; -const defaultSvgHeight = 375; -const margin = { top: 0, right: 0, bottom: 10, left: 0 }; -const baseWidth = 8; -const circleRadius = 2 * baseWidth; -const graphPadding = 3 * circleRadius; -const highlightBarWidth = 3 * circleRadius; -const successRateColorScale = d3.scaleQuantize() - .domain([0, 1]) - .range(["#8B0000", "#FF6347", "#FF4500", "#FFA500","#008000"]); - -export default class ScatterPlot extends React.Component { - constructor(props) { - super(props); - this.renderNodeTooltipDatum = this.renderNodeTooltipDatum.bind(this); - this.state = this.getChartDimensions(); - } - - componentWillMount() { - this.initializeScales(); - } - - componentDidMount() { - this.svg = d3.select("." + this.props.containerClassName) - .append("svg") - .attr("class", "scatterplot") - .attr("width", defaultSvgWidth) - .attr("height", defaultSvgHeight) - .append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - this.xAxis = this.svg.append("g") - .attr("class", "x-axis") - .attr("transform", "translate(0," + (this.state.height - graphPadding) + ")"); - - this.yAxis = this.svg.append("g") - .attr("class", "y-axis") - .attr("transform", "translate(" + this.state.width + ",0)"); - - this.sidebar = d3.select(".scatterplot-display") - .append("div").attr("class", "sidebar-tooltip"); - - this.initializeVerticalHighlight(); - this.renderAxisLabels(); - this.updateGraph(); - } - - 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.updateGraph(); - } - - initializeVerticalHighlight() { - this.updateScales(this.props.data); - - // highlight bar to show x position - this.verticalHighlight = this.svg.append("rect") - .attr("class", "vertical-highlight") - .attr("width", highlightBarWidth) - .attr("height", this.state.height); - - // overlay on which to attach mouse events - // attach this after all other items are attached otherwise they block mouse events - this.overlay = this.svg.append("rect") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")") - .attr("class", "overlay") - .attr("width", this.state.width) - .attr("height", this.state.height); - this.overlayNode = d3.select(".overlay").node(); - - this.overlayTooltip = this.svg.append("g") - .attr("class", "overlay-tooltip") - .attr("height", this.state.height); - - this.highlightFirstDatapoint(); - } - - highlightFirstDatapoint() { - // when graph is initially loaded / reloaded, set highlight and sidebar to first datapoint - let firstDatapoint = _.first(this.props.data); - if (firstDatapoint) { - let firstLatency = _.get(firstDatapoint, ["latency", "P99"]); - let firstLatencyX = this.xScale(firstLatency); - let nearestDatapoints = this.getNearbyDatapoints(firstLatencyX, this.props.data); - - this.verticalHighlight - .attr("transform", "translate(" + (firstLatencyX - (highlightBarWidth/2)) + ", 0)"); - this.renderSidebarTooltip(nearestDatapoints); - this.overlayTooltip.text(''); - } - } - - 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 - }; - } - - initializeScales() { - this.xScale = d3.scaleLinear().range([graphPadding, this.state.width - graphPadding]); - this.yScale = d3.scaleLinear().range([this.state.height - graphPadding, graphPadding]); - } - - updateScales(data) { - this.xScale.domain(d3.extent(data, d => d.latency.P99)); - this.yScale.domain([0, 1]); - - this.updateAxes(); - } - - updateAxes() { - 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["SUCCESS_RATE"]); - - // 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); - } - - renderAxisLabels() { - // text label for the x axis - this.svg.append("text") - .attr("class", "axis-label x-axis-label") - .attr("y", this.state.height - 12) - .attr("x", 0) - .text("p99 Latency (ms)"); - - // text label for the y axis - this.svg.append("text") - .attr("class", "axis-label y-axis-label") - .attr("y", 20) - .attr("x", this.state.width) - .attr("dx", "-3em") - .text("Success rate"); - } - - getNearbyDatapoints(x, data) { - // return nodes that have nearby x-coordinates - let x0 = this.xScale.invert(x - highlightBarWidth); - let x1 = this.xScale.invert(x + highlightBarWidth); - - if (x0 === x1) { - // handle case where all the x points are in one column - let datapointsX = this.xScale(_.first(data).latency.P99); - if (Math.abs(x - datapointsX < highlightBarWidth)) { - return data; - } else { - return []; - } - } else { - return _(data).filter(d => { - return d.latency.P99 <= x1 && d.latency.P99 >= x0; - }).orderBy('successRate', 'desc').value(); - } - } - - updateGraph() { - this.updateScales(this.props.data); - - this.scatterPlot = this.svg.selectAll(".dot") - .data(this.props.data); - - this.scatterPlot.exit().remove(); - - let spNode = this.scatterPlot.node(); - - this.scatterPlot - .enter() - .append("circle") - .attr("class", "dot") - .attr("r", circleRadius) - .merge(this.scatterPlot) // newfangled d3 'update' selection - .attr("cx", d => this.xScale(d.latency.P99)) - .attr("cy", d => this.yScale(d.successRate)) - .style("fill", d => successRateColorScale(d.successRate)) - .style("stroke", d => successRateColorScale(d.successRate)) - .on("mousemove", () => { - if (spNode) { - let currXPos = d3.mouse(spNode)[0]; - this.positionOverlayHighlightAndTooltip(currXPos); - } - }); - - this.highlightFirstDatapoint(); - this.overlay - .on("mousemove", () => { - let currXPos = d3.mouse(this.overlayNode)[0]; - this.positionOverlayHighlightAndTooltip(currXPos); - }); - } - - positionOverlayHighlightAndTooltip(currXPos) { - let nearestDatapoints = this.getNearbyDatapoints(currXPos, this.props.data); - this.renderOverlayTooltip(nearestDatapoints); - this.renderSidebarTooltip(nearestDatapoints); - this.verticalHighlight.attr("transform", "translate(" + (currXPos - highlightBarWidth / 2) + ", 0)"); - - let bbox = this.overlayTooltip.node().getBBox(); - - let overlayTooltipYPos = 0; - let firstLabelPosition = _.isEmpty(nearestDatapoints) ? null : this.getTooltipLabelY(nearestDatapoints[0]); - if (firstLabelPosition + bbox.height > this.state.height - 50) { - // if there are a bunch of nodes at 0, the labels could extend below the chart - // translate upward if this is the case - overlayTooltipYPos -= bbox.height; - - // re-render tooltip labels, squished together - this.renderOverlayTooltip(nearestDatapoints, true); - } - - let overlayTooltipXPos = currXPos + highlightBarWidth / 2 + baseWidth; - if (currXPos > defaultSvgWidth / 2) { - // display tooltip to the left if we're on the RH side of the graph - overlayTooltipXPos = currXPos - bbox.width - baseWidth - highlightBarWidth / 2; - } - - this.overlayTooltip - .attr("transform", "translate(" + overlayTooltipXPos + ", " + overlayTooltipYPos + ")") - .raise(); - } - - renderSidebarTooltip(data) { - let innerHtml = _.map(data, d => this.renderNodeTooltipDatum(d)); - this.sidebar.html(innerHtml.join('')); - } - - renderOverlayTooltip(data, ignoreNodeSpacing = false) { - this.overlayTooltip.text(''); - let labelWithPosition = this.computeTooltipLabelPositions(data, ignoreNodeSpacing); - - _.each(labelWithPosition, d => { - this.overlayTooltip - .append("text").text(d.name) - .attr("x", 0) - .attr("y", d.computedY); - }); - } - - getTooltipLabelY(datum) { - // position the tooltip label roughly aligned with the center of the node - return this.yScale(datum.successRate) + circleRadius / 2 - 5; - } - - computeTooltipLabelPositions(data, ignoreNodeSpacing = false) { - // in the case that there are multiple nodes in the highlighted area, - // try to position each label next to its corresponding node - // if the nodes are too close together, simply list the node labels - let positions = _.map(data, d => { - return { - name: d.name, - computedY: this.getTooltipLabelY(d) - }; - }); - - if (_.size(positions) > 1) { - _.each(positions, (_d, i) => { - if (i > 0) { - if (positions[i].computedY - positions[i - 1].computedY < 10) { - // labels are too close together, don't label at the node Y values - ignoreNodeSpacing = true; - } - } - }); - if (ignoreNodeSpacing) { - let basePos = positions[0].computedY; - _.each(positions, (d, i) => { - d.computedY = basePos + i * 15; - }); - } - } - return positions; - } - - renderNodeTooltipDatum(d) { - let latency = metricToFormatter["LATENCY"](d.latency.P99); - let sr = metricToFormatter["SUCCESS_RATE"](d.successRate); - return `