@pie-lib/plot 2.7.4-next.0 → 2.7.4-next.1618
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 +0 -335
- package/CHANGELOG.md +211 -6
- package/NEXT.CHANGELOG.json +1 -0
- package/lib/grid-draggable.js +52 -16
- package/lib/grid-draggable.js.map +1 -1
- package/lib/label.js +17 -5
- package/lib/label.js.map +1 -1
- package/lib/root.js +85 -36
- package/lib/root.js.map +1 -1
- package/lib/trig.js +1 -1
- package/lib/trig.js.map +1 -1
- package/lib/utils.js +24 -2
- package/lib/utils.js.map +1 -1
- package/package.json +7 -4
- package/src/__tests__/__snapshots__/grid-draggable.test.jsx.snap +185 -0
- package/src/__tests__/__snapshots__/root.test.jsx.snap +18 -0
- package/src/__tests__/draggable.test.jsx +23 -0
- package/src/__tests__/grid-draggable.test.jsx +326 -0
- package/src/__tests__/root.test.jsx +118 -0
- package/src/__tests__/trig.test.js +174 -0
- package/src/__tests__/utils.test.js +233 -0
- package/src/grid-draggable.jsx +52 -8
- package/src/label.jsx +14 -3
- package/src/root.jsx +86 -39
- package/src/trig.js +1 -1
- package/src/utils.js +14 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { AssertionError } from 'assert';
|
|
2
|
+
import * as utils from '../utils';
|
|
3
|
+
|
|
4
|
+
const xy = utils.xy;
|
|
5
|
+
|
|
6
|
+
const tick = (isMajor, v) => ({
|
|
7
|
+
major: isMajor,
|
|
8
|
+
value: v,
|
|
9
|
+
x: v,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const major = tick.bind(null, true);
|
|
13
|
+
const minor = tick.bind(null, false);
|
|
14
|
+
|
|
15
|
+
describe('utils', () => {
|
|
16
|
+
describe('getDelta', () => {
|
|
17
|
+
const assertDelta = (from, to, delta) => {
|
|
18
|
+
it(`returns a delta of: ${delta} for ${from} -> ${to}`, () => {
|
|
19
|
+
const d = utils.getDelta(from, to);
|
|
20
|
+
expect(d.x).toEqual(delta.x);
|
|
21
|
+
expect(d.y).toEqual(delta.y);
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
assertDelta(xy(0, 0), xy(0, 1), xy(0, 1));
|
|
25
|
+
assertDelta(xy(0, 1), xy(0, 0), xy(0, -1));
|
|
26
|
+
assertDelta(xy(1, 1), xy(3, 3), xy(2, 2));
|
|
27
|
+
assertDelta(xy(-1, -1), xy(3, 3), xy(4, 4));
|
|
28
|
+
assertDelta(xy(-1, -1), xy(-2, -5), xy(-1, -4));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('polygonToArea', () => {
|
|
32
|
+
const assertPolygon = (points, area) => {
|
|
33
|
+
it(`converts ${points} -> ${area}`, () => {
|
|
34
|
+
const result = utils.polygonToArea(points);
|
|
35
|
+
expect(result).toEqual(area);
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
assertPolygon([xy(0, 0), xy(1, 1), xy(1, -1)], {
|
|
39
|
+
left: 0,
|
|
40
|
+
top: 1,
|
|
41
|
+
bottom: -1,
|
|
42
|
+
right: 1,
|
|
43
|
+
});
|
|
44
|
+
assertPolygon([xy(0, 0), xy(3, 0), xy(2, -1), xy(4, -3), xy(1, -4), xy(2, -2)], {
|
|
45
|
+
left: 0,
|
|
46
|
+
top: 0,
|
|
47
|
+
bottom: -4,
|
|
48
|
+
right: 4,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('buildTickModel', () => {
|
|
53
|
+
let scaleFn;
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
scaleFn = jest.fn(function(v) {
|
|
57
|
+
return v;
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('builds major only ticks', () => {
|
|
62
|
+
let result = utils.buildTickModel({ min: 0, max: 2 }, { minor: 0 }, 1, scaleFn);
|
|
63
|
+
expect(result).toEqual([major(0), major(1), major(2)]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('builds minor + major ticks', () => {
|
|
67
|
+
let result = utils.buildTickModel({ min: 0, max: 2 }, { minor: 1 }, 0.5, scaleFn);
|
|
68
|
+
expect(result).toEqual([major(0), minor(0.5), major(1), minor(1.5), major(2)]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('snapTo', () => {
|
|
73
|
+
let assertSnapTo = (min, max, interval, value, expected) => {
|
|
74
|
+
it(`snaps ${value} to ${expected} with domain ${min}<->${max} with interval: ${interval} `, () => {
|
|
75
|
+
let result = utils.snapTo(min, max, interval, value);
|
|
76
|
+
expect(result).toEqual(expected);
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
describe('with 0, 10, 0.25', () => {
|
|
81
|
+
let a = assertSnapTo.bind(null, 0, 10, 0.25);
|
|
82
|
+
a(1, 1);
|
|
83
|
+
a(1.2, 1.25);
|
|
84
|
+
a(0.2, 0.25);
|
|
85
|
+
a(5.2, 5.25);
|
|
86
|
+
a(5.125, 5.25);
|
|
87
|
+
a(5.124, 5);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('with 0, 10, 1', () => {
|
|
91
|
+
let a = assertSnapTo.bind(null, 0, 10, 1);
|
|
92
|
+
a(0, 0);
|
|
93
|
+
a(10, 10);
|
|
94
|
+
a(100, 10);
|
|
95
|
+
a(1, 1);
|
|
96
|
+
a(1.2, 1);
|
|
97
|
+
a(0.2, 0);
|
|
98
|
+
a(5.2, 5);
|
|
99
|
+
a(5.001, 5);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('getInterval', () => {
|
|
104
|
+
let assertGetInterval = (min, max, ticks, expected) => {
|
|
105
|
+
let paramsDescription = JSON.stringify(ticks);
|
|
106
|
+
it(`converts: ${paramsDescription} to ${JSON.stringify(expected)}`, () => {
|
|
107
|
+
let result = utils.getInterval({ min: min, max: max }, ticks);
|
|
108
|
+
expect(result).toEqual(expected);
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
describe('with bad params', () => {
|
|
113
|
+
it('throws an error if min > max', () => {
|
|
114
|
+
expect(() => {
|
|
115
|
+
let result = utils.convertFrequencyToInterval(
|
|
116
|
+
{ min: 11, max: 10, tickFrequency: 1, betweenTickCount: 0 },
|
|
117
|
+
{ interval: 10, major: 10 },
|
|
118
|
+
);
|
|
119
|
+
console.log('result: ', result);
|
|
120
|
+
}).toThrow(Error);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('throws an error if min = max', () => {
|
|
124
|
+
expect(() => {
|
|
125
|
+
let result = utils.convertFrequencyToInterval(
|
|
126
|
+
{ min: 10, max: 10, tickFrequency: 1, betweenTickCount: 0 },
|
|
127
|
+
{ interval: 10, major: 10 },
|
|
128
|
+
);
|
|
129
|
+
console.log('result: ', result);
|
|
130
|
+
}).toThrow(Error);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('with domain 0 -> 1', () => {
|
|
135
|
+
let a = assertGetInterval.bind(null, 0, 1);
|
|
136
|
+
a({ major: 2, minor: 0 }, 1);
|
|
137
|
+
a({ major: 2, minor: 1 }, 0.5);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('with domain 0 -> 10', () => {
|
|
141
|
+
let a = assertGetInterval.bind(null, 0, 10);
|
|
142
|
+
|
|
143
|
+
it('throws an error if the tick frequency is less than 2', () => {
|
|
144
|
+
expect(() => {
|
|
145
|
+
let result = utils.convertFrequencyToInterval(
|
|
146
|
+
{ min: 0, max: 10, tickFrequency: 1, betweenTickCount: 0 },
|
|
147
|
+
{ interval: 10, major: 10 },
|
|
148
|
+
);
|
|
149
|
+
console.log('result: ', result);
|
|
150
|
+
}).toThrow(Error);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
a({ major: 2, minor: 9 }, 1);
|
|
154
|
+
a({ major: 2, minor: 0 }, 10);
|
|
155
|
+
a({ major: 3, minor: 0 }, 5);
|
|
156
|
+
a({ major: 3, minor: 1 }, 2.5);
|
|
157
|
+
a({ major: 4, minor: 0 }, 3.3333);
|
|
158
|
+
a({ major: 5, minor: 0 }, 2.5);
|
|
159
|
+
a({ major: 6, minor: 0 }, 2);
|
|
160
|
+
a({ major: 7, minor: 0 }, 1.6667);
|
|
161
|
+
a({ major: 8, minor: 0 }, 1.4286);
|
|
162
|
+
a({ major: 9, minor: 0 }, 1.25);
|
|
163
|
+
a({ major: 10, minor: 0 }, 1.1111);
|
|
164
|
+
a({ major: 11, minor: 0 }, 1);
|
|
165
|
+
a({ major: 11, minor: 1 }, 0.5);
|
|
166
|
+
a({ major: 11, minor: 2 }, 0.3333);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('with domain 0 -> 100', () => {
|
|
170
|
+
let a = assertGetInterval.bind(null, 0, 100);
|
|
171
|
+
a({ major: 11, minor: 1 }, 5);
|
|
172
|
+
a({ major: 101, minor: 0 }, 1);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('with domain -5 - 5', () => {
|
|
176
|
+
let a = assertGetInterval.bind(null, -5, 5);
|
|
177
|
+
a({ major: 11, minor: 0 }, 1);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('with domain 0 - 5', () => {
|
|
181
|
+
let a = assertGetInterval.bind(null, 0, 5);
|
|
182
|
+
a({ major: 11, minor: 0 }, 0.5);
|
|
183
|
+
a({ major: 11, minor: 2 }, 0.1667);
|
|
184
|
+
a({ major: 11, minor: 1 }, 0.25);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('findLongestWord', () => {
|
|
189
|
+
it('should return 0 if label is undefined', () => {
|
|
190
|
+
const label = undefined;
|
|
191
|
+
const result = utils.findLongestWord(label);
|
|
192
|
+
|
|
193
|
+
expect(result).toEqual(0);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should return 0 if label is null', () => {
|
|
197
|
+
const label = null;
|
|
198
|
+
const result = utils.findLongestWord(label);
|
|
199
|
+
|
|
200
|
+
expect(result).toEqual(0);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should return 6 if the longest word from label has 6 letters', () => {
|
|
204
|
+
const label = 'Number of months';
|
|
205
|
+
const result = utils.findLongestWord(label);
|
|
206
|
+
|
|
207
|
+
expect(result).toEqual(6);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('amountToIncreaseWidth', () => {
|
|
212
|
+
it('should return 0 if longestWord is undefined', () => {
|
|
213
|
+
const longestWord = undefined;
|
|
214
|
+
const result = utils.amountToIncreaseWidth(longestWord);
|
|
215
|
+
|
|
216
|
+
expect(result).toEqual(0);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should return 0 if longestWord is null', () => {
|
|
220
|
+
const longestWord = null;
|
|
221
|
+
const result = utils.amountToIncreaseWidth(longestWord);
|
|
222
|
+
|
|
223
|
+
expect(result).toEqual(0);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should return 150 if longestWord is 10', () => {
|
|
227
|
+
const longestWord = 10;
|
|
228
|
+
const result = utils.amountToIncreaseWidth(longestWord);
|
|
229
|
+
|
|
230
|
+
expect(result).toEqual(200);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
package/src/grid-draggable.jsx
CHANGED
|
@@ -96,10 +96,57 @@ export const gridDraggable = (opts) => (Comp) => {
|
|
|
96
96
|
return scaled;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Retrieves the coordinates of a mouse or touch event relative to an SVG element.
|
|
101
|
+
* This method has been overwritten from the d3-selection library's clientPoint to handle touch events and improve clarity.
|
|
102
|
+
* @param {Element} node - The SVG element.
|
|
103
|
+
* @param {Event} event - The mouse or touch event.
|
|
104
|
+
* @returns {Array} - An array containing the coordinates [x, y] relative to the SVG element.
|
|
105
|
+
*/
|
|
106
|
+
getClientPoint = (node, event) => {
|
|
107
|
+
if (!node || !event) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const svg = node.ownerSVGElement || node;
|
|
111
|
+
|
|
112
|
+
if (svg && svg.createSVGPoint) {
|
|
113
|
+
let point = svg.createSVGPoint();
|
|
114
|
+
// Check if it's a touch event and use the first touch point
|
|
115
|
+
if (event.touches && event.touches.length > 0) {
|
|
116
|
+
const touch = event.touches[0];
|
|
117
|
+
point.x = touch.clientX;
|
|
118
|
+
point.y = touch.clientY;
|
|
119
|
+
} else {
|
|
120
|
+
// Fall back to mouse event properties
|
|
121
|
+
point.x = event.clientX;
|
|
122
|
+
point.y = event.clientY;
|
|
123
|
+
}
|
|
124
|
+
if (node.getScreenCTM) {
|
|
125
|
+
point = point.matrixTransform(node.getScreenCTM().inverse());
|
|
126
|
+
return [point.x, point.y];
|
|
127
|
+
} else {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const rect = node.getBoundingClientRect();
|
|
133
|
+
if (rect) {
|
|
134
|
+
return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop];
|
|
135
|
+
} else {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
99
140
|
skipDragOutsideOfBounds = (dd, e, graphProps) => {
|
|
100
|
-
//
|
|
141
|
+
// Ignore drag movement outside of the domain and range.
|
|
101
142
|
const rootNode = graphProps.getRootNode();
|
|
102
|
-
const
|
|
143
|
+
const clientPoint = this.getClientPoint(rootNode, e);
|
|
144
|
+
|
|
145
|
+
if (clientPoint === null) {
|
|
146
|
+
return true; // Indicate that the drag is outside of bounds
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const [rawX, rawY] = clientPoint;
|
|
103
150
|
const { scale, domain, range } = graphProps;
|
|
104
151
|
let x = scale.x.invert(rawX);
|
|
105
152
|
let y = scale.y.invert(rawY);
|
|
@@ -195,13 +242,10 @@ export const gridDraggable = (opts) => (Comp) => {
|
|
|
195
242
|
};
|
|
196
243
|
|
|
197
244
|
render() {
|
|
198
|
-
|
|
199
|
-
//Note: we pull onClick out so that it's not in ...rest.
|
|
200
|
-
const { disabled, onClick, ...rest } = this.props;
|
|
201
|
-
/* eslint-enable no-unused-vars */
|
|
202
|
-
|
|
245
|
+
const { disabled, ...rest } = this.props;
|
|
203
246
|
const grid = this.grid();
|
|
204
|
-
|
|
247
|
+
|
|
248
|
+
// prevent the text select icon from rendering.
|
|
205
249
|
const onMouseDown = (e) => e.nativeEvent.preventDefault();
|
|
206
250
|
|
|
207
251
|
/**
|
package/src/label.jsx
CHANGED
|
@@ -4,7 +4,7 @@ import cn from 'classnames';
|
|
|
4
4
|
import EditableHtml from '@pie-lib/editable-html';
|
|
5
5
|
import { withStyles } from '@material-ui/core/styles';
|
|
6
6
|
import PropTypes from 'prop-types';
|
|
7
|
-
|
|
7
|
+
import { extractTextFromHTML, isEmptyString } from './utils';
|
|
8
8
|
const LabelComponent = (props) => {
|
|
9
9
|
const {
|
|
10
10
|
classes,
|
|
@@ -20,6 +20,8 @@ const LabelComponent = (props) => {
|
|
|
20
20
|
side,
|
|
21
21
|
onChange,
|
|
22
22
|
mathMlOptions = {},
|
|
23
|
+
charactersLimit,
|
|
24
|
+
titleHeight,
|
|
23
25
|
} = props;
|
|
24
26
|
const [rotatedToHorizontal, setRotatedToHorizontal] = useState(false);
|
|
25
27
|
const activePlugins = [
|
|
@@ -40,8 +42,8 @@ const LabelComponent = (props) => {
|
|
|
40
42
|
chartValue ||
|
|
41
43
|
(isChartLeftLabel && `${graphHeight - 70}px`) ||
|
|
42
44
|
(side === 'left' && `${graphHeight - 8}px`) ||
|
|
43
|
-
(isChartBottomLabel && `${graphHeight -
|
|
44
|
-
(side === 'bottom' && `${graphHeight -
|
|
45
|
+
(isChartBottomLabel && `${graphHeight - 60 + titleHeight}px`) ||
|
|
46
|
+
(side === 'bottom' && `${graphHeight - 120 + titleHeight}px`) ||
|
|
45
47
|
0,
|
|
46
48
|
left:
|
|
47
49
|
(side === 'right' && `${graphWidth - 8}px`) ||
|
|
@@ -66,6 +68,7 @@ const LabelComponent = (props) => {
|
|
|
66
68
|
[classes.rotateRightLabel]: side === 'right' && !rotatedToHorizontal,
|
|
67
69
|
[classes.editLabel]: rotatedToHorizontal,
|
|
68
70
|
[classes.customBottom]: isChartBottomLabel || isDefineChartBottomLabel,
|
|
71
|
+
[classes.displayNone]: disabledLabel && !isChart && isEmptyString(extractTextFromHTML(text)),
|
|
69
72
|
})}
|
|
70
73
|
style={rotatedToHorizontal ? rotatedStyle : defaultStyle}
|
|
71
74
|
onClick={rotateLabel}
|
|
@@ -79,12 +82,14 @@ const LabelComponent = (props) => {
|
|
|
79
82
|
placeholder={!disabledLabel && placeholder}
|
|
80
83
|
toolbarOpts={{
|
|
81
84
|
position: side === 'bottom' ? 'top' : 'bottom',
|
|
85
|
+
noPadding: true,
|
|
82
86
|
noBorder: true,
|
|
83
87
|
}}
|
|
84
88
|
disableScrollbar
|
|
85
89
|
activePlugins={activePlugins}
|
|
86
90
|
onDone={() => setRotatedToHorizontal(false)}
|
|
87
91
|
mathMlOptions={mathMlOptions}
|
|
92
|
+
charactersLimit={charactersLimit}
|
|
88
93
|
/>
|
|
89
94
|
)}
|
|
90
95
|
</div>
|
|
@@ -104,6 +109,9 @@ LabelComponent.propTypes = {
|
|
|
104
109
|
text: PropTypes.string,
|
|
105
110
|
side: PropTypes.string,
|
|
106
111
|
onChange: PropTypes.func,
|
|
112
|
+
mathMlOptions: PropTypes.object,
|
|
113
|
+
charactersLimit: PropTypes.number,
|
|
114
|
+
titleHeight: PropTypes.number,
|
|
107
115
|
};
|
|
108
116
|
|
|
109
117
|
export default withStyles((theme) => ({
|
|
@@ -148,4 +156,7 @@ export default withStyles((theme) => ({
|
|
|
148
156
|
customBottom: {
|
|
149
157
|
position: 'absolute',
|
|
150
158
|
},
|
|
159
|
+
displayNone: {
|
|
160
|
+
display: 'none',
|
|
161
|
+
},
|
|
151
162
|
}))(LabelComponent);
|
package/src/root.jsx
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ChildrenType } from './types';
|
|
3
2
|
import { withStyles } from '@material-ui/core/styles';
|
|
4
|
-
import { select, mouse } from 'd3-selection';
|
|
5
3
|
import PropTypes from 'prop-types';
|
|
6
|
-
import {
|
|
4
|
+
import { select, mouse } from 'd3-selection';
|
|
5
|
+
import cn from 'classnames';
|
|
6
|
+
|
|
7
7
|
import { color, Readable } from '@pie-lib/render-ui';
|
|
8
8
|
import EditableHtml from '@pie-lib/editable-html';
|
|
9
|
-
import
|
|
9
|
+
import { ChildrenType } from './types';
|
|
10
|
+
import { GraphPropsType } from './types';
|
|
10
11
|
import Label from './label';
|
|
12
|
+
import { extractTextFromHTML, isEmptyObject, isEmptyString } from './utils';
|
|
11
13
|
|
|
12
14
|
export class Root extends React.Component {
|
|
15
|
+
constructor(props) {
|
|
16
|
+
super(props);
|
|
17
|
+
this.state = {
|
|
18
|
+
titleHeight: 0,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
static propTypes = {
|
|
14
23
|
title: PropTypes.string,
|
|
15
24
|
children: ChildrenType,
|
|
@@ -29,6 +38,8 @@ export class Root extends React.Component {
|
|
|
29
38
|
rootRef: PropTypes.func,
|
|
30
39
|
onChangeLabels: PropTypes.func,
|
|
31
40
|
titlePlaceholder: PropTypes.string,
|
|
41
|
+
mathMlOptions: PropTypes.object,
|
|
42
|
+
labelsCharactersLimit: PropTypes.number,
|
|
32
43
|
};
|
|
33
44
|
|
|
34
45
|
mouseMove = (g) => {
|
|
@@ -54,6 +65,7 @@ export class Root extends React.Component {
|
|
|
54
65
|
componentDidMount() {
|
|
55
66
|
const g = select(this.g);
|
|
56
67
|
g.on('mousemove', this.mouseMove.bind(this, g));
|
|
68
|
+
this.measureTitleHeight();
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
componentWillUnmount() {
|
|
@@ -61,9 +73,19 @@ export class Root extends React.Component {
|
|
|
61
73
|
g.on('mousemove', null);
|
|
62
74
|
}
|
|
63
75
|
|
|
76
|
+
componentDidUpdate(prevProps) {
|
|
77
|
+
if (prevProps.title !== this.props.title) {
|
|
78
|
+
this.measureTitleHeight();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
64
82
|
onChangeLabel = (newValue, side) => {
|
|
65
83
|
const { labels, onChangeLabels, isChart } = this.props;
|
|
66
84
|
|
|
85
|
+
if (!onChangeLabels) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
67
89
|
if (isChart) {
|
|
68
90
|
if (side === 'left') {
|
|
69
91
|
onChangeLabels('range', newValue);
|
|
@@ -80,6 +102,18 @@ export class Root extends React.Component {
|
|
|
80
102
|
});
|
|
81
103
|
};
|
|
82
104
|
|
|
105
|
+
measureTitleHeight = () => {
|
|
106
|
+
const titleElement = this.titleRef;
|
|
107
|
+
if (titleElement) {
|
|
108
|
+
const titleHeight = titleElement.clientHeight;
|
|
109
|
+
this.setState({ titleHeight, prevTitle: this.props.title });
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
handleKeyDown = () => {
|
|
114
|
+
setTimeout(this.measureTitleHeight, 0);
|
|
115
|
+
};
|
|
116
|
+
|
|
83
117
|
render() {
|
|
84
118
|
const {
|
|
85
119
|
disabledTitle,
|
|
@@ -99,6 +133,7 @@ export class Root extends React.Component {
|
|
|
99
133
|
title,
|
|
100
134
|
rootRef,
|
|
101
135
|
mathMlOptions = {},
|
|
136
|
+
labelsCharactersLimit,
|
|
102
137
|
} = this.props;
|
|
103
138
|
const {
|
|
104
139
|
size: { width = 500, height = 500 },
|
|
@@ -107,14 +142,18 @@ export class Root extends React.Component {
|
|
|
107
142
|
} = graphProps;
|
|
108
143
|
|
|
109
144
|
const topPadding = 40;
|
|
110
|
-
const leftPadding =
|
|
111
|
-
const
|
|
145
|
+
const leftPadding = isEmptyString(extractTextFromHTML(labels?.left)) && isEmptyObject(labelsPlaceholders) ? 48 : 70;
|
|
146
|
+
const rightPadding =
|
|
147
|
+
isEmptyString(extractTextFromHTML(labels?.right)) && isEmptyObject(labelsPlaceholders) ? 48 : 70;
|
|
148
|
+
const finalWidth = width + leftPadding + rightPadding + (domain.padding || 0) * 2;
|
|
112
149
|
const finalHeight = height + topPadding * 2 + (range.padding || 0) * 2;
|
|
113
150
|
|
|
114
151
|
const activeTitlePlugins = [
|
|
115
152
|
'bold',
|
|
116
153
|
'italic',
|
|
117
154
|
'underline',
|
|
155
|
+
'superscript',
|
|
156
|
+
'subscript',
|
|
118
157
|
'strikethrough',
|
|
119
158
|
'math',
|
|
120
159
|
// 'languageCharacters'
|
|
@@ -124,11 +163,11 @@ export class Root extends React.Component {
|
|
|
124
163
|
const nbOfVerticalLines = parseInt(width / 100);
|
|
125
164
|
const nbOfHorizontalLines = parseInt(actualHeight / 100);
|
|
126
165
|
const sideGridlinesPadding = parseInt(actualHeight % 100);
|
|
127
|
-
|
|
166
|
+
const { titleHeight } = this.state;
|
|
128
167
|
return (
|
|
129
168
|
<div className={classes.root}>
|
|
130
169
|
{showPixelGuides && (
|
|
131
|
-
<div className={classes.topPixelGuides} style={{ marginLeft: isChart ?
|
|
170
|
+
<div className={classes.topPixelGuides} style={{ marginLeft: isChart ? 80 : showLabels ? 30 : 10 }}>
|
|
132
171
|
{[...Array(nbOfVerticalLines + 1).keys()].map((value) => (
|
|
133
172
|
<Readable false key={`top-guide-${value}`}>
|
|
134
173
|
<div className={classes.topPixelIndicator}>
|
|
@@ -142,34 +181,37 @@ export class Root extends React.Component {
|
|
|
142
181
|
{showTitle &&
|
|
143
182
|
(disabledTitle ? (
|
|
144
183
|
<div
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
}
|
|
184
|
+
ref={(r) => (this.titleRef = r)}
|
|
185
|
+
style={{
|
|
186
|
+
...(isChart && { width: finalWidth }),
|
|
187
|
+
...(isEmptyString(extractTextFromHTML(title)) && { display: 'none' }),
|
|
188
|
+
}}
|
|
150
189
|
className={cn(isChart ? classes.chartTitle : classes.graphTitle, classes.disabledTitle)}
|
|
151
190
|
dangerouslySetInnerHTML={{ __html: title || '' }}
|
|
152
191
|
/>
|
|
153
192
|
) : (
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
193
|
+
<div ref={(r) => (this.titleRef = r)}>
|
|
194
|
+
<EditableHtml
|
|
195
|
+
style={
|
|
196
|
+
isChart && {
|
|
197
|
+
width: finalWidth,
|
|
198
|
+
}
|
|
158
199
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
200
|
+
className={cn(
|
|
201
|
+
{ [classes.rightMargin]: showPixelGuides },
|
|
202
|
+
isChart ? classes.chartTitle : classes.graphTitle,
|
|
203
|
+
)}
|
|
204
|
+
markup={title || ''}
|
|
205
|
+
onChange={onChangeTitle}
|
|
206
|
+
placeholder={
|
|
207
|
+
(defineChart && titlePlaceholder) || (!disabledTitle && 'Click here to add a title for this graph')
|
|
208
|
+
}
|
|
209
|
+
toolbarOpts={{ noPadding: true, noBorder: true }}
|
|
210
|
+
activePlugins={activeTitlePlugins}
|
|
211
|
+
disableScrollbar
|
|
212
|
+
onKeyDown={this.handleKeyDown}
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
173
215
|
))}
|
|
174
216
|
{showLabels && !isChart && (
|
|
175
217
|
<Label
|
|
@@ -181,6 +223,7 @@ export class Root extends React.Component {
|
|
|
181
223
|
graphWidth={finalWidth}
|
|
182
224
|
onChange={(value) => this.onChangeLabel(value, 'top')}
|
|
183
225
|
mathMlOptions={mathMlOptions}
|
|
226
|
+
charactersLimit={labelsCharactersLimit}
|
|
184
227
|
/>
|
|
185
228
|
)}
|
|
186
229
|
<div className={classes.wrapper}>
|
|
@@ -196,6 +239,7 @@ export class Root extends React.Component {
|
|
|
196
239
|
isDefineChartLeftLabel={isChart && defineChart}
|
|
197
240
|
onChange={(value) => this.onChangeLabel(value, 'left')}
|
|
198
241
|
mathMlOptions={mathMlOptions}
|
|
242
|
+
charactersLimit={labelsCharactersLimit}
|
|
199
243
|
/>
|
|
200
244
|
)}
|
|
201
245
|
<svg width={finalWidth} height={finalHeight} className={defineChart ? classes.defineChart : classes.chart}>
|
|
@@ -207,7 +251,7 @@ export class Root extends React.Component {
|
|
|
207
251
|
}
|
|
208
252
|
}}
|
|
209
253
|
className={classes.graphBox}
|
|
210
|
-
transform={`translate(${leftPadding}, ${topPadding})`}
|
|
254
|
+
transform={`translate(${leftPadding + (domain.padding || 0)}, ${topPadding + (range.padding || 0)})`}
|
|
211
255
|
>
|
|
212
256
|
{children}
|
|
213
257
|
</g>
|
|
@@ -222,6 +266,7 @@ export class Root extends React.Component {
|
|
|
222
266
|
graphWidth={finalWidth}
|
|
223
267
|
onChange={(value) => this.onChangeLabel(value, 'right')}
|
|
224
268
|
mathMlOptions={mathMlOptions}
|
|
269
|
+
charactersLimit={labelsCharactersLimit}
|
|
225
270
|
/>
|
|
226
271
|
)}
|
|
227
272
|
{showPixelGuides && (
|
|
@@ -229,7 +274,7 @@ export class Root extends React.Component {
|
|
|
229
274
|
className={classes.sidePixelGuides}
|
|
230
275
|
style={{
|
|
231
276
|
paddingTop: sideGridlinesPadding,
|
|
232
|
-
marginTop:
|
|
277
|
+
marginTop: 31,
|
|
233
278
|
}}
|
|
234
279
|
>
|
|
235
280
|
{[...Array(nbOfHorizontalLines + 1).keys()].reverse().map((value) => (
|
|
@@ -248,10 +293,12 @@ export class Root extends React.Component {
|
|
|
248
293
|
placeholder={labelsPlaceholders?.bottom}
|
|
249
294
|
graphHeight={finalHeight}
|
|
250
295
|
graphWidth={finalWidth}
|
|
296
|
+
titleHeight={titleHeight}
|
|
251
297
|
isChartBottomLabel={isChart && !defineChart}
|
|
252
298
|
isDefineChartBottomLabel={isChart && defineChart}
|
|
253
299
|
onChange={(value) => this.onChangeLabel(value, 'bottom')}
|
|
254
300
|
mathMlOptions={mathMlOptions}
|
|
301
|
+
charactersLimit={labelsCharactersLimit}
|
|
255
302
|
/>
|
|
256
303
|
)}
|
|
257
304
|
</div>
|
|
@@ -259,13 +306,15 @@ export class Root extends React.Component {
|
|
|
259
306
|
}
|
|
260
307
|
}
|
|
261
308
|
|
|
309
|
+
// use default color theme style to avoid color contrast issues
|
|
262
310
|
const styles = (theme) => ({
|
|
263
311
|
root: {
|
|
264
312
|
border: `solid 1px ${color.primaryLight()}`,
|
|
265
|
-
color: color.
|
|
266
|
-
backgroundColor:
|
|
313
|
+
color: color.defaults.TEXT,
|
|
314
|
+
backgroundColor: theme.palette.common.white,
|
|
267
315
|
touchAction: 'none',
|
|
268
316
|
position: 'relative',
|
|
317
|
+
boxSizing: 'unset', // to override the default border-box in IBX that breaks the component width layout
|
|
269
318
|
},
|
|
270
319
|
wrapper: {
|
|
271
320
|
display: 'flex',
|
|
@@ -284,13 +333,13 @@ const styles = (theme) => ({
|
|
|
284
333
|
userSelect: 'none',
|
|
285
334
|
},
|
|
286
335
|
graphTitle: {
|
|
287
|
-
color: color.
|
|
336
|
+
color: color.defaults.TEXT,
|
|
288
337
|
fontSize: theme.typography.fontSize + 2,
|
|
289
338
|
padding: `${theme.spacing.unit * 1.5}px ${theme.spacing.unit / 2}px 0`,
|
|
290
339
|
textAlign: 'center',
|
|
291
340
|
},
|
|
292
341
|
chartTitle: {
|
|
293
|
-
color: color.
|
|
342
|
+
color: color.defaults.TEXT,
|
|
294
343
|
fontSize: theme.typography.fontSize + 4,
|
|
295
344
|
padding: `${theme.spacing.unit * 1.5}px ${theme.spacing.unit / 2}px 0`,
|
|
296
345
|
textAlign: 'center',
|
|
@@ -306,7 +355,6 @@ const styles = (theme) => ({
|
|
|
306
355
|
paddingTop: '6px',
|
|
307
356
|
},
|
|
308
357
|
topPixelIndicator: {
|
|
309
|
-
color: color.primaryLight(),
|
|
310
358
|
display: 'flex',
|
|
311
359
|
flexDirection: 'column',
|
|
312
360
|
alignItems: 'center',
|
|
@@ -321,7 +369,6 @@ const styles = (theme) => ({
|
|
|
321
369
|
marginRight: '6px',
|
|
322
370
|
},
|
|
323
371
|
sidePixelIndicator: {
|
|
324
|
-
color: color.primaryLight(),
|
|
325
372
|
textAlign: 'right',
|
|
326
373
|
height: '20px',
|
|
327
374
|
pointerEvents: 'none',
|
package/src/trig.js
CHANGED
package/src/utils.js
CHANGED
|
@@ -154,3 +154,17 @@ export const amountToIncreaseWidth = (longestWord) => {
|
|
|
154
154
|
|
|
155
155
|
return longestWord * 20;
|
|
156
156
|
};
|
|
157
|
+
|
|
158
|
+
export const extractTextFromHTML = (htmlString) => {
|
|
159
|
+
const parser = new DOMParser();
|
|
160
|
+
const doc = parser?.parseFromString(htmlString, 'text/html');
|
|
161
|
+
return doc?.body?.textContent || '';
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const isEmptyObject = (obj) => {
|
|
165
|
+
return obj && Object.keys(obj).length === 0 && obj.constructor === Object;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const isEmptyString = (str) => {
|
|
169
|
+
return typeof str === 'string' && str.trim() === '';
|
|
170
|
+
};
|