UNPKG

@mozaic-ds/chart

Version:

This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.

388 lines (349 loc) 12.5 kB
import type { TooltipChartType } from '../types/TooltipChartType'; import { getPatternIndexWithShift } from './FormatUtilities'; import PatternsFunctions from '../services/PatternFunctions'; const { getPatternCanvas } = PatternsFunctions(); type BodyItem = { after: string[]; before: string[]; lines: string[]; }; export type TooltipElements = { chartType: TooltipChartType; firstLineLabel?: string; secondLineLabel?: string; patternShifting?: number; }; type LabelColors = { backgroundColor: unknown; borderColor: unknown; }; type PointStyle = { pointStyle: unknown; rotation: number; }; // ../..ts-ignore export type Context = { chart: { canvas: HTMLElement; tooltip?: { x: number; y: number; }; }; replay?: unknown; tooltip: { dataPoints: { dataset?: { type?: string; }; dataIndex?: number; datasetIndex?: number; raw: any; }[]; opacity: number; body: { after: string[]; before: string[]; lines: string[]; }[]; title: string[]; labelColors: Array<LabelColors>; labelPointStyles: Array<PointStyle>; caretX: number; caretY: number; }; }; export class GenericTooltipService { chartType = ''; datasetIndex = 0; dataIndex = 0; titleLines = ['']; dataToDisplay = ''; xValue = ''; yValue = ''; patternShifting = 0; createTooltip( context: Context, retrieveData: (context: Context) => string, tooltipInputElements: TooltipElements, patternsColors: string[], patternsList: (( hover: boolean, color: string, disableAccessibility: boolean ) => CanvasPattern)[], disableAccessibility: boolean = false ) { if (!context.tooltip.dataPoints) { return; } this.datasetIndex = context.tooltip?.dataPoints[0].datasetIndex || 0; this.dataIndex = context.tooltip.dataPoints[0].dataIndex || 0; this.xValue = tooltipInputElements.firstLineLabel || ''; this.yValue = tooltipInputElements.secondLineLabel || ''; this.chartType = tooltipInputElements.chartType; this.dataToDisplay = retrieveData(context); this.patternShifting = tooltipInputElements.patternShifting || 0; let tooltipEl = document.querySelector( '#chartjs-tooltip' ) as HTMLElement | null; if (!tooltipEl) { tooltipEl = this.createNewTooltipElement(); } const tooltipModel = context.tooltip; if (tooltipModel.opacity === 0) { tooltipEl.style.opacity = '0'; return; } if (tooltipModel.body) { this.titleLines = tooltipModel.title || []; const bodyLines = tooltipModel.body.map(this.getBody); let style = 'background: white;'; style += 'border-bottom: 1px solid #CCCCCC;'; style += 'border-radius: 5px;'; style += 'padding: 10px 20px'; const innerHtml = `<div style="${style}" class="tooltipTitle">`; const body = this.chartType === 'DOUGHNUT' ? [tooltipModel.title[0].split('(')[0].trim()] : bodyLines[0]; let legendIconStyle = ''; let legendInnerStyle = ''; const datasetType = context.tooltip?.dataPoints[0]?.dataset?.type; if (this.chartType === 'RADAR' || this.chartType === 'LINE_CHART') { legendIconStyle = this.createLegendStyle(context); legendInnerStyle = this.createLegendInnerStyle(context); } else if ( this.chartType === 'BAR_CHART' || this.chartType === 'DETAILS_BAR_CHART' || this.chartType === 'DOUGHNUT' ) { legendIconStyle = this.createPatternLegendStyle(context); } else if (this.chartType === 'MIXED_BAR_LINE_CHART') { if (datasetType === 'bar') { legendIconStyle = this.createPatternLegendStyle(context); } else { legendIconStyle = this.createLegendStyle(context); legendInnerStyle = this.createLegendInnerStyle(context); } } this.addLegendToDom( innerHtml, legendIconStyle, legendInnerStyle, body, style, tooltipEl, patternsColors, patternsList, disableAccessibility, datasetType ); } this.handleTooltipPosition(context, tooltipModel, tooltipEl); } protected handleTooltipPosition( context: Context, tooltipModel: { caretX: number; caretY: number }, tooltipEl: HTMLElement ) { const position = context.chart.canvas.getBoundingClientRect(); const screenWidth = window.innerWidth; const left = position.left + window.scrollX + tooltipModel.caretX; const top = position.top + window.scrollY + tooltipModel.caretY; tooltipEl.style.left = left + 'px'; tooltipEl.style.top = top + 'px'; tooltipEl.style.height = 'auto'; tooltipEl.style.minWidth = '17rem'; tooltipEl.style.opacity = '1'; tooltipEl.style.position = 'absolute'; tooltipEl.style.zIndex = '1999999999'; // Make sure the tooltip is on top of mozaic overlay tooltipEl.style.backgroundColor = 'white'; tooltipEl.style.pointerEvents = 'none'; if (tooltipEl.getBoundingClientRect().width + left > screenWidth) { tooltipEl.style.left = left - tooltipEl.getBoundingClientRect().width + 'px'; } } protected createNewTooltipElement() { const tooltipEl = document.createElement('div'); tooltipEl.id = 'chartjs-tooltip'; tooltipEl.style.backgroundColor = 'white'; tooltipEl.style.borderRadius = '5px'; tooltipEl.style.transition = 'opacity .5s'; tooltipEl.style.boxShadow = '0px 1px 5px rgba(0, 0, 0, 0.2)'; tooltipEl.innerHTML = '<div class="tooltipCtn"></div>'; document.body.appendChild(tooltipEl); return tooltipEl; } createPatternLegendStyle(context: Context) { return this.createCommonLegendSquareStyle(context); } createLegendStyle(context: Context) { let legendIconStyle = `background-color:${context.tooltip.labelColors[0].backgroundColor}`; legendIconStyle += this.createCommonLegendSquareStyle(context); return legendIconStyle; } private createCommonLegendSquareStyle(context: Context) { let style = `;border: 2px solid ${context.tooltip.labelColors[0].borderColor}`; style += ';min-height: 20px'; style += ';min-width: 20px'; style += ';border-radius: 5px'; style += ';margin-right: 10px'; style += ';display: flex'; style += ';align-items: center'; style += ';justify-content: center'; style += ';background: rgba(0, 0, 0, 0.1);'; return style; } createLegendInnerStyle(context: Context) { let legendIconInnerStyle = 'height: 12px'; legendIconInnerStyle += ';width: 12px'; legendIconInnerStyle += ';background-color: #FFF'; legendIconInnerStyle += `;border: 2px solid ${context.tooltip.labelColors[0].borderColor};`; if (context.tooltip.labelPointStyles[0].pointStyle === 'circle') { legendIconInnerStyle += 'border-radius: 25px;'; } else if (context.tooltip.labelPointStyles[0].pointStyle === 'rectRot') { legendIconInnerStyle += 'transform: rotate(45deg);'; } return legendIconInnerStyle; } addLegendToDom( innerHTMLtext: string, legendIconStyle: string, legendIconInnerStyle: string, body: Array<string>, style: string, tooltipEl: HTMLElement, patternsColors: string[], patternsList: (( hover: boolean, color: string, disableAccessibility: boolean ) => CanvasPattern)[], disableAccessibility: boolean = false, datasetType?: string ) { let innerHtml = innerHTMLtext; let legendImage = `<div class="legendIcon" style="${legendIconStyle}">`; const legendSubImage = `<div style="${legendIconInnerStyle}"></div>`; legendImage += `${legendSubImage}</div>`; const innerHtmlToAdd = this.setInnerHtmlToAdd(body, style, legendImage); innerHtml += innerHtmlToAdd; const tableRoot = tooltipEl?.querySelector( '.tooltipCtn' ) as HTMLElement | null; if (tableRoot?.innerHTML != null) { datasetType ? this.setInnerHtmlAndPattern( tableRoot, innerHtml, patternsColors, patternsList, disableAccessibility, datasetType ) : this.setInnerHtmlAndPattern( tableRoot, innerHtml, patternsColors, patternsList, disableAccessibility ); } } setInnerHtmlToAdd(body: Array<string>, style: string, legendImage: string) { const legendText = body[0].split(':')[0]; const fontProperties = 'font-family: Arial; font-size: 16px'; const spanText = `<span style="${fontProperties}">${legendText}</span>`; if (this.chartType === 'RADAR') { return this.returnRadarHtml(style, legendImage, spanText); } else if (this.chartType === 'DOUGHNUT') { return this.returnDoughnutHtml(legendImage, spanText); } else { return this.returnDetailsBarchartHtml(style, legendImage, spanText); } } returnDoughnutHtml(legendImage: string, spanText: string) { const fontProperties = 'font-family: Arial; font-size: 16px'; const legendText = `<span style="${fontProperties}">${spanText.split('(')[0]}</span>`; let doughnutHtml = `<div style="${fontProperties}; display: flex; align-items: center; justify-content: space-between">`; doughnutHtml += `<div style="display:flex; align-items: center;" >${legendImage + legendText}</div>`; doughnutHtml += `<div style="${fontProperties}; margin-left:3rem;">${this.dataToDisplay}</div>`; doughnutHtml += '</div></div>'; return doughnutHtml; } returnRadarHtml(style: string, legendImage: string, spanText: string) { const fontProperties = 'font-family: Arial; font-size: 16px'; let radarHtml = `<div style="${fontProperties}; display: flex; align-items: center;">${legendImage + spanText}</div>`; radarHtml += '</div>'; radarHtml += `<div style="${fontProperties}; ${style}; border: none; display:flex; justify-content: space-between;">`; radarHtml += `<div>${this.titleLines[0]}</div>`; radarHtml += `<div style="margin-left: 20px;">${this.dataToDisplay}</div>`; radarHtml += '</div>'; radarHtml += '</div><div>'; return radarHtml; } returnDetailsBarchartHtml( style: string, legendImage: string, spanText: string ) { const fontProperties = 'font-family: Arial; font-size: 16px'; let barChartHtml = `<div style="${fontProperties}; display: flex; align-items: center;">${legendImage + spanText}</div>`; barChartHtml += '</div>'; barChartHtml += `<div style="${fontProperties}; ${style}; display:flex; justify-content: space-between;">`; barChartHtml += `<div>${this.xValue}</div>`; barChartHtml += `<div style="margin-left: 20px;">${this.titleLines[0]}</div>`; barChartHtml += '</div>'; barChartHtml += `<div style="${fontProperties}; ${style}; border-: none; display:flex; justify-content: space-between;">`; barChartHtml += `<div>${this.yValue}</div>`; barChartHtml += `<div style="margin-left: 20px;">${this.dataToDisplay}</div>`; barChartHtml += '</div>'; return barChartHtml; } setInnerHtmlAndPattern( tableRoot: HTMLElement, innerHtml: string, patternsColors: string[], patternsList: (( hover: boolean, color: string, disableAccessibility: boolean ) => CanvasPattern)[], disableAccessibility: boolean = false, datasetType?: string ) { tableRoot.innerHTML = innerHtml; const legendIconHtml = document.querySelector('.legendIcon') as HTMLElement; const img: HTMLImageElement = new Image(); let index: number; if (this.chartType === 'DOUGHNUT') { index = this.dataIndex + 1; } else { index = this.datasetIndex + 1; } const patternIndex = getPatternIndexWithShift(index, this.patternShifting); if ( this.chartType !== 'LINE_CHART' && this.chartType !== 'RADAR' && datasetType !== 'line' ) { const pattern: CanvasPattern = patternsList[patternIndex - 1]( false, patternsColors[patternIndex - 1], disableAccessibility ); const patternCanvas: HTMLCanvasElement = getPatternCanvas( pattern, 22, 22 ); img.src = patternCanvas.toDataURL(); legendIconHtml.style.backgroundImage = `url(${img.src})`; } } getBody(bodyItem: BodyItem) { return bodyItem.lines; } }