mirror of
https://github.com/timberjoegithub/grafana-statusmap.git
synced 2026-07-22 15:53:09 +00:00
feat: pagination
Continuation of PR #93. - reworked layout for pagination controls on graph - use BucketMatrixPager for pagination logic and state
This commit is contained in:
@@ -102,6 +102,20 @@
|
||||
width: 70.0rem!important
|
||||
}
|
||||
|
||||
.statusmap-pagination {
|
||||
// space between legend and pagination controls
|
||||
margin-top: 5px;
|
||||
label {
|
||||
margin-right:0px;
|
||||
}
|
||||
input {
|
||||
margin-right: 0px;
|
||||
}
|
||||
.last-in-group {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-buttons{text-align: right;}
|
||||
|
||||
.status-heatmap-legend-wrapper {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export enum ExtraSeriesFormat {
|
||||
Date = 'Date',
|
||||
Raw = 'Raw',
|
||||
}
|
||||
|
||||
export enum ExtraSeriesFormatValue {
|
||||
Date = 'YYYY/MM/DD/HH_mm_ss',
|
||||
Raw = '',
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 10 KiB |
+24
-24
@@ -1,37 +1,37 @@
|
||||
<div class="status-heatmap-wrapper">
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
<div class="gf-form" ng-if="ctrl.panel.usingPagination">
|
||||
<label class="gf-form-label width-9">Items per page</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right"
|
||||
bs-tooltip="'Number of items to show per page.'" ng-model="ctrl.pageSizeViewer" ng-change="ctrl.changePaginationSize()"
|
||||
ng-model-onblur>
|
||||
</div>
|
||||
|
||||
<div class="status-heatmap-canvas-wrapper">
|
||||
|
||||
<div class="datapoints-warning" ng-if="ctrl.multipleValues || ctrl.noColorDefined || ctrl.noDatapoints">
|
||||
<span class="small" ng-if="ctrl.multipleValues" bs-tooltip="'{{ctrl.dataWarnings.multipleValues.tip}}'">{{ctrl.dataWarnings.multipleValues.title}}</span>
|
||||
<span class="small" ng-if="ctrl.noColorDefined" bs-tooltip="'{{ctrl.dataWarnings.noColorDefined.tip}}'">{{ctrl.dataWarnings.noColorDefined.title}}</span>
|
||||
<span class="small" ng-if="ctrl.noDatapoints" bs-tooltip="'{{ctrl.dataWarnings.noDatapoints.tip}}'">{{ctrl.dataWarnings.noDatapoints.title}}</span>
|
||||
</div>
|
||||
<div class="statusmap-panel" ng-dblclick="ctrl.zoomOut()"></div>
|
||||
</div>
|
||||
|
||||
<div class="status-heatmap-panel" ng-dblclick="ctrl.zoomOut()"></div>
|
||||
</div>
|
||||
<div class="pagination" ng-if="ctrl.panel.usingPagination">
|
||||
Showing {{ctrl.firstPageElement}} to {{ctrl.lastPageElement}} of {{ctrl.totalElements}} entries
|
||||
<div class="pagination-buttons">
|
||||
<button class="btn btn-inverse" ng-disabled="ctrl.currentPage == 0" ng-click="ctrl.currentPage=ctrl.currentPage-1; ctrl.render()">
|
||||
Previous
|
||||
</button>
|
||||
{{ctrl.currentPage+1}}/{{ctrl.numberOfPages}}
|
||||
<button class="btn btn-inverse" ng-disabled="ctrl.currentPage >= ctrl.cardsData.targets.length/ctrl.pageSizeViewer - 1 || ctrl.numberOfPages == 0" ng-click="ctrl.currentPage=ctrl.currentPage+1; ctrl.render()">
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-heatmap-legend-wrapper" ng-if="ctrl.panel.legend.show">
|
||||
<status-heatmap-legend></status-heatmap-legend>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="statusmap-pagination gf-form" ng-if="ctrl.panel.usingPagination">
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-disabled="!pager.hasPrev()" ng-click="ctrl.onPrevPage()">
|
||||
<i class="fa fa-arrow-left"></i> Previous
|
||||
</button>
|
||||
<label class="gf-form-label">{{pager.currentPage+1}}/{{pager.pages()}}</label>
|
||||
<button class="btn btn-inverse last-in-group" ng-disabled="!pager.hasNext()" ng-click="ctrl.onNextPage()">
|
||||
Next <i class="fa fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">Rows per page</label>
|
||||
<input type="number" class="gf-form-input width-3 last-in-group" placeholder="12" data-placement="top"
|
||||
bs-tooltip="'Number of rows to show per page.'" ng-model="ctrl.pageSizeViewer" ng-change="ctrl.onChangePageSize()"
|
||||
ng-model-onblur>
|
||||
</div>
|
||||
<label class="gf-form-label">Show {{pager.pageStartRow()}} to {{pager.pageEndRow()}} of {{pager.totalRows()}} rows</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
+35
-72
@@ -16,7 +16,7 @@ import {loadPluginCss} from 'app/plugins/sdk';
|
||||
import { MetricsPanelCtrl } from 'app/plugins/sdk';
|
||||
import { AnnotationsSrv } from 'app/features/annotations/annotations_srv';
|
||||
import { CoreEvents, PanelEvents } from './libs/grafana/events/index';
|
||||
import { Bucket, BucketMatrix } from './statusmap_data';
|
||||
import {Bucket, BucketMatrix, BucketMatrixPager } from './statusmap_data';
|
||||
import rendering from './rendering';
|
||||
import { Polygrafill } from './libs/polygrafill/index';
|
||||
|
||||
@@ -70,6 +70,7 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
|
||||
data: any;
|
||||
bucketMatrix: BucketMatrix;
|
||||
bucketMatrixPager: BucketMatrixPager;
|
||||
|
||||
graph: any;
|
||||
discreteHelper: ColorModeDiscrete;
|
||||
@@ -78,10 +79,6 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
colorSchemes: any = [];
|
||||
unitFormats: any;
|
||||
|
||||
cardsDataComplete: any;
|
||||
cardsDataLabelsComplete: any;
|
||||
ticksWhenPaginating: [];
|
||||
|
||||
dataWarnings: {[warningId: string]: {title: string, tip: string}} = {};
|
||||
multipleValues: boolean;
|
||||
noColorDefined: boolean;
|
||||
@@ -92,12 +89,8 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
annotations: object[] = [];
|
||||
annotationsPromise: any;
|
||||
|
||||
// TODO remove this transient variable: use ng-model-options="{ getterSetter: true }"
|
||||
pageSizeViewer: number = 15;
|
||||
currentPage: number = 0;
|
||||
numberOfPages: number = 1;
|
||||
totalElements: number = 0;
|
||||
firstPageElement: number = 1;
|
||||
lastPageElement: number = 5;
|
||||
|
||||
panelDefaults: any = {
|
||||
// datasource name, null = default datasource
|
||||
@@ -144,10 +137,10 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
useMax: true,
|
||||
|
||||
seriesFilterIndex: -1,
|
||||
|
||||
// Pagination options
|
||||
usingPagination: false,
|
||||
pageSize: 15,
|
||||
allowAllElements: false,
|
||||
availableValues: []
|
||||
pageSize: 15
|
||||
};
|
||||
|
||||
/** @ngInject */
|
||||
@@ -163,10 +156,14 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
migratePanelConfig(this.panel);
|
||||
_.defaultsDeep(this.panel, this.panelDefaults);
|
||||
|
||||
if (this.panel.usingPagination) {
|
||||
this.setPaginationSize(this.panel.pageSize);
|
||||
this.setCurrentPage(0);
|
||||
}
|
||||
this.bucketMatrix = new BucketMatrix();
|
||||
|
||||
// Create pager for bucketMatrix and restore page size.
|
||||
this.bucketMatrixPager = new BucketMatrixPager();
|
||||
this.bucketMatrixPager.setEnable(this.panel.usingPagination);
|
||||
this.bucketMatrixPager.setDefaultPageSize(this.panel.pageSize);
|
||||
this.bucketMatrixPager.setPageSize(this.panel.pageSize);
|
||||
$scope.pager = this.bucketMatrixPager;
|
||||
|
||||
this.opacityScales = opacityScales;
|
||||
this.colorModes = colorModes;
|
||||
@@ -220,66 +217,35 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
changeDefaultPaginationSize(defaultPageSize: number): void {
|
||||
this.bucketMatrixPager.setDefaultPageSize(defaultPageSize);
|
||||
this.bucketMatrixPager.setPageSize(defaultPageSize);
|
||||
this.pageSizeViewer = defaultPageSize;
|
||||
|
||||
this.render();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
changePaginationSize(): void {
|
||||
onChangePageSize(): void {
|
||||
if (this.pageSizeViewer <= 0) {
|
||||
this.pageSizeViewer = 1;
|
||||
this.pageSizeViewer = this.bucketMatrixPager.defaultPageSize;
|
||||
}
|
||||
|
||||
this.currentPage = 0;
|
||||
this.bucketMatrixPager.setPageSize(this.pageSizeViewer);
|
||||
this.bucketMatrixPager.setCurrent(0);
|
||||
|
||||
this.render();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
getPaginationSize(): number {
|
||||
return this.pageSizeViewer;
|
||||
onPrevPage(): void {
|
||||
this.bucketMatrixPager.switchToPrev();
|
||||
this.render();
|
||||
}
|
||||
|
||||
setPaginationSize(pageSize: number): void {
|
||||
this.pageSizeViewer = pageSize;
|
||||
onNextPage(): void {
|
||||
this.bucketMatrixPager.switchToNext();
|
||||
this.render();
|
||||
}
|
||||
|
||||
getCurrentPage(): number {
|
||||
return this.currentPage;
|
||||
}
|
||||
|
||||
setCurrentPage(currentPage: number): void {
|
||||
this.currentPage = currentPage;
|
||||
}
|
||||
|
||||
getNumberOfPages(): number {
|
||||
return this.numberOfPages;
|
||||
}
|
||||
|
||||
getTotalElements(): number {
|
||||
return this.totalElements;
|
||||
}
|
||||
|
||||
setTotalElements(totalElements: number): void {
|
||||
this.totalElements = totalElements;
|
||||
}
|
||||
|
||||
getFirstPageElement(): number {
|
||||
return this.firstPageElement;
|
||||
}
|
||||
|
||||
setFirstPageElement(firstPageElement: number): void {
|
||||
this.firstPageElement = firstPageElement;
|
||||
}
|
||||
|
||||
getLastPageElement(): number {
|
||||
return this.lastPageElement;
|
||||
}
|
||||
|
||||
setLastPageElement(lastPageElement: number): void {
|
||||
this.lastPageElement = lastPageElement;
|
||||
}
|
||||
|
||||
// getChartWidth returns an approximation of chart canvas width or
|
||||
// a saved value calculated during a render.
|
||||
@@ -402,16 +368,17 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
if (!this.intervalMs) {
|
||||
this.calculateInterval();
|
||||
}
|
||||
this.bucketMatrix = this.convertDataToBuckets(dataList, this.range.from.valueOf(), this.range.to.valueOf(), this.intervalMs, true);
|
||||
this.noDatapoints = this.bucketMatrix.noDatapoints;
|
||||
|
||||
if (this.panel.usingPagination && this.cardsData.targets) {
|
||||
this.numberOfPages = Math.ceil(this.cardsData.targets.length/this.pageSizeViewer);
|
||||
this.totalElements = this.cardsData.targets.length;
|
||||
} else {
|
||||
this.setPaginationSize(this.cardsData.targets.length);
|
||||
let newBucketMatrix = this.convertDataToBuckets(dataList, this.range.from.valueOf(), this.range.to.valueOf(), this.intervalMs, true);
|
||||
|
||||
this.bucketMatrix = newBucketMatrix;
|
||||
this.bucketMatrixPager.bucketMatrix = newBucketMatrix;
|
||||
if (newBucketMatrix.targets.length !== this.bucketMatrix.targets.length) {
|
||||
this.bucketMatrixPager.setCurrent(0);
|
||||
}
|
||||
|
||||
this.noDatapoints = this.bucketMatrix.noDatapoints;
|
||||
|
||||
if (this.annotationsPromise) {
|
||||
this.annotationsPromise.then(
|
||||
(result: { alertState: any; annotations: any }) => {
|
||||
@@ -427,7 +394,7 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
() => {
|
||||
this.loading = false;
|
||||
this.annotations = [];
|
||||
this.render(); // this.render(this.data);???
|
||||
this.render();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
@@ -443,10 +410,6 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
this.unitFormats = kbn.getUnitFormats();
|
||||
}
|
||||
|
||||
paginate() {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
// onRender will be called before StatusmapRenderer.onRender.
|
||||
// Decide if warning should be displayed over cards.
|
||||
onRender() {
|
||||
|
||||
@@ -216,119 +216,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">Pagination options</h5>
|
||||
<h5 class="section-heading">Pagination</h5>
|
||||
<div class="gf-form">
|
||||
<gf-form-switch class="gf-form" label="Paginate elements" label-class="width-12"
|
||||
checked="ctrl.panel.usingPagination" on-change="ctrl.paginate()"></gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label="Enable pagination" label-class="width-12"
|
||||
checked="ctrl.panel.usingPagination" on-change="ctrl.render()"></gf-form-switch>
|
||||
</div>
|
||||
<div class="gf-form" ng-if="ctrl.panel.usingPagination">
|
||||
<label class="gf-form-label width-9">Items per Page by default</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right"
|
||||
bs-tooltip="Number of items to show by default" ng-model="ctrl.panel.pageSize" ng-change="ctrl.changeDefaultPaginationSize(ctrl.panel.pageSize)"
|
||||
<label class="gf-form-label width-10">Rows per page</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="top"
|
||||
bs-tooltip="'Number of rows to show by default'" ng-model="ctrl.panel.pageSize" ng-change="ctrl.changeDefaultPaginationSize(ctrl.panel.pageSize)"
|
||||
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>
|
||||
|
||||
+15
-104
@@ -7,7 +7,7 @@ import * as d3 from 'd3';
|
||||
import * as d3ScaleChromatic from './libs/d3-scale-chromatic/index';
|
||||
import {StatusmapTooltip} from './tooltip';
|
||||
import {AnnotationTooltip} from './annotations';
|
||||
import { Bucket, BucketMatrix } from './statusmap_data';
|
||||
import { Bucket, BucketMatrix, BucketMatrixPager } from './statusmap_data';
|
||||
import { StatusHeatmapCtrl, renderComplete } from './module';
|
||||
import { CoreEvents, PanelEvents } from './libs/grafana/events/index';
|
||||
|
||||
@@ -61,6 +61,7 @@ export class StatusmapRenderer {
|
||||
yGridSize: number = 0;
|
||||
|
||||
bucketMatrix: BucketMatrix;
|
||||
bucketMatrixPager: BucketMatrixPager;
|
||||
panel: any;
|
||||
$heatmap: any;
|
||||
tooltip: StatusmapTooltip;
|
||||
@@ -134,9 +135,12 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
if (this.panel.usingPagination) {
|
||||
height -= this.panel.legend.show ? 140 : 10; // bottom padding and space for legend. Change margin in .status-heatmap-color-legend !
|
||||
// TODO get height of pagination controls.
|
||||
// reserve height for legend and for a row of pagination controls.
|
||||
height -= this.panel.legend.show ? 70 : 40; // bottom padding and space for legend. Change margin in .status-heatmap-color-legend !
|
||||
} else {
|
||||
height -= this.panel.legend.show ? 50 : 10; // bottom padding and space for legend. Change margin in .status-heatmap-color-legend !
|
||||
// reserve height for legend
|
||||
height -= this.panel.legend.show ? 32 : 4; // bottom padding and space for legend. Change margin in .status-heatmap-color-legend !
|
||||
}
|
||||
|
||||
this.$heatmap.css('height', height + 'px');
|
||||
@@ -214,9 +218,7 @@ export class StatusmapRenderer {
|
||||
// divide chart height by ticks for cards drawing
|
||||
getYScale(ticks: any[]) {
|
||||
let range:any[] = [];
|
||||
//let step = this.chartHeight / ticks.length;
|
||||
console.log('GETYSCALE - RENDERING', this.ctrl.getPaginationSize());
|
||||
let step = this.chartHeight / this.ctrl.getPaginationSize();
|
||||
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++) {
|
||||
@@ -230,9 +232,7 @@ export class StatusmapRenderer {
|
||||
// divide chart height by ticks with offset for ticks drawing
|
||||
getYAxisScale(ticks: any[]) {
|
||||
let range:any[] = [];
|
||||
//let step = this.chartHeight / ticks.length;
|
||||
console.log('GETYAXISSCALE - RENDERING', this.ctrl.getPaginationSize());
|
||||
let step = this.chartHeight / this.ctrl.getPaginationSize();
|
||||
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++) {
|
||||
@@ -244,26 +244,9 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
addYAxis() {
|
||||
let ticks = this.bucketMatrixPager.targets();
|
||||
|
||||
let ticks;
|
||||
|
||||
if(this.ctrl.ticksWhenPaginating !== undefined && this.ctrl.panel.usingPagination) {
|
||||
ticks = _.uniq(_.map(this.ctrl.ticksWhenPaginating));
|
||||
} else {
|
||||
ticks = this.bucketMatrix.targets;
|
||||
//ticks = _.uniq(_.map(this.data, d => d.target));
|
||||
}
|
||||
|
||||
// // Set default Y min and max if no data
|
||||
// if (_.isEmpty(this.data)) {
|
||||
// ticks = [''];
|
||||
// }
|
||||
//
|
||||
// // TODO
|
||||
// // New version!
|
||||
// let ticks = this.bucketMatrix.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') {
|
||||
@@ -352,17 +335,9 @@ export class StatusmapRenderer {
|
||||
|
||||
// calculate yOffset for YAxis
|
||||
this.yGridSize = this.chartHeight;
|
||||
if (this.ctrl.panel.usingPagination) {
|
||||
// Pagination mode overrides count of rows.
|
||||
//this.yGridSize = Math.floor(this.chartHeight / this.ctrl.ticksWhenPaginating.length);
|
||||
console.log('addHeatmapCanvas - rendering',this.ctrl.getPaginationSize());
|
||||
this.yGridSize = Math.floor(this.chartHeight / this.ctrl.getPaginationSize());
|
||||
} else {
|
||||
if (this.bucketMatrix.targets.length > 0) {
|
||||
this.yGridSize = Math.floor(this.chartHeight / this.bucketMatrix.targets.length);
|
||||
}
|
||||
if (this.bucketMatrixPager.targets().length > 0) {
|
||||
this.yGridSize = Math.floor(this.chartHeight / this.bucketMatrixPager.targets().length);
|
||||
}
|
||||
|
||||
this.cardHeight = this.yGridSize ? this.yGridSize - this.cardVSpacing : 0;
|
||||
this.yOffset = this.cardHeight / 2;
|
||||
|
||||
@@ -398,7 +373,7 @@ export class StatusmapRenderer {
|
||||
this.setOpacityScale(maxValue);
|
||||
|
||||
// Draw cards from buckets.
|
||||
this.heatmap.selectAll(".statusmap-cards-row").data(this.bucketMatrix.targets)
|
||||
this.heatmap.selectAll(".statusmap-cards-row").data(this.bucketMatrixPager.targets())
|
||||
.enter()
|
||||
.selectAll(".statustmap-card")
|
||||
.data((target:string) => this.bucketMatrix.buckets[target])
|
||||
@@ -778,75 +753,14 @@ export class StatusmapRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
this.panel = this.ctrl.panel;
|
||||
this.timeRange = this.ctrl.range;
|
||||
this.bucketMatrix = this.ctrl.bucketMatrix;
|
||||
this.bucketMatrixPager = this.ctrl.bucketMatrixPager;
|
||||
|
||||
if (!this.bucketMatrix || !this.setElementHeight()) {
|
||||
return;
|
||||
} else {
|
||||
// TODO pagination!
|
||||
this.ctrl.cardsDataComplete = this.ctrl.bucketMatrix.buckets;
|
||||
|
||||
if(this.ctrl.panel.usingPagination) {
|
||||
if (!this.bucketMatrix.targets) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ctrl.setFirstPageElement((this.ctrl.getCurrentPage()*this.ctrl.getPaginationSize())+1);
|
||||
|
||||
let elems = this.ctrl.getTotalElements();
|
||||
|
||||
console.log('total elems in add heatmap canvas', elems);
|
||||
if (elems < this.ctrl.getFirstPageElement()) {
|
||||
this.ctrl.setCurrentPage(0);
|
||||
this.render();
|
||||
}
|
||||
|
||||
if (this.ctrl.getPaginationSize() >= this.ctrl.getTotalElements()) {
|
||||
this.ctrl.setLastPageElement(this.ctrl.getTotalElements());
|
||||
} else {
|
||||
if ((this.ctrl.getCurrentPage()+1) === this.ctrl.getNumberOfPages() && (this.ctrl.getCurrentPage()>0)) {
|
||||
this.ctrl.setLastPageElement(this.ctrl.getTotalElements());
|
||||
} else {
|
||||
this.ctrl.setLastPageElement((this.ctrl.getCurrentPage() * this.ctrl.getPaginationSize())+this.ctrl.getPaginationSize());
|
||||
}
|
||||
}
|
||||
|
||||
let cardsList = this.ctrl.bucketMatrix.targets.slice(this.ctrl.getPaginationSize()*this.ctrl.getCurrentPage(),
|
||||
(this.ctrl.getPaginationSize()*this.ctrl.getCurrentPage())+this.ctrl.getPaginationSize());
|
||||
|
||||
let cardsToShow = [];
|
||||
let labelsToShow = [];
|
||||
|
||||
// Rewrite for bucket matrix.
|
||||
for (let i = 0; i < this.cardsData.cards.length; i++) {
|
||||
const card = this.cardsData.cards[i];
|
||||
|
||||
for (let j = 0; j < cardsList.length; j++) {
|
||||
const value = cardsList[j];
|
||||
|
||||
if (card.y === value) {
|
||||
cardsToShow.push(card);
|
||||
labelsToShow.push(value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const labelsToShowClean = [...new Set(labelsToShow)];
|
||||
|
||||
this.cardsData.cards = cardsToShow.slice();
|
||||
this.ctrl.ticksWhenPaginating = labelsToShowClean;
|
||||
|
||||
cardsToShow = undefined;
|
||||
} else {
|
||||
const pageSize = this.ctrl.bucketMatrix.targets.length
|
||||
this.ctrl.getPaginationSize(pageSize);
|
||||
this.ctrl.ticksWhenPaginating = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw default axes and return if no data
|
||||
@@ -861,9 +775,6 @@ export class StatusmapRenderer {
|
||||
this.scope.chartHeight = this.chartHeight;
|
||||
this.scope.chartWidth = this.chartWidth;
|
||||
this.scope.chartTop = this.chartTop;
|
||||
|
||||
// TODO pagination. Why this needed?
|
||||
//this.ctrl.cardsData.cards = this.ctrl.cardsDataComplete.slice();
|
||||
}
|
||||
|
||||
_renderAnnotations() {
|
||||
|
||||
+120
-1
@@ -62,6 +62,7 @@ class BucketMatrix {
|
||||
// a flag that indicate that buckets has stub values
|
||||
noDatapoints: boolean = false;
|
||||
|
||||
// An array of row labels
|
||||
targets: string[] = [];
|
||||
rangeMs: number = 0;
|
||||
intervalMs: number = 0;
|
||||
@@ -93,4 +94,122 @@ class BucketMatrix {
|
||||
}
|
||||
}
|
||||
|
||||
export {Bucket, BucketMatrix };
|
||||
export var pagerChanged:any = {name:'statusmap-pager-changed'};
|
||||
|
||||
class BucketMatrixPager {
|
||||
bucketMatrix: BucketMatrix;
|
||||
enable: boolean;
|
||||
defaultPageSize: number = -1;
|
||||
pageSize: number = -1;
|
||||
currentPage: number = 0;
|
||||
|
||||
constructor() {
|
||||
let m = new BucketMatrix();
|
||||
|
||||
this.bucketMatrix = m;
|
||||
}
|
||||
|
||||
// An array of row labels for current page.
|
||||
targets(): string[] {
|
||||
if (!this.enable) {
|
||||
return this.bucketMatrix.targets;
|
||||
}
|
||||
|
||||
return this.bucketMatrix.targets.slice(this.pageSize * this.currentPage, this.pageSize * (this.currentPage+1) );
|
||||
}
|
||||
|
||||
buckets(): {[yLabel: string]: Bucket[]} {
|
||||
if (!this.enable) {
|
||||
return this.bucketMatrix.buckets;
|
||||
}
|
||||
|
||||
let buckets: {[yLabel: string]: Bucket[]} = {}
|
||||
let me = this;
|
||||
|
||||
this.targets().map(function (rowLabel) {
|
||||
buckets[rowLabel] = me.bucketMatrix.buckets[rowLabel];
|
||||
})
|
||||
|
||||
return buckets;
|
||||
}
|
||||
|
||||
setEnable(enable: boolean) {
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
setCurrent(num: number): void {
|
||||
this.currentPage = num;
|
||||
}
|
||||
|
||||
setDefaultPageSize(num: number): void {
|
||||
this.defaultPageSize = num;
|
||||
}
|
||||
|
||||
setPageSize(num: number): void {
|
||||
this.pageSize = num;
|
||||
}
|
||||
|
||||
pages(): number {
|
||||
return Math.ceil(this.totalRows() / this.pageSize);
|
||||
}
|
||||
|
||||
totalRows(): number {
|
||||
return this.bucketMatrix.targets.length;
|
||||
}
|
||||
|
||||
pageStartRow(): number {
|
||||
if (!this.enable) {
|
||||
return 1;
|
||||
}
|
||||
return (this.pageSize * this.currentPage) + 1;
|
||||
}
|
||||
|
||||
pageEndRow(): number {
|
||||
if (!this.enable) {
|
||||
return this.totalRows();
|
||||
}
|
||||
|
||||
let last = this.pageSize * (this.currentPage+1);
|
||||
if (last > this.totalRows()) {
|
||||
return this.totalRows();
|
||||
}
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
hasNext(): boolean {
|
||||
if (!this.enable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this.currentPage+1)*this.pageSize >= this.totalRows()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// currentPage >= ctrl.bucketMatrix.targets.length/ctrl.pageSizeViewer - 1 || ctrl.numberOfPages == 0
|
||||
}
|
||||
|
||||
hasPrev(): boolean {
|
||||
if (!this.enable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.currentPage > 0;
|
||||
}
|
||||
|
||||
switchToNext() {
|
||||
if (this.hasNext()) {
|
||||
this.currentPage = this.currentPage + 1;
|
||||
}
|
||||
}
|
||||
|
||||
switchToPrev() {
|
||||
if (this.hasPrev()) {
|
||||
this.currentPage = this.currentPage - 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export {Bucket, BucketMatrix, BucketMatrixPager };
|
||||
@@ -1,228 +0,0 @@
|
||||
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;
|
||||
|
||||
if (this.panel.usingPagination) {
|
||||
card = this.panelCtrl.cardsDataComplete[cardId];
|
||||
} else {
|
||||
card = 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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user