mirror of
https://github.com/timberjoegithub/grafana-statusmap.git
synced 2026-07-22 15:53:09 +00:00
feat: use grafana-toolkit to build a plugin
- fix linter warnings - use yarn: remove Gruntfile.js and package-lock.json ++
This commit is contained in:
+287
-233
@@ -1,64 +1,68 @@
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import moment from 'moment';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import {appEvents, contextSrv} from 'app/core/core';
|
||||
|
||||
/* eslint-disable id-blacklist, no-restricted-imports, @typescript-eslint/ban-types */
|
||||
// import moment from 'moment';
|
||||
import { toUtc } from '@grafana/data';
|
||||
|
||||
import kbn from 'grafana/app/core/utils/kbn';
|
||||
import { appEvents, contextSrv } from 'grafana/app/core/core';
|
||||
import * as d3 from 'd3';
|
||||
import * as d3ScaleChromatic from './libs/d3-scale-chromatic/index';
|
||||
import {StatusmapTooltip} from './tooltip';
|
||||
import {AnnotationTooltip} from './annotations';
|
||||
import { d3ScaleChromatic } from './d3/d3-scale-chromatic';
|
||||
import { StatusmapTooltip } from './tooltip';
|
||||
import { AnnotationTooltip } from './annotations';
|
||||
import { Bucket, BucketMatrix, BucketMatrixPager } from './statusmap_data';
|
||||
import { StatusHeatmapCtrl, renderComplete } from './module';
|
||||
import { CoreEvents, PanelEvents } from './libs/grafana/events/index';
|
||||
|
||||
let MIN_CARD_SIZE = 5,
|
||||
CARD_H_SPACING = 2,
|
||||
CARD_V_SPACING = 2,
|
||||
CARD_ROUND = 0,
|
||||
DATA_RANGE_WIDING_FACTOR = 1.2,
|
||||
DEFAULT_X_TICK_SIZE_PX = 100,
|
||||
DEFAULT_Y_TICK_SIZE_PX = 50,
|
||||
X_AXIS_TICK_PADDING = 10,
|
||||
Y_AXIS_TICK_PADDING = 5,
|
||||
MIN_SELECTION_WIDTH = 2;
|
||||
CARD_H_SPACING = 2,
|
||||
CARD_V_SPACING = 2,
|
||||
CARD_ROUND = 0,
|
||||
DATA_RANGE_WIDING_FACTOR = 1.2,
|
||||
DEFAULT_X_TICK_SIZE_PX = 100,
|
||||
// @ts-ignore
|
||||
DEFAULT_Y_TICK_SIZE_PX = 50,
|
||||
X_AXIS_TICK_PADDING = 10,
|
||||
Y_AXIS_TICK_PADDING = 5,
|
||||
MIN_SELECTION_WIDTH = 2;
|
||||
|
||||
export default function rendering(scope: any, elem: any, attrs: any, ctrl: any) {
|
||||
return new StatusmapRenderer(scope, elem, attrs, ctrl);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
class Statusmap {
|
||||
$svg: any;
|
||||
svg: any;
|
||||
bucketMatrix: BucketMatrix;
|
||||
|
||||
timeRange: {from: number, to: number} = {from:0, to:0};
|
||||
timeRange: { from: number; to: number } = { from: 0, to: 0 };
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
export class StatusmapRenderer {
|
||||
width: number = 0;
|
||||
height: number = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
yScale: any;
|
||||
xScale: any;
|
||||
chartWidth: number = 0;
|
||||
chartHeight: number = 0;
|
||||
chartTop: number = 0;
|
||||
chartBottom: number = 0;
|
||||
yAxisWidth: number = 0;
|
||||
xAxisHeight: number = 0;
|
||||
cardVSpacing: number = 0;
|
||||
cardHSpacing: number = 0;
|
||||
cardRound: number = 0;
|
||||
cardWidth: number = 0;
|
||||
cardHeight: number = 0;
|
||||
chartWidth = 0;
|
||||
chartHeight = 0;
|
||||
chartTop = 0;
|
||||
chartBottom = 0;
|
||||
yAxisWidth = 0;
|
||||
xAxisHeight = 0;
|
||||
cardVSpacing = 0;
|
||||
cardHSpacing = 0;
|
||||
cardRound = 0;
|
||||
cardWidth = 0;
|
||||
cardHeight = 0;
|
||||
colorScale: any;
|
||||
opacityScale: any;
|
||||
mouseUpHandler: any;
|
||||
xGridSize: number = 0;
|
||||
yGridSize: number = 0;
|
||||
xGridSize = 0;
|
||||
yGridSize = 0;
|
||||
|
||||
bucketMatrix: BucketMatrix;
|
||||
bucketMatrixPager: BucketMatrixPager;
|
||||
@@ -94,6 +98,7 @@ export class StatusmapRenderer {
|
||||
|
||||
this.ctrl.events.on(PanelEvents.render, this.onRender.bind(this));
|
||||
|
||||
// @ts-ignore
|
||||
this.ctrl.tickValueFormatter = this.tickValueFormatter.bind(this);
|
||||
|
||||
/////////////////////////////
|
||||
@@ -102,9 +107,9 @@ export class StatusmapRenderer {
|
||||
|
||||
// Shared crosshair and tooltip this.empty = true;
|
||||
|
||||
appEvents.on( CoreEvents.graphHover, this.onGraphHover.bind(this), this.scope);
|
||||
appEvents.on(CoreEvents.graphHover, this.onGraphHover.bind(this), this.scope);
|
||||
|
||||
appEvents.on( CoreEvents.graphHoverClear, this.onGraphHoverClear.bind(this), this.scope);
|
||||
appEvents.on(CoreEvents.graphHoverClear, this.onGraphHoverClear.bind(this), this.scope);
|
||||
|
||||
// Register selection listeners
|
||||
this.$heatmap.on('mousedown', this.onMouseDown.bind(this));
|
||||
@@ -126,7 +131,6 @@ export class StatusmapRenderer {
|
||||
this.ctrl.renderingCompleted();
|
||||
}
|
||||
|
||||
|
||||
setElementHeight(): boolean {
|
||||
try {
|
||||
var height = this.ctrl.height || this.panel.height || this.ctrl.row.height;
|
||||
@@ -146,26 +150,29 @@ export class StatusmapRenderer {
|
||||
this.$heatmap.css('height', height + 'px');
|
||||
|
||||
return true;
|
||||
} catch (e) { // IE throws errors sometimes
|
||||
} catch (e) {
|
||||
// IE throws errors sometimes
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getYAxisWidth(elem: any): number {
|
||||
const axisText = elem.selectAll(".axis-y text").nodes();
|
||||
const maxTextWidth = _.max(_.map(axisText, text => {
|
||||
// Use SVG getBBox method
|
||||
return text.getBBox().width;
|
||||
}));
|
||||
const axisText = elem.selectAll('.axis-y text').nodes();
|
||||
const maxTextWidth = _.max(
|
||||
_.map(axisText, text => {
|
||||
// Use SVG getBBox method
|
||||
return text.getBBox().width;
|
||||
})
|
||||
);
|
||||
|
||||
return Math.ceil(maxTextWidth);
|
||||
}
|
||||
|
||||
getXAxisHeight(elem: any): number {
|
||||
let axisLine = elem.select(".axis-x line");
|
||||
let axisLine = elem.select('.axis-x line');
|
||||
if (!axisLine.empty()) {
|
||||
let axisLinePosition = parseFloat(elem.select(".axis-x line").attr("y2"));
|
||||
let canvasWidth = parseFloat(elem.attr("height"));
|
||||
let axisLinePosition = parseFloat(elem.select('.axis-x line').attr('y2'));
|
||||
let canvasWidth = parseFloat(elem.attr('height'));
|
||||
return canvasWidth - axisLinePosition;
|
||||
} else {
|
||||
// Default height
|
||||
@@ -179,9 +186,10 @@ export class StatusmapRenderer {
|
||||
// .domain([this.timeRange.from, this.timeRange.to])
|
||||
// .range([this.xGridSize/2, this.chartWidth-this.xGridSize/2]);
|
||||
// Buckets without the most recent
|
||||
this.scope.xScale = this.xScale = d3.scaleTime()
|
||||
.domain([this.timeRange.from, this.timeRange.to])
|
||||
.range([this.xGridSize/2, this.chartWidth-this.xGridSize/2]);
|
||||
this.scope.xScale = this.xScale = d3
|
||||
.scaleTime()
|
||||
.domain([this.timeRange.from, this.timeRange.to])
|
||||
.range([this.xGridSize / 2, this.chartWidth - this.xGridSize / 2]);
|
||||
|
||||
let ticks = this.chartWidth / DEFAULT_X_TICK_SIZE_PX;
|
||||
let grafanaTimeFormatter = grafanaTimeFormat(ticks, this.timeRange.from, this.timeRange.to);
|
||||
@@ -194,87 +202,97 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
let xAxis = d3
|
||||
.axisBottom(this.xScale)
|
||||
.ticks(ticks)
|
||||
.tickFormat(timeFormat)
|
||||
.tickPadding(X_AXIS_TICK_PADDING)
|
||||
.tickSize(this.chartHeight);
|
||||
.axisBottom(this.xScale)
|
||||
.ticks(ticks)
|
||||
.tickFormat(timeFormat)
|
||||
.tickPadding(X_AXIS_TICK_PADDING)
|
||||
.tickSize(this.chartHeight);
|
||||
|
||||
let posY = this.chartTop; // this.margin.top !
|
||||
let posX = this.yAxisWidth;
|
||||
|
||||
this.heatmap.append("g")
|
||||
.attr("class", "axis axis-x")
|
||||
.attr("transform", "translate(" + posX + "," + posY + ")")
|
||||
.call(xAxis);
|
||||
this.heatmap
|
||||
.append('g')
|
||||
.attr('class', 'axis axis-x')
|
||||
.attr('transform', 'translate(' + posX + ',' + posY + ')')
|
||||
.call(xAxis);
|
||||
|
||||
// Remove horizontal line in the top of axis labels (called domain in d3)
|
||||
this.heatmap
|
||||
.select(".axis-x")
|
||||
.select(".domain")
|
||||
.remove();
|
||||
.select('.axis-x')
|
||||
.select('.domain')
|
||||
.remove();
|
||||
}
|
||||
|
||||
// divide chart height by ticks for cards drawing
|
||||
getYScale(ticks: any[]) {
|
||||
let range:any[] = [];
|
||||
let range: any[] = [];
|
||||
let step = this.chartHeight / ticks.length;
|
||||
// svg has y=0 on the top, so top card should have a minimal value in range
|
||||
range.push(step);
|
||||
for (let i = 1; i < ticks.length; i++) {
|
||||
range.push(step * (i+1));
|
||||
range.push(step * (i + 1));
|
||||
}
|
||||
return d3.scaleOrdinal()
|
||||
.domain(ticks)
|
||||
.range(range);
|
||||
return d3
|
||||
.scaleOrdinal()
|
||||
.domain(ticks)
|
||||
.range(range);
|
||||
}
|
||||
|
||||
// divide chart height by ticks with offset for ticks drawing
|
||||
getYAxisScale(ticks: any[]) {
|
||||
let range:any[] = [];
|
||||
let range: any[] = [];
|
||||
let step = this.chartHeight / ticks.length;
|
||||
// svg has y=0 on the top, so top tick should have a minimal value in range
|
||||
range.push(this.yOffset);
|
||||
for (let i = 1; i < ticks.length; i++) {
|
||||
range.push(step * i + this.yOffset);
|
||||
}
|
||||
return d3.scaleOrdinal()
|
||||
.domain(ticks)
|
||||
.range(range);
|
||||
return d3
|
||||
.scaleOrdinal()
|
||||
.domain(ticks)
|
||||
.range(range);
|
||||
}
|
||||
|
||||
addYAxis() {
|
||||
let ticks = this.bucketMatrixPager.targets();
|
||||
|
||||
// TODO move sorting into bucketMatrixPager.
|
||||
if (this.panel.yAxisSort == 'a → z') {
|
||||
ticks.sort((a, b) => a.localeCompare(b, 'en', {ignorePunctuation: false, numeric: true}));
|
||||
} else if (this.panel.yAxisSort == 'z → a') {
|
||||
ticks.sort((b, a) => a.localeCompare(b, 'en', {ignorePunctuation: false, numeric: true}));
|
||||
if (this.panel.yAxisSort === 'a → z') {
|
||||
ticks.sort((a, b) => a.localeCompare(b, 'en', { ignorePunctuation: false, numeric: true }));
|
||||
} else if (this.panel.yAxisSort === 'z → a') {
|
||||
ticks.sort((b, a) => a.localeCompare(b, 'en', { ignorePunctuation: false, numeric: true }));
|
||||
}
|
||||
|
||||
let yAxisScale = this.getYAxisScale(ticks);
|
||||
this.scope.yScale = this.yScale = this.getYScale(ticks);
|
||||
|
||||
let yAxis = d3
|
||||
.axisLeft(yAxisScale)
|
||||
.tickValues(ticks)
|
||||
.tickSizeInner(0 - this.width)
|
||||
.tickPadding(Y_AXIS_TICK_PADDING);
|
||||
// @ts-ignore
|
||||
.axisLeft(yAxisScale)
|
||||
.tickValues(ticks)
|
||||
.tickSizeInner(0 - this.width)
|
||||
.tickPadding(Y_AXIS_TICK_PADDING);
|
||||
|
||||
this.heatmap
|
||||
.append("g")
|
||||
.attr("class", "axis axis-y")
|
||||
.call(yAxis);
|
||||
.append('g')
|
||||
.attr('class', 'axis axis-y')
|
||||
.call(yAxis);
|
||||
|
||||
// Calculate Y axis width first, then move axis into visible area
|
||||
let posY = this.margin.top;
|
||||
let posX = this.getYAxisWidth(this.heatmap) + Y_AXIS_TICK_PADDING;
|
||||
this.heatmap.select(".axis-y").attr("transform", "translate(" + posX + "," + posY + ")");
|
||||
this.heatmap.select('.axis-y').attr('transform', 'translate(' + posX + ',' + posY + ')');
|
||||
|
||||
// Remove vertical line in the right of axis labels (called domain in d3)
|
||||
this.heatmap.select(".axis-y").select(".domain").remove();
|
||||
this.heatmap.select(".axis-y").selectAll(".tick line").remove();
|
||||
this.heatmap
|
||||
.select('.axis-y')
|
||||
.select('.domain')
|
||||
.remove();
|
||||
this.heatmap
|
||||
.select('.axis-y')
|
||||
.selectAll('.tick line')
|
||||
.remove();
|
||||
}
|
||||
|
||||
// Wide Y values range and adjust to bucket size
|
||||
@@ -296,7 +314,7 @@ export class StatusmapRenderer {
|
||||
y_min = 0;
|
||||
}
|
||||
|
||||
return {y_min, y_max};
|
||||
return { y_min, y_max };
|
||||
}
|
||||
|
||||
tickValueFormatter(decimals, scaledDecimals = null) {
|
||||
@@ -320,10 +338,11 @@ export class StatusmapRenderer {
|
||||
|
||||
// Insert svg as a first child into heatmap_elem
|
||||
// as the frozen tooltip moving logiс assumes that tooltip is the last child.
|
||||
this.heatmap = d3.select(heatmap_elem)
|
||||
.insert("svg",":first-child")
|
||||
.attr("width", this.width)
|
||||
.attr("height", this.height);
|
||||
this.heatmap = d3
|
||||
.select(heatmap_elem)
|
||||
.insert('svg', ':first-child')
|
||||
.attr('width', this.width)
|
||||
.attr('height', this.height);
|
||||
|
||||
this.chartHeight = this.height - this.margin.top - this.margin.bottom;
|
||||
this.chartTop = this.margin.top;
|
||||
@@ -348,22 +367,28 @@ export class StatusmapRenderer {
|
||||
|
||||
// TODO allow per-y cardWidth!
|
||||
// we need to fill chartWidth with xBucketSize cards.
|
||||
this.xGridSize = this.chartWidth / (this.bucketMatrix.xBucketSize+1);
|
||||
this.xGridSize = this.chartWidth / (this.bucketMatrix.xBucketSize + 1);
|
||||
this.cardWidth = this.xGridSize - this.cardHSpacing;
|
||||
|
||||
this.addXAxis();
|
||||
this.xAxisHeight = this.getXAxisHeight(this.heatmap);
|
||||
|
||||
if (!this.panel.yAxis.show) {
|
||||
this.heatmap.select(".axis-y").selectAll("line").style("opacity", 0);
|
||||
this.heatmap
|
||||
.select('.axis-y')
|
||||
.selectAll('line')
|
||||
.style('opacity', 0);
|
||||
}
|
||||
|
||||
if (!this.panel.xAxis.show) {
|
||||
this.heatmap.select(".axis-x").selectAll("line").style("opacity", 0);
|
||||
this.heatmap
|
||||
.select('.axis-x')
|
||||
.selectAll('line')
|
||||
.style('opacity', 0);
|
||||
}
|
||||
}
|
||||
|
||||
addStatusmap():void {
|
||||
addStatusmap(): void {
|
||||
let maxValue = this.panel.color.max != null ? this.panel.color.max : this.bucketMatrix.maxValue;
|
||||
let minValue = this.panel.color.min != null ? this.panel.color.min : this.bucketMatrix.minValue;
|
||||
|
||||
@@ -373,37 +398,39 @@ export class StatusmapRenderer {
|
||||
this.setOpacityScale(maxValue);
|
||||
|
||||
// Draw cards from buckets.
|
||||
this.heatmap.selectAll(".statusmap-cards-row").data(this.bucketMatrixPager.targets())
|
||||
this.heatmap
|
||||
.selectAll('.statusmap-cards-row')
|
||||
.data(this.bucketMatrixPager.targets())
|
||||
.enter()
|
||||
.selectAll(".statustmap-card")
|
||||
.data((target:string) => this.bucketMatrix.buckets[target])
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("cardId", (b:Bucket) => b.id)
|
||||
.attr("xid", (b:Bucket) => b.xid)
|
||||
.attr("yid", (b:Bucket) => b.yLabel)
|
||||
.attr("x", this.getCardX.bind(this))
|
||||
.attr("width", this.getCardWidth.bind(this))
|
||||
.attr("y", this.getCardY.bind(this))
|
||||
.attr("height", this.getCardHeight.bind(this))
|
||||
.attr("rx", this.cardRound)
|
||||
.attr("ry", this.cardRound)
|
||||
.attr("class", (b:Bucket) => b.isEmpty() ? "empty-card" : "bordered statusmap-card")
|
||||
.style("fill", this.getCardColor.bind(this))
|
||||
.style("stroke", this.getCardColor.bind(this))
|
||||
.style("stroke-width", 0)
|
||||
//.style("stroke-width", getCardStrokeWidth)
|
||||
//.style("stroke-dasharray", "3,3")
|
||||
.style("opacity", this.getCardOpacity.bind(this));
|
||||
.selectAll('.statustmap-card')
|
||||
.data((target: string) => this.bucketMatrix.buckets[target])
|
||||
.enter()
|
||||
.append('rect')
|
||||
.attr('cardId', (b: Bucket) => b.id)
|
||||
.attr('xid', (b: Bucket) => b.xid)
|
||||
.attr('yid', (b: Bucket) => b.yLabel)
|
||||
.attr('x', this.getCardX.bind(this))
|
||||
.attr('width', this.getCardWidth.bind(this))
|
||||
.attr('y', this.getCardY.bind(this))
|
||||
.attr('height', this.getCardHeight.bind(this))
|
||||
.attr('rx', this.cardRound)
|
||||
.attr('ry', this.cardRound)
|
||||
.attr('class', (b: Bucket) => (b.isEmpty() ? 'empty-card' : 'bordered statusmap-card'))
|
||||
.style('fill', this.getCardColor.bind(this))
|
||||
.style('stroke', this.getCardColor.bind(this))
|
||||
.style('stroke-width', 0)
|
||||
//.style('stroke-width', getCardStrokeWidth)
|
||||
//.style('stroke-dasharray', '3,3')
|
||||
.style('opacity', this.getCardOpacity.bind(this));
|
||||
|
||||
// Set mouse events on cards.
|
||||
let $cards = this.$heatmap.find(".statusmap-card + .bordered");
|
||||
let $cards = this.$heatmap.find('.statusmap-card + .bordered');
|
||||
$cards
|
||||
.on("mouseenter", (event) => {
|
||||
.on('mouseenter', event => {
|
||||
this.tooltip.mouseOverBucket = true;
|
||||
this.highlightCard(event);
|
||||
})
|
||||
.on("mouseleave", (event) => {
|
||||
.on('mouseleave', event => {
|
||||
this.tooltip.mouseOverBucket = false;
|
||||
this.resetCardHighLight(event);
|
||||
});
|
||||
@@ -411,37 +438,40 @@ export class StatusmapRenderer {
|
||||
this._renderAnnotations();
|
||||
|
||||
this.ctrl.events.emit(renderComplete, {
|
||||
"chartWidth": this.chartWidth
|
||||
chartWidth: this.chartWidth,
|
||||
});
|
||||
}
|
||||
|
||||
highlightCard(event) {
|
||||
const color = d3.select(event.target).style("fill");
|
||||
const color = d3.select(event.target).style('fill');
|
||||
const highlightColor = d3.color(color).darker(2);
|
||||
const strokeColor = d3.color(color).brighter(4);
|
||||
const current_card = d3.select(event.target);
|
||||
this.tooltip.originalFillColor = color;
|
||||
current_card
|
||||
.style("fill", highlightColor.toString())
|
||||
.style("stroke", strokeColor.toString())
|
||||
.style("stroke-width", 1);
|
||||
.style('fill', highlightColor.toString())
|
||||
.style('stroke', strokeColor.toString())
|
||||
.style('stroke-width', 1);
|
||||
}
|
||||
|
||||
resetCardHighLight(event) {
|
||||
d3.select(event.target)
|
||||
.style("fill", this.tooltip.originalFillColor)
|
||||
.style("stroke", this.tooltip.originalFillColor)
|
||||
.style("stroke-width", 0);
|
||||
.style('fill', this.tooltip.originalFillColor)
|
||||
.style('stroke', this.tooltip.originalFillColor)
|
||||
.style('stroke-width', 0);
|
||||
}
|
||||
|
||||
getColorScale(maxValue, minValue = 0) {
|
||||
let colorScheme = _.find(this.ctrl.colorSchemes, {value: this.panel.color.colorScheme});
|
||||
let colorScheme = _.find(this.ctrl.colorSchemes, { value: this.panel.color.colorScheme });
|
||||
// @ts-ignore
|
||||
let colorInterpolator = d3ScaleChromatic[colorScheme.value];
|
||||
let colorScaleInverted = colorScheme.invert === 'always' ||
|
||||
(colorScheme.invert === 'dark' && !contextSrv.user.lightTheme);
|
||||
let colorScaleInverted =
|
||||
// @ts-ignore
|
||||
colorScheme.invert === 'always' || (colorScheme.invert === 'dark' && !contextSrv.user.lightTheme);
|
||||
|
||||
if (maxValue == minValue)
|
||||
if (maxValue === minValue) {
|
||||
maxValue = minValue + 1;
|
||||
}
|
||||
|
||||
let start = colorScaleInverted ? maxValue : minValue;
|
||||
let end = colorScaleInverted ? minValue : maxValue;
|
||||
@@ -451,13 +481,16 @@ export class StatusmapRenderer {
|
||||
|
||||
setOpacityScale(maxValue) {
|
||||
if (this.panel.color.colorScale === 'linear') {
|
||||
this.opacityScale = d3.scaleLinear()
|
||||
.domain([0, maxValue])
|
||||
.range([0, 1]);
|
||||
this.opacityScale = d3
|
||||
.scaleLinear()
|
||||
.domain([0, maxValue])
|
||||
.range([0, 1]);
|
||||
} else if (this.panel.color.colorScale === 'sqrt') {
|
||||
this.opacityScale = d3.scalePow().exponent(this.panel.color.exponent)
|
||||
.domain([0, maxValue])
|
||||
.range([0, 1]);
|
||||
this.opacityScale = d3
|
||||
.scalePow()
|
||||
.exponent(this.panel.color.exponent)
|
||||
.domain([0, maxValue])
|
||||
.range([0, 1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,12 +499,12 @@ export class StatusmapRenderer {
|
||||
// cx is the center of the card. Card should be placed to the left.
|
||||
//let cx = this.xScale(d.x);
|
||||
let rightX = (b.relTo / this.bucketMatrix.rangeMs) * this.chartWidth;
|
||||
let cx = rightX - this.cardWidth/2;
|
||||
let cx = rightX - this.cardWidth / 2;
|
||||
|
||||
if (cx - this.cardWidth/2 < 0) {
|
||||
x = this.yAxisWidth + this.cardHSpacing/2;
|
||||
if (cx - this.cardWidth / 2 < 0) {
|
||||
x = this.yAxisWidth + this.cardHSpacing / 2;
|
||||
} else {
|
||||
x = this.yAxisWidth + cx - this.cardWidth/2;
|
||||
x = this.yAxisWidth + cx - this.cardWidth / 2;
|
||||
}
|
||||
|
||||
return x;
|
||||
@@ -483,17 +516,17 @@ export class StatusmapRenderer {
|
||||
let w;
|
||||
|
||||
let rightX = (b.relTo / this.bucketMatrix.rangeMs) * this.chartWidth;
|
||||
let cx = rightX - this.cardWidth/2;
|
||||
let cx = rightX - this.cardWidth / 2;
|
||||
//let cx = this.xScale(d.x);
|
||||
|
||||
if (cx < this.cardWidth/2) {
|
||||
if (cx < this.cardWidth / 2) {
|
||||
// Center should not exceed half of card.
|
||||
// Cut card to the left to prevent overlay of y axis.
|
||||
let cutted_width = (cx - this.cardHSpacing/2) + this.cardWidth/2;
|
||||
let cutted_width = cx - this.cardHSpacing / 2 + this.cardWidth / 2;
|
||||
w = cutted_width > 0 ? cutted_width : 0;
|
||||
} else if (this.chartWidth - cx < this.cardWidth/2) {
|
||||
} else if (this.chartWidth - cx < this.cardWidth / 2) {
|
||||
// Cut card to the right to prevent overlay of right graph edge.
|
||||
w = this.cardWidth/2 + (this.chartWidth - cx - this.cardHSpacing/2);
|
||||
w = this.cardWidth / 2 + (this.chartWidth - cx - this.cardHSpacing / 2);
|
||||
} else {
|
||||
w = this.cardWidth;
|
||||
}
|
||||
@@ -501,8 +534,8 @@ export class StatusmapRenderer {
|
||||
// Card width should be MIN_CARD_SIZE at least
|
||||
w = Math.max(w, MIN_CARD_SIZE);
|
||||
|
||||
if (this.cardHSpacing == 0) {
|
||||
w = w+1;
|
||||
if (this.cardHSpacing === 0) {
|
||||
w = w + 1;
|
||||
}
|
||||
|
||||
return w;
|
||||
@@ -511,18 +544,18 @@ export class StatusmapRenderer {
|
||||
// Top y for card.
|
||||
// yScale gives ???
|
||||
getCardY(b: Bucket) {
|
||||
return this.yScale(b.yLabel) + this.chartTop - this.cardHeight - this.cardVSpacing/2;
|
||||
return this.yScale(b.yLabel) + this.chartTop - this.cardHeight - this.cardVSpacing / 2;
|
||||
}
|
||||
|
||||
getCardHeight(b: Bucket) {
|
||||
//return 20;
|
||||
let ys = this.yScale(b.yLabel);
|
||||
let y = ys + this.chartTop - this.cardHeight - this.cardVSpacing/2;
|
||||
let y = ys + this.chartTop - this.cardHeight - this.cardVSpacing / 2;
|
||||
let h = this.cardHeight;
|
||||
|
||||
// Cut card height to prevent overlay
|
||||
if (y < this.chartTop) {
|
||||
h = ys - this.cardVSpacing/2;
|
||||
h = ys - this.cardVSpacing / 2;
|
||||
} else if (ys > this.chartBottom) {
|
||||
h = this.chartBottom - y;
|
||||
} else if (y + this.cardHeight > this.chartBottom) {
|
||||
@@ -534,8 +567,8 @@ export class StatusmapRenderer {
|
||||
// Card height should be MIN_CARD_SIZE at least
|
||||
h = Math.max(h, MIN_CARD_SIZE);
|
||||
|
||||
if (this.cardVSpacing == 0) {
|
||||
h = h+1
|
||||
if (this.cardVSpacing === 0) {
|
||||
h = h + 1;
|
||||
}
|
||||
|
||||
return h;
|
||||
@@ -547,7 +580,7 @@ export class StatusmapRenderer {
|
||||
} else if (this.panel.color.mode === 'spectrum') {
|
||||
return this.colorScale(bucket.value);
|
||||
} else if (this.panel.color.mode === 'discrete') {
|
||||
if (this.panel.seriesFilterIndex != null && this.panel.seriesFilterIndex != -1) {
|
||||
if (this.panel.seriesFilterIndex !== null && this.panel.seriesFilterIndex !== -1) {
|
||||
return this.ctrl.discreteExtraSeries.getBucketColorSingle(bucket.values[this.panel.seriesFilterIndex]);
|
||||
} else {
|
||||
return this.ctrl.discreteExtraSeries.getBucketColor(bucket.values);
|
||||
@@ -556,7 +589,7 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
getCardOpacity(bucket: Bucket) {
|
||||
if (this.panel.nullPointMode === 'as empty' && bucket.value == null ) {
|
||||
if (this.panel.nullPointMode === 'as empty' && bucket.value == null) {
|
||||
return 0;
|
||||
}
|
||||
if (this.panel.color.mode === 'opacity') {
|
||||
@@ -573,7 +606,6 @@ export class StatusmapRenderer {
|
||||
return '0';
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////
|
||||
// Selection and crosshair //
|
||||
/////////////////////////////
|
||||
@@ -594,22 +626,26 @@ export class StatusmapRenderer {
|
||||
this.onMouseUp();
|
||||
};
|
||||
|
||||
$(document).one("mouseup", this.mouseUpHandler.bind(this));
|
||||
$(document).one('mouseup', this.mouseUpHandler.bind(this));
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
$(document).unbind("mouseup", this.mouseUpHandler.bind(this));
|
||||
$(document).unbind('mouseup', this.mouseUpHandler.bind(this));
|
||||
this.mouseUpHandler = null;
|
||||
this.selection.active = false;
|
||||
|
||||
let selectionRange = Math.abs(this.selection.x2 - this.selection.x1);
|
||||
if (this.selection.x2 >= 0 && selectionRange > MIN_SELECTION_WIDTH) {
|
||||
let timeFrom = this.xScale.invert(Math.min(this.selection.x1, this.selection.x2) - this.yAxisWidth - this.xGridSize/2);
|
||||
let timeTo = this.xScale.invert(Math.max(this.selection.x1, this.selection.x2) - this.yAxisWidth - this.xGridSize/2);
|
||||
let timeFrom = this.xScale.invert(
|
||||
Math.min(this.selection.x1, this.selection.x2) - this.yAxisWidth - this.xGridSize / 2
|
||||
);
|
||||
let timeTo = this.xScale.invert(
|
||||
Math.max(this.selection.x1, this.selection.x2) - this.yAxisWidth - this.xGridSize / 2
|
||||
);
|
||||
|
||||
this.ctrl.timeSrv.setTime({
|
||||
from: moment.utc(timeFrom),
|
||||
to: moment.utc(timeTo)
|
||||
from: toUtc(timeFrom),
|
||||
to: toUtc(timeTo),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -623,7 +659,9 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
onMouseMove(event: MouseEvent) {
|
||||
if (!this.heatmap) { return; }
|
||||
if (!this.heatmap) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offset = this.getEventOffset(event);
|
||||
if (this.selection.active) {
|
||||
@@ -644,7 +682,7 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
// TODO emit an event and move logic to panelCtrl
|
||||
public onMouseClick(e: MouseEvent) {
|
||||
onMouseClick(e: MouseEvent) {
|
||||
if (this.ctrl.panel.tooltip.freezeOnClick) {
|
||||
this.tooltip.showFrozen(e);
|
||||
this.tooltip.destroy();
|
||||
@@ -669,21 +707,23 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
emitGraphHoverEvent(event) {
|
||||
let x = this.xScale.invert(event.offsetX - this.yAxisWidth - this.xGridSize/2).valueOf();
|
||||
let x = this.xScale.invert(event.offsetX - this.yAxisWidth - this.xGridSize / 2).valueOf();
|
||||
let y = this.yScale(event.offsetY);
|
||||
let pos = {
|
||||
pageX: event.pageX,
|
||||
pageY: event.pageY,
|
||||
x: x, x1: x,
|
||||
y: y, y1: y,
|
||||
panelRelY: 0
|
||||
x: x,
|
||||
x1: x,
|
||||
y: y,
|
||||
y1: y,
|
||||
panelRelY: 0,
|
||||
};
|
||||
|
||||
// Set minimum offset to prevent showing legend from another panel
|
||||
pos.panelRelY = Math.max(event.offsetY / this.height, 0.001);
|
||||
|
||||
// broadcast to other graph panels that we are hovering
|
||||
appEvents.emit(CoreEvents.graphHover, {pos: pos, panel: this.panel});
|
||||
appEvents.emit(CoreEvents.graphHover, { pos: pos, panel: this.panel });
|
||||
}
|
||||
|
||||
limitSelection(x2) {
|
||||
@@ -694,17 +734,18 @@ export class StatusmapRenderer {
|
||||
|
||||
drawSelection(posX1, posX2) {
|
||||
if (this.heatmap) {
|
||||
this.heatmap.selectAll(".status-heatmap-selection").remove();
|
||||
this.heatmap.selectAll('.status-heatmap-selection').remove();
|
||||
let selectionX = Math.min(posX1, posX2);
|
||||
let selectionWidth = Math.abs(posX1 - posX2);
|
||||
|
||||
if (selectionWidth > MIN_SELECTION_WIDTH) {
|
||||
this.heatmap.append("rect")
|
||||
.attr("class", "status-heatmap-selection")
|
||||
.attr("x", selectionX)
|
||||
.attr("width", selectionWidth)
|
||||
.attr("y", this.chartTop)
|
||||
.attr("height", this.chartHeight);
|
||||
this.heatmap
|
||||
.append('rect')
|
||||
.attr('class', 'status-heatmap-selection')
|
||||
.attr('x', selectionX)
|
||||
.attr('width', selectionWidth)
|
||||
.attr('y', this.chartTop)
|
||||
.attr('height', this.chartHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,28 +755,28 @@ export class StatusmapRenderer {
|
||||
this.selection.x2 = -1;
|
||||
|
||||
if (this.heatmap) {
|
||||
this.heatmap.selectAll(".status-heatmap-selection").remove();
|
||||
this.heatmap.selectAll('.status-heatmap-selection').remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
drawCrosshair(position) {
|
||||
if (this.heatmap) {
|
||||
this.heatmap.selectAll(".status-heatmap-crosshair").remove();
|
||||
this.heatmap.selectAll('.status-heatmap-crosshair').remove();
|
||||
|
||||
let posX = position;
|
||||
posX = Math.max(posX, this.yAxisWidth);
|
||||
posX = Math.min(posX, this.chartWidth + this.yAxisWidth);
|
||||
|
||||
this.heatmap.append("g")
|
||||
.attr("class", "status-heatmap-crosshair")
|
||||
.attr("transform", "translate(" + posX + ",0)")
|
||||
.append("line")
|
||||
.attr("x1", 1)
|
||||
.attr("y1", this.chartTop)
|
||||
.attr("x2", 1)
|
||||
.attr("y2", this.chartBottom)
|
||||
.attr("stroke-width", 1);
|
||||
this.heatmap
|
||||
.append('g')
|
||||
.attr('class', 'status-heatmap-crosshair')
|
||||
.attr('transform', 'translate(' + posX + ',0)')
|
||||
.append('line')
|
||||
.attr('x1', 1)
|
||||
.attr('y1', this.chartTop)
|
||||
.attr('x2', 1)
|
||||
.attr('y2', this.chartBottom)
|
||||
.attr('stroke-width', 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,7 +790,7 @@ export class StatusmapRenderer {
|
||||
|
||||
clearCrosshair() {
|
||||
if (this.heatmap) {
|
||||
this.heatmap.selectAll(".status-heatmap-crosshair").remove();
|
||||
this.heatmap.selectAll('.status-heatmap-crosshair').remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -778,7 +819,7 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
_renderAnnotations() {
|
||||
if (!this.ctrl.annotations || this.ctrl.annotations.length == 0) {
|
||||
if (!this.ctrl.annotations || this.ctrl.annotations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -786,52 +827,65 @@ export class StatusmapRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
let annoData = _.map(this.ctrl.annotations, (d,i) => ({"x": Math.floor(this.yAxisWidth + this.xScale(d.time)), "id":i, "anno": d.source}));
|
||||
let annoData = _.map(this.ctrl.annotations, (d, i) => ({
|
||||
// @ts-ignore
|
||||
x: Math.floor(this.yAxisWidth + this.xScale(d.time)),
|
||||
id: i,
|
||||
// @ts-ignore
|
||||
anno: d.source,
|
||||
}));
|
||||
|
||||
//({"ctrl_annotations": this.ctrl.annotations, "annoData": annoData});
|
||||
|
||||
let anno = this.heatmap
|
||||
.append("g")
|
||||
.attr("class", "statusmap-annotations")
|
||||
.attr("transform", "translate(0.5,0)")
|
||||
.selectAll(".statusmap-annotations")
|
||||
.data(annoData)
|
||||
.enter().append("g")
|
||||
;
|
||||
anno.append("line")
|
||||
//.attr("class", "statusmap-annotation-tick")
|
||||
.attr("x1", d => d.x)
|
||||
.attr("y1", this.chartTop)
|
||||
.attr("x2", d => d.x)
|
||||
.attr("y2", this.chartBottom)
|
||||
.style("stroke", d => d.anno.iconColor)
|
||||
.style("stroke-width", 1)
|
||||
.style("stroke-dasharray", "3,3")
|
||||
;
|
||||
anno.append("polygon")
|
||||
.attr("points", d => [[d.x, this.chartBottom+1], [d.x-5, this.chartBottom+6], [d.x+5, this.chartBottom+6]].join(" "))
|
||||
.style("stroke-width", 0)
|
||||
.style("fill", d => d.anno.iconColor)
|
||||
;
|
||||
// Polygons didn't fire mouseevents
|
||||
anno.append("rect")
|
||||
.attr("x", d => d.x-5)
|
||||
.attr("width", 10)
|
||||
.attr("y", this.chartBottom+1)
|
||||
.attr("height", 5)
|
||||
.attr("class", "statusmap-annotation-tick")
|
||||
.attr("annoId", d => d.id)
|
||||
.style("opacity", 0)
|
||||
;
|
||||
.append('g')
|
||||
.attr('class', 'statusmap-annotations')
|
||||
.attr('transform', 'translate(0.5,0)')
|
||||
.selectAll('.statusmap-annotations')
|
||||
.data(annoData)
|
||||
.enter()
|
||||
.append('g');
|
||||
|
||||
let $ticks = this.$heatmap.find(".statusmap-annotation-tick");
|
||||
anno
|
||||
.append('line')
|
||||
//.attr("class", "statusmap-annotation-tick")
|
||||
.attr('x1', d => d.x)
|
||||
.attr('y1', this.chartTop)
|
||||
.attr('x2', d => d.x)
|
||||
.attr('y2', this.chartBottom)
|
||||
.style('stroke', d => d.anno.iconColor)
|
||||
.style('stroke-width', 1)
|
||||
.style('stroke-dasharray', '3,3');
|
||||
|
||||
anno
|
||||
.append('polygon')
|
||||
.attr('points', d =>
|
||||
[
|
||||
[d.x, this.chartBottom + 1],
|
||||
[d.x - 5, this.chartBottom + 6],
|
||||
[d.x + 5, this.chartBottom + 6],
|
||||
].join(' ')
|
||||
)
|
||||
.style('stroke-width', 0)
|
||||
.style('fill', d => d.anno.iconColor);
|
||||
|
||||
// Polygons didn't fire mouseevents
|
||||
anno
|
||||
.append('rect')
|
||||
.attr('x', d => d.x - 5)
|
||||
.attr('width', 10)
|
||||
.attr('y', this.chartBottom + 1)
|
||||
.attr('height', 5)
|
||||
.attr('class', 'statusmap-annotation-tick')
|
||||
.attr('annoId', d => d.id)
|
||||
.style('opacity', 0);
|
||||
|
||||
let $ticks = this.$heatmap.find('.statusmap-annotation-tick');
|
||||
$ticks
|
||||
.on("mouseenter", (event) => {
|
||||
.on('mouseenter', event => {
|
||||
this.annotationTooltip.mouseOverAnnotationTick = true;
|
||||
})
|
||||
.on("mouseleave", (event) => {
|
||||
.on('mouseleave', event => {
|
||||
this.annotationTooltip.mouseOverAnnotationTick = false;
|
||||
});
|
||||
}
|
||||
@@ -840,24 +894,24 @@ export class StatusmapRenderer {
|
||||
function grafanaTimeFormat(ticks, min, max) {
|
||||
if (min && max && ticks) {
|
||||
let range = max - min;
|
||||
let secPerTick = (range/ticks) / 1000;
|
||||
let secPerTick = range / ticks / 1000;
|
||||
let oneDay = 86400000;
|
||||
let oneYear = 31536000000;
|
||||
|
||||
if (secPerTick <= 45) {
|
||||
return "%H:%M:%S";
|
||||
return '%H:%M:%S';
|
||||
}
|
||||
if (secPerTick <= 7200 || range <= oneDay) {
|
||||
return "%H:%M";
|
||||
return '%H:%M';
|
||||
}
|
||||
if (secPerTick <= 80000) {
|
||||
return "%m/%d %H:%M";
|
||||
return '%m/%d %H:%M';
|
||||
}
|
||||
if (secPerTick <= 2419200 || range <= oneYear) {
|
||||
return "%m/%d";
|
||||
return '%m/%d';
|
||||
}
|
||||
return "%Y-%m";
|
||||
return '%Y-%m';
|
||||
}
|
||||
|
||||
return "%H:%M";
|
||||
return '%H:%M';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user