-
Notifications
You must be signed in to change notification settings - Fork 2
/
trackerUtils.ts
190 lines (172 loc) · 7.33 KB
/
trackerUtils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import {SvgSelection, TrackerSelection} from "./d3types";
import * as d3 from "d3";
import {Selection} from "d3";
import {Datum} from "./datumSeries";
import {containerDimensionsFrom, Dimensions, Margin, plotDimensionsFrom} from "./margins";
import {mouseInPlotAreaFor, textWidthOf} from "./utils";
import {AxisLocation, ContinuousNumericAxis} from "./axes";
import {TrackerAxisInfo, TrackerAxisUpdate, TrackerLabelLocation} from "./Tracker";
import React from "react";
export interface TrackerLabelFont {
size: number
color: string
family: string
weight: number
}
export const defaultTrackerLabelFont: TrackerLabelFont = {
size: 12,
color: '#d2933f',
weight: 300,
family: 'sans-serif'
}
export interface TrackerStyle {
visible: boolean;
color: string,
lineWidth: number,
}
export const defaultTrackerStyle: TrackerStyle = {
visible: false,
color: '#d2933f',
lineWidth: 1,
};
/**
* Creates or returns the existing SVG elements for displaying a tracker line
* @param chartId The ID of the chart
* @param container The SVG container
* @param svg The SVG selection
* @param plotDimensions The dimensions of the plot
* @param margin The margins around the plot
* @param style The tracker style
* @param labelFont The font used for the axis labels
* @param label A function that returns the tracker label string for a given x-value
* @param labelStyle The location style for the tracker (i.e. on the axes, next to the mouse, none shown)
* @param onTrackerUpdate A callback function the accepts the current tracker's axis information
* @return The tracker selection
*/
export function trackerControlInstance(
chartId: number,
container: SVGSVGElement,
svg: SvgSelection,
plotDimensions: Dimensions,
margin: Margin,
style: TrackerStyle,
labelFont: TrackerLabelFont,
label: Map<ContinuousNumericAxis, (x: number) => string>,
labelStyle: TrackerLabelLocation,
onTrackerUpdate: (update: TrackerAxisUpdate) => void
): TrackerSelection {
const trackerLine = svg
.append<SVGLineElement>('line')
.attr('id', `stream-chart-tracker-line-${chartId}`)
.attr('class', 'tracker')
.attr('y1', margin.top)
.attr('y2', plotDimensions.height + margin.top - margin.bottom)
.attr('stroke', style.color)
.attr('stroke-width', style.lineWidth)
.attr('opacity', 0) as Selection<SVGLineElement, Datum, null, undefined>
label.forEach((labelFn, axis) => {
// create the text element holding the tracker time
const label = axis.location === AxisLocation.Top ?
Math.max(0, margin.top - 20) :
Math.max(0, plotDimensions.height + margin.top - 3)
svg
.append<SVGTextElement>('text')
.attr('id', `stream-chart-tracker-label-${chartId}-${axis.location}`)
.attr('y', label)
.attr('fill', labelFont.color)
.attr('font-family', labelFont.family)
.attr('font-size', labelFont.size)
.attr('font-weight', labelFont.weight)
.attr('opacity', 0)
.text(() => '')
})
const containerDimensions = containerDimensionsFrom(plotDimensions, margin)
svg.on(
'mousemove',
event => handleShowTracker(
chartId, container, event, margin, containerDimensions, style, labelFont, label, labelStyle, onTrackerUpdate
)
)
return trackerLine
}
/**
* Removes the tracker control from the chart
* @param svg The svg selection holding the tracker contol
*/
export function removeTrackerControl(svg: SvgSelection) {
svg.on('mousemove', () => null)
}
/**
* Callback when the mouse tracker is to be shown
* @param chartId The ID number of the chart
* @param container The svg container
* @param event The mouse-over series event
* @param margin The plot margins
* @param dimensions The container dimensions (i.e. the plot dimensions plus its margins)
* @param trackerStyle The style settings for the tracker line
* @param labelFont The style settings for the tracker font
* @param label A function that returns the tracker label string
* @param labelStyle Where to display the tracker labels (i.e. with-mouse, with-axes, no-where)
* @param onTrackerUpdate A callback function the accepts the current tracker's axis information
*/
function handleShowTracker(
chartId: number,
container: SVGSVGElement,
// event: MouseEvent,
event: React.MouseEvent<SVGSVGElement>,
margin: Margin,
dimensions: Dimensions,
trackerStyle: TrackerStyle,
labelFont: TrackerLabelFont,
label: Map<ContinuousNumericAxis, (x: number) => string>,
labelStyle: TrackerLabelLocation,
onTrackerUpdate: (update: TrackerAxisUpdate) => void
): void {
// determine whether the mouse is in the plot area
const [x, y] = d3.pointer(event, container)
const inPlot = mouseInPlotAreaFor(x, y, margin, dimensions)
const updateInfo: Array<[string, TrackerAxisInfo]> = Array.from(label.entries())
.map(([axis, trackerLabel]) => {
// when the mouse is in the plot area, then set the opacity of the tracker line and label to 1,
// which means it is fully visible. when the mouse is not in the plot area, set the opacity to 0,
// which means the tracker line and label are invisible.
d3.select<SVGLineElement, Datum>(`#stream-chart-tracker-line-${chartId}`)
.attr('x1', x)
.attr('x2', x)
.attr('stroke', trackerStyle.color)
.attr('stroke-width', trackerStyle.lineWidth)
.attr('opacity', () => inPlot ? 1 : 0)
const label = d3.select<SVGTextElement, any>(`#stream-chart-tracker-label-${chartId}-${axis.location}`)
.attr('fill', labelFont.color)
.attr('font-family', labelFont.family)
.attr('font-size', labelFont.size)
.attr('font-weight', labelFont.weight)
.attr('opacity', () => inPlot ? 1 : 0)
.text(() => trackerLabel(x))
// when the label-style is to be with the mouse
if (labelStyle === TrackerLabelLocation.WithMouse) {
// todo the offsets should be based on the font size of the tracker label
const topOffset = 30
const offset = axis.location === AxisLocation.Top ? topOffset : 10
const {height} = plotDimensionsFrom(dimensions.width, dimensions.height, margin)
const labelY = Math.min(
Math.max(margin.top + 15 + topOffset - offset, y - offset),
margin.top + height - margin.bottom - offset
)
label.attr('y', labelY)
}
// adjust the label position when the tracker is at the right-most edges of the plot so that
// the label remains visible (i.e. doesn't get clipped)
const xOffset = TrackerLabelLocation.WithMouse ? 10 : 0
const labelWidth = textWidthOf(label)
label.attr('x', Math.min(dimensions.width - margin.right - labelWidth, x + xOffset))
const trackerInfo: TrackerAxisInfo = {
x: axis.scale.invert(x - margin.left),
axisLocation: axis.location
}
return [axis.axisId, trackerInfo]
})
if (inPlot) {
onTrackerUpdate(new Map(updateInfo))
}
}