import { MarkerElem, MarkersConf, MarkerSerie } from '../models/marker'; import { LineTimeSerie, LineOptions } from '../types'; import { Margin, PodState } from '@chartwerk/core'; import d3 from 'd3'; export class Markers { private _layerContainer = null; private _chartHeight = 0; constructor( private _chartContainer: d3.Selection, private _markerConf: MarkersConf, private _state: PodState, private _margin: Margin, ) { } clear() { if(this._layerContainer !== null) { this._layerContainer.remove(); } this._chartContainer.selectAll('.marker-content').remove(); } render(metricContainer: d3.Selection, chartHeight: number) { this._chartHeight = chartHeight; this._layerContainer = metricContainer .append('g') .attr('class', 'markers-layer'); for(const serie of this._markerConf.series) { this.renderSerie(serie); } } private _getLinePosition(marker: MarkerElem): number { return this._state.xScale(marker.x); } private _renderCircle(marker: MarkerElem) { const linePosition = this._getLinePosition(marker); let circle = this._layerContainer.append('circle') .attr('class', 'gap-circle') .attr('stroke', marker.color) .attr('stroke-width', '2px') .attr('r', 4) .attr('cx', linePosition) .attr('cy', 5) circle .attr('pointer-events', 'all') .style('cursor', 'pointer') .on('mousemove', () => { const onMouseMove = this._markerConf.events?.onMouseMove; if(onMouseMove) { onMouseMove(marker); return } if(marker.alwaysDisplay) { return; } this._chartContainer .selectAll(`.marker-content-${marker.x}`) .style('visibility', 'visible') .style('z-index', 9999); }) .on('mouseout', () => { const onMouseOut = this._markerConf.events?.onMouseOut; if(onMouseOut) { onMouseOut() return } if(marker.alwaysDisplay) { return; } this._chartContainer .selectAll(`.marker-content-${marker.x}`) .style('visibility', 'hidden') .style('z-index', 1); }); } private _renderLine(marker: MarkerElem) { const linePosition = this._getLinePosition(marker); this._layerContainer.append('line') .attr('class', 'gap-line') .attr('stroke', marker.color) .attr('stroke-width', '1px') .attr('stroke-opacity', '0.3') .attr('stroke-dasharray', '4') .attr('x1', linePosition) .attr('x2', linePosition) .attr('y1', 0) // @ts-ignore // TODO: remove ignore but boxParams are protected .attr('y2', this._state.boxParams.height) .attr('pointer-events', 'none'); } private _renderTooltip(marker: MarkerElem) { if(marker.html === undefined) { return; } const linePosition = this._getLinePosition(marker); this._chartContainer .append('div') .attr('class', `marker-content marker-content-${marker.x}`) // @ts-ignore // TODO: remove ignore but boxParams are protected .style('top', `${this._state.boxParams.height - this._chartHeight}px`) .style('left', `${linePosition + this._margin.left + 10}px`) .style('visibility', marker.alwaysDisplay ? 'visible' : 'hidden') .style('position', 'absolute') .style('border', '1px solid black') .style('background-color', 'rgb(33, 37, 41)') .style('color', 'rgb(255, 255, 255)') .style('line-height', '1.55') .style('font-size', '0.875rem') .style('border-radius', '0.5rem') .style('padding', 'calc(0.3125rem) 0.625rem') .style('position', 'absolute') .style('white-space', 'nowrap') .style('pointer-events', 'none') .style('z-index', 1) .html(marker.html); } protected renderSerie(serie: MarkerSerie) { serie.data.forEach((marker: MarkerElem) => { this._renderLine(marker); this._renderCircle(marker); this._renderTooltip(marker); }); } }