Created a new tooltip when click in a value to have different external links. Which links can be created with values from the series.

This commit is contained in:
Joaquin Jimenez Garcia
2020-02-04 16:31:43 +01:00
parent 1e7ef33b67
commit 18574c3fa1
38 changed files with 1241 additions and 63 deletions
+3 -3
View File
@@ -77,7 +77,7 @@ coreModule.directive('statusHeatmapLegend', function() {
drawOpacityLegend(elem, colorOptions, rangeFrom, rangeTo, maxValue, minValue);
} else if (panel.color.mode === 'discrete') {
let colorOptions = panel.color;
drawDiscreteColorLegend(elem, colorOptions, ctrl.discreteHelper);
drawDiscreteColorLegend(elem, colorOptions, ctrl.discreteExtraSeries);
}
}
}
@@ -148,7 +148,7 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue
drawLegendValues(elem, opacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth);
}
function drawDiscreteColorLegend(elem, colorOptions, discreteHelper) {
function drawDiscreteColorLegend(elem, colorOptions, discreteExtraSeries) {
let legendElem = $(elem).find('svg');
let legend = d3.select(legendElem.get(0));
clearLegend(elem);
@@ -196,7 +196,7 @@ function drawDiscreteColorLegend(elem, colorOptions, discreteHelper) {
.attr("width", itemWidth + 1) // Overlap rectangles to prevent gaps
.attr("height", legendHeight)
.attr("stroke-width", 0)
.attr("fill", d => discreteHelper.getDiscreteColor(d));
.attr("fill", d => discreteExtraSeries.getDiscreteColor(d));
drawDiscreteLegendValues(elem, colorOptions, legendWidth);
}
+52 -1
View File
@@ -12,7 +12,7 @@ declare class DiscreteColorThreshold {
tooltip: string;
}
// Helper methods to handle discrete color mode
// Extra Series methods to handle discrete color mode
export class ColorModeDiscrete {
scope: any;
panelCtrl: StatusHeatmapCtrl;
@@ -42,6 +42,23 @@ export class ColorModeDiscrete {
return tooltips;
}
convertValueToTooltips(values) {
let thresholds = this.panel.color.thresholds;
let tooltips = [];
for (let i = 0; i < thresholds.length; i++) {
//for (let j = 0; j < values.length; j++) {
if (values == thresholds[i].value) {
tooltips.push({
"tooltip": thresholds[i].tooltip?thresholds[i].tooltip:values,
"color": thresholds[i].color
});
//}
}
}
return tooltips;
}
getNotMatchedValues(values:any[]) {
let notMatched:any[] = [];
for (let j = 0; j < values.length; j++) {
@@ -71,6 +88,22 @@ export class ColorModeDiscrete {
return color;
}
getBucketColorSingle(value) {
//let thresholds = this.panel.color.thresholds;
if (value == null) {
// treat as null value
return 'rgba(0,0,0,1)';
//return this.getMatchedThreshold(null).color;
}
let threshold = this.getMatchedThreshold(value);
if (!threshold || !threshold.color || threshold.color == "") {
return 'rgba(0,0,0,1)';
} else {
return threshold.color;
}
}
// returns color from first matched thresold in order from 0 to thresholds.length
getBucketColor(values) {
let thresholds = this.panel.color.thresholds;
@@ -109,6 +142,24 @@ export class ColorModeDiscrete {
return 'rgba(0,0,0,1)';
}
updateCardsValuesHasColorInfoSingle() {
if (!this.panelCtrl.cardsData) {
return;
}
this.panelCtrl.cardsData.noColorDefined = false;
var cards = this.panelCtrl.cardsData.cards;
for (var i = 0; i < cards.length; i++) {
cards[i].noColorDefined = false;
var values = cards[i].value;
var threshold = this.getMatchedThreshold(values);
if (!threshold || !threshold.color || threshold.color == "") {
cards[i].noColorDefined = true;
this.panelCtrl.cardsData.noColorDefined = true;
}
}
}
updateCardsValuesHasColorInfo() {
if (!this.panelCtrl.cardsData) {
return
+29 -2
View File
@@ -47,10 +47,25 @@
color: $text-color;
.discrete-item {
color: #52545c;
color: #ffffff;
padding: 1px;
font-weight: bold;
text-shadow: 0 0 0.2em #FFF, 0 0 0.2em #FFF, 0 0 0.2em #FFF;
text-shadow: 0 0 0.2em #212124, 0 0 0.2em #212124, 0 0 0.2em #212124;
}
}
.statusmap-tooltip-extraseries {
white-space: nowrap;
font-size: $font-size-sm;
background-color: $graph-tooltip-bg;
color: $text-color;
.discrete-item {
color: #ffffff;
padding: 1px;
font-weight: bold;
margin-bottom: 0;
text-shadow: 0 0 0.2em #212124, 0 0 0.2em #212124, 0 0 0.2em #212124;
}
}
@@ -71,6 +86,18 @@
stroke: rgba(102, 102, 102, 0.8);
}
.width-c-40 {
width: 40.0rem!important
}
.width-c-50 {
width: 50.0rem!important
}
.width-c-60 {
width: 60.0rem!important
}
.width-c-70 {
width: 70.0rem!important
}
.status-heatmap-legend-wrapper {
margin: 0 10px;
+9
View File
@@ -0,0 +1,9 @@
export enum ExtraSeriesFormat {
Date = 'Date',
Raw = 'Raw',
}
export enum ExtraSeriesFormatValue {
Date = 'YYYY/MM/DD/HH_mm_ss',
Raw = '',
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+94 -11
View File
@@ -19,6 +19,7 @@ import rendering from './rendering';
// import { labelFormats } from './xAxisLabelFormats';
import {statusHeatmapOptionsEditor} from './options_editor';
import {ColorModeDiscrete} from "./color_mode_discrete";
import { ExtraSeriesFormat, ExtraSeriesFormatValue } from './extra_series_format';
const CANVAS = 'CANVAS';
const SVG = 'SVG';
@@ -93,8 +94,9 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
graph: any;
multipleValues: boolean;
noColorDefined: boolean;
discreteHelper: ColorModeDiscrete;
discreteExtraSeries: ColorModeDiscrete;
dataWarnings: DataWarnings;
extraSeriesFormats: any = [];
annotations: object[] = [];
annotationsPromise: any;
@@ -145,11 +147,25 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
nullPointMode: 'as empty',
yAxisSort: 'metrics',
highlightCards: true,
useMax: true
useMax: true,
urls: [{
tooltip: '',
label: '',
base_url: '',
useExtraSeries: false,
useseriesname: true,
forcelowercase: true,
icon_fa: 'external-link',
extraSeries: {
index: -1
}
}],
seriesFilterIndex: -1,
usingUrl: false
};
/** @ngInject */
constructor($scope: any, $injector: auto.IInjectorService, private annotationsSrv: AnnotationsSrv) {
constructor($scope: any, $injector: auto.IInjectorService, timeSrv, private annotationsSrv: AnnotationsSrv, $window, datasourceSrv, variableSrv, templateSrv) {
super($scope, $injector);
_.defaultsDeep(this.panel, this.panelDefaults);
@@ -157,6 +173,20 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
this.opacityScales = opacityScales;
this.colorModes = colorModes;
this.colorSchemes = colorSchemes;
this.variableSrv = variableSrv;
this.extraSeriesFormats = ExtraSeriesFormat;
this.renderLink = (link, scopedVars, format) => {
var scoped = {}
for (var key in scopedVars) {
scoped[key] = { value: scopedVars[key] }
}
if (format) {
return this.templateSrv.replace(link, scoped, format)
} else {
return this.templateSrv.replace(link, scoped)
}
}
// default graph width for discrete card width calculation
this.graph = {
@@ -166,7 +196,7 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
this.multipleValues = false;
this.noColorDefined = false;
this.discreteHelper = new ColorModeDiscrete($scope);
this.discreteExtraSeries = new ColorModeDiscrete($scope);
this.dataWarnings = {
"noColorDefined": {
@@ -180,6 +210,9 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
};
this.annotations = [];
this.annotationsSrv = annotationsSrv;
this.timeSrv = timeSrv;
this.events.on('render', this.onRender.bind(this));
this.events.on('data-received', this.onDataReceived.bind(this));
@@ -189,6 +222,7 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
this.events.on('refresh', this.postRefresh.bind(this));
// custom event from rendering.js
this.events.on('render-complete', this.onRenderComplete.bind(this));
this.events.on('onChangeType', this.onChangeType.bind(this));
}
onRenderComplete(data):void {
@@ -196,6 +230,20 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
this.renderingCompleted();
}
onChangeType(url): void {
switch (url.type) {
case ExtraSeriesFormat.Date:
url.extraSeries.format = ExtraSeriesFormatValue.Date;
break;
case ExtraSeriesFormat.Raw:
url.extraSeries.format = ExtraSeriesFormatValue.Raw;
break;
default:
url.extraSeries.format = ExtraSeriesFormatValue.Raw;
break;
}
}
getChartWidth():number {
const wndWidth = $(window).width();
// gripPos.w is a width in grid's measurements. Grid size in Grafana is 24.
@@ -222,7 +270,7 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
let rangeMs = this.range.to.valueOf() - this.range.from.valueOf();
// this is minimal interval! kbn.round_interval will lower it.
intervalMs = this.discreteHelper.roundIntervalCeil(rangeMs / maxCardsCount);
intervalMs = this.discreteExtraSeries.roundIntervalCeil(rangeMs / maxCardsCount);
// Calculate low limit of interval
let lowLimitMs = 1; // 1 millisecond default low limit
@@ -292,8 +340,6 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
this.data = dataList;
this.cardsData = this.convertToCards(this.data);
console.log("OnDataReceived");
this.annotationsPromise.then(
(result: { alertState: any; annotations: any }) => {
this.loading = false;
@@ -303,13 +349,11 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
} else {
this.annotations = [];
}
console.log("annotationsPromise result " + this.annotations.length + " annotations");
this.render();
},
() => {
this.loading = false;
this.annotations = [];
console.log("annotationsPromise onrejected");
this.render();
}
);
@@ -334,7 +378,11 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
this.noColorDefined = false;
if (this.panel.color.mode === 'discrete') {
this.discreteHelper.updateCardsValuesHasColorInfo();
if (this.panel.seriesFilterIndex == -1) {
this.discreteExtraSeries.updateCardsValuesHasColorInfo();
} else {
this.discreteExtraSeries.updateCardsValuesHasColorInfoSingle();
}
if (this.cardsData) {
this.noColorDefined = this.cardsData.noColorDefined;
}
@@ -361,6 +409,26 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
this.render();
}
onEditorAddUrl = () => {
this.panel.urls.push({
label: '',
base_url: '',
useExtraSeries: false,
useseriesname: true,
forcelowercase: true,
icon_fa: 'external-link',
extraSeries: {
index: -1
}
});
this.render();
}
onEditorRemoveUrl = (index) => {
this.panel.urls.splice(index, 1);
this.render();
}
onEditorRemoveThreshold(index:number) {
this.panel.color.thresholds.splice(index, 1);
this.render();
@@ -371,6 +439,12 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
this.render();
}
onEditorRemoveUrls = () => {
this.panel.urls = [];
this.render();
}
onEditorAddThreeLights() {
this.panel.color.thresholds.push({color: "red", value: 2, tooltip: "error" });
this.panel.color.thresholds.push({color: "yellow", value: 1, tooltip: "warning" });
@@ -395,6 +469,12 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
rendering(scope, elem, attrs, ctrl);
}
retrieveTimeVar() {
var time = this.timeSrv.timeRangeForUrl();
var var_time = '&from=' + time.from + '&to=' + time.to;
return var_time;
}
// group values into buckets by target
convertToCards(data) {
let cardsData = <CardsStorage> {
@@ -433,6 +513,9 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
let card = new Card();
card.id = i*cardsData.xBucketSize + j;
card.values = [];
card.columns = [];
card.multipleValues = false;
card.noColorDefined = false;
card.y = target;
card.x = -1;
@@ -453,7 +536,7 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
if (card.values.length > 1) {
cardsData.multipleValues = true;
card.multipleValues = true;
card.value = card.maxValue; // max value by default
card.value = this.panel.seriesFilterIndex != -1 ? card.values[this.panel.seriesFilterIndex] : card.maxValue;
} else {
card.value = card.maxValue; // max value by default
}
+97 -1
View File
@@ -194,9 +194,105 @@
</div>
</div>
<div class="section gf-form-group">
<div class="section gf-form-group width-30">
<h5 class="section-heading">Filtering</h5>
<div class="gf-form">
<label class="gf-form-label width-9">Show only serie with Index</label>
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right"
bs-tooltip="'Filter with the serie index to show the value on graph. Use -1 to get all values'" ng-model="ctrl.panel.seriesFilterIndex" ng-change="ctrl.refresh()"
ng-model-onblur>
</div>
</div>
<div class="section gf-form-group">
<h5 class="section-heading">URL</h5>
<div class="gf-form">
Insert the url to navigate<br />
</div>
<div class="gf-form">
<gf-form-switch class="gf-form" label="Show extraSeries Tooltip when clicking elements" label-class="width-12"
checked="ctrl.panel.usingUrl" on-change="ctrl.render()"></gf-form-switch>
</div>
<div class="section gf-form-group" ng-if="ctrl.panel.usingUrl">
<br>
<div class="gf-form-inline">
<div class="gf-form"></div>
<div class="gf-form">
<button class="btn btn-inverse" ng-click="ctrl.onEditorAddUrl()">
<i class="fa fa-plus"></i> Add new URL
</button>
</div>
<div class="gf-form">
<button class="btn btn-inverse" ng-click="ctrl.onEditorRemoveUrls()">
<i class="fa fa-minus"></i> Remove all URLs
</button>
</div>
</div>
<div class="gf-form-block" ng-repeat="url in ctrl.panel.urls">
<div class="gf-form">
<label class="gf-form-label width-2">{{ $index }}</label>
<label class="gf-form-label width-2">
<a class="pointer" tabindex="1" ng-click="ctrl.onEditorRemoveUrl($index)">
<i class="fa fa-trash" />
</a>
</label>
<label class="gf-form-label width-4">Label: </label>
<input type="text" class="gf-form-input width-16" placeholder="My URL" data-placement="right"
ng-model="url.label" ng-change="ctrl.refresh()">
<label class="gf-form-label width-4">URL: </label>
<input type="text" class="gf-form-input width-c-50" placeholder="https://www.google.es" data-placement="right"
bs-tooltip="'This is the url to be shown when the user clicks on it'" ng-model="url.base_url"
ng-change="ctrl.refresh()">
<info-popover mode="right-normal">
<p>Specify an URL (relative or absolute)</p>
<span>
Use special variables to specify cell values:
<br>
<em>$series_label</em> name of the serie
<br>
<em>$(template.name)</em> name of the templated to be replaced
<br>
<em>$time</em> append &from...&to on the URL
<br>
<em>$series_extra</em> use extraSeries value to use it on URL
</span>
</info-popover>
</div>
<div class="gf-form">
<label class="gf-form-label width-4">Icon: </label>
<input type="text" class="gf-form-input width-12" placeholder="FA Icon" data-placement="right"
bs-tooltip="'The icon shown on URL'" ng-model="url.icon_fa" ng-change="ctrl.refresh()">
<gf-form-switch class="gf-form" label-class="width-8" label="Force lowercase" checked="url.forcelowercase"
on-change="ctrl.render()">
</gf-form-switch>
<gf-form-switch class="gf-form" label-class="width-6" label="Use Extra Serie Data" checked="url.useExtraSeries"
on-change="ctrl.render()">
</gf-form-switch>
<div class="gf-form" ng-if="url.useExtraSeries == true">
<label class="gf-form-label width-8">Extra Series Index: </label>
<input type="number" class="gf-form-input width-12" placeholder="0" ng-if="url.useExtraSeries == true"
data-placement="right" bs-tooltip="'Fields index to use its value on URL using $series_extra. Use -1 to disable it'" ng-model="url.extraSeries.index"
ng-change="ctrl.refresh()">
<label class="gf-form-label width-9">Type</label>
<div class="gf-form-select-wrapper width-8">
<select class="input-small gf-form-input" ng-model="url.type" ng-options="s for s in ctrl.extraSeriesFormats" ng-change="ctrl.onChangeType(url)"></select>
</div>
<div ng-if="url.type === 'Date'">
<label class="gf-form-label width-12">Extra Series Date Format: </label>
<input type="text" class="gf-form-input width-12" placeholder="YYYY/MM/DD/HH_mm_ss" ng-if="url.useExtraSeries == true"
data-placement="right" bs-tooltip="'Date format to transformat extraSeries'" ng-model="url.extraSeries.format"
ng-change="ctrl.refresh()">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
+25 -3
View File
@@ -7,6 +7,7 @@ import {tickStep, getScaledDecimals, getFlotTickSize} from 'app/core/utils/ticks
import * as d3 from 'd3';
import * as d3ScaleChromatic from './libs/d3-scale-chromatic/index';
import {StatusmapTooltip} from './tooltip';
import {StatusHeatmapTooltipExtraSeries} from './tooltipextraseries';
import {AnnotationTooltip} from './annotations';
let MIN_CARD_SIZE = 5,
@@ -50,6 +51,7 @@ export class StatusmapRenderer {
panel: any;
$heatmap: any;
tooltip: StatusmapTooltip;
tooltipExtraSeries:StatusHeatmapTooltipExtraSeries;
annotationTooltip: AnnotationTooltip;
heatmap: any;
timeRange: any;
@@ -64,6 +66,7 @@ export class StatusmapRenderer {
// $heatmap is JQuery object, but heatmap is D3
this.$heatmap = this.elem.find('.status-heatmap-panel');
this.tooltip = new StatusmapTooltip(this.$heatmap, this.scope);
this.tooltipExtraSeries = new StatusHeatmapTooltipExtraSeries(this.$heatmap, this.scope);
this.annotationTooltip = new AnnotationTooltip(this.$heatmap, this.scope);
this.yOffset = 0;
@@ -94,6 +97,7 @@ export class StatusmapRenderer {
this.$heatmap.on('mousedown', this.onMouseDown.bind(this));
this.$heatmap.on('mousemove', this.onMouseMove.bind(this));
this.$heatmap.on('mouseleave', this.onMouseLeave.bind(this));
this.$heatmap.on('click', this.onMouseClick.bind(this));
}
onGraphHoverClear() {
@@ -507,7 +511,11 @@ export class StatusmapRenderer {
} else if (this.panel.color.mode === 'spectrum') {
return this.colorScale(d.value);
} else if (this.panel.color.mode === 'discrete') {
return this.ctrl.discreteHelper.getBucketColor(d.values);
if (this.panel.seriesFilterIndex != -1 || this.panel.seriesFilterIndex != null) {
return this.ctrl.discreteExtraSeries.getBucketColorSingle(d.values[this.panel.seriesFilterIndex]);
} else {
return this.ctrl.discreteExtraSeries.getBucketColor(d.values);
}
}
}
@@ -572,10 +580,17 @@ export class StatusmapRenderer {
this.clearSelection();
}
onMouseLeave() {
onMouseLeave(e) {
appEvents.emit('graph-hover-clear');
this.clearCrosshair();
//annotationTooltip.destroy();
if (e.relatedTarget) {
if (e.relatedTarget.className == "statusmap-tooltip-extraseries graph-tooltip grafana-tooltip" || e.relatedTarget.className == "graph-tooltip-time" ) {
} else {
this.tooltipExtraSeries.destroy();
}
}
this.annotationTooltip.destroy();
}
onMouseMove(event) {
@@ -599,6 +614,13 @@ export class StatusmapRenderer {
}
}
public onMouseClick(event) {
this.tooltipExtraSeries.show(event)
if (this.ctrl.panel.usingUrl) {
this.tooltip.destroy();
}
}
getEventPos(event, offset) {
const x = this.xScale.invert(offset.x - this.yAxisWidth).valueOf();
const y = this.yScale.invert(offset.y - this.chartTop);
@@ -739,7 +761,7 @@ export class StatusmapRenderer {
let annoData = _.map(this.ctrl.annotations, (d,i) => ({"x": Math.floor(this.yAxisWidth + this.xScale(d.time)), "id":i, "anno": d.source}));
//console.log({"ctrl_annotations": this.ctrl.annotations, "annoData": annoData});
//({"ctrl_annotations": this.ctrl.annotations, "annoData": annoData});
let anno = this.heatmap
.append("g")
+1
View File
@@ -6,6 +6,7 @@ class Card {
id: number = 0;
// Array of values in this bucket
values: any[] = [];
columns: any[] = [];
// card has multiple values
multipleValues: boolean = false;
// card has values that has no color
+9 -2
View File
@@ -89,8 +89,15 @@ export class StatusmapTooltip {
let tooltipHtml = `<div class="graph-tooltip-time">${time}</div>
<div class="statusmap-histogram"></div>`;
let statuses;
if (this.panel.color.mode === 'discrete') {
let statuses = this.panelCtrl.discreteHelper.convertValuesToTooltips(values);
if (this.panel.seriesFilterIndex > 0) {
statuses = this.panelCtrl.discreteExtraSeries.convertValueToTooltips(value);
} else {
statuses = this.panelCtrl.discreteExtraSeries.convertValuesToTooltips(values);
}
let statusesHtml = '';
if (statuses.length === 1) {
statusesHtml = "status:";
@@ -130,7 +137,7 @@ export class StatusmapTooltip {
// Discrete mode errors
if (this.panel.color.mode === 'discrete') {
if (card.noColorDefined) {
let badValues = this.panelCtrl.discreteHelper.getNotColoredValues(values);
let badValues = this.panelCtrl.discreteExtraSeries.getNotColoredValues(values);
tooltipHtml += `<div><b>Error:</b> ${this.panelCtrl.dataWarnings.noColorDefined.title}
<br>not colored values:
<ul>
+222
View File
@@ -0,0 +1,222 @@
import d3 from 'd3';
import _ from 'lodash';
import $ from 'jquery';
import { ExtraSeriesFormatValue } from './extra_series_format';
const TOOLTIP_PADDING_X = -50;
const TOOLTIP_PADDING_Y = 5;
export class StatusHeatmapTooltipExtraSeries {
scope: any;
dashboard: any;
panelCtrl: any;
panel: any;
heatmapPanel: any;
mouseOverBucket: any;
originalFillColor: any;
tooltip: any;
constructor(elem: any, scope: any) {
this.scope = scope;
this.dashboard = scope.ctrl.dashboard;
this.panelCtrl = scope.ctrl;
this.panel = scope.ctrl.panel;
this.heatmapPanel = elem;
this.mouseOverBucket = false;
this.originalFillColor = null;
elem.on("mouseover", this.onMouseOver.bind(this));
elem.on("click", this.onMouseClick.bind(this));
}
public onMouseOver(e: Event) {
if (!this.panel.usingUrl || !this.scope.ctrl.data || _.isEmpty(this.scope.ctrl.data)) {
return;
}
}
public onMouseClick(e: Event) {
if (!this.panel.usingUrl) {
return;
}
this.destroy();
this.add();
}
public add() {
this.tooltip = d3.select("body")
.append("div")
.attr("class", "statusmap-tooltip-extraseries graph-tooltip grafana-tooltip");
}
public destroy() {
if (this.tooltip) {
this.tooltip.remove();
}
this.tooltip = null;
}
public show(pos: any) {
if (!this.panel.usingUrl || !this.tooltip) {
return;
}
// shared tooltip mode
if (pos.panelRelY) {
return;
}
let cardId: any = d3.select(pos.target).attr('cardId');
if (!cardId) {
this.destroy();
return;
}
let card: any = this.panelCtrl.cardsData.cards[cardId];
if (!card) {
this.destroy();
return;
}
if (card.value == null) {
this.destroy();
return;
}
let x: any = card.x;
let y: any = card.y;
let value: any = card.value;
let values: any = card.values;
let tooltipTimeFormat: string = 'YYYY-MM-DD HH:mm:ss';
let time: Date = this.dashboard.formatDate(+x, tooltipTimeFormat);
let tooltipHtml: string = `<div class="graph-tooltip-time">${time}</div>`
if (this.panel.color.mode === 'discrete') {
let statuses: any = "";
if (this.panelCtrl.panel.seriesFilterIndex == -1) {
statuses = this.panelCtrl.discreteExtraSeries.convertValuesToTooltips(values);
} else {
statuses = this.panelCtrl.discreteExtraSeries.convertValueToTooltips(value);
}
tooltipHtml += `
<div>
name: <b>${y}</b>
<div>
<br>
<span>status:</span>
<ul>
${_.join(_.map(statuses, v => `<p style="background-color: ${v.color}; text-align:center" class="discrete-item">${v.tooltip}</p>`), "")}
</ul>
</div>
</div> <br>`;
}
tooltipHtml += `<div class="statusmap-histogram"></div>`;
let urls: any = this.panelCtrl.panel.urls
let rtime: any = this.panelCtrl.retrieveTimeVar();
let curl: any = JSON.parse(JSON.stringify(urls));
for (let i = 0; i < curl.length; i++) {
//Change name var
curl[i].base_url = _.replace(curl[i].base_url, /\$series_label/g, y)
//Set up extra series
if (curl[i].useExtraSeries == true) {
let tf: any = curl[i].extraSeries.format
let vh: any = card.values[curl[i].extraSeries.index]
//let extraSeries: any = this.dashboard.formatDate(+vh, tf)
let series_extra: any = this.formatExtraSeries(vh,tf);
curl[i].base_url = _.replace(curl[i].base_url, /\$series_extra/g, series_extra)
}
//Change time var
curl[i].base_url = _.replace(curl[i].base_url, /\$time/g, rtime)
//Replace vars
curl[i].base_url = this.panelCtrl.renderLink(curl[i].base_url, this.panelCtrl.variableSrv.variables)
}
if (this.panelCtrl.panel.usingUrl == true) {
tooltipHtml += `
<div bs-tooltip='Settings' data-placement="right">
${_.join(_.map(curl, v => `<div ><a href="${v.forcelowercase ? v.base_url.toLowerCase() : v.base_url}" target="_blank"><div class="dashlist-item">
<p class="dashlist-link dashlist-link-dash-db"><span style="word-wrap: break-word;" class="dash-title">${v.label ? v.label : (v.base_url != "" ? _.truncate(v.base_url) : "Empty URL" )}</span><span class="dashlist-star">
<i class="fa fa-${v.icon_fa}"></i>
</span></p> </div></a><div>`), "")}
</div> <br>`;
}
// "Ambiguous bucket state: Multiple values!";
if (!this.panel.useMax && card.multipleValues) {
tooltipHtml += `<div><b>Error:</b> ${this.panelCtrl.dataWarnings.multipleValues.title}</div>`;
}
// Discrete mode errors
if (this.panel.color.mode === 'discrete') {
if (card.noColorDefined) {
let badValues: any = this.panelCtrl.discreteExtraSeries.getNotColoredValues(values);
tooltipHtml += `<div><b>Error:</b> ${this.panelCtrl.dataWarnings.noColorDefined.title}
<br>not colored values:
<ul>
${_.join(_.map(badValues, v => `<li>${v}</li>`), "")}
</ul>
</div>`;
}
}
this.tooltip.html(tooltipHtml);
this.move(pos);
}
public formatExtraSeries (value: any, type: any) {
let extraSeries: any = '';
switch(type) {
case ExtraSeriesFormatValue.Date:
extraSeries = this.dashboard.formatDate(+value, type);
return extraSeries;
case ExtraSeriesFormatValue.Raw:
extraSeries = value;
return extraSeries;
default:
return extraSeries;
}
}
public move(pos: any) {
if (!this.tooltip) {
return;
}
let elem: any = $(this.tooltip.node())[0];
let tooltipWidth = elem.clientWidth;
let tooltipHeight = elem.clientHeight;
let left = pos.pageX + TOOLTIP_PADDING_X;
let top = pos.pageY + TOOLTIP_PADDING_Y;
if (pos.pageX + tooltipWidth + 40 > window.innerWidth) {
left = pos.pageX - tooltipWidth - TOOLTIP_PADDING_X;
}
if (pos.pageY - window.pageYOffset + tooltipHeight + 20 > window.innerHeight) {
top = pos.pageY - tooltipHeight - TOOLTIP_PADDING_Y;
}
return this.tooltip
.style("left", left + "px")
.style("top", top + "px");
}
}