304 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Vue
		
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Vue
		
	
	
	
<script>
 | 
						|
  import { scaleLinear, scaleTime } from 'd3-scale';
 | 
						|
  import { axisLeft, axisBottom } from 'd3-axis';
 | 
						|
  import { max, extent } from 'd3-array';
 | 
						|
  import { select } from 'd3-selection';
 | 
						|
  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';
 | 
						|
 | 
						|
  const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select };
 | 
						|
 | 
						|
  export default {
 | 
						|
    components: {
 | 
						|
      GraphLegend,
 | 
						|
      GraphFlag,
 | 
						|
      GraphDeployment,
 | 
						|
      GraphPath,
 | 
						|
    },
 | 
						|
 | 
						|
    mixins: [MonitoringMixin],
 | 
						|
 | 
						|
    props: {
 | 
						|
      graphData: {
 | 
						|
        type: Object,
 | 
						|
        required: true,
 | 
						|
      },
 | 
						|
      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,
 | 
						|
      },
 | 
						|
    },
 | 
						|
 | 
						|
    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,
 | 
						|
        },
 | 
						|
        currentDataIndex: 0,
 | 
						|
        currentXCoordinate: 0,
 | 
						|
        currentFlagPosition: 0,
 | 
						|
        showFlag: false,
 | 
						|
        showFlagContent: false,
 | 
						|
        timeSeries: [],
 | 
						|
        realPixelRatio: 1,
 | 
						|
      };
 | 
						|
    },
 | 
						|
 | 
						|
    computed: {
 | 
						|
      outerViewBox() {
 | 
						|
        return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
 | 
						|
      },
 | 
						|
 | 
						|
      innerViewBox() {
 | 
						|
        return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
 | 
						|
      },
 | 
						|
 | 
						|
      axisTransform() {
 | 
						|
        return `translate(70, ${this.graphHeight - 100})`;
 | 
						|
      },
 | 
						|
 | 
						|
      paddingBottomRootSvg() {
 | 
						|
        return {
 | 
						|
          paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`,
 | 
						|
        };
 | 
						|
      },
 | 
						|
 | 
						|
      deploymentFlagData() {
 | 
						|
        return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag);
 | 
						|
      },
 | 
						|
    },
 | 
						|
 | 
						|
    watch: {
 | 
						|
      updateAspectRatio() {
 | 
						|
        if (this.updateAspectRatio) {
 | 
						|
          this.graphHeight = 450;
 | 
						|
          this.graphWidth = 600;
 | 
						|
          this.measurements = measurements.large;
 | 
						|
          this.draw();
 | 
						|
          eventHub.$emit('toggleAspectRatio');
 | 
						|
        }
 | 
						|
      },
 | 
						|
 | 
						|
      hoverData() {
 | 
						|
        this.positionFlag();
 | 
						|
      },
 | 
						|
    },
 | 
						|
 | 
						|
    mounted() {
 | 
						|
      this.draw();
 | 
						|
    },
 | 
						|
 | 
						|
    methods: {
 | 
						|
      draw() {
 | 
						|
        const breakpointSize = bp.getBreakpointSize();
 | 
						|
        const query = this.graphData.queries[0];
 | 
						|
        this.margin = measurements.large.margin;
 | 
						|
        if (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;
 | 
						|
        this.baseGraphWidth = this.graphWidth;
 | 
						|
 | 
						|
        // pixel offsets inside the svg and outside are not 1:1
 | 
						|
        this.realPixelRatio = (this.$refs.baseSvg.clientWidth / this.baseGraphWidth);
 | 
						|
 | 
						|
        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());
 | 
						|
        point.x = point.x += 7;
 | 
						|
        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);
 | 
						|
 | 
						|
        eventHub.$emit('hoverChanged', {
 | 
						|
          hoveredDate,
 | 
						|
          currentDeployXPos,
 | 
						|
        });
 | 
						|
      },
 | 
						|
 | 
						|
      renderAxesPaths() {
 | 
						|
        this.timeSeries = createTimeSeries(
 | 
						|
          this.graphData.queries,
 | 
						|
          this.graphWidth,
 | 
						|
          this.graphHeight,
 | 
						|
          this.graphHeightOffset,
 | 
						|
        );
 | 
						|
 | 
						|
        if (this.timeSeries.length > 3) {
 | 
						|
          this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
 | 
						|
        }
 | 
						|
 | 
						|
        const axisXScale = d3.scaleTime()
 | 
						|
          .range([0, this.graphWidth - 70]);
 | 
						|
        const axisYScale = d3.scaleLinear()
 | 
						|
          .range([this.graphHeight - this.graphHeightOffset, 0]);
 | 
						|
 | 
						|
        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))]);
 | 
						|
 | 
						|
        const xAxis = d3.axisBottom()
 | 
						|
          .scale(axisXScale)
 | 
						|
          .tickFormat(timeScaleFormat);
 | 
						|
 | 
						|
        const yAxis = d3.axisLeft()
 | 
						|
          .scale(axisYScale)
 | 
						|
          .ticks(measurements.yTicks);
 | 
						|
 | 
						|
        d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
 | 
						|
 | 
						|
        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
 | 
						|
      },
 | 
						|
    },
 | 
						|
  };
 | 
						|
</script>
 | 
						|
 | 
						|
<template>
 | 
						|
  <div
 | 
						|
    class="prometheus-graph"
 | 
						|
    @mouseover="showFlagContent = true"
 | 
						|
    @mouseleave="showFlagContent = false"
 | 
						|
  >
 | 
						|
    <h5 class="text-center graph-title">
 | 
						|
      {{ graphData.title }}
 | 
						|
    </h5>
 | 
						|
    <div
 | 
						|
      class="prometheus-svg-container"
 | 
						|
      :style="paddingBottomRootSvg"
 | 
						|
    >
 | 
						|
      <svg
 | 
						|
        :viewBox="outerViewBox"
 | 
						|
        ref="baseSvg"
 | 
						|
      >
 | 
						|
        <g
 | 
						|
          class="x-axis"
 | 
						|
          :transform="axisTransform"
 | 
						|
        />
 | 
						|
        <g
 | 
						|
          class="y-axis"
 | 
						|
          transform="translate(70, 20)"
 | 
						|
        />
 | 
						|
        <graph-legend
 | 
						|
          :graph-width="graphWidth"
 | 
						|
          :graph-height="graphHeight"
 | 
						|
          :margin="margin"
 | 
						|
          :measurements="measurements"
 | 
						|
          :legend-title="legendTitle"
 | 
						|
          :y-axis-label="yAxisLabel"
 | 
						|
          :time-series="timeSeries"
 | 
						|
          :unit-of-display="unitOfDisplay"
 | 
						|
          :current-data-index="currentDataIndex"
 | 
						|
        />
 | 
						|
        <svg
 | 
						|
          class="graph-data"
 | 
						|
          :viewBox="innerViewBox"
 | 
						|
          ref="graphData"
 | 
						|
        >
 | 
						|
          <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"
 | 
						|
          />
 | 
						|
          <graph-deployment
 | 
						|
            :deployment-data="reducedDeploymentData"
 | 
						|
            :graph-height="graphHeight"
 | 
						|
            :graph-height-offset="graphHeightOffset"
 | 
						|
          />
 | 
						|
          <rect
 | 
						|
            class="prometheus-graph-overlay"
 | 
						|
            :width="(graphWidth - 70)"
 | 
						|
            :height="(graphHeight - 100)"
 | 
						|
            transform="translate(-5, 20)"
 | 
						|
            ref="graphOverlay"
 | 
						|
            @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"
 | 
						|
        :current-data-index="currentDataIndex"
 | 
						|
        :legend-title="legendTitle"
 | 
						|
        :deployment-flag-data="deploymentFlagData"
 | 
						|
      />
 | 
						|
    </div>
 | 
						|
  </div>
 | 
						|
</template>
 |