From 986c91f0832191c25130762a4c62c58eb12f0cfc Mon Sep 17 00:00:00 2001 From: Tiago Melo Date: Wed, 28 Feb 2018 17:53:55 +0000 Subject: [PATCH] mgr/dashboard_v2: improve tooltip of cdSparkline Added a new scss and class for the tooltip. Signed-off-by: Tiago Melo --- .../sparkline/sparkline.component.html | 5 +- .../sparkline/sparkline.component.scss | 5 +- .../sparkline/sparkline.component.ts | 38 +++++- .../src/app/shared/models/chart-tooltip.ts | 117 ++++++++++++++++++ .../frontend/src/styles/chart-tooltip.scss | 62 ++++++++++ 5 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/shared/models/chart-tooltip.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/styles/chart-tooltip.scss diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.html b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.html index 91f0d73e0f240..4b7a1b8722393 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.html +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.html @@ -1,10 +1,13 @@
- +
+
+
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.scss b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.scss index ba8a8ebb7d88a..ec7d98291e8ce 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.scss +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.scss @@ -1,4 +1,5 @@ +@import '../../../../styles/chart-tooltip.scss'; + .chart-container { - position: relative; - margin: auto; + position: static !important; } diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.ts index 35b3d91c8d135..fa20ce301864a 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.ts +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/components/sparkline/sparkline.component.ts @@ -1,12 +1,17 @@ -import { Component, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { Component, ElementRef, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core'; import { Input } from '@angular/core'; +import { ChartTooltip } from '../../../shared/models/chart-tooltip'; + @Component({ selector: 'cd-sparkline', templateUrl: './sparkline.component.html', styleUrls: ['./sparkline.component.scss'] }) export class SparklineComponent implements OnInit, OnChanges { + @ViewChild('sparkCanvas') chartCanvasRef: ElementRef; + @ViewChild('sparkTooltip') chartTooltipRef: ElementRef; + @Input() data: any; @Input() style = { @@ -40,7 +45,10 @@ export class SparklineComponent implements OnInit, OnChanges { } }, tooltips: { - enabled: true + enabled: false, + mode: 'index', + intersect: false, + custom: undefined }, scales: { yAxes: [ @@ -66,7 +74,31 @@ export class SparklineComponent implements OnInit, OnChanges { constructor() {} - ngOnInit() {} + ngOnInit() { + const getStyleTop = (tooltip, positionY) => { + return (tooltip.caretY - tooltip.height - tooltip.yPadding - 5) + 'px'; + }; + + const getStyleLeft = (tooltip, positionX) => { + return positionX + tooltip.caretX + 'px'; + }; + + const chartTooltip = new ChartTooltip( + this.chartCanvasRef, + this.chartTooltipRef, + getStyleLeft, + getStyleTop + ); + + chartTooltip.customColors = { + backgroundColor: this.colors[0].pointBackgroundColor, + borderColor: this.colors[0].pointBorderColor + }; + + this.options.tooltips.custom = tooltip => { + chartTooltip.customTooltips(tooltip); + }; + } ngOnChanges(changes: SimpleChanges) { this.datasets[0].data = changes['data'].currentValue; diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/models/chart-tooltip.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/models/chart-tooltip.ts new file mode 100644 index 0000000000000..56962f3da69ff --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/models/chart-tooltip.ts @@ -0,0 +1,117 @@ +import { ElementRef } from '@angular/core'; + +import * as _ from 'lodash'; + +export class ChartTooltip { + tooltipEl: any; + chartEl: any; + getStyleLeft: Function; + getStyleTop: Function; + customColors = { + backgroundColor: undefined, + borderColor: undefined + }; + checkOffset = false; + + /** + * Creates an instance of ChartTooltip. + * @param {ElementRef} chartCanvas Canvas Element + * @param {ElementRef} chartTooltip Tooltip Element + * @param {Function} getStyleLeft Function that calculates the value of Left + * @param {Function} getStyleTop Function that calculates the value of Top + * @memberof ChartTooltip + */ + constructor( + chartCanvas: ElementRef, + chartTooltip: ElementRef, + getStyleLeft: Function, + getStyleTop: Function + ) { + this.chartEl = chartCanvas.nativeElement; + this.getStyleLeft = getStyleLeft; + this.getStyleTop = getStyleTop; + this.tooltipEl = chartTooltip.nativeElement; + } + + /** + * Implementation of a ChartJS custom tooltip function. + * + * @param {any} tooltip + * @memberof ChartTooltip + */ + customTooltips(tooltip) { + // Hide if no tooltip + if (tooltip.opacity === 0) { + this.tooltipEl.style.opacity = 0; + return; + } + + // Set caret Position + this.tooltipEl.classList.remove('above', 'below', 'no-transform'); + if (tooltip.yAlign) { + this.tooltipEl.classList.add(tooltip.yAlign); + } else { + this.tooltipEl.classList.add('no-transform'); + } + + // Set Text + if (tooltip.body) { + const titleLines = tooltip.title || []; + const bodyLines = tooltip.body.map(bodyItem => { + return bodyItem.lines; + }); + + let innerHtml = ''; + + titleLines.forEach(title => { + innerHtml += '' + this.getTitle(title) + ''; + }); + innerHtml += ''; + + bodyLines.forEach((body, i) => { + const colors = tooltip.labelColors[i]; + let style = 'background:' + (this.customColors.backgroundColor || colors.backgroundColor); + style += '; border-color:' + (this.customColors.borderColor || colors.borderColor); + style += '; border-width: 2px'; + const span = ''; + innerHtml += '' + span + this.getBody(body) + ''; + }); + innerHtml += ''; + + const tableRoot = this.tooltipEl.querySelector('table'); + tableRoot.innerHTML = innerHtml; + } + + const positionY = this.chartEl.offsetTop; + const positionX = this.chartEl.offsetLeft; + + // Display, position, and set styles for font + if (this.checkOffset) { + const halfWidth = tooltip.width / 2; + this.tooltipEl.classList.remove('transform-left'); + this.tooltipEl.classList.remove('transform-right'); + if (tooltip.caretX - halfWidth < 0) { + this.tooltipEl.classList.add('transform-left'); + } else if (tooltip.caretX + halfWidth > this.chartEl.width) { + this.tooltipEl.classList.add('transform-right'); + } + } + + this.tooltipEl.style.left = this.getStyleLeft(tooltip, positionX); + this.tooltipEl.style.top = this.getStyleTop(tooltip, positionY); + + this.tooltipEl.style.opacity = 1; + this.tooltipEl.style.fontFamily = tooltip._fontFamily; + this.tooltipEl.style.fontSize = tooltip.fontSize; + this.tooltipEl.style.fontStyle = tooltip._fontStyle; + this.tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px'; + } + + getBody(body) { + return body; + } + + getTitle(title) { + return title; + } +} diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/styles/chart-tooltip.scss b/src/pybind/mgr/dashboard_v2/frontend/src/styles/chart-tooltip.scss new file mode 100644 index 0000000000000..835bb362db431 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/styles/chart-tooltip.scss @@ -0,0 +1,62 @@ +.chart-container { + position: absolute; + margin: auto; + cursor: pointer; + overflow: visible; +} + +canvas { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.chartjs-tooltip { + opacity: 0; + position: absolute; + background: rgba(0, 0, 0, 0.7); + color: white; + border-radius: 3px; + -webkit-transition: all 0.1s ease; + transition: all 0.1s ease; + pointer-events: none; + font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif !important; + + -webkit-transform: translate(-50%, 0); + transform: translate(-50%, 0); + + &.transform-left { + transform: translate(-10%, 0); + + &::after { + left: 10%; + } + } + + &.transform-right { + transform: translate(-90%, 0); + + &::after { + left: 90%; + } + } +} + +.chartjs-tooltip::after { + content: ' '; + position: absolute; + top: 100%; /* At the bottom of the tooltip */ + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: black transparent transparent transparent; +} + +::ng-deep .chartjs-tooltip-key { + display: inline-block; + width: 10px; + height: 10px; + margin-right: 10px; +} -- 2.39.5