diff --git a/examples/markers.html b/examples/markers.html
index 5f8d5f9..a08430d 100644
--- a/examples/markers.html
+++ b/examples/markers.html
@@ -13,9 +13,18 @@
const startTime = 1701790172908;
const timeSerieData = [5, 6, 3, 7, 5, 6, 8, 4, 5, 6, 4, 3, 5, 7, 8]
.map((el, idx) => [startTime + idx * 1000, el]);
- // TODO: make this one-dimensinal data when implemented
- const markersData1 = [3, 6, 9].map(el => [startTime + el * 1000]);
- const markersData2 = [4, 11].map(el => [startTime + el * 1000]);
+ const markersData1 = [3, 6, 9].map(el => ({
+ x: startTime + el * 1000,
+ color: 'red',
+ alwaysDisplay: false,
+ html: new Date(startTime).toISOString(),
+ }));
+ const markersData2 = [4, 11].map(el => ({
+ x: startTime + el * 1000,
+ color: 'blue',
+ alwaysDisplay: true,
+ html: new Date(startTime).toISOString(),
+ }));
let options = {
renderLegend: false,
axis: {
@@ -31,8 +40,8 @@
options,
{
series: [
- { data: markersData1, color: 'red' },
- { data: markersData2, color: 'blue' },
+ { data: markersData1 },
+ { data: markersData2 },
]
}
);
diff --git a/examples/markers_select.html b/examples/markers_select.html
index 48a1623..f801e3b 100644
--- a/examples/markers_select.html
+++ b/examples/markers_select.html
@@ -13,11 +13,11 @@
const startTime = 1701790172908;
const timeSerieData = [5, 6, 3, 7, 5, 6, 8, 4, 5, 6, 4, 3, 5, 7, 8]
.map((el, idx) => [startTime + idx * 1000, el]);
- // TODO: make this one-dimensinal data when implemented
- const markersData = [3, 6, 9].map(el => [
- startTime + el * 1000,
- { el }
- ]);
+ const markersData = [3, 6, 9].map(el => ({
+ x: startTime + el * 1000,
+ payload: el,
+ color: 'red',
+ }));
let options = {
renderLegend: false,
axis: {
@@ -33,7 +33,7 @@
options,
{
series: [
- { data: markersData, color: 'red' },
+ { data: markersData },
],
events: {
onMouseMove: (el) => { console.log(el); },
diff --git a/src/components/markers.ts b/src/components/markers.ts
index a481059..9d9c290 100644
--- a/src/components/markers.ts
+++ b/src/components/markers.ts
@@ -1,59 +1,137 @@
-import { MarkersConf, MarkerSerie } from "../models/marker";
-import { PodState } from "@chartwerk/core";
-import { LineTimeSerie, LineOptions } from "../types";
+import { MarkerElem, MarkersConf, MarkerSerie } from '../models/marker';
+import { LineTimeSerie, LineOptions } from '../types';
-import d3 from "d3";
+import { PodState } from '@chartwerk/core';
+
+import d3 from 'd3';
export class Markers {
- // TODO: more semantic name
- private _d3Holder = null;
+ private _layerContainer = null;
+ private _chartHeight = 0;
- constructor(private _markerConf: MarkersConf, private _state: PodState) {
+ constructor(
+ private _chartContainer: d3.Selection,
+ private _markerConf: MarkersConf,
+ private _state: PodState
+ ) { }
+ clear() {
+ if(this._layerContainer !== null) {
+ this._layerContainer.remove();
+ }
+ this._chartContainer.selectAll('.marker-content').remove();
}
- render(metricContainer: d3.Selection) {
- if(this._d3Holder !== null) {
- this._d3Holder.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);
}
- this._d3Holder = metricContainer.append('g').attr('class', 'markers-layer');
- for (const ms of this._markerConf.series) {
- this.renderSerie(ms);
+ }
+
+ 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')
+ .attr('class', `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 + 50}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((d) => {
- let linePosition = this._state.xScale(d[0]) as number;
- this._d3Holder.append('line')
- .attr('class', 'gap-line')
- .attr('stroke', serie.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');
- let circle = this._d3Holder.append('circle')
- .attr('class', 'gap-circle')
- .attr('stroke', serie.color)
- .attr('stroke-width', '2px')
- .attr('r', 4)
- .attr('cx', linePosition)
- .attr('cy', 5)
-
- if(this._markerConf !== undefined) {
- circle
- .attr('pointer-events', 'all')
- .style('cursor', 'pointer')
- .on('mousemove', () => this._markerConf.events.onMouseMove(d))
- .on('mouseout', () => this._markerConf.events.onMouseOut())
- }
-
+ serie.data.forEach((marker: MarkerElem) => {
+ this._renderLine(marker);
+ this._renderCircle(marker);
+ this._renderTooltip(marker);
});
-
}
-}
\ No newline at end of file
+}
diff --git a/src/index.ts b/src/index.ts
index 36cad59..ce5fc5d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -51,8 +51,8 @@ class LinePod extends ChartwerkPod {
this._renderMetric(serie);
}
if(this._markersConf !== undefined) {
- this._markersLayer = new Markers(this._markersConf, this.state);
- this._markersLayer.render(this.metricContainer);
+ this._markersLayer = new Markers(this.d3Node, this._markersConf, this.state);
+ this._markersLayer.render(this.metricContainer, this.height);
}
this._segmentsLayer = new Segments(this._segmentSeries, this.state);
@@ -62,6 +62,7 @@ class LinePod extends ChartwerkPod {
clearAllMetrics(): void {
// TODO: temporary hack before it will be implemented in core.
this.chartContainer.selectAll('.metric-el').remove();
+ this._markersLayer?.clear();
}
initLineGenerator(): void {
diff --git a/src/models/marker.ts b/src/models/marker.ts
index 7b75deb..f0ae211 100644
--- a/src/models/marker.ts
+++ b/src/models/marker.ts
@@ -1,9 +1,13 @@
-export type MarkerElem = [number, any?];
+export type MarkerElem = {
+ x: number;
+ color: string;
+ html?: string;
+ alwaysDisplay?: boolean;
+ payload?: any;
+}
export type MarkerSerie = {
- color: string;
- // TODO: make one-dimensional array with only x
- data: MarkerElem[] // [x, payload] payload is any data for tooltip
+ data: MarkerElem[];
}
export type MarkersConf = {