@pie-lib/plot 4.0.4-next.30 → 4.0.4-next.34
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/CHANGELOG.json +17 -0
- package/CHANGELOG.md +838 -0
- package/LICENSE.md +5 -0
- package/lib/draggable.js +44 -0
- package/lib/draggable.js.map +1 -0
- package/lib/graph-props.js +46 -0
- package/lib/graph-props.js.map +1 -0
- package/lib/grid-draggable.js +361 -0
- package/lib/grid-draggable.js.map +1 -0
- package/lib/index.js +44 -0
- package/lib/index.js.map +1 -0
- package/lib/label.js +173 -0
- package/lib/label.js.map +1 -0
- package/lib/root.js +474 -0
- package/lib/root.js.map +1 -0
- package/lib/trig.js +149 -0
- package/lib/trig.js.map +1 -0
- package/lib/types.js +40 -0
- package/lib/types.js.map +1 -0
- package/lib/utils.js +165 -0
- package/lib/utils.js.map +1 -0
- package/package.json +25 -35
- package/src/__tests__/draggable.test.jsx +41 -0
- package/src/__tests__/grid-draggable.test.jsx +487 -0
- package/src/__tests__/root.test.jsx +277 -0
- package/src/__tests__/trig.test.js +163 -0
- package/src/__tests__/utils.test.js +229 -0
- package/src/draggable.jsx +11 -0
- package/src/graph-props.js +34 -0
- package/src/grid-draggable.jsx +332 -0
- package/src/index.js +9 -0
- package/src/label.jsx +199 -0
- package/src/root.jsx +485 -0
- package/src/trig.js +151 -0
- package/src/types.js +41 -0
- package/src/utils.js +167 -0
- package/dist/_virtual/_rolldown/runtime.js +0 -20
- package/dist/draggable.d.ts +0 -13
- package/dist/draggable.js +0 -13
- package/dist/graph-props.d.ts +0 -22
- package/dist/graph-props.js +0 -29
- package/dist/grid-draggable.d.ts +0 -91
- package/dist/grid-draggable.js +0 -168
- package/dist/index.d.ts +0 -16
- package/dist/index.js +0 -8
- package/dist/label.d.ts +0 -30
- package/dist/label.js +0 -132
- package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js +0 -16
- package/dist/node_modules/.bun/invariant@2.2.4/node_modules/invariant/browser.js +0 -28
- package/dist/node_modules/.bun/react-draggable@4.6.0_6dbf9a050bc9aadb/node_modules/react-draggable/build/cjs/chunk-D5BXCJ5G.js +0 -503
- package/dist/node_modules/.bun/react-draggable@4.6.0_6dbf9a050bc9aadb/node_modules/react-draggable/build/cjs/cjs.js +0 -5
- package/dist/root.d.ts +0 -68
- package/dist/root.js +0 -302
- package/dist/trig.d.ts +0 -41
- package/dist/trig.js +0 -47
- package/dist/types.d.ts +0 -125
- package/dist/types.js +0 -46
- package/dist/utils.d.ts +0 -44
- package/dist/utils.js +0 -82
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import invariant from 'invariant';
|
|
2
|
+
import { snapTo } from './utils';
|
|
3
|
+
import { scaleLinear } from 'd3-scale';
|
|
4
|
+
|
|
5
|
+
const createSnapMinAndMax = ({ min, max, step }) => {
|
|
6
|
+
// for graphing, if step is a value with decimals, we have to calculate the min & max for the grid taking in consideration that 0 has to be exactly in the middle
|
|
7
|
+
// for example, if min: -5 & max: 5 & step: 0.75, in order to keep 0 in the middle we have to set min: -4.5 & max: 4.5
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
step,
|
|
11
|
+
min: parseInt(min / step) * step,
|
|
12
|
+
max: parseInt(max / step) * step,
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const create = (domain, range, size, getRootNode) => {
|
|
17
|
+
invariant(domain.min < domain.max, 'domain: min must be less than max');
|
|
18
|
+
invariant(range.min < range.max, 'range: min must be less than max');
|
|
19
|
+
|
|
20
|
+
const domainMinMax = createSnapMinAndMax(domain);
|
|
21
|
+
const rangeMinMax = createSnapMinAndMax(range);
|
|
22
|
+
|
|
23
|
+
const scale = {
|
|
24
|
+
x: scaleLinear().domain([domain.min, domain.max]).range([0, size.width]),
|
|
25
|
+
y: scaleLinear().domain([range.max, range.min]).range([0, size.height]),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const snap = {
|
|
29
|
+
x: snapTo.bind(null, domainMinMax.min, domainMinMax.max, domainMinMax.step),
|
|
30
|
+
y: snapTo.bind(null, rangeMinMax.min, rangeMinMax.max, rangeMinMax.step),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return { scale, snap, domain, range, size, getRootNode };
|
|
34
|
+
};
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { GraphPropsType } from './types';
|
|
4
|
+
import { DraggableCore } from './draggable';
|
|
5
|
+
import debug from 'debug';
|
|
6
|
+
import * as utils from './utils';
|
|
7
|
+
import { isFunction } from 'lodash-es';
|
|
8
|
+
import invariant from 'invariant';
|
|
9
|
+
import { pointer } from 'd3-selection';
|
|
10
|
+
|
|
11
|
+
const log = debug('pie-lib:plot:grid-draggable');
|
|
12
|
+
|
|
13
|
+
export const deltaFn = (scale, snap, val) => (delta) => {
|
|
14
|
+
const normalized = delta + scale(0);
|
|
15
|
+
const inverted = scale.invert(normalized);
|
|
16
|
+
|
|
17
|
+
const fixDecimalsArithmetic = (snap(val + inverted).toFixed(4) * 1000) / 1000;
|
|
18
|
+
|
|
19
|
+
return fixDecimalsArithmetic;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a Component that is draggable, within a bounded grid.
|
|
24
|
+
* @param {*} opts
|
|
25
|
+
*/
|
|
26
|
+
export const gridDraggable = (opts) => (Comp) => {
|
|
27
|
+
invariant(
|
|
28
|
+
!!opts && isFunction(opts.fromDelta) && isFunction(opts.bounds) && isFunction(opts.anchorPoint),
|
|
29
|
+
'You must supply an object with: { anchorPoint: Function, fromDelta: Function, bounds: Function }',
|
|
30
|
+
);
|
|
31
|
+
return class GridDraggable extends React.Component {
|
|
32
|
+
static propTypes = {
|
|
33
|
+
disabled: PropTypes.bool,
|
|
34
|
+
onDragStart: PropTypes.func,
|
|
35
|
+
onDrag: PropTypes.func,
|
|
36
|
+
onDragStop: PropTypes.func,
|
|
37
|
+
onClick: PropTypes.func,
|
|
38
|
+
onMove: PropTypes.func,
|
|
39
|
+
graphProps: GraphPropsType.isRequired,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
constructor(props) {
|
|
43
|
+
super(props);
|
|
44
|
+
this.state = {
|
|
45
|
+
startX: null,
|
|
46
|
+
startY: null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
grid = () => {
|
|
51
|
+
const { graphProps } = this.props;
|
|
52
|
+
const { scale, domain, range } = graphProps;
|
|
53
|
+
return {
|
|
54
|
+
x: scale.x(domain.step) - scale.x(0),
|
|
55
|
+
y: scale.y(range.step) - scale.y(0),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
onStart = (e) => {
|
|
59
|
+
const { onDragStart } = this.props;
|
|
60
|
+
if (document.activeElement) {
|
|
61
|
+
document.activeElement.blur();
|
|
62
|
+
}
|
|
63
|
+
this._didDrag = false;
|
|
64
|
+
this.setState({ startX: e.clientX, startY: e.clientY });
|
|
65
|
+
|
|
66
|
+
// Intercept the native 'click' event that the browser fires after mouseup.
|
|
67
|
+
// We use a one-time capture-phase listener so we can suppress it when a
|
|
68
|
+
// real drag occurred, preventing Bg's d3 click listener from creating a new mark.
|
|
69
|
+
const target = e.target;
|
|
70
|
+
const onNativeClick = (clickEvent) => {
|
|
71
|
+
target.removeEventListener('click', onNativeClick, true);
|
|
72
|
+
if (this._didDrag) {
|
|
73
|
+
clickEvent.stopPropagation();
|
|
74
|
+
clickEvent.preventDefault();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
target.addEventListener('click', onNativeClick, true);
|
|
78
|
+
|
|
79
|
+
if (onDragStart) {
|
|
80
|
+
onDragStart();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
position = () => {
|
|
84
|
+
const { x, y } = opts.anchorPoint(this.props);
|
|
85
|
+
const { graphProps } = this.props;
|
|
86
|
+
const { scale, snap } = graphProps;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
anchorPoint: {
|
|
90
|
+
x,
|
|
91
|
+
y,
|
|
92
|
+
},
|
|
93
|
+
x: deltaFn(scale.x, snap.x, x),
|
|
94
|
+
y: deltaFn(scale.y, snap.y, y),
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
tiny = (key, event) => {
|
|
99
|
+
const K = key.toUpperCase();
|
|
100
|
+
const end = event[`client${K}`];
|
|
101
|
+
const start = this.state[`start${K}`];
|
|
102
|
+
const delta = Math.abs(end - start);
|
|
103
|
+
const out = delta < Math.abs(this.grid()[key]) / 10;
|
|
104
|
+
log('[tiny] key: ', key, 'delta: ', delta, 'out: ', out);
|
|
105
|
+
return out;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
getScaledBounds = () => {
|
|
109
|
+
const bounds = opts.bounds(this.props, this.props.graphProps);
|
|
110
|
+
log('bounds: ', bounds);
|
|
111
|
+
const grid = this.grid();
|
|
112
|
+
|
|
113
|
+
let scaled = {
|
|
114
|
+
left: bounds.left * grid.x,
|
|
115
|
+
right: bounds.right * grid.x,
|
|
116
|
+
top: bounds.top * grid.y,
|
|
117
|
+
bottom: bounds.bottom * grid.y,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Normalize Y bounds so that:
|
|
121
|
+
// - top is <= 0 (negative or zero, allowing upward movement)
|
|
122
|
+
// - bottom is >= 0 (positive or zero, allowing downward movement)
|
|
123
|
+
// This compensates for the inverted Y scale (range.max -> 0, range.min -> size.height)
|
|
124
|
+
// Add a small buffer (1 grid unit) to ensure we can reach exact boundaries
|
|
125
|
+
const buffer = Math.abs(grid.y);
|
|
126
|
+
scaled = {
|
|
127
|
+
...scaled,
|
|
128
|
+
top: Math.min(0, scaled.top) - buffer, // More negative to allow reaching max
|
|
129
|
+
bottom: Math.abs(scaled.bottom) + buffer, // More positive to allow reaching min
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
log('[getScaledBounds]: ', scaled);
|
|
133
|
+
return scaled;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Retrieves the coordinates of a mouse or touch event relative to an SVG element.
|
|
138
|
+
* This method has been overwritten from the d3-selection library's clientPoint to handle touch events and improve clarity.
|
|
139
|
+
* @param {Element} node - The SVG element.
|
|
140
|
+
* @param {Event} event - The mouse or touch event.
|
|
141
|
+
* @returns {Array} - An array containing the coordinates [x, y] relative to the SVG element.
|
|
142
|
+
*/
|
|
143
|
+
getClientPoint = (node, event) => {
|
|
144
|
+
if (!node || !event) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const svg = node.ownerSVGElement || node;
|
|
148
|
+
|
|
149
|
+
if (svg && svg.createSVGPoint) {
|
|
150
|
+
let point = svg.createSVGPoint();
|
|
151
|
+
// Check if it's a touch event and use the first touch point
|
|
152
|
+
if (event.touches && event.touches.length > 0) {
|
|
153
|
+
const touch = event.touches[0];
|
|
154
|
+
point.x = touch.clientX;
|
|
155
|
+
point.y = touch.clientY;
|
|
156
|
+
} else {
|
|
157
|
+
// Fall back to mouse event properties
|
|
158
|
+
point.x = event.clientX;
|
|
159
|
+
point.y = event.clientY;
|
|
160
|
+
}
|
|
161
|
+
if (node.getScreenCTM) {
|
|
162
|
+
point = point.matrixTransform(node.getScreenCTM().inverse());
|
|
163
|
+
return [point.x, point.y];
|
|
164
|
+
} else {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const rect = node.getBoundingClientRect();
|
|
170
|
+
if (rect) {
|
|
171
|
+
return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop];
|
|
172
|
+
} else {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
skipDragOutsideOfBounds = (dd, e, graphProps) => {
|
|
178
|
+
// Ignore drag movement outside of the domain and range.
|
|
179
|
+
const rootNode = graphProps.getRootNode();
|
|
180
|
+
const clientPoint = this.getClientPoint(rootNode, e);
|
|
181
|
+
|
|
182
|
+
if (clientPoint === null) {
|
|
183
|
+
return true; // Indicate that the drag is outside of bounds
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const [rawX, rawY] = clientPoint;
|
|
187
|
+
const { scale, domain, range } = graphProps;
|
|
188
|
+
let x = scale.x.invert(rawX);
|
|
189
|
+
let y = scale.y.invert(rawY);
|
|
190
|
+
|
|
191
|
+
const xOutside = (dd.deltaX > 0 && x < domain.min) || (dd.deltaX < 0 && x > domain.max);
|
|
192
|
+
const yOutside = (dd.deltaY > 0 && y > range.max) || (dd.deltaY < 0 && y < range.min);
|
|
193
|
+
return xOutside || yOutside;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
onDrag = (e, dd) => {
|
|
197
|
+
const { onDrag, graphProps, disabled } = this.props;
|
|
198
|
+
|
|
199
|
+
// Track drag movement BEFORE any early returns so that onStop always
|
|
200
|
+
// knows a real drag occurred, even when onDrag prop is absent or disabled.
|
|
201
|
+
if (Math.abs(dd.deltaX) > 1 || Math.abs(dd.deltaY) > 1) {
|
|
202
|
+
this._didDrag = true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!onDrag || disabled) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const bounds = this.getScaledBounds();
|
|
210
|
+
|
|
211
|
+
if (dd.deltaX < 0 && dd.deltaX < bounds.left) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (dd.deltaX > 0 && dd.deltaX > bounds.right) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (dd.deltaY < 0 && dd.deltaY < bounds.top) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (dd.deltaY > 0 && dd.deltaY > bounds.bottom) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (this.skipDragOutsideOfBounds(dd, e, graphProps)) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const dragArg = this.applyDelta({ x: dd.deltaX, y: dd.deltaY });
|
|
232
|
+
|
|
233
|
+
if (dragArg !== undefined || dragArg !== null) {
|
|
234
|
+
onDrag(dragArg);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
getDelta = (point) => {
|
|
239
|
+
const pos = this.position();
|
|
240
|
+
|
|
241
|
+
const p = {
|
|
242
|
+
x: pos.x(point.x),
|
|
243
|
+
y: pos.y(point.y),
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
return utils.getDelta(pos.anchorPoint, p);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
applyDelta = (point) => {
|
|
250
|
+
const delta = this.getDelta(point);
|
|
251
|
+
log('[applyDelta] delta:', delta);
|
|
252
|
+
return opts.fromDelta(this.props, delta);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
onStop = (e, dd) => {
|
|
256
|
+
log('[onStop] dd:', dd);
|
|
257
|
+
const { onDragStop, onClick, disabled } = this.props;
|
|
258
|
+
|
|
259
|
+
if (onDragStop && !disabled) {
|
|
260
|
+
onDragStop();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
log('[onStop] lastX/Y: ', dd.lastX, dd.lastY);
|
|
264
|
+
const isClick = !this._didDrag;
|
|
265
|
+
|
|
266
|
+
if (isClick) {
|
|
267
|
+
// For non-disabled marks, stop propagation so the Bg d3 listener
|
|
268
|
+
// doesn't also create a new mark on top of this one.
|
|
269
|
+
// Disabled/background marks allow propagation so Bg can handle the click.
|
|
270
|
+
if (!disabled && typeof e?.stopPropagation === 'function') {
|
|
271
|
+
e.stopPropagation();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (onClick) {
|
|
275
|
+
log('call onClick');
|
|
276
|
+
this.setState({ startX: null, startY: null });
|
|
277
|
+
const { graphProps } = this.props;
|
|
278
|
+
const { scale, snap } = graphProps;
|
|
279
|
+
try {
|
|
280
|
+
const [rawX, rawY] = pointer(e, e.target);
|
|
281
|
+
let x = scale.x.invert(rawX);
|
|
282
|
+
let y = scale.y.invert(rawY);
|
|
283
|
+
x = snap.x(x);
|
|
284
|
+
y = snap.y(y);
|
|
285
|
+
onClick({ x, y });
|
|
286
|
+
} catch (_) {
|
|
287
|
+
// pointer() can fail on SVG elements (e.g. <circle>) that lack a valid
|
|
288
|
+
// coordinate transform. Label-mode callbacks use props data, not coords.
|
|
289
|
+
onClick({});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
this.setState({ startX: null, startY: null });
|
|
297
|
+
// return false to prevent state updates in the underlying draggable - a move will have triggered an update already.
|
|
298
|
+
return false;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
render() {
|
|
302
|
+
// we extract onClick here to prevent it from being passed to the DraggableCore
|
|
303
|
+
// and to prevent it from being included in the ...rest that gets passed to the Comp
|
|
304
|
+
// because otherwise it is called on every drag event
|
|
305
|
+
// eslint-disable-next-line no-unused-vars
|
|
306
|
+
const { disabled, onClick, ...rest } = this.props;
|
|
307
|
+
const grid = this.grid();
|
|
308
|
+
|
|
309
|
+
// prevent the text select icon from rendering.
|
|
310
|
+
const onMouseDown = (e) => e.nativeEvent.preventDefault();
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* TODO: This shouldnt be necessary, we should be able to use the r-d classnames.
|
|
314
|
+
* But they aren't being unset. If we continue with this lib, we'll have to fix this.
|
|
315
|
+
*/
|
|
316
|
+
const isDragging = this.state ? !!this.state.startX : false;
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<DraggableCore
|
|
320
|
+
onMouseDown={onMouseDown}
|
|
321
|
+
onStart={this.onStart}
|
|
322
|
+
onDrag={this.onDrag}
|
|
323
|
+
onStop={this.onStop}
|
|
324
|
+
axis={opts.axis || 'both'}
|
|
325
|
+
grid={[grid.x, grid.y]}
|
|
326
|
+
>
|
|
327
|
+
<Comp {...rest} disabled={disabled} isDragging={isDragging} onClick={isDragging ? undefined : onClick} />
|
|
328
|
+
</DraggableCore>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import Root from './root';
|
|
2
|
+
import Draggable from './draggable';
|
|
3
|
+
import { gridDraggable } from './grid-draggable';
|
|
4
|
+
import * as utils from './utils';
|
|
5
|
+
import * as trig from './trig';
|
|
6
|
+
import * as types from './types';
|
|
7
|
+
import { create as createGraphProps } from './graph-props';
|
|
8
|
+
|
|
9
|
+
export { Root, Draggable, gridDraggable, utils, trig, types, createGraphProps };
|
package/src/label.jsx
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { styled } from '@mui/material/styles';
|
|
3
|
+
import { Readable } from '@pie-lib/render-ui';
|
|
4
|
+
import EditableHtml from '@pie-lib/editable-html-tip-tap';
|
|
5
|
+
import PropTypes from 'prop-types';
|
|
6
|
+
import { extractTextFromHTML, isEmptyString } from './utils';
|
|
7
|
+
|
|
8
|
+
const styles = {
|
|
9
|
+
axisLabel: {
|
|
10
|
+
fontSize: 12,
|
|
11
|
+
textAlign: 'center',
|
|
12
|
+
margin: 4,
|
|
13
|
+
padding: '4px 0',
|
|
14
|
+
},
|
|
15
|
+
chartLabel: {
|
|
16
|
+
fontSize: 16,
|
|
17
|
+
textAlign: 'center',
|
|
18
|
+
margin: 4,
|
|
19
|
+
padding: '4px 0',
|
|
20
|
+
},
|
|
21
|
+
disabledLabel: {
|
|
22
|
+
pointerEvents: 'none',
|
|
23
|
+
width: '100%',
|
|
24
|
+
},
|
|
25
|
+
editLabel: {
|
|
26
|
+
position: 'absolute',
|
|
27
|
+
backgroundColor: 'white',
|
|
28
|
+
borderRadius: 4,
|
|
29
|
+
boxShadow: '0px 5px 8px rgba(0,0,0,0.15)',
|
|
30
|
+
zIndex: 10,
|
|
31
|
+
},
|
|
32
|
+
rotateLeftLabel: {
|
|
33
|
+
transform: 'rotate(-90deg)',
|
|
34
|
+
transformOrigin: '0 0',
|
|
35
|
+
position: 'absolute',
|
|
36
|
+
},
|
|
37
|
+
rotateRightLabel: {
|
|
38
|
+
transform: 'rotate(90deg)',
|
|
39
|
+
transformOrigin: '0 0',
|
|
40
|
+
position: 'absolute',
|
|
41
|
+
},
|
|
42
|
+
customBottom: {
|
|
43
|
+
position: 'absolute',
|
|
44
|
+
},
|
|
45
|
+
displayNone: {
|
|
46
|
+
display: 'none',
|
|
47
|
+
},
|
|
48
|
+
centerPlaceholder: {
|
|
49
|
+
'& .ProseMirror p.is-editor-empty::before, & .ProseMirror div.is-editor-empty::before': {
|
|
50
|
+
left: 0,
|
|
51
|
+
right: 0,
|
|
52
|
+
width: '100%',
|
|
53
|
+
textAlign: 'center',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
const LabelWrapper = styled('div')({
|
|
60
|
+
...styles.centerPlaceholder,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const LabelContent = styled('div')({
|
|
64
|
+
...styles.disabledLabel,
|
|
65
|
+
'& p': {
|
|
66
|
+
margin: 0,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const LabelComponent = (props) => {
|
|
71
|
+
const {
|
|
72
|
+
disabledLabel,
|
|
73
|
+
graphHeight,
|
|
74
|
+
graphWidth,
|
|
75
|
+
isChartBottomLabel,
|
|
76
|
+
isDefineChartBottomLabel,
|
|
77
|
+
isChartLeftLabel,
|
|
78
|
+
isDefineChartLeftLabel,
|
|
79
|
+
placeholder,
|
|
80
|
+
text,
|
|
81
|
+
side,
|
|
82
|
+
onChange,
|
|
83
|
+
mathMlOptions = {},
|
|
84
|
+
charactersLimit,
|
|
85
|
+
titleHeight,
|
|
86
|
+
preventNewLines,
|
|
87
|
+
} = props;
|
|
88
|
+
|
|
89
|
+
const [rotatedToHorizontal, setRotatedToHorizontal] = useState(false);
|
|
90
|
+
|
|
91
|
+
const activePlugins = ['bold', 'italic', 'underline', 'strikethrough', 'math'];
|
|
92
|
+
|
|
93
|
+
const isChart = isChartBottomLabel || isChartLeftLabel || isDefineChartBottomLabel || isDefineChartLeftLabel;
|
|
94
|
+
|
|
95
|
+
const chartValue = side === 'left' && isDefineChartLeftLabel && graphHeight - 220;
|
|
96
|
+
|
|
97
|
+
const defaultStyle = {
|
|
98
|
+
width: chartValue || (side === 'left' || side === 'right' ? graphHeight - 8 : graphWidth - 8),
|
|
99
|
+
top:
|
|
100
|
+
chartValue ||
|
|
101
|
+
(isChartLeftLabel && `${graphHeight - 70}px`) ||
|
|
102
|
+
(side === 'left' && `${graphHeight - 8}px`) ||
|
|
103
|
+
(isChartBottomLabel && `${graphHeight - 60 + titleHeight}px`) ||
|
|
104
|
+
(side === 'bottom' && `${graphHeight - 120 + titleHeight}px`) ||
|
|
105
|
+
0,
|
|
106
|
+
left:
|
|
107
|
+
(side === 'right' && `${graphWidth - 8}px`) ||
|
|
108
|
+
((isDefineChartLeftLabel || isDefineChartBottomLabel) && '40px') ||
|
|
109
|
+
(isChartBottomLabel && '-10px') ||
|
|
110
|
+
0,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const rotatedStyle = {
|
|
114
|
+
width: graphWidth - 8,
|
|
115
|
+
top: side === 'right' ? `${graphHeight - 22}px` : 0,
|
|
116
|
+
left: 0,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const rotateLabel = () => {
|
|
120
|
+
if (!disabledLabel && (side === 'left' || side === 'right')) {
|
|
121
|
+
setRotatedToHorizontal(true);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const exitEditMode = () => {
|
|
126
|
+
setRotatedToHorizontal(false);
|
|
127
|
+
|
|
128
|
+
// blur active element because rotation is causing editing issues on exit
|
|
129
|
+
requestAnimationFrame(() => {
|
|
130
|
+
document.activeElement?.blur?.();
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const onKeyDown = (event) => {
|
|
135
|
+
if (preventNewLines && event.key === 'Enter') {
|
|
136
|
+
// prevent adding new lines - cancelling event
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return false;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<Readable false>
|
|
145
|
+
<LabelWrapper
|
|
146
|
+
onClick={rotateLabel}
|
|
147
|
+
style={{
|
|
148
|
+
...(rotatedToHorizontal ? rotatedStyle : defaultStyle),
|
|
149
|
+
...(isChart ? styles.chartLabel : styles.axisLabel),
|
|
150
|
+
...(side === 'left' && !rotatedToHorizontal ? styles.rotateLeftLabel : {}),
|
|
151
|
+
...(side === 'right' && !rotatedToHorizontal ? styles.rotateRightLabel : {}),
|
|
152
|
+
...(rotatedToHorizontal ? styles.editLabel : {}),
|
|
153
|
+
...(isChartBottomLabel || isDefineChartBottomLabel ? styles.customBottom : {}),
|
|
154
|
+
...(disabledLabel && !isChart && isEmptyString(extractTextFromHTML(text)) && styles.displayNone),
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
{disabledLabel ? (
|
|
158
|
+
<LabelContent dangerouslySetInnerHTML={{ __html: text || '' }} />
|
|
159
|
+
) : (
|
|
160
|
+
<EditableHtml
|
|
161
|
+
markup={text || ''}
|
|
162
|
+
onChange={onChange}
|
|
163
|
+
placeholder={!disabledLabel && placeholder}
|
|
164
|
+
toolbarOpts={{
|
|
165
|
+
position: side === 'bottom' ? 'top' : 'bottom',
|
|
166
|
+
noPadding: true,
|
|
167
|
+
noBorder: true,
|
|
168
|
+
}}
|
|
169
|
+
disableScrollbar
|
|
170
|
+
activePlugins={activePlugins}
|
|
171
|
+
onDone={exitEditMode}
|
|
172
|
+
onKeyDown={onKeyDown}
|
|
173
|
+
mathMlOptions={mathMlOptions}
|
|
174
|
+
charactersLimit={charactersLimit}
|
|
175
|
+
/>
|
|
176
|
+
)}
|
|
177
|
+
</LabelWrapper>
|
|
178
|
+
</Readable>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
LabelComponent.propTypes = {
|
|
183
|
+
disabledLabel: PropTypes.bool,
|
|
184
|
+
graphHeight: PropTypes.number,
|
|
185
|
+
graphWidth: PropTypes.number,
|
|
186
|
+
isChartBottomLabel: PropTypes.bool,
|
|
187
|
+
isDefineChartBottomLabel: PropTypes.bool,
|
|
188
|
+
isChartLeftLabel: PropTypes.bool,
|
|
189
|
+
isDefineChartLeftLabel: PropTypes.bool,
|
|
190
|
+
placeholder: PropTypes.string,
|
|
191
|
+
text: PropTypes.string,
|
|
192
|
+
side: PropTypes.string,
|
|
193
|
+
onChange: PropTypes.func,
|
|
194
|
+
mathMlOptions: PropTypes.object,
|
|
195
|
+
charactersLimit: PropTypes.number,
|
|
196
|
+
titleHeight: PropTypes.number,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export default LabelComponent;
|