@pie-lib/plot 2.23.1-next.0 → 2.24.1

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/esm/index.js ADDED
@@ -0,0 +1,1278 @@
1
+ import React, { useState } from 'react';
2
+ import { withStyles } from '@material-ui/core/styles';
3
+ import PropTypes from 'prop-types';
4
+ import { mouse, select, clientPoint } from 'd3-selection';
5
+ import cn from 'classnames';
6
+ import { color, Readable } from '@pie-lib/render-ui';
7
+ import EditableHtml from '@pie-lib/editable-html';
8
+ import invariant from 'invariant';
9
+ import range from 'lodash/range';
10
+ import Point from '@mapbox/point-geometry';
11
+ import head from 'lodash/head';
12
+ import tail from 'lodash/tail';
13
+ import isEqual from 'lodash/isEqual';
14
+ import Draggable, { DraggableCore } from 'react-draggable';
15
+ import debug from 'debug';
16
+ import isFunction from 'lodash/isFunction';
17
+ import { scaleLinear } from 'd3-scale';
18
+
19
+ function _extends() {
20
+ _extends = Object.assign || function (target) {
21
+ for (var i = 1; i < arguments.length; i++) {
22
+ var source = arguments[i];
23
+
24
+ for (var key in source) {
25
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
26
+ target[key] = source[key];
27
+ }
28
+ }
29
+ }
30
+
31
+ return target;
32
+ };
33
+
34
+ return _extends.apply(this, arguments);
35
+ }
36
+
37
+ function _objectWithoutPropertiesLoose(source, excluded) {
38
+ if (source == null) return {};
39
+ var target = {};
40
+ var sourceKeys = Object.keys(source);
41
+ var key, i;
42
+
43
+ for (i = 0; i < sourceKeys.length; i++) {
44
+ key = sourceKeys[i];
45
+ if (excluded.indexOf(key) >= 0) continue;
46
+ target[key] = source[key];
47
+ }
48
+
49
+ return target;
50
+ }
51
+
52
+ const BaseDomainRangeType = {
53
+ min: PropTypes.number.isRequired,
54
+ max: PropTypes.number.isRequired,
55
+ step: PropTypes.number
56
+ };
57
+ const DomainType = PropTypes.shape(BaseDomainRangeType);
58
+ const RangeType = PropTypes.shape(BaseDomainRangeType);
59
+ const SizeType = PropTypes.shape({
60
+ width: PropTypes.number.isRequired,
61
+ height: PropTypes.number.isRequired
62
+ });
63
+ const PointType = PropTypes.shape({
64
+ x: PropTypes.number.isRequired,
65
+ y: PropTypes.number.isRequired
66
+ });
67
+ const ChildrenType = PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired;
68
+ const ScaleType = PropTypes.shape({
69
+ x: PropTypes.func.isRequired,
70
+ y: PropTypes.func.isRequired
71
+ });
72
+ const SnapType = PropTypes.shape({
73
+ x: PropTypes.func.isRequired,
74
+ y: PropTypes.func.isRequired
75
+ });
76
+ const GraphPropsType = PropTypes.shape({
77
+ scale: ScaleType.isRequired,
78
+ snap: SnapType.isRequired,
79
+ domain: DomainType.isRequired,
80
+ range: RangeType.isRequired,
81
+ size: SizeType.isRequired
82
+ });
83
+
84
+ var types = /*#__PURE__*/Object.freeze({
85
+ __proto__: null,
86
+ BaseDomainRangeType: BaseDomainRangeType,
87
+ ChildrenType: ChildrenType,
88
+ DomainType: DomainType,
89
+ GraphPropsType: GraphPropsType,
90
+ PointType: PointType,
91
+ RangeType: RangeType,
92
+ ScaleType: ScaleType,
93
+ SizeType: SizeType,
94
+ SnapType: SnapType
95
+ });
96
+
97
+ const xy = (x, y) => ({
98
+ x,
99
+ y
100
+ });
101
+ const buildSizeArray = (size, padding) => {
102
+ padding = Number.isFinite(padding) ? Math.max(padding, 0) : 0;
103
+ invariant(padding < size, 'padding must be less than size');
104
+ const half = Math.round(padding * 0.5);
105
+ return [half, size - half];
106
+ };
107
+ const tickCount = (min, max, step) => {
108
+ invariant(min < max, 'min must be less than max');
109
+ const size = Math.abs(min - max);
110
+ return Math.round(size / step);
111
+ };
112
+ function getInterval(domain, ticks) {
113
+ const {
114
+ min,
115
+ max
116
+ } = domain;
117
+ const {
118
+ major,
119
+ minor
120
+ } = ticks;
121
+
122
+ if (min >= max) {
123
+ throw new Error(`min is > max: ${min} > ${max}`);
124
+ }
125
+
126
+ const distance = max - min;
127
+ const minorTicks = minor > 0 ? minor + 1 : 1;
128
+ const normalizedMajor = major - 1;
129
+
130
+ if (isNaN(normalizedMajor)) {
131
+ throw new Error('Tick Frequency must be 2 or higher');
132
+ }
133
+
134
+ if (normalizedMajor <= 0) {
135
+ throw new Error('Tick Frequency must be 2 or higher');
136
+ }
137
+
138
+ const divider = normalizedMajor * minorTicks;
139
+ const raw = distance / divider;
140
+ return parseFloat(Number(raw).toFixed(4));
141
+ }
142
+
143
+ const mkRange = (min, max, interval) => {
144
+ const raw = range(min, max, interval);
145
+ /* Fix the last step due to rounding errors */
146
+
147
+ raw.splice(raw.length, 1, max);
148
+ return raw;
149
+ };
150
+
151
+ function snapTo(min, max, interval, value) {
152
+ if (value >= max) {
153
+ return max;
154
+ }
155
+
156
+ if (value <= min) {
157
+ return min;
158
+ }
159
+
160
+ let rng = mkRange(min, max, interval);
161
+ rng = rng.filter(v => {
162
+ return Math.abs(value - v) <= interval;
163
+ });
164
+ return rng.length && rng.reduce((prev, curr) => {
165
+ const currentDistance = Math.abs(curr - value);
166
+ const previousDistance = Math.abs(prev - value);
167
+ return currentDistance <= previousDistance ? curr : prev;
168
+ });
169
+ }
170
+ function buildTickModel(domain, ticks, interval, scaleFn) {
171
+ const rng = mkRange(domain.min, domain.max, interval);
172
+ return rng.map((r, index) => {
173
+ const isMajor = index % (ticks.minor + 1) === 0;
174
+ return {
175
+ value: r,
176
+ major: isMajor,
177
+ x: scaleFn(r)
178
+ };
179
+ });
180
+ }
181
+ const polygonToArea = points => {
182
+ const h = head(points);
183
+ const area = {
184
+ left: h.x,
185
+ top: h.y,
186
+ bottom: h.y,
187
+ right: h.x
188
+ };
189
+ return tail(points).reduce((a, p) => {
190
+ a.left = Math.min(a.left, p.x);
191
+ a.top = Math.max(a.top, p.y);
192
+ a.bottom = Math.min(a.bottom, p.y);
193
+ a.right = Math.max(a.right, p.x);
194
+ return a;
195
+ }, area);
196
+ };
197
+ const lineToArea = (from, to) => {
198
+ const left = Math.min(from.x, to.x);
199
+ const top = Math.max(from.y, to.y);
200
+ const bottom = Math.min(from.y, to.y);
201
+ const right = Math.max(from.x, to.x);
202
+ return {
203
+ left,
204
+ top,
205
+ bottom,
206
+ right
207
+ };
208
+ };
209
+ const bounds = (area, domain, range) => {
210
+ return {
211
+ left: domain.min - area.left,
212
+ right: Math.abs(area.right - domain.max),
213
+ top: Math.abs(area.top - range.max),
214
+ bottom: range.min - area.bottom
215
+ };
216
+ };
217
+ const point = o => new Point(o.x, o.y);
218
+ const getDelta = (from, to) => {
219
+ return point(to).sub(point(from));
220
+ };
221
+ const bandKey = (d, index) => `${index}-${d.label || '-'}`;
222
+ const isDomainRangeEqual = (graphProps, nextGraphProps) => {
223
+ return isEqual(graphProps.domain, nextGraphProps.domain) && isEqual(graphProps.range, nextGraphProps.range);
224
+ }; // findLongestWord is also used in graphing
225
+
226
+ const findLongestWord = label => {
227
+ let longestWord = (label || '').replace(/<[^>]+>/g, '').split(' ').sort((a, b) => b.length - a.length);
228
+ return longestWord[0].length;
229
+ }; // amountToIncreaseWidth is also used in graphing
230
+
231
+ const amountToIncreaseWidth = longestWord => {
232
+ if (!longestWord) {
233
+ return 0;
234
+ }
235
+
236
+ return longestWord * 20;
237
+ };
238
+ const extractTextFromHTML = htmlString => {
239
+ var _doc$body;
240
+
241
+ const parser = new DOMParser();
242
+ const doc = parser == null ? void 0 : parser.parseFromString(htmlString, 'text/html');
243
+ return (doc == null ? void 0 : (_doc$body = doc.body) == null ? void 0 : _doc$body.textContent) || '';
244
+ };
245
+ const isEmptyObject = obj => {
246
+ return obj && Object.keys(obj).length === 0 && obj.constructor === Object;
247
+ };
248
+ const isEmptyString = str => {
249
+ return typeof str === 'string' && str.trim() === '';
250
+ };
251
+
252
+ var utils = /*#__PURE__*/Object.freeze({
253
+ __proto__: null,
254
+ amountToIncreaseWidth: amountToIncreaseWidth,
255
+ bandKey: bandKey,
256
+ bounds: bounds,
257
+ buildSizeArray: buildSizeArray,
258
+ buildTickModel: buildTickModel,
259
+ extractTextFromHTML: extractTextFromHTML,
260
+ findLongestWord: findLongestWord,
261
+ getDelta: getDelta,
262
+ getInterval: getInterval,
263
+ isDomainRangeEqual: isDomainRangeEqual,
264
+ isEmptyObject: isEmptyObject,
265
+ isEmptyString: isEmptyString,
266
+ lineToArea: lineToArea,
267
+ point: point,
268
+ polygonToArea: polygonToArea,
269
+ snapTo: snapTo,
270
+ tickCount: tickCount,
271
+ xy: xy
272
+ });
273
+
274
+ const LabelComponent = props => {
275
+ const {
276
+ classes,
277
+ disabledLabel,
278
+ graphHeight,
279
+ graphWidth,
280
+ isChartBottomLabel,
281
+ isDefineChartBottomLabel,
282
+ isChartLeftLabel,
283
+ isDefineChartLeftLabel,
284
+ placeholder,
285
+ text,
286
+ side,
287
+ onChange,
288
+ mathMlOptions = {},
289
+ charactersLimit,
290
+ titleHeight
291
+ } = props;
292
+ const [rotatedToHorizontal, setRotatedToHorizontal] = useState(false);
293
+ const activePlugins = ['bold', 'italic', 'underline', 'strikethrough', 'math' // 'languageCharacters'
294
+ ];
295
+ const isChart = isChartBottomLabel || isChartLeftLabel || isDefineChartBottomLabel || isDefineChartLeftLabel;
296
+ const chartValue = side === 'left' && isDefineChartLeftLabel && graphHeight - 220;
297
+ const defaultStyle = {
298
+ width: chartValue || (side === 'left' || side === 'right' ? graphHeight - 8 : graphWidth - 8),
299
+ top: chartValue || isChartLeftLabel && `${graphHeight - 70}px` || side === 'left' && `${graphHeight - 8}px` || isChartBottomLabel && `${graphHeight - 60 + titleHeight}px` || side === 'bottom' && `${graphHeight - 120 + titleHeight}px` || 0,
300
+ left: side === 'right' && `${graphWidth - 8}px` || (isDefineChartLeftLabel || isDefineChartBottomLabel) && '40px' || isChartBottomLabel && '-10px' || 0
301
+ };
302
+ const rotatedStyle = {
303
+ width: graphWidth - 8,
304
+ top: side === 'right' && `${graphHeight - 22}px` || 0,
305
+ left: 0
306
+ };
307
+
308
+ const rotateLabel = () => !disabledLabel && (side === 'left' || side === 'right') && setRotatedToHorizontal(true);
309
+
310
+ return /*#__PURE__*/React.createElement(Readable, {
311
+ false: true
312
+ }, /*#__PURE__*/React.createElement("div", {
313
+ className: cn(isChart ? classes.chartLabel : classes.axisLabel, {
314
+ [classes.rotateLeftLabel]: side === 'left' && !rotatedToHorizontal,
315
+ [classes.rotateRightLabel]: side === 'right' && !rotatedToHorizontal,
316
+ [classes.editLabel]: rotatedToHorizontal,
317
+ [classes.customBottom]: isChartBottomLabel || isDefineChartBottomLabel,
318
+ [classes.displayNone]: disabledLabel && !isChart && isEmptyString(extractTextFromHTML(text))
319
+ }),
320
+ style: rotatedToHorizontal ? rotatedStyle : defaultStyle,
321
+ onClick: rotateLabel
322
+ }, disabledLabel ? /*#__PURE__*/React.createElement("div", {
323
+ className: classes.disabledLabel,
324
+ dangerouslySetInnerHTML: {
325
+ __html: text || ''
326
+ }
327
+ }) : /*#__PURE__*/React.createElement(EditableHtml, {
328
+ markup: text || '',
329
+ onChange: onChange,
330
+ placeholder: !disabledLabel && placeholder,
331
+ toolbarOpts: {
332
+ position: side === 'bottom' ? 'top' : 'bottom',
333
+ noPadding: true,
334
+ noBorder: true
335
+ },
336
+ disableScrollbar: true,
337
+ activePlugins: activePlugins,
338
+ onDone: () => setRotatedToHorizontal(false),
339
+ mathMlOptions: mathMlOptions,
340
+ charactersLimit: charactersLimit
341
+ })));
342
+ };
343
+
344
+ LabelComponent.propTypes = {
345
+ classes: PropTypes.object,
346
+ disabledLabel: PropTypes.bool,
347
+ graphHeight: PropTypes.number,
348
+ graphWidth: PropTypes.number,
349
+ isChartBottomLabel: PropTypes.bool,
350
+ isDefineChartBottomLabel: PropTypes.bool,
351
+ isChartLeftLabel: PropTypes.bool,
352
+ isDefineChartLeftLabel: PropTypes.bool,
353
+ placeholder: PropTypes.string,
354
+ text: PropTypes.string,
355
+ side: PropTypes.string,
356
+ onChange: PropTypes.func,
357
+ mathMlOptions: PropTypes.object,
358
+ charactersLimit: PropTypes.number,
359
+ titleHeight: PropTypes.number
360
+ };
361
+ var Label = withStyles(theme => ({
362
+ label: {
363
+ fill: color.secondary()
364
+ },
365
+ axisLabel: {
366
+ fontSize: theme.typography.fontSize - 2,
367
+ textAlign: 'center',
368
+ margin: theme.spacing.unit / 2,
369
+ padding: `${theme.spacing.unit / 2}px 0`
370
+ },
371
+ chartLabel: {
372
+ fontSize: theme.typography.fontSize + 2,
373
+ textAlign: 'center',
374
+ margin: theme.spacing.unit / 2,
375
+ padding: `${theme.spacing.unit / 2}px 0`
376
+ },
377
+ disabledLabel: {
378
+ pointerEvents: 'none',
379
+ width: '100%'
380
+ },
381
+ editLabel: {
382
+ position: 'absolute',
383
+ backgroundColor: 'white',
384
+ borderRadius: '4px',
385
+ boxShadow: '0px 5px 8px rgba(0, 0, 0, 0.15)',
386
+ zIndex: 10
387
+ },
388
+ rotateLeftLabel: {
389
+ '-webkit-transform': 'rotate(-90deg)',
390
+ transformOrigin: '0 0',
391
+ transformStyle: 'preserve-3d',
392
+ position: 'absolute'
393
+ },
394
+ rotateRightLabel: {
395
+ '-webkit-transform': 'rotate(90deg)',
396
+ transformOrigin: '0 0',
397
+ transformStyle: 'preserve-3d',
398
+ position: 'absolute'
399
+ },
400
+ customBottom: {
401
+ position: 'absolute'
402
+ },
403
+ displayNone: {
404
+ display: 'none'
405
+ }
406
+ }))(LabelComponent);
407
+
408
+ class Root extends React.Component {
409
+ constructor(props) {
410
+ super(props);
411
+
412
+ this.mouseMove = g => {
413
+ const {
414
+ graphProps,
415
+ onMouseMove
416
+ } = this.props;
417
+
418
+ if (!onMouseMove) {
419
+ return;
420
+ }
421
+
422
+ const {
423
+ scale,
424
+ snap
425
+ } = graphProps;
426
+ const coords = mouse(g._groups[0][0]);
427
+ const x = scale.x.invert(coords[0]);
428
+ const y = scale.y.invert(coords[1]);
429
+ const snapped = {
430
+ x: snap.x(x),
431
+ y: snap.y(y)
432
+ };
433
+ onMouseMove(snapped);
434
+ };
435
+
436
+ this.onChangeLabel = (newValue, side) => {
437
+ const {
438
+ labels,
439
+ onChangeLabels,
440
+ isChart
441
+ } = this.props;
442
+
443
+ if (!onChangeLabels) {
444
+ return;
445
+ }
446
+
447
+ if (isChart) {
448
+ if (side === 'left') {
449
+ onChangeLabels('range', newValue);
450
+ } else {
451
+ onChangeLabels('domain', newValue);
452
+ }
453
+
454
+ return;
455
+ }
456
+
457
+ onChangeLabels(_extends({}, labels, {
458
+ [side]: newValue
459
+ }));
460
+ };
461
+
462
+ this.measureTitleHeight = () => {
463
+ const titleElement = this.titleRef;
464
+
465
+ if (titleElement) {
466
+ const titleHeight = titleElement.clientHeight;
467
+ this.setState({
468
+ titleHeight,
469
+ prevTitle: this.props.title
470
+ });
471
+ }
472
+ };
473
+
474
+ this.handleKeyDown = () => {
475
+ setTimeout(this.measureTitleHeight, 0);
476
+ };
477
+
478
+ this.state = {
479
+ titleHeight: 0
480
+ };
481
+ }
482
+
483
+ componentDidMount() {
484
+ const g = select(this.g);
485
+ g.on('mousemove', this.mouseMove.bind(this, g));
486
+ this.measureTitleHeight();
487
+ }
488
+
489
+ componentWillUnmount() {
490
+ const g = select(this.g);
491
+ g.on('mousemove', null);
492
+ }
493
+
494
+ componentDidUpdate(prevProps) {
495
+ if (prevProps.title !== this.props.title) {
496
+ this.measureTitleHeight();
497
+ }
498
+ }
499
+
500
+ render() {
501
+ const {
502
+ disabledTitle,
503
+ disabledLabels,
504
+ labels,
505
+ labelsPlaceholders,
506
+ titlePlaceholder,
507
+ graphProps,
508
+ children,
509
+ classes,
510
+ defineChart,
511
+ onChangeTitle,
512
+ isChart,
513
+ showLabels,
514
+ showPixelGuides,
515
+ showTitle,
516
+ title,
517
+ rootRef,
518
+ mathMlOptions = {},
519
+ labelsCharactersLimit
520
+ } = this.props;
521
+ const {
522
+ size: {
523
+ width = 500,
524
+ height = 500
525
+ },
526
+ domain,
527
+ range
528
+ } = graphProps;
529
+ const topPadding = 40;
530
+ const leftPadding = isEmptyString(extractTextFromHTML(labels == null ? void 0 : labels.left)) && isEmptyObject(labelsPlaceholders) ? 48 : 70;
531
+ const rightPadding = isEmptyString(extractTextFromHTML(labels == null ? void 0 : labels.right)) && isEmptyObject(labelsPlaceholders) ? 48 : 70;
532
+ const finalWidth = width + leftPadding + rightPadding + (domain.padding || 0) * 2;
533
+ const finalHeight = height + topPadding * 2 + (range.padding || 0) * 2;
534
+ const activeTitlePlugins = ['bold', 'italic', 'underline', 'superscript', 'subscript', 'strikethrough', 'math' // 'languageCharacters'
535
+ ];
536
+ const actualHeight = defineChart && showPixelGuides ? height - 160 : height;
537
+ const nbOfVerticalLines = parseInt(width / 100);
538
+ const nbOfHorizontalLines = parseInt(actualHeight / 100);
539
+ const sideGridlinesPadding = parseInt(actualHeight % 100);
540
+ const {
541
+ titleHeight
542
+ } = this.state;
543
+ return /*#__PURE__*/React.createElement("div", {
544
+ className: classes.root
545
+ }, showPixelGuides && /*#__PURE__*/React.createElement("div", {
546
+ className: classes.topPixelGuides,
547
+ style: {
548
+ marginLeft: isChart ? 80 : showLabels ? 30 : 10
549
+ }
550
+ }, [...Array(nbOfVerticalLines + 1).keys()].map(value => /*#__PURE__*/React.createElement(Readable, {
551
+ false: true,
552
+ key: `top-guide-${value}`
553
+ }, /*#__PURE__*/React.createElement("div", {
554
+ className: classes.topPixelIndicator
555
+ }, /*#__PURE__*/React.createElement("div", null, value * 100, "px"), /*#__PURE__*/React.createElement("div", null, "|"))))), showTitle && (disabledTitle ? /*#__PURE__*/React.createElement("div", {
556
+ ref: r => this.titleRef = r,
557
+ style: _extends({}, isChart && {
558
+ width: finalWidth
559
+ }, isEmptyString(extractTextFromHTML(title)) && {
560
+ display: 'none'
561
+ }),
562
+ className: cn(isChart ? classes.chartTitle : classes.graphTitle, classes.disabledTitle),
563
+ dangerouslySetInnerHTML: {
564
+ __html: title || ''
565
+ }
566
+ }) : /*#__PURE__*/React.createElement("div", {
567
+ ref: r => this.titleRef = r
568
+ }, /*#__PURE__*/React.createElement(EditableHtml, {
569
+ style: isChart && {
570
+ width: finalWidth
571
+ },
572
+ className: cn({
573
+ [classes.rightMargin]: showPixelGuides
574
+ }, isChart ? classes.chartTitle : classes.graphTitle),
575
+ markup: title || '',
576
+ onChange: onChangeTitle,
577
+ placeholder: defineChart && titlePlaceholder || !disabledTitle && 'Click here to add a title for this graph',
578
+ toolbarOpts: {
579
+ noPadding: true,
580
+ noBorder: true
581
+ },
582
+ activePlugins: activeTitlePlugins,
583
+ disableScrollbar: true,
584
+ onKeyDown: this.handleKeyDown
585
+ }))), showLabels && !isChart && /*#__PURE__*/React.createElement(Label, {
586
+ side: "top",
587
+ text: labels.top,
588
+ disabledLabel: disabledLabels,
589
+ placeholder: labelsPlaceholders == null ? void 0 : labelsPlaceholders.top,
590
+ graphHeight: finalHeight,
591
+ graphWidth: finalWidth,
592
+ onChange: value => this.onChangeLabel(value, 'top'),
593
+ mathMlOptions: mathMlOptions,
594
+ charactersLimit: labelsCharactersLimit
595
+ }), /*#__PURE__*/React.createElement("div", {
596
+ className: classes.wrapper
597
+ }, showLabels && /*#__PURE__*/React.createElement(Label, {
598
+ side: "left",
599
+ text: labels.left,
600
+ disabledLabel: disabledLabels,
601
+ placeholder: labelsPlaceholders == null ? void 0 : labelsPlaceholders.left,
602
+ graphHeight: finalHeight,
603
+ graphWidth: finalWidth,
604
+ isChartLeftLabel: isChart && !defineChart,
605
+ isDefineChartLeftLabel: isChart && defineChart,
606
+ onChange: value => this.onChangeLabel(value, 'left'),
607
+ mathMlOptions: mathMlOptions,
608
+ charactersLimit: labelsCharactersLimit
609
+ }), /*#__PURE__*/React.createElement("svg", {
610
+ width: finalWidth,
611
+ height: finalHeight,
612
+ className: defineChart ? classes.defineChart : classes.chart
613
+ }, /*#__PURE__*/React.createElement("g", {
614
+ ref: r => {
615
+ this.g = r;
616
+
617
+ if (rootRef) {
618
+ rootRef(r);
619
+ }
620
+ },
621
+ className: classes.graphBox,
622
+ transform: `translate(${leftPadding + (domain.padding || 0)}, ${topPadding + (range.padding || 0)})`
623
+ }, children)), showLabels && !isChart && /*#__PURE__*/React.createElement(Label, {
624
+ side: "right",
625
+ text: labels.right,
626
+ disabledLabel: disabledLabels,
627
+ placeholder: labelsPlaceholders == null ? void 0 : labelsPlaceholders.right,
628
+ graphHeight: finalHeight,
629
+ graphWidth: finalWidth,
630
+ onChange: value => this.onChangeLabel(value, 'right'),
631
+ mathMlOptions: mathMlOptions,
632
+ charactersLimit: labelsCharactersLimit
633
+ }), showPixelGuides && /*#__PURE__*/React.createElement("div", {
634
+ className: classes.sidePixelGuides,
635
+ style: {
636
+ paddingTop: sideGridlinesPadding,
637
+ marginTop: 31
638
+ }
639
+ }, [...Array(nbOfHorizontalLines + 1).keys()].reverse().map(value => /*#__PURE__*/React.createElement(Readable, {
640
+ false: true,
641
+ key: `top-guide-${value}`
642
+ }, /*#__PURE__*/React.createElement("div", {
643
+ className: classes.sidePixelIndicator
644
+ }, "\u2501 ", value * 100, "px"))))), showLabels && /*#__PURE__*/React.createElement(Label, {
645
+ side: "bottom",
646
+ text: labels.bottom,
647
+ disabledLabel: disabledLabels,
648
+ placeholder: labelsPlaceholders == null ? void 0 : labelsPlaceholders.bottom,
649
+ graphHeight: finalHeight,
650
+ graphWidth: finalWidth,
651
+ titleHeight: titleHeight,
652
+ isChartBottomLabel: isChart && !defineChart,
653
+ isDefineChartBottomLabel: isChart && defineChart,
654
+ onChange: value => this.onChangeLabel(value, 'bottom'),
655
+ mathMlOptions: mathMlOptions,
656
+ charactersLimit: labelsCharactersLimit
657
+ }));
658
+ }
659
+
660
+ } // use default color theme style to avoid color contrast issues
661
+
662
+ Root.propTypes = {
663
+ title: PropTypes.string,
664
+ children: ChildrenType,
665
+ defineChart: PropTypes.bool,
666
+ disabledLabels: PropTypes.bool,
667
+ disabledTitle: PropTypes.bool,
668
+ graphProps: GraphPropsType.isRequired,
669
+ isChart: PropTypes.bool,
670
+ labels: PropTypes.object,
671
+ labelsPlaceholders: PropTypes.object,
672
+ onChangeTitle: PropTypes.func,
673
+ onMouseMove: PropTypes.func,
674
+ classes: PropTypes.object.isRequired,
675
+ showLabels: PropTypes.bool,
676
+ showTitle: PropTypes.bool,
677
+ showPixelGuides: PropTypes.bool,
678
+ rootRef: PropTypes.func,
679
+ onChangeLabels: PropTypes.func,
680
+ titlePlaceholder: PropTypes.string,
681
+ mathMlOptions: PropTypes.object,
682
+ labelsCharactersLimit: PropTypes.number
683
+ };
684
+
685
+ const styles = theme => ({
686
+ root: {
687
+ border: `solid 1px ${color.primaryLight()}`,
688
+ color: color.defaults.TEXT,
689
+ backgroundColor: theme.palette.common.white,
690
+ touchAction: 'none',
691
+ position: 'relative',
692
+ boxSizing: 'unset' // to override the default border-box in IBX that breaks the component width layout
693
+
694
+ },
695
+ wrapper: {
696
+ display: 'flex',
697
+ position: 'relative'
698
+ },
699
+ svg: {},
700
+ defineChart: {
701
+ paddingLeft: '50px',
702
+ overflow: 'visible'
703
+ },
704
+ chart: {
705
+ overflow: 'visible'
706
+ },
707
+ graphBox: {
708
+ cursor: 'pointer',
709
+ userSelect: 'none'
710
+ },
711
+ graphTitle: {
712
+ color: color.defaults.TEXT,
713
+ fontSize: theme.typography.fontSize + 2,
714
+ padding: `${theme.spacing.unit * 1.5}px ${theme.spacing.unit / 2}px 0`,
715
+ textAlign: 'center'
716
+ },
717
+ chartTitle: {
718
+ color: color.defaults.TEXT,
719
+ fontSize: theme.typography.fontSize + 4,
720
+ padding: `${theme.spacing.unit * 1.5}px ${theme.spacing.unit / 2}px 0`,
721
+ textAlign: 'center'
722
+ },
723
+ disabledTitle: {
724
+ pointerEvents: 'none'
725
+ },
726
+ rightMargin: {
727
+ marginRight: '74px'
728
+ },
729
+ topPixelGuides: {
730
+ display: 'flex',
731
+ paddingTop: '6px'
732
+ },
733
+ topPixelIndicator: {
734
+ display: 'flex',
735
+ flexDirection: 'column',
736
+ alignItems: 'center',
737
+ width: '100px',
738
+ pointerEvents: 'none',
739
+ userSelect: 'none'
740
+ },
741
+ sidePixelGuides: {
742
+ width: '70px',
743
+ display: 'flex',
744
+ flexDirection: 'column',
745
+ marginRight: '6px'
746
+ },
747
+ sidePixelIndicator: {
748
+ textAlign: 'right',
749
+ height: '20px',
750
+ pointerEvents: 'none',
751
+ userSelect: 'none',
752
+ '&:not(:last-child)': {
753
+ marginBottom: '80px'
754
+ }
755
+ }
756
+ });
757
+
758
+ var root = withStyles(styles)(Root);
759
+
760
+ class LocalDraggable extends Draggable {
761
+ componentWillReceiveProps(next) {
762
+ super.componentWillReceiveProps(next); //Remove the x/y state as these values have now been updated and will come through as props.
763
+
764
+ this.setState({
765
+ x: 0,
766
+ y: 0
767
+ });
768
+ }
769
+
770
+ }
771
+
772
+ const _excluded = ["disabled"];
773
+ const log$1 = debug('pie-lib:plot:grid-draggable');
774
+ const deltaFn = (scale, snap, val) => delta => {
775
+ const normalized = delta + scale(0);
776
+ const inverted = scale.invert(normalized);
777
+ const fixDecimalsArithmetic = snap(val + inverted).toFixed(4) * 1000 / 1000;
778
+ return fixDecimalsArithmetic;
779
+ };
780
+ /**
781
+ * Creates a Component that is draggable, within a bounded grid.
782
+ * @param {*} opts
783
+ */
784
+
785
+ const gridDraggable = opts => Comp => {
786
+ var _class;
787
+
788
+ invariant(!!opts && isFunction(opts.fromDelta) && isFunction(opts.bounds) && isFunction(opts.anchorPoint), 'You must supply an object with: { anchorPoint: Function, fromDelta: Function, bounds: Function }');
789
+ return _class = class GridDraggable extends React.Component {
790
+ constructor(...args) {
791
+ super(...args);
792
+
793
+ this.grid = () => {
794
+ const {
795
+ graphProps
796
+ } = this.props;
797
+ const {
798
+ scale,
799
+ domain,
800
+ range
801
+ } = graphProps;
802
+ return {
803
+ x: scale.x(domain.step) - scale.x(0),
804
+ y: scale.y(range.step) - scale.y(0)
805
+ };
806
+ };
807
+
808
+ this.onStart = e => {
809
+ const {
810
+ onDragStart
811
+ } = this.props;
812
+
813
+ if (document.activeElement) {
814
+ document.activeElement.blur();
815
+ }
816
+
817
+ this.setState({
818
+ startX: e.clientX,
819
+ startY: e.clientY
820
+ });
821
+
822
+ if (onDragStart) {
823
+ onDragStart();
824
+ }
825
+ };
826
+
827
+ this.position = () => {
828
+ const {
829
+ x,
830
+ y
831
+ } = opts.anchorPoint(this.props);
832
+ const {
833
+ graphProps
834
+ } = this.props;
835
+ const {
836
+ scale,
837
+ snap
838
+ } = graphProps;
839
+ return {
840
+ anchorPoint: {
841
+ x,
842
+ y
843
+ },
844
+ x: deltaFn(scale.x, snap.x, x),
845
+ y: deltaFn(scale.y, snap.y, y)
846
+ };
847
+ };
848
+
849
+ this.tiny = (key, event) => {
850
+ const K = key.toUpperCase();
851
+ const end = event[`client${K}`];
852
+ const start = this.state[`start${K}`];
853
+ const delta = Math.abs(end - start);
854
+ const out = delta < Math.abs(this.grid()[key]) / 10;
855
+ log$1('[tiny] key: ', key, 'delta: ', delta, 'out: ', out);
856
+ return out;
857
+ };
858
+
859
+ this.getScaledBounds = () => {
860
+ const bounds = opts.bounds(this.props, this.props.graphProps);
861
+ log$1('bounds: ', bounds);
862
+ const grid = this.grid();
863
+ const scaled = {
864
+ left: bounds.left / grid.interval * grid.x,
865
+ right: bounds.right / grid.interval * grid.x,
866
+ top: bounds.top / grid.interval * grid.y,
867
+ bottom: bounds.bottom / grid.interval * grid.y
868
+ };
869
+ log$1('[getScaledBounds]: ', scaled);
870
+ return scaled;
871
+ };
872
+
873
+ this.getClientPoint = (node, event) => {
874
+ if (!node || !event) {
875
+ return null;
876
+ }
877
+
878
+ const svg = node.ownerSVGElement || node;
879
+
880
+ if (svg && svg.createSVGPoint) {
881
+ let point = svg.createSVGPoint(); // Check if it's a touch event and use the first touch point
882
+
883
+ if (event.touches && event.touches.length > 0) {
884
+ const touch = event.touches[0];
885
+ point.x = touch.clientX;
886
+ point.y = touch.clientY;
887
+ } else {
888
+ // Fall back to mouse event properties
889
+ point.x = event.clientX;
890
+ point.y = event.clientY;
891
+ }
892
+
893
+ if (node.getScreenCTM) {
894
+ point = point.matrixTransform(node.getScreenCTM().inverse());
895
+ return [point.x, point.y];
896
+ } else {
897
+ return null;
898
+ }
899
+ }
900
+
901
+ const rect = node.getBoundingClientRect();
902
+
903
+ if (rect) {
904
+ return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop];
905
+ } else {
906
+ return null;
907
+ }
908
+ };
909
+
910
+ this.skipDragOutsideOfBounds = (dd, e, graphProps) => {
911
+ // Ignore drag movement outside of the domain and range.
912
+ const rootNode = graphProps.getRootNode();
913
+ const clientPoint = this.getClientPoint(rootNode, e);
914
+
915
+ if (clientPoint === null) {
916
+ return true; // Indicate that the drag is outside of bounds
917
+ }
918
+
919
+ const [rawX, rawY] = clientPoint;
920
+ const {
921
+ scale,
922
+ domain,
923
+ range
924
+ } = graphProps;
925
+ let x = scale.x.invert(rawX);
926
+ let y = scale.y.invert(rawY);
927
+ const xOutside = dd.deltaX > 0 && x < domain.min || dd.deltaX < 0 && x > domain.max;
928
+ const yOutside = dd.deltaY > 0 && y > range.max || dd.deltaY < 0 && y < range.min;
929
+ return xOutside || yOutside;
930
+ };
931
+
932
+ this.onDrag = (e, dd) => {
933
+ const {
934
+ onDrag,
935
+ graphProps
936
+ } = this.props;
937
+
938
+ if (!onDrag) {
939
+ return;
940
+ }
941
+
942
+ const bounds = this.getScaledBounds();
943
+
944
+ if (dd.deltaX < 0 && dd.deltaX < bounds.left) {
945
+ return;
946
+ }
947
+
948
+ if (dd.deltaX > 0 && dd.deltaX > bounds.right) {
949
+ return;
950
+ }
951
+
952
+ if (dd.deltaY < 0 && dd.deltaY < bounds.top) {
953
+ return;
954
+ }
955
+
956
+ if (dd.deltaY > 0 && dd.deltaY > bounds.bottom) {
957
+ return;
958
+ }
959
+
960
+ if (this.skipDragOutsideOfBounds(dd, e, graphProps)) {
961
+ return;
962
+ }
963
+
964
+ const dragArg = this.applyDelta({
965
+ x: dd.deltaX,
966
+ y: dd.deltaY
967
+ });
968
+
969
+ if (dragArg !== undefined || dragArg !== null) {
970
+ onDrag(dragArg);
971
+ }
972
+ };
973
+
974
+ this.getDelta = point => {
975
+ const pos = this.position();
976
+ const p = {
977
+ x: pos.x(point.x),
978
+ y: pos.y(point.y)
979
+ };
980
+ return getDelta(pos.anchorPoint, p);
981
+ };
982
+
983
+ this.applyDelta = point => {
984
+ const delta = this.getDelta(point);
985
+ log$1('[applyDelta] delta:', delta);
986
+ return opts.fromDelta(this.props, delta);
987
+ };
988
+
989
+ this.onStop = (e, dd) => {
990
+ log$1('[onStop] dd:', dd);
991
+ const {
992
+ onDragStop,
993
+ onClick
994
+ } = this.props;
995
+
996
+ if (onDragStop) {
997
+ onDragStop();
998
+ }
999
+
1000
+ log$1('[onStop] lastX/Y: ', dd.lastX, dd.lastY);
1001
+ const isClick = this.tiny('x', e) && this.tiny('y', e);
1002
+
1003
+ if (isClick) {
1004
+ if (onClick) {
1005
+ log$1('call onClick');
1006
+ this.setState({
1007
+ startX: null
1008
+ });
1009
+ const {
1010
+ graphProps
1011
+ } = this.props;
1012
+ const {
1013
+ scale,
1014
+ snap
1015
+ } = graphProps;
1016
+ const [rawX, rawY] = clientPoint(e.target, e);
1017
+ let x = scale.x.invert(rawX);
1018
+ let y = scale.y.invert(rawY);
1019
+ x = snap.x(x);
1020
+ y = snap.y(y);
1021
+ onClick({
1022
+ x,
1023
+ y
1024
+ });
1025
+ return false;
1026
+ }
1027
+ }
1028
+
1029
+ this.setState({
1030
+ startX: null,
1031
+ startY: null
1032
+ }); // return false to prevent state updates in the underlying draggable - a move will have triggered an update already.
1033
+
1034
+ return false;
1035
+ };
1036
+ }
1037
+
1038
+ render() {
1039
+ const _this$props = this.props,
1040
+ {
1041
+ disabled
1042
+ } = _this$props,
1043
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded);
1044
+
1045
+ const grid = this.grid(); // prevent the text select icon from rendering.
1046
+
1047
+ const onMouseDown = e => e.nativeEvent.preventDefault();
1048
+ /**
1049
+ * TODO: This shouldnt be necessary, we should be able to use the r-d classnames.
1050
+ * But they aren't being unset. If we continue with this lib, we'll have to fix this.
1051
+ */
1052
+
1053
+
1054
+ const isDragging = this.state ? !!this.state.startX : false;
1055
+ return /*#__PURE__*/React.createElement(DraggableCore, {
1056
+ disabled: disabled,
1057
+ onMouseDown: onMouseDown,
1058
+ onStart: this.onStart,
1059
+ onDrag: this.onDrag,
1060
+ onStop: this.onStop,
1061
+ axis: opts.axis || 'both',
1062
+ grid: [grid.x, grid.y]
1063
+ }, /*#__PURE__*/React.createElement(Comp, _extends({}, rest, {
1064
+ disabled: disabled,
1065
+ isDragging: isDragging
1066
+ })));
1067
+ }
1068
+
1069
+ }, _class.propTypes = {
1070
+ disabled: PropTypes.bool,
1071
+ onDragStart: PropTypes.func,
1072
+ onDrag: PropTypes.func,
1073
+ onDragStop: PropTypes.func,
1074
+ onClick: PropTypes.func,
1075
+ onMove: PropTypes.func,
1076
+ graphProps: GraphPropsType.isRequired
1077
+ }, _class;
1078
+ };
1079
+
1080
+ const log = debug('pie-lib:plot:trig');
1081
+ const toDegrees = radians => radians * (180 / Math.PI);
1082
+ const toRadians = degrees => degrees * (Math.PI / 180);
1083
+ /**
1084
+ * return angle in radians between 2 points using counting degrees counter clockwise
1085
+ *
1086
+ * 0,0 + 1,1 = 45 in radians
1087
+ * 1,1 + 0,0 = 45?
1088
+ * @param {Point} a
1089
+ * @param {Point} b
1090
+ */
1091
+
1092
+ const angle = (a, b) => {
1093
+ const vd = b.y - a.y;
1094
+ const hd = b.x - a.x;
1095
+ log(a, b, 'vd: ', vd, 'hd: ', hd);
1096
+ const radians = Math.atan2(vd, hd);
1097
+ return radians < 0 ? radians + Math.PI * 2 : radians;
1098
+ };
1099
+ const NINETY = Math.PI / 2;
1100
+ const ONE_EIGHTY = Math.PI;
1101
+ const TWO_SEVENTY = ONE_EIGHTY + NINETY;
1102
+ const acuteXAngle = a => {
1103
+ log(toDegrees(a));
1104
+
1105
+ if (a < NINETY) {
1106
+ return a;
1107
+ }
1108
+
1109
+ if (a < ONE_EIGHTY) {
1110
+ return Math.abs(ONE_EIGHTY - a);
1111
+ }
1112
+
1113
+ if (a < TWO_SEVENTY) {
1114
+ return Math.abs(ONE_EIGHTY - a);
1115
+ }
1116
+
1117
+ return Math.abs(Math.PI * 2 - a);
1118
+ };
1119
+ const acuteYAngle = a => NINETY - acuteXAngle(a);
1120
+ const hypotenuse = (a, alpha) => {
1121
+ const out = Math.abs(a / Math.sin(alpha));
1122
+ return out;
1123
+ };
1124
+ /**
1125
+ * return 2 edge points for a,b within domain + range.
1126
+ * - one edge is from following a -> b to the bounds
1127
+ * - one edge is from following b -> a to the bounds
1128
+ * @param {{min: number, max: number}} domain
1129
+ * @param {{min: number, max: number}} range
1130
+ * @param {{x: number, y: number}} a
1131
+ * @param {{x: number, y: number}} b
1132
+ * @returns [{x: number, y: number}, {x: number, y: number}]
1133
+ */
1134
+
1135
+ const edges = (domain, range) => (a, b) => {
1136
+ // const xDest =
1137
+ const destX = a.x < b.x ? domain.max : domain.min;
1138
+ const destY = a.y < b.y ? range.max : range.min;
1139
+ const aToB = diffEdge(xy(destX, destY), a, b);
1140
+ const dX = b.x < a.x ? domain.max : domain.min;
1141
+ const dY = b.y < a.y ? range.max : range.min;
1142
+ const bToA = diffEdge(xy(dX, dY), b, a);
1143
+ return [aToB, bToA];
1144
+ };
1145
+ /** get length of side A of a triangle from H and angle Alpha */
1146
+
1147
+ const getOpposingSide = (hyp, angle) => {
1148
+ log('[getOpposingSide] hyp: ', hyp, 'angle:', angle);
1149
+ return Math.abs(hyp * Math.sin(angle));
1150
+ };
1151
+
1152
+ const getShortestSide = (xh, yh) => {
1153
+ if (Number.isFinite(xh) && Number.isFinite(yh)) {
1154
+ if (xh === 0 && yh > 0) {
1155
+ return 'y';
1156
+ }
1157
+
1158
+ if (yh === 0 && xh > 0) {
1159
+ return 'x';
1160
+ }
1161
+
1162
+ return xh < yh ? 'x' : 'y';
1163
+ }
1164
+
1165
+ if (isNaN(xh) && !isNaN(yh)) {
1166
+ return 'y';
1167
+ }
1168
+
1169
+ if (!isNaN(xh) && isNaN(yh)) {
1170
+ return 'x';
1171
+ }
1172
+
1173
+ if (xh === Infinity) {
1174
+ return 'y';
1175
+ }
1176
+
1177
+ if (yh === Infinity) {
1178
+ return 'x';
1179
+ } // eslint-disable-next-line no-console
1180
+
1181
+
1182
+ console.warn('hypotenuse - which is shorter? x:', xh, 'y:', yh);
1183
+ };
1184
+ /**
1185
+ * return the difference between bounds and a as a Point
1186
+ * @param {*} bounds
1187
+ */
1188
+
1189
+
1190
+ const diffEdge = (bounds, a, b) => {
1191
+ let l = log.enabled ? log.bind(log, `diffEdge: [${a.x},${a.y} -> ${b.x},${b.y}]`) : () => {};
1192
+ const xRadians = angle(a, b);
1193
+ l('x angle', toDegrees(xRadians));
1194
+ const yRadians = Math.abs(xRadians - toRadians(90));
1195
+ l('y angle', toDegrees(yRadians));
1196
+ const xSide = Math.abs(a.x - bounds.x);
1197
+ /**
1198
+ * Draw 2 triangles:
1199
+ * 1 with a horizontal line from a to the graph x edge
1200
+ * 1 with a vertical line from a to the graph y edge
1201
+ * Calculate the hypotenuse for both, whichever is shorter
1202
+ * indicates that we should use that triangle to get the edge point.
1203
+ */
1204
+
1205
+ const xH = hypotenuse(xSide, yRadians);
1206
+ const ySide = Math.abs(a.y - bounds.y);
1207
+ const yH = hypotenuse(ySide, xRadians);
1208
+ l('x: side', xSide, 'h:', xH);
1209
+ l('y: side', ySide, 'h:', yH);
1210
+ const side = getShortestSide(xH, yH);
1211
+
1212
+ if (side !== 'x' && side !== 'y') {
1213
+ throw new Error('Cant decide which hypotenuse to use');
1214
+ }
1215
+
1216
+ const point = side === 'x' ? new Point(xSide, getOpposingSide(xH, xRadians)) : new Point(getOpposingSide(yH, yRadians), ySide);
1217
+ l('point:', point);
1218
+ const multiplier = new Point(b.x < a.x ? -1 : 1, b.y < a.y ? -1 : 1);
1219
+ l('multiplier:', multiplier);
1220
+ const out = point.multByPoint(multiplier);
1221
+ l('out:', out);
1222
+ const normalized = out.add(new Point(a.x, a.y));
1223
+ l('normalized:', normalized);
1224
+ return normalized;
1225
+ };
1226
+
1227
+ var trig = /*#__PURE__*/Object.freeze({
1228
+ __proto__: null,
1229
+ acuteXAngle: acuteXAngle,
1230
+ acuteYAngle: acuteYAngle,
1231
+ angle: angle,
1232
+ diffEdge: diffEdge,
1233
+ edges: edges,
1234
+ getOpposingSide: getOpposingSide,
1235
+ hypotenuse: hypotenuse,
1236
+ toDegrees: toDegrees,
1237
+ toRadians: toRadians
1238
+ });
1239
+
1240
+ const createSnapMinAndMax = ({
1241
+ min,
1242
+ max,
1243
+ step
1244
+ }) => {
1245
+ // 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
1246
+ // 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
1247
+ return {
1248
+ step,
1249
+ min: parseInt(min / step) * step,
1250
+ max: parseInt(max / step) * step
1251
+ };
1252
+ };
1253
+
1254
+ const create = (domain, range, size, getRootNode) => {
1255
+ invariant(domain.min < domain.max, 'domain: min must be less than max');
1256
+ invariant(range.min < range.max, 'range: min must be less than max');
1257
+ const domainMinMax = createSnapMinAndMax(domain);
1258
+ const rangeMinMax = createSnapMinAndMax(range);
1259
+ const scale = {
1260
+ x: scaleLinear().domain([domain.min, domain.max]).range([0, size.width]),
1261
+ y: scaleLinear().domain([range.max, range.min]).range([0, size.height])
1262
+ };
1263
+ const snap = {
1264
+ x: snapTo.bind(null, domainMinMax.min, domainMinMax.max, domainMinMax.step),
1265
+ y: snapTo.bind(null, rangeMinMax.min, rangeMinMax.max, rangeMinMax.step)
1266
+ };
1267
+ return {
1268
+ scale,
1269
+ snap,
1270
+ domain,
1271
+ range,
1272
+ size,
1273
+ getRootNode
1274
+ };
1275
+ };
1276
+
1277
+ export { LocalDraggable as Draggable, root as Root, create as createGraphProps, gridDraggable, trig, types, utils };
1278
+ //# sourceMappingURL=index.js.map