@windborne/grapher 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle.js +1 -1
- package/bundle.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +225 -48
- package/src/components/context_menu.js +4 -4
- package/src/components/tooltip.js +207 -58
- package/src/grapher.scss +5 -0
- package/src/helpers/custom_prop_types.js +5 -2
- package/src/renderer/graph_body_renderer.js +4 -3
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -15,31 +15,11 @@ You _shouldn't_ use this package if:
|
|
|
15
15
|
- You don't want to add a react dependency (though the rendering engine may be used without react).
|
|
16
16
|
- Asset size is a big concern. Despite not having dependencies, this is a relatively heavy engine compared to alternatives.
|
|
17
17
|
|
|
18
|
-
## As a
|
|
19
|
-
|
|
20
|
-
### Range selection
|
|
21
|
-
|
|
22
|
-
### Axes
|
|
23
|
-
|
|
24
|
-
### Tooltips
|
|
25
|
-
|
|
26
|
-
### Options
|
|
27
|
-
Percentile
|
|
28
|
-
|
|
29
|
-
Auto-scale Y
|
|
30
|
-
|
|
31
|
-
## As a developer
|
|
18
|
+
## As a developer importing this package
|
|
32
19
|
|
|
33
20
|
### Installing
|
|
34
21
|
|
|
35
|
-
|
|
36
|
-
Add an .npmrc with the following contents:
|
|
37
|
-
```
|
|
38
|
-
@windborne:registry=https://npm.pkg.github.com
|
|
39
|
-
registry=https://registry.npmjs.org
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Then, `npm install @windborne/grapher`.
|
|
22
|
+
`npm install @windborne/grapher`.
|
|
43
23
|
|
|
44
24
|
It also requires React version 16.8 or greater as a peer dependency
|
|
45
25
|
|
|
@@ -65,60 +45,257 @@ You can find a full list in the package.json devDependencies, but they can be su
|
|
|
65
45
|
- React and babel (include @babel/polyfill, @babel/preset-react, etc). You likely already have this in your project
|
|
66
46
|
- Sass support (eg node-sass, sass-loader)
|
|
67
47
|
- Webgl loader (webpack-glsl-loader)
|
|
68
|
-
|
|
69
|
-
### props
|
|
70
|
-
Like any react component, grapher is primarily configured via props.
|
|
71
|
-
Refer to the grapher proptypes for information
|
|
72
48
|
|
|
73
|
-
|
|
74
|
-
|
|
49
|
+
# Grapher Component
|
|
50
|
+
|
|
51
|
+
## Props
|
|
52
|
+
Like any React component, Grapher is primarily configured via props.
|
|
53
|
+
Refer to the Grapher PropTypes for complete type information.
|
|
54
|
+
|
|
55
|
+
### Core Props
|
|
56
|
+
|
|
57
|
+
**series** (required)
|
|
58
|
+
This sets the data for the graph and is the only property that is truly required.
|
|
59
|
+
See "Series" section for more details.
|
|
75
60
|
|
|
76
|
-
**webgl
|
|
61
|
+
**webgl**
|
|
62
|
+
If true, will render with WebGL rather than a 2D context.
|
|
77
63
|
This is more performant, but uses more resources.
|
|
78
64
|
|
|
79
|
-
**requireWASM
|
|
65
|
+
**requireWASM**
|
|
66
|
+
If true, will wait until the WASM extensions are ready before it renders.
|
|
80
67
|
This can be useful when your app has expensive initialization.
|
|
81
68
|
|
|
82
|
-
|
|
69
|
+
### Series Format
|
|
70
|
+
The `series` prop requires an array of objects, where each object represents a data series with the following properties:
|
|
71
|
+
|
|
72
|
+
- **data** (required): The actual data points (see Data Formats below)
|
|
73
|
+
- **type**: Data type or 'infer' to automatically detect
|
|
74
|
+
- **xKey**: Property name for x-values when using object data
|
|
75
|
+
- **yKey**: Property name for y-values when using object data
|
|
76
|
+
- **xUnixDates**: Whether x-values are Unix timestamps (boolean)
|
|
77
|
+
- **color**: Series color (string or number)
|
|
78
|
+
- **name**: Series name for display in legend
|
|
79
|
+
- **xLabel**: Label for x-axis
|
|
80
|
+
- **yLabel**: Label for y-axis
|
|
81
|
+
- **rendering**: Visual representation ('line', 'bar', or 'area', defaults to 'line')
|
|
82
|
+
- **ignoreDiscontinuities**: Whether to connect points across gaps (boolean)
|
|
83
|
+
- **dashed**: Whether to use dashed lines (boolean)
|
|
84
|
+
- **dashPattern**: Array defining dash pattern (array of numbers)
|
|
85
|
+
- **width**: Line width (number)
|
|
86
|
+
- **axis**: Axis specification for the series (string or object)
|
|
87
|
+
- **rangeSelectorWidth**: Width of the range selector for this series (number)
|
|
88
|
+
- **expandYWith**: Values to include when calculating y-axis range (array of numbers)
|
|
89
|
+
- **defaultAlwaysTooltipped**: Whether to always show tooltip for this series (boolean)
|
|
90
|
+
- **square**: Whether to render the series with square points (boolean)
|
|
91
|
+
- **shiftXBy**: Value to shift x-coordinates by (number)
|
|
92
|
+
- **graph**: Affects which graph this series belongs to in multigrapher (number)
|
|
93
|
+
- **background**: Background configuration (object)
|
|
94
|
+
- **hideFromKey**: Whether to hide this series from the legend (boolean)
|
|
95
|
+
- **showIndividualPoints**: Whether to show individual data points (boolean)
|
|
96
|
+
- **negativeColor**: Color for negative values
|
|
97
|
+
- **gradient**: Gradient configuration, only applies to area rendering (array)
|
|
98
|
+
- **zeroLineWidth**: Width of the zero line, only applies to bar and area rendering (number)
|
|
99
|
+
- **zeroLineColor**: Color of the zero line, only applies to bar and area rendering (string)
|
|
100
|
+
- **pointRadius**: Radius of points, only applies to area rendering (number)
|
|
101
|
+
- **tooltipWidth**: Expected width of the tooltip (number). Will make the tooltip switch sides when this width plus the tooltip left position is greater than the graph width.
|
|
102
|
+
|
|
103
|
+
#### Series Data Formats
|
|
104
|
+
Grapher supports multiple data formats within a series:
|
|
105
|
+
|
|
106
|
+
1. **Array of y-values**: Simple array where index is used as x-value
|
|
107
|
+
2. **Array of [x,y] tuples**: Each point defined as [x, y] pair
|
|
108
|
+
3. **Array of objects**: Objects with properties for x and y values (as per the xKey and yKey properties)
|
|
109
|
+
4. **Observable**: Object with an observe method, which may emit tuples or objects
|
|
110
|
+
5. **Generator function**: Function that generates an array data points as a function of zoom
|
|
111
|
+
|
|
112
|
+
### Event Handlers
|
|
113
|
+
|
|
114
|
+
**onAxisChange**
|
|
115
|
+
If passed in, this function will be called every time the axes change.
|
|
83
116
|
It will be called with an array of objects, where each object is a single series object with the `axis`
|
|
84
117
|
property set to {left, right}-{index}. This can be useful for saving state between reloads.
|
|
85
118
|
|
|
86
|
-
**onRenderTime
|
|
119
|
+
**onRenderTime**
|
|
120
|
+
If passed in, this function will be called every time the grapher renders.
|
|
87
121
|
It will be called with diagnostic information about how long rendering took.
|
|
88
122
|
|
|
89
|
-
**
|
|
90
|
-
|
|
123
|
+
**onPointDrag**
|
|
124
|
+
Callback function that fires when draggable points are moved.
|
|
91
125
|
|
|
92
|
-
**
|
|
93
|
-
|
|
126
|
+
**onDraggablePointsDoubleClick**
|
|
127
|
+
Callback function that fires when draggable points are double-clicked.
|
|
94
128
|
|
|
95
|
-
**
|
|
129
|
+
**timingFrameCount**
|
|
130
|
+
Sets the number of frames for when the state controller's `averageLoopTime` method is called.
|
|
96
131
|
|
|
97
|
-
|
|
132
|
+
### Appearance
|
|
98
133
|
|
|
99
|
-
**
|
|
134
|
+
**theme**
|
|
135
|
+
Sets the theme of grapher to either 'day', 'night', or 'export'.
|
|
136
|
+
You can also override any CSS property directly in a stylesheet.
|
|
100
137
|
|
|
101
|
-
**
|
|
138
|
+
**title**
|
|
139
|
+
Sets the title text for the graph.
|
|
102
140
|
|
|
103
|
-
**
|
|
141
|
+
**fullscreen**
|
|
142
|
+
If true, displays the graph in fullscreen mode.
|
|
104
143
|
|
|
105
|
-
**
|
|
144
|
+
**bodyHeight**
|
|
145
|
+
Sets the height of the graph body (i.e., excluding range graph, series controls, etc.).
|
|
106
146
|
|
|
107
|
-
**
|
|
147
|
+
**height**
|
|
148
|
+
Sets the height of the entire graph.
|
|
108
149
|
|
|
109
|
-
**
|
|
150
|
+
**width**
|
|
151
|
+
Sets the width of the graph.
|
|
152
|
+
|
|
153
|
+
### Display Options
|
|
154
|
+
|
|
155
|
+
**showAxes**
|
|
156
|
+
Whether to show the axes on the graph.
|
|
157
|
+
|
|
158
|
+
**showRangeGraph**
|
|
159
|
+
Whether to show the smaller range graph below the main graph.
|
|
160
|
+
|
|
161
|
+
**showRangeSelectors**
|
|
162
|
+
Whether to show the top bar with range selection (e.g., "last day" button) and other options.
|
|
163
|
+
|
|
164
|
+
**showSeriesKey**
|
|
165
|
+
Whether to show the key of which series have which colors.
|
|
110
166
|
|
|
111
167
|
**showTooltips**
|
|
168
|
+
Whether to display tooltips when hovering over data points.
|
|
112
169
|
|
|
113
|
-
**
|
|
170
|
+
**showGrid**
|
|
171
|
+
Whether to show grid lines on the graph.
|
|
172
|
+
|
|
173
|
+
**showAxisColors**
|
|
174
|
+
Whether to color-code axes based on the series they represent.
|
|
175
|
+
|
|
176
|
+
**bigLabels**
|
|
177
|
+
If true, uses larger text for labels.
|
|
178
|
+
|
|
179
|
+
**xTickUnit**
|
|
180
|
+
Specifies the unit for x-axis ticks. Currently supports 'year'.
|
|
181
|
+
|
|
182
|
+
**xAxisIntegersOnly**
|
|
183
|
+
If true, only displays integer values on the x-axis.
|
|
184
|
+
|
|
185
|
+
**clockStyle**
|
|
186
|
+
Format for displaying time, either '12h' or '24h'.
|
|
187
|
+
|
|
188
|
+
**timeZone**
|
|
189
|
+
Time zone for date/time display. Can be 'local', 'utc', or a full timezone string.
|
|
190
|
+
|
|
191
|
+
**markRangeGraphDates**
|
|
192
|
+
Whether to mark significant dates on the range graph.
|
|
114
193
|
|
|
115
194
|
**tooltipOptions**
|
|
195
|
+
Configures tooltip appearance and behavior with properties including:
|
|
196
|
+
- `includeSeriesLabel`: Whether to show series name in tooltip
|
|
197
|
+
- `includeXLabel`: Whether to show x-axis label in tooltip
|
|
198
|
+
- `includeYLabel`: Whether to show y-axis label in tooltip
|
|
199
|
+
- `includeXValue`: Whether to show x-axis value in tooltip
|
|
200
|
+
- `includeYValue`: Whether to show y-axis value in tooltip
|
|
201
|
+
- `floating`: Whether tooltip floats or is fixed position
|
|
202
|
+
- `alwaysFixedPosition`: Forces tooltip to always use fixed position
|
|
203
|
+
- `floatPosition`: Placement of floating tooltip ('top' or 'bottom')
|
|
204
|
+
- `floatDelta`: Pixel offset for floating tooltip positioning
|
|
205
|
+
- `savingDisabled`: Prevents tooltip settings from being saved
|
|
206
|
+
- `customTooltip`: A react component to use as a custom tooltip. See [examples/custom_tooltips_graph.js](examples/custom_tooltips_graph.js) for an example. If used in conjunction with `combineTooltips`, see [examples/combined_tooltips_graph.js](examples/combined_tooltips_graph.js)
|
|
207
|
+
- `combineTooltips`: If true, combines multiple tooltips into one when multiple series are shown. Can alternatively be set to a threshold in pixels for how close values need to be in order to be combined.
|
|
208
|
+
|
|
209
|
+
**customBoundsSelectors**
|
|
210
|
+
Array of custom range selector objects with properties:
|
|
211
|
+
- `label`: Display text for the selector
|
|
212
|
+
- `calculator`: Function that determines the bounds
|
|
213
|
+
- `datesOnly`: If true, only works with date values
|
|
214
|
+
|
|
215
|
+
**customBoundsSelectorsOnly**
|
|
216
|
+
If true, only displays custom bounds selectors.
|
|
217
|
+
|
|
218
|
+
**defaultBoundsCalculator**
|
|
219
|
+
String identifier for the default bounds calculator to use.
|
|
220
|
+
|
|
221
|
+
**defaultShowOptions**
|
|
222
|
+
Default visibility of the options panel.
|
|
223
|
+
|
|
224
|
+
**defaultShowIndividualPoints**
|
|
225
|
+
Default setting for showing individual data points.
|
|
226
|
+
|
|
227
|
+
**defaultShowSidebar**
|
|
228
|
+
Default visibility of the sidebar.
|
|
229
|
+
|
|
230
|
+
**defaultShowAnnotations**
|
|
231
|
+
Default visibility of annotations.
|
|
232
|
+
|
|
233
|
+
**defaultLineWidth**
|
|
234
|
+
Default width of the lines in the graph.
|
|
235
|
+
|
|
236
|
+
**boundsSelectionEnabled**
|
|
237
|
+
Whether to enable the bounds selection feature.
|
|
238
|
+
|
|
239
|
+
**sidebarEnabled**
|
|
240
|
+
Whether to enable the sidebar.
|
|
116
241
|
|
|
117
|
-
**
|
|
242
|
+
**percentile**
|
|
243
|
+
Sets the percentile value for calculations.
|
|
118
244
|
|
|
119
|
-
### Usage from ruby
|
|
120
245
|
|
|
121
|
-
###
|
|
246
|
+
### Advanced Features
|
|
247
|
+
|
|
248
|
+
**tooltipOptions**
|
|
249
|
+
Configures tooltip appearance and behavior with properties including:
|
|
250
|
+
- `includeSeriesLabel`: Whether to show series name in tooltip
|
|
251
|
+
- `includeXLabel`: Whether to show x-axis label in tooltip
|
|
252
|
+
- `includeYLabel`: Whether to show y-axis label in tooltip
|
|
253
|
+
- `includeXValue`: Whether to show x-axis value in tooltip
|
|
254
|
+
- `includeYValue`: Whether to show y-axis value in tooltip
|
|
255
|
+
- `floating`: Whether tooltip floats or is fixed position
|
|
256
|
+
- `alwaysFixedPosition`: Forces tooltip to always use fixed position
|
|
257
|
+
- `floatPosition`: Placement of floating tooltip ('top' or 'bottom')
|
|
258
|
+
- `floatDelta`: Pixel offset for floating tooltip positioning
|
|
259
|
+
- `savingDisabled`: Prevents tooltip settings from being saved
|
|
260
|
+
|
|
261
|
+
**customBoundsSelectors**
|
|
262
|
+
Array of custom range selector objects with properties:
|
|
263
|
+
- `label`: Display text for the selector
|
|
264
|
+
- `calculator`: Function that determines the bounds
|
|
265
|
+
- `datesOnly`: If true, only works with date values
|
|
266
|
+
|
|
267
|
+
**customBoundsSelectorsOnly**
|
|
268
|
+
If true, only displays custom bounds selectors.
|
|
269
|
+
|
|
270
|
+
**defaultBoundsCalculator**
|
|
271
|
+
String identifier for the default bounds calculator to use.
|
|
272
|
+
|
|
273
|
+
**annotations**
|
|
274
|
+
Array of annotation objects to display on the graph with properties:
|
|
275
|
+
- `x`: Position on x-axis (string, number, or Date) where annotation should appear
|
|
276
|
+
- `xEnd`: Optional end position for range annotations
|
|
277
|
+
- `series`: Optional array of series names the annotation applies to
|
|
278
|
+
- `content`: Text content of the annotation
|
|
279
|
+
|
|
280
|
+
**draggablePoints**
|
|
281
|
+
Array of interactive point objects with properties:
|
|
282
|
+
- `x`: X-coordinate position
|
|
283
|
+
- `y`: Y-coordinate position
|
|
284
|
+
- `radius`: Optional size of the point
|
|
285
|
+
- `fillColor`: Optional interior color
|
|
286
|
+
- `strokeColor`: Optional outline color
|
|
287
|
+
- `strokeWidth`: Optional outline width
|
|
288
|
+
- `onClick`: Optional click handler function
|
|
289
|
+
- `onDoubleClick`: Optional double-click handler function
|
|
290
|
+
|
|
291
|
+
**verticalLines**
|
|
292
|
+
Array of vertical line objects to display on the graph with properties:
|
|
293
|
+
- `x`: X-coordinate position where the line should appear
|
|
294
|
+
- `color`: Optional line color
|
|
295
|
+
- `width`: Optional line width
|
|
296
|
+
- `markTop`: Whether to add a marker at the top of the line
|
|
297
|
+
- `style`: Optional styling object for the line
|
|
298
|
+
- `markerStyle`: Optional styling object for the marker
|
|
122
299
|
|
|
123
300
|
## Developing
|
|
124
301
|
Other than an `npm install`, you'll need to install rust and [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/).
|
|
@@ -41,7 +41,7 @@ export default class ContextMenu extends React.PureComponent {
|
|
|
41
41
|
|
|
42
42
|
const style = { left: x, top: y, width: '150px'};
|
|
43
43
|
|
|
44
|
-
if (!showing || !value || value.toLocaleString() === 'Invalid Date') {
|
|
44
|
+
if (!showing || !value || value.toLocaleString() === 'Invalid Date' || isNaN(x) || isNaN(y)) {
|
|
45
45
|
return null;
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -61,9 +61,9 @@ export default class ContextMenu extends React.PureComponent {
|
|
|
61
61
|
|
|
62
62
|
ContextMenu.propTypes = {
|
|
63
63
|
contextMenu: PropTypes.shape({
|
|
64
|
-
x: PropTypes.number
|
|
65
|
-
y: PropTypes.number
|
|
66
|
-
showing: PropTypes.bool
|
|
64
|
+
x: PropTypes.number,
|
|
65
|
+
y: PropTypes.number,
|
|
66
|
+
showing: PropTypes.bool,
|
|
67
67
|
value: PropTypes.oneOfType([
|
|
68
68
|
PropTypes.instanceOf(Date),
|
|
69
69
|
PropTypes.number,
|
|
@@ -25,6 +25,10 @@ function getYLabelContent({ yLabel, y, fullYPrecision}) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
if (typeof yLabel === 'object') {
|
|
29
|
+
return formatY(y);
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
return yLabel || formatY(y);
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -73,7 +77,8 @@ TooltipLabel.propTypes = {
|
|
|
73
77
|
export default class Tooltip extends React.PureComponent {
|
|
74
78
|
|
|
75
79
|
render() {
|
|
76
|
-
|
|
80
|
+
const textPadding = 3;
|
|
81
|
+
let height = 12*3 + 2*textPadding;
|
|
77
82
|
|
|
78
83
|
if (!this.props.includeSeriesLabel) {
|
|
79
84
|
height -= 12;
|
|
@@ -91,7 +96,7 @@ export default class Tooltip extends React.PureComponent {
|
|
|
91
96
|
const halfHeight = height/2;
|
|
92
97
|
const caretPadding = 4;
|
|
93
98
|
|
|
94
|
-
const textTop = -halfHeight +
|
|
99
|
+
const textTop = -halfHeight + textPadding;
|
|
95
100
|
|
|
96
101
|
const formatXOptions = {
|
|
97
102
|
clockStyle: this.props.clockStyle,
|
|
@@ -107,76 +112,187 @@ export default class Tooltip extends React.PureComponent {
|
|
|
107
112
|
formatXOptions
|
|
108
113
|
};
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<svg>
|
|
113
|
-
{
|
|
114
|
-
this.props.tooltips.map(({ x, y, color, pixelY, pixelX, series, index, xLabel, yLabel, fullYPrecision }, i) => {
|
|
115
|
-
const axisLabel = (series.name || series.yKey || index).toString();
|
|
116
|
-
const width = Math.max(axisLabel.length, (xLabel || formatX(x, formatXOptions)).length + 4, getYLabelContent({ yLabel, y, fullYPrecision}).length + 4) * 7.5;
|
|
115
|
+
const preparedTooltips = this.props.tooltips.map((tooltip) => {
|
|
116
|
+
const { x, y, pixelY, pixelX, series, index, xLabel, yLabel, fullYPrecision } = tooltip;
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
const axisLabel = (series.name || series.yKey || index).toString();
|
|
119
|
+
let width = Math.max(axisLabel.length, (xLabel || formatX(x, formatXOptions)).length + 4, getYLabelContent({ yLabel, y, fullYPrecision}).length + 4) * 7.5;
|
|
120
|
+
if (series.tooltipWidth) {
|
|
121
|
+
width = series.tooltipWidth;
|
|
122
|
+
}
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
if (pixelX >= this.props.elementWidth - (width + 2*caretSize + caretPadding)) {
|
|
122
|
-
multiplier = -1;
|
|
123
|
-
}
|
|
124
|
+
let fixedPosition = this.props.elementWidth < (width + 2*caretSize + 2*caretPadding);
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
let multiplier = 1;
|
|
127
|
+
if (pixelX >= this.props.elementWidth - (width + 2*caretSize + caretPadding)) {
|
|
128
|
+
multiplier = -1;
|
|
129
|
+
}
|
|
128
130
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
if (pixelX < width + 2*caretSize + caretPadding && multiplier === -1) {
|
|
132
|
+
fixedPosition = true;
|
|
133
|
+
}
|
|
132
134
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
} else {
|
|
137
|
-
textLeft += 6;
|
|
138
|
-
}
|
|
135
|
+
if (y === null) {
|
|
136
|
+
fixedPosition = true;
|
|
137
|
+
}
|
|
139
138
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
if (this.props.alwaysFixedPosition) {
|
|
140
|
+
fixedPosition = true;
|
|
141
|
+
}
|
|
143
142
|
|
|
144
|
-
|
|
143
|
+
let textLeft = caretSize + caretPadding;
|
|
144
|
+
if (multiplier < 0) {
|
|
145
|
+
textLeft = -width - textLeft;
|
|
146
|
+
} else {
|
|
147
|
+
textLeft += 6;
|
|
148
|
+
}
|
|
145
149
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
y,
|
|
150
|
-
axisLabel,
|
|
151
|
-
xLabel,
|
|
152
|
-
yLabel,
|
|
153
|
-
...passThroughProps
|
|
154
|
-
};
|
|
150
|
+
if (!isFinite(pixelX)) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
155
153
|
|
|
156
|
-
|
|
157
|
-
if (fixedPosition || this.props.alwaysFixedPosition) {
|
|
158
|
-
textLeft = 6;
|
|
154
|
+
const transform = `translate(${pixelX},${pixelY})`;
|
|
159
155
|
|
|
160
|
-
|
|
156
|
+
const commonLabelProps = {
|
|
157
|
+
fullYPrecision: fullYPrecision || this.props.maxPrecision,
|
|
158
|
+
x,
|
|
159
|
+
y,
|
|
160
|
+
axisLabel,
|
|
161
|
+
xLabel,
|
|
162
|
+
yLabel,
|
|
163
|
+
...passThroughProps
|
|
164
|
+
};
|
|
161
165
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
166
|
+
let yTranslation = 0;
|
|
167
|
+
let baseLeft;
|
|
165
168
|
|
|
166
|
-
|
|
169
|
+
if (fixedPosition) {
|
|
170
|
+
textLeft = 6;
|
|
167
171
|
|
|
168
|
-
|
|
169
|
-
if (this.props.floatPosition === 'bottom') {
|
|
170
|
-
yTranslation = this.props.elementHeight + halfHeight + 4;
|
|
171
|
-
} else {
|
|
172
|
-
yTranslation = -height;
|
|
173
|
-
}
|
|
172
|
+
baseLeft = this.props.elementWidth / 2 - width / 2;
|
|
174
173
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
if (width > this.props.elementWidth && !this.props.floating) {
|
|
175
|
+
baseLeft -= Y_AXIS_WIDTH * this.props.axisCount / 2;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
yTranslation = 18;
|
|
179
|
+
|
|
180
|
+
if (this.props.floating) {
|
|
181
|
+
if (this.props.floatPosition === 'bottom') {
|
|
182
|
+
yTranslation = this.props.elementHeight + halfHeight + 4;
|
|
183
|
+
} else {
|
|
184
|
+
yTranslation = -height;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (this.props.floatDelta) {
|
|
188
|
+
yTranslation += this.props.floatDelta;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
...tooltip,
|
|
195
|
+
label: axisLabel,
|
|
196
|
+
indexInAxis: series.axis.series.indexOf(series),
|
|
197
|
+
axisLabel,
|
|
198
|
+
width,
|
|
199
|
+
fixedPosition,
|
|
200
|
+
multiplier,
|
|
201
|
+
textLeft,
|
|
202
|
+
transform,
|
|
203
|
+
commonLabelProps,
|
|
204
|
+
textTop,
|
|
205
|
+
height,
|
|
206
|
+
caretSize,
|
|
207
|
+
halfHeight,
|
|
208
|
+
caretPadding,
|
|
209
|
+
yTranslation,
|
|
210
|
+
baseLeft
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const CustomTooltipComponent = this.props.customTooltip;
|
|
215
|
+
|
|
216
|
+
let groupedTooltips;
|
|
217
|
+
if (this.props.combineTooltips) {
|
|
218
|
+
let combinationThreshold = 50; // in px how close tooltips should be to combine
|
|
219
|
+
if (typeof this.props.combineTooltips === 'number') {
|
|
220
|
+
combinationThreshold = this.props.combineTooltips;
|
|
221
|
+
}
|
|
179
222
|
|
|
223
|
+
groupedTooltips = [];
|
|
224
|
+
|
|
225
|
+
for (let tooltip of preparedTooltips) {
|
|
226
|
+
let added = false;
|
|
227
|
+
for (let group of groupedTooltips) {
|
|
228
|
+
if (Math.abs(group.pixelX - tooltip.pixelX) <= combinationThreshold) {
|
|
229
|
+
group.tooltips.push(tooltip);
|
|
230
|
+
if (tooltip.pixelX > group.pixelX) {
|
|
231
|
+
group.pixelX = tooltip.pixelX;
|
|
232
|
+
group.multiplier = tooltip.multiplier;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (tooltip.pixelY < group.pixelY) {
|
|
236
|
+
group.pixelY = tooltip.pixelY;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
added = true;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!added) {
|
|
245
|
+
groupedTooltips.push({
|
|
246
|
+
pixelX: tooltip.pixelX,
|
|
247
|
+
pixelY: tooltip.pixelY,
|
|
248
|
+
multiplier: tooltip.multiplier,
|
|
249
|
+
tooltips: [tooltip]
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (let group of groupedTooltips) {
|
|
255
|
+
let totalHeight = 0;
|
|
256
|
+
let maxWidth = 0;
|
|
257
|
+
|
|
258
|
+
// sort by indexInAxis
|
|
259
|
+
group.tooltips.sort((a, b) => a.indexInAxis - b.indexInAxis);
|
|
260
|
+
|
|
261
|
+
for (let i = 0; i < group.tooltips.length; i++) {
|
|
262
|
+
group.tooltips[i].textTop = totalHeight;
|
|
263
|
+
totalHeight += group.tooltips[i].height;
|
|
264
|
+
maxWidth = Math.max(maxWidth, group.tooltips[i].width);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for (let i = 0; i < group.tooltips.length; i++) {
|
|
268
|
+
group.tooltips[i].textTop -= totalHeight/2;
|
|
269
|
+
group.tooltips[i].textTop += textPadding;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
group.height = totalHeight;
|
|
273
|
+
group.halfHeight = totalHeight / 2;
|
|
274
|
+
group.caretSize = caretSize;
|
|
275
|
+
group.width = maxWidth;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div className="grapher-tooltip">
|
|
281
|
+
<svg>
|
|
282
|
+
{
|
|
283
|
+
preparedTooltips.map((tooltip, i) => {
|
|
284
|
+
const { color, fixedPosition, width, transform, baseLeft, commonLabelProps, yTranslation, multiplier, textLeft, textTop } = tooltip;
|
|
285
|
+
|
|
286
|
+
if (this.props.customTooltip || groupedTooltips) {
|
|
287
|
+
return (
|
|
288
|
+
<g key={i} transform={transform} className="tooltip-item">
|
|
289
|
+
<circle r={4} fill={color}/>
|
|
290
|
+
</g>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// display in a fixed position if not wide enough
|
|
295
|
+
if (fixedPosition) {
|
|
180
296
|
return (
|
|
181
297
|
<g key={i} className="tooltip-item tooltip-item-fixed">
|
|
182
298
|
<circle r={4} fill={color} transform={transform} />
|
|
@@ -207,7 +323,40 @@ export default class Tooltip extends React.PureComponent {
|
|
|
207
323
|
);
|
|
208
324
|
})
|
|
209
325
|
}
|
|
326
|
+
|
|
327
|
+
{
|
|
328
|
+
!this.props.customTooltip && groupedTooltips &&
|
|
329
|
+
groupedTooltips.map(({ tooltips, pixelX, pixelY, halfHeight, multiplier, color, width }, i) =>
|
|
330
|
+
<g key={i} transform={`translate(${pixelX},${pixelY})`} className="tooltip-item">
|
|
331
|
+
<path stroke={color} d={`M${multiplier*caretPadding},0 L${multiplier*caretSize*2},-${caretSize} V-${halfHeight} h${multiplier*width} V${halfHeight} h${multiplier*-width} V${caretSize} L${multiplier*caretPadding},0`} />
|
|
332
|
+
|
|
333
|
+
{
|
|
334
|
+
tooltips.map((tooltip, j) =>
|
|
335
|
+
<TooltipLabel
|
|
336
|
+
key={j}
|
|
337
|
+
textTop={tooltip.textTop}
|
|
338
|
+
textLeft={tooltip.textLeft}
|
|
339
|
+
{...tooltip.commonLabelProps}
|
|
340
|
+
/>
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
</g>
|
|
344
|
+
)
|
|
345
|
+
}
|
|
210
346
|
</svg>
|
|
347
|
+
|
|
348
|
+
{
|
|
349
|
+
this.props.customTooltip &&
|
|
350
|
+
(groupedTooltips || preparedTooltips).map((tooltip, i) =>
|
|
351
|
+
<div
|
|
352
|
+
key={i}
|
|
353
|
+
className="custom-tooltip-container"
|
|
354
|
+
style={{top: tooltip.pixelY, left: tooltip.pixelX}}
|
|
355
|
+
>
|
|
356
|
+
<CustomTooltipComponent {...tooltip} />
|
|
357
|
+
</div>
|
|
358
|
+
)
|
|
359
|
+
}
|
|
211
360
|
</div>
|
|
212
361
|
);
|
|
213
362
|
}
|
|
@@ -233,7 +382,7 @@ Tooltip.propTypes = {
|
|
|
233
382
|
pixelY: PropTypes.number,
|
|
234
383
|
color: PropTypes.string,
|
|
235
384
|
xLabel: PropTypes.string,
|
|
236
|
-
yLabel: PropTypes.
|
|
385
|
+
yLabel: PropTypes.any,
|
|
237
386
|
fullYPrecision: PropTypes.bool
|
|
238
387
|
})),
|
|
239
388
|
axisCount: PropTypes.number.isRequired,
|