UNPKG

veritatisquam

Version:
333 lines (284 loc) 7.99 kB
import { Component } from '../component'; import { Tools } from '../../tools'; import { DOMUtils } from '../../services'; import { ChartModel } from '../../model'; import { Events, TruncationTypes } from '../../interfaces'; import * as Configuration from '../../configuration'; // Carbon position service import Position, { PLACEMENTS } from '@carbon/utils-position'; // import the settings for the css prefix import settings from 'carbon-components/es/globals/js/settings'; // D3 Imports import { select, mouse } from 'd3-selection'; export class Tooltip extends Component { type = 'tooltip'; // flag for checking whether tooltip event listener is added or not isEventListenerAdded = false; tooltip: any; positionService = new Position(); constructor(model: ChartModel, services: any, configs?: any) { super(model, services, configs); this.init(); } handleShowTooltip = (e) => { const data = e.detail.data || e.detail.items; const defaultHTML = this.getTooltipHTML(e); const tooltipTextContainer = DOMUtils.appendOrSelect( this.tooltip, 'div.content-box' ); // if there is a provided tooltip HTML function call it if (Tools.getProperty(this.getOptions(), 'tooltip', 'customHTML')) { if (e.detail.content) { const labelHTML = `<div class="title-tooltip"><p>${e.detail.content}</p></div>`; tooltipTextContainer.html(labelHTML); } else { tooltipTextContainer.html( this.model .getOptions() .tooltip.customHTML(data, defaultHTML) ); } } else { // Use default tooltip tooltipTextContainer.html(defaultHTML); } // Position the tooltip this.positionTooltip(e); // Fade in this.tooltip.classed('hidden', false).attr('aria-hidden', false); }; handleHideTooltip = () => { this.tooltip.classed('hidden', true).attr('aria-hidden', true); }; addTooltipEventListener() { // listen to move-tooltip Custom Events to move the tooltip this.services.events.addEventListener( Events.Tooltip.MOVE, (e: CustomEvent) => { this.positionTooltip(e); } ); // listen to show-tooltip Custom Events to render the tooltip this.services.events.addEventListener( Events.Tooltip.SHOW, this.handleShowTooltip ); // listen to hide-tooltip Custom Events to hide the tooltip this.services.events.addEventListener( Events.Tooltip.HIDE, this.handleHideTooltip ); // listen to chart-mouseout event to hide the tooltip this.services.events.addEventListener( Events.Chart.MOUSEOUT, this.handleHideTooltip ); } removeTooltipEventListener() { // remove move-tooltip Custom Events this.services.events.removeEventListener(Events.Tooltip.MOVE, null); // remove show-tooltip Custom Events this.services.events.removeEventListener( Events.Tooltip.SHOW, this.handleShowTooltip ); // remove hide-tooltip Custom Events this.services.events.removeEventListener( Events.Tooltip.HIDE, this.handleHideTooltip ); // remove the listener on chart-mouseout this.services.events.removeEventListener( Events.Chart.MOUSEOUT, this.handleHideTooltip ); } getItems(e: CustomEvent) { if (e.detail.items) { return e.detail.items; } return []; } formatItems(items) { const options = this.getOptions(); // get user provided custom values for truncation const truncationType = Tools.getProperty( options, 'tooltip', 'truncation', 'type' ); const truncationThreshold = Tools.getProperty( options, 'tooltip', 'truncation', 'threshold' ); const truncationNumCharacter = Tools.getProperty( options, 'tooltip', 'truncation', 'numCharacter' ); // truncate the label if it's too long // only applies to discrete type if (truncationType !== TruncationTypes.NONE) { return items.map((item) => { item.value = item.value ? this.valueFormatter(item.value) : item.value; if (item.label && item.label.length > truncationThreshold) { item.label = Tools.truncateLabel( item.label, truncationType, truncationNumCharacter ); } if (item.value && item.value.length > truncationThreshold) { item.value = Tools.truncateLabel( item.value, truncationType, truncationNumCharacter ); } return item; }); } return items; } getTooltipHTML(e: CustomEvent) { let defaultHTML; if (e.detail.content) { defaultHTML = `<div class="title-tooltip"><p>${e.detail.content}</p></div>`; } else { const items = this.getItems(e); const formattedItems = this.formatItems(items); defaultHTML = `<ul class='multi-tooltip'>` + formattedItems .map( (item) => `<li> <div class="datapoint-tooltip ${item.bold ? 'bold' : ''}"> ${item.class ? `<a class="tooltip-color ${item.class}"></a>` : ''} ${ item.color ? '<a style="background-color: ' + item.color + '" class="tooltip-color"></a>' : '' } <p class="label">${item.label || ''}</p> <p class="value">${item.value || ''}</p> </div> </li>` ) .join('') + `</ul>`; } return defaultHTML; } valueFormatter(value: any) { const options = this.getOptions(); const valueFormatter = Tools.getProperty( options, 'tooltip', 'valueFormatter' ); if (valueFormatter) { return valueFormatter(value); } return value.toLocaleString(); } render() { const options = this.getOptions(); const isTooltipEnabled = Tools.getProperty( options, 'tooltip', 'enabled' ); if (isTooltipEnabled) { // Grab the tooltip element const holder = select(this.services.domUtils.getHolder()); const chartprefix = Tools.getProperty(options, 'style', 'prefix'); this.tooltip = DOMUtils.appendOrSelect( holder, `div.${settings.prefix}--${chartprefix}--tooltip` ); this.tooltip.style('max-width', null); if (!this.isEventListenerAdded) { this.addTooltipEventListener(); this.isEventListenerAdded = true; } this.tooltip.classed('hidden', true); } else if (!isTooltipEnabled && this.isEventListenerAdded) { // remove tooltip eventListener this.removeTooltipEventListener(); this.isEventListenerAdded = false; } } positionTooltip(e: CustomEvent) { const holder = this.services.domUtils.getHolder(); const target = this.tooltip.node(); const options = this.getOptions(); const isTopZoomBarEnabled = Tools.getProperty( options, 'zoomBar', 'top', 'enabled' ); let mouseRelativePos = Tools.getProperty(e, 'detail', 'mousePosition'); if (!mouseRelativePos) { mouseRelativePos = mouse(holder); } else { const zoombarType = Tools.getProperty( options, 'zoomBar', 'top', 'type' ); const zoombarHeight = Configuration.zoomBar.height[zoombarType]; // if the mouse position is from event (ruler) // we need add zoom bar height if (isTopZoomBarEnabled) { mouseRelativePos[1] += zoombarHeight + Configuration.zoomBar.spacerHeight; // TODO - we need to add toolbar height when toolbar is available } } let pos; // Find out whether tooltip should be shown on the left or right side const bestPlacementOption = this.positionService.findBestPlacementAt( { left: mouseRelativePos[0], top: mouseRelativePos[1], }, target, [ PLACEMENTS.RIGHT, PLACEMENTS.LEFT, PLACEMENTS.TOP, PLACEMENTS.BOTTOM, ], () => ({ width: holder.offsetWidth, height: holder.offsetHeight, }) ); let { horizontalOffset } = Configuration.tooltips; if (bestPlacementOption === PLACEMENTS.LEFT) { horizontalOffset *= -1; } // Get coordinates to where tooltip should be positioned pos = this.positionService.findPositionAt( { left: mouseRelativePos[0] + horizontalOffset, top: mouseRelativePos[1], }, target, bestPlacementOption ); this.positionService.setElement(target, pos); } }