debian-mirror-gitlab/app/assets/javascripts/monitoring/components/graph.vue

320 lines
9.3 KiB
Vue
Raw Normal View History

2018-03-17 18:26:18 +05:30
<script>
2018-05-09 12:01:36 +05:30
import { scaleLinear, scaleTime } from 'd3-scale';
import { axisLeft, axisBottom } from 'd3-axis';
import _ from 'underscore';
import { max, extent } from 'd3-array';
import { select } from 'd3-selection';
import GraphAxis from './graph/axis.vue';
import GraphLegend from './graph/legend.vue';
import GraphFlag from './graph/flag.vue';
import GraphDeployment from './graph/deployment.vue';
import GraphPath from './graph/path.vue';
import MonitoringMixin from '../mixins/monitoring_mixins';
import eventHub from '../event_hub';
import measurements from '../utils/measurements';
import { bisectDate, timeScaleFormat } from '../utils/date_time_formatters';
import createTimeSeries from '../utils/multiple_time_series';
import bp from '../../breakpoints';
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select };
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
export default {
components: {
GraphAxis,
GraphFlag,
GraphDeployment,
GraphPath,
GraphLegend,
},
mixins: [MonitoringMixin],
props: {
graphData: {
type: Object,
required: true,
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
updateAspectRatio: {
type: Boolean,
required: true,
},
deploymentData: {
type: Array,
required: true,
},
hoverData: {
type: Object,
required: false,
default: () => ({}),
},
projectPath: {
type: String,
required: true,
},
tagsPath: {
type: String,
required: true,
},
showLegend: {
type: Boolean,
required: false,
default: true,
},
smallGraph: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
baseGraphHeight: 450,
baseGraphWidth: 600,
graphHeight: 450,
graphWidth: 600,
graphHeightOffset: 120,
margin: {},
unitOfDisplay: '',
yAxisLabel: '',
legendTitle: '',
reducedDeploymentData: [],
measurements: measurements.large,
currentData: {
time: new Date(),
value: 0,
2018-03-27 19:54:05 +05:30
},
2018-05-09 12:01:36 +05:30
currentXCoordinate: 0,
2018-10-15 14:42:47 +05:30
currentCoordinates: [],
2018-05-09 12:01:36 +05:30
showFlag: false,
showFlagContent: false,
timeSeries: [],
realPixelRatio: 1,
};
},
computed: {
outerViewBox() {
return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
innerViewBox() {
return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
},
axisTransform() {
return `translate(70, ${this.graphHeight - 100})`;
},
paddingBottomRootSvg() {
2018-03-17 18:26:18 +05:30
return {
2018-05-09 12:01:36 +05:30
paddingBottom: `${Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth || 0}%`,
2018-03-17 18:26:18 +05:30
};
},
2018-05-09 12:01:36 +05:30
deploymentFlagData() {
return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag);
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
},
watch: {
updateAspectRatio() {
if (this.updateAspectRatio) {
this.graphHeight = 450;
this.graphWidth = 600;
this.measurements = measurements.large;
this.draw();
eventHub.$emit('toggleAspectRatio');
}
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
hoverData() {
this.positionFlag();
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
},
mounted() {
this.draw();
},
methods: {
draw() {
const breakpointSize = bp.getBreakpointSize();
const query = this.graphData.queries[0];
this.margin = measurements.large.margin;
if (this.smallGraph || breakpointSize === 'xs' || breakpointSize === 'sm') {
this.graphHeight = 300;
this.margin = measurements.small.margin;
this.measurements = measurements.small;
}
this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.graphData.y_label || 'Values';
this.legendTitle = query.label || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
this.baseGraphHeight = this.graphHeight - 50;
this.baseGraphWidth = this.graphWidth;
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
// pixel offsets inside the svg and outside are not 1:1
this.realPixelRatio = this.$refs.baseSvg.clientWidth / this.baseGraphWidth;
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
this.renderAxesPaths();
this.formatDeployments();
},
handleMouseOverGraph(e) {
let point = this.$refs.graphData.createSVGPoint();
point.x = e.clientX;
point.y = e.clientY;
point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
2018-11-08 19:23:39 +05:30
point.x += 7;
2018-05-09 12:01:36 +05:30
const firstTimeSeries = this.timeSeries[0];
const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x);
const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1);
const d0 = firstTimeSeries.values[overlayIndex - 1];
const d1 = firstTimeSeries.values[overlayIndex];
if (d0 === undefined || d1 === undefined) return;
const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
const hoveredDataIndex = evalTime ? overlayIndex : overlayIndex - 1;
const hoveredDate = firstTimeSeries.values[hoveredDataIndex].time;
const currentDeployXPos = this.mouseOverDeployInfo(point.x);
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
eventHub.$emit('hoverChanged', {
hoveredDate,
currentDeployXPos,
});
},
renderAxesPaths() {
this.timeSeries = createTimeSeries(
this.graphData.queries,
this.graphWidth,
this.graphHeight,
this.graphHeightOffset,
);
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
if (_.findWhere(this.timeSeries, { renderCanary: true })) {
this.timeSeries = this.timeSeries.map(series => ({ ...series, renderCanary: true }));
}
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
const axisXScale = d3.scaleTime().range([0, this.graphWidth - 70]);
const axisYScale = d3.scaleLinear().range([this.graphHeight - this.graphHeightOffset, 0]);
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []);
axisXScale.domain(d3.extent(allValues, d => d.time));
axisYScale.domain([0, d3.max(allValues.map(d => d.value))]);
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
const xAxis = d3
.axisBottom()
.scale(axisXScale)
.ticks(this.graphWidth / 120)
.tickFormat(timeScaleFormat);
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
const yAxis = d3
.axisLeft()
.scale(axisYScale)
.ticks(measurements.yTicks);
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
d3
.select(this.$refs.baseSvg)
.select('.x-axis')
.call(xAxis);
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
const width = this.graphWidth;
d3
.select(this.$refs.baseSvg)
.select('.y-axis')
.call(yAxis)
.selectAll('.tick')
.each(function createTickLines(d, i) {
if (i > 0) {
d3
.select(this)
.select('line')
.attr('x2', width)
.attr('class', 'axis-tick');
} // Avoid adding the class to the first tick, to prevent coloring
}); // This will select all of the ticks once they're rendered
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
},
};
2018-03-17 18:26:18 +05:30
</script>
<template>
<div
class="prometheus-graph"
@mouseover="showFlagContent = true"
@mouseleave="showFlagContent = false"
>
2018-11-08 19:23:39 +05:30
<div class="prometheus-graph-header">
<h5 class="prometheus-graph-title">
{{ graphData.title }}
</h5>
<div class="prometheus-graph-widgets">
<slot></slot>
</div>
</div>
2018-03-17 18:26:18 +05:30
<div
:style="paddingBottomRootSvg"
2018-11-08 19:23:39 +05:30
class="prometheus-svg-container"
2018-03-17 18:26:18 +05:30
>
<svg
ref="baseSvg"
2018-11-08 19:23:39 +05:30
:viewBox="outerViewBox"
2018-03-17 18:26:18 +05:30
>
<g
:transform="axisTransform"
2018-11-08 19:23:39 +05:30
class="x-axis"
2018-03-17 18:26:18 +05:30
/>
<g
class="y-axis"
transform="translate(70, 20)"
/>
2018-05-09 12:01:36 +05:30
<graph-axis
2018-03-17 18:26:18 +05:30
:graph-width="graphWidth"
:graph-height="graphHeight"
:margin="margin"
:measurements="measurements"
:y-axis-label="yAxisLabel"
:unit-of-display="unitOfDisplay"
/>
<svg
ref="graphData"
2018-11-08 19:23:39 +05:30
:viewBox="innerViewBox"
class="graph-data"
2018-03-17 18:26:18 +05:30
>
<graph-path
v-for="(path, index) in timeSeries"
:key="index"
:generated-line-path="path.linePath"
:generated-area-path="path.areaPath"
:line-style="path.lineStyle"
:line-color="path.lineColor"
:area-color="path.areaColor"
2018-10-15 14:42:47 +05:30
:current-coordinates="currentCoordinates[index]"
:current-time-series-index="index"
:show-dot="showFlagContent"
2018-03-17 18:26:18 +05:30
/>
<graph-deployment
:deployment-data="reducedDeploymentData"
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
/>
<rect
2018-11-08 19:23:39 +05:30
ref="graphOverlay"
2018-03-17 18:26:18 +05:30
:width="(graphWidth - 70)"
:height="(graphHeight - 100)"
2018-11-08 19:23:39 +05:30
class="prometheus-graph-overlay"
2018-03-17 18:26:18 +05:30
transform="translate(-5, 20)"
@mousemove="handleMouseOverGraph($event)"
/>
</svg>
</svg>
<graph-flag
:real-pixel-ratio="realPixelRatio"
:current-x-coordinate="currentXCoordinate"
:current-data="currentData"
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
:show-flag-content="showFlagContent"
:time-series="timeSeries"
:unit-of-display="unitOfDisplay"
:legend-title="legendTitle"
:deployment-flag-data="deploymentFlagData"
2018-10-15 14:42:47 +05:30
:current-coordinates="currentCoordinates"
2018-03-17 18:26:18 +05:30
/>
</div>
2018-05-09 12:01:36 +05:30
<graph-legend
v-if="showLegend"
:legend-title="legendTitle"
:time-series="timeSeries"
/>
2018-03-17 18:26:18 +05:30
</div>
</template>