@pie-lib/graphing-solution-set 2.16.0-beta.0

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.
Files changed (151) hide show
  1. package/CHANGELOG.json +1 -0
  2. package/CHANGELOG.md +16 -0
  3. package/LICENSE.md +5 -0
  4. package/NEXT.CHANGELOG.json +1 -0
  5. package/lib/__tests__/graph-with-controls.test.js +191 -0
  6. package/lib/__tests__/graph.test.js +290 -0
  7. package/lib/__tests__/grid.test.js +40 -0
  8. package/lib/__tests__/labels.test.js +59 -0
  9. package/lib/__tests__/mark-label.test.js +154 -0
  10. package/lib/__tests__/toggle-bar.test.js +54 -0
  11. package/lib/__tests__/tool-menu.test.js +43 -0
  12. package/lib/__tests__/undo-redo.test.js +42 -0
  13. package/lib/__tests__/use-debounce.test.js +28 -0
  14. package/lib/__tests__/utils.js +72 -0
  15. package/lib/__tests__/utils.test.js +133 -0
  16. package/lib/axis/__tests__/arrow.test.js +68 -0
  17. package/lib/axis/__tests__/axes.test.js +214 -0
  18. package/lib/axis/arrow.js +115 -0
  19. package/lib/axis/axes.js +415 -0
  20. package/lib/axis/index.js +26 -0
  21. package/lib/bg.js +139 -0
  22. package/lib/container/actions.js +24 -0
  23. package/lib/container/index.js +166 -0
  24. package/lib/container/marks.js +27 -0
  25. package/lib/container/middleware.js +25 -0
  26. package/lib/container/reducer.js +25 -0
  27. package/lib/coordinates-label.js +109 -0
  28. package/lib/graph-with-controls.js +372 -0
  29. package/lib/graph.js +419 -0
  30. package/lib/grid-setup.js +462 -0
  31. package/lib/grid.js +176 -0
  32. package/lib/index.js +51 -0
  33. package/lib/labels.js +299 -0
  34. package/lib/mark-label.js +208 -0
  35. package/lib/toggle-bar.js +336 -0
  36. package/lib/tool-menu.js +325 -0
  37. package/lib/tools/index.js +29 -0
  38. package/lib/tools/line/__tests__/component.test.js +56 -0
  39. package/lib/tools/line/component.js +106 -0
  40. package/lib/tools/line/index.js +16 -0
  41. package/lib/tools/polygon/__tests__/component.test.js +245 -0
  42. package/lib/tools/polygon/__tests__/index.test.js +95 -0
  43. package/lib/tools/polygon/__tests__/line.test.js +43 -0
  44. package/lib/tools/polygon/__tests__/polygon.test.js +73 -0
  45. package/lib/tools/polygon/component.js +457 -0
  46. package/lib/tools/polygon/index.js +106 -0
  47. package/lib/tools/polygon/line.js +151 -0
  48. package/lib/tools/polygon/polygon.js +171 -0
  49. package/lib/tools/shared/__tests__/arrow-head.test.js +62 -0
  50. package/lib/tools/shared/arrow-head.js +75 -0
  51. package/lib/tools/shared/line/__tests__/index.test.js +291 -0
  52. package/lib/tools/shared/line/__tests__/line-path.test.js +78 -0
  53. package/lib/tools/shared/line/__tests__/with-root-edge.test.js +122 -0
  54. package/lib/tools/shared/line/index.js +637 -0
  55. package/lib/tools/shared/line/line-path.js +145 -0
  56. package/lib/tools/shared/line/with-root-edge.js +155 -0
  57. package/lib/tools/shared/point/__tests__/arrow-point.test.js +137 -0
  58. package/lib/tools/shared/point/__tests__/base-point.test.js +134 -0
  59. package/lib/tools/shared/point/arrow-point.js +113 -0
  60. package/lib/tools/shared/point/arrow.js +96 -0
  61. package/lib/tools/shared/point/base-point.js +151 -0
  62. package/lib/tools/shared/point/index.js +94 -0
  63. package/lib/tools/shared/styles.js +49 -0
  64. package/lib/tools/shared/types.js +19 -0
  65. package/lib/undo-redo.js +107 -0
  66. package/lib/use-debounce.js +32 -0
  67. package/lib/utils.js +314 -0
  68. package/package.json +50 -0
  69. package/src/__tests__/__snapshots__/graph-with-controls.test.jsx.snap +114 -0
  70. package/src/__tests__/__snapshots__/graph.test.jsx.snap +213 -0
  71. package/src/__tests__/__snapshots__/grid.test.jsx.snap +54 -0
  72. package/src/__tests__/__snapshots__/labels.test.jsx.snap +30 -0
  73. package/src/__tests__/__snapshots__/mark-label.test.jsx.snap +37 -0
  74. package/src/__tests__/__snapshots__/toggle-bar.test.jsx.snap +7 -0
  75. package/src/__tests__/__snapshots__/tool-menu.test.jsx.snap +35 -0
  76. package/src/__tests__/__snapshots__/undo-redo.test.jsx.snap +15 -0
  77. package/src/__tests__/graph-with-controls.test.jsx +131 -0
  78. package/src/__tests__/graph.test.jsx +230 -0
  79. package/src/__tests__/grid.test.jsx +20 -0
  80. package/src/__tests__/labels.test.jsx +38 -0
  81. package/src/__tests__/mark-label.test.jsx +68 -0
  82. package/src/__tests__/toggle-bar.test.jsx +36 -0
  83. package/src/__tests__/tool-menu.test.jsx +29 -0
  84. package/src/__tests__/undo-redo.test.jsx +25 -0
  85. package/src/__tests__/use-debounce.test.js +21 -0
  86. package/src/__tests__/utils.js +38 -0
  87. package/src/__tests__/utils.test.js +151 -0
  88. package/src/axis/__tests__/__snapshots__/arrow.test.jsx.snap +33 -0
  89. package/src/axis/__tests__/__snapshots__/axes.test.jsx.snap +122 -0
  90. package/src/axis/__tests__/arrow.test.jsx +39 -0
  91. package/src/axis/__tests__/axes.test.jsx +220 -0
  92. package/src/axis/arrow.jsx +62 -0
  93. package/src/axis/axes.jsx +307 -0
  94. package/src/axis/index.js +2 -0
  95. package/src/bg.jsx +96 -0
  96. package/src/container/actions.js +8 -0
  97. package/src/container/index.jsx +86 -0
  98. package/src/container/marks.js +14 -0
  99. package/src/container/middleware.js +7 -0
  100. package/src/container/reducer.js +5 -0
  101. package/src/coordinates-label.jsx +73 -0
  102. package/src/graph-with-controls.jsx +263 -0
  103. package/src/graph.jsx +334 -0
  104. package/src/grid-setup.jsx +427 -0
  105. package/src/grid.jsx +135 -0
  106. package/src/index.js +7 -0
  107. package/src/labels.jsx +214 -0
  108. package/src/mark-label.jsx +136 -0
  109. package/src/toggle-bar.jsx +242 -0
  110. package/src/tool-menu.jsx +294 -0
  111. package/src/tools/index.js +8 -0
  112. package/src/tools/line/__tests__/__snapshots__/component.test.jsx.snap +20 -0
  113. package/src/tools/line/__tests__/component.test.jsx +36 -0
  114. package/src/tools/line/component.jsx +77 -0
  115. package/src/tools/line/index.js +4 -0
  116. package/src/tools/polygon/__tests__/__snapshots__/component.test.jsx.snap +94 -0
  117. package/src/tools/polygon/__tests__/__snapshots__/line.test.jsx.snap +44 -0
  118. package/src/tools/polygon/__tests__/__snapshots__/polygon.test.jsx.snap +53 -0
  119. package/src/tools/polygon/__tests__/component.test.jsx +214 -0
  120. package/src/tools/polygon/__tests__/index.test.js +65 -0
  121. package/src/tools/polygon/__tests__/line.test.jsx +25 -0
  122. package/src/tools/polygon/__tests__/polygon.test.jsx +44 -0
  123. package/src/tools/polygon/component.jsx +336 -0
  124. package/src/tools/polygon/index.js +52 -0
  125. package/src/tools/polygon/line.jsx +78 -0
  126. package/src/tools/polygon/polygon.jsx +101 -0
  127. package/src/tools/shared/__tests__/__snapshots__/arrow-head.test.jsx.snap +32 -0
  128. package/src/tools/shared/__tests__/arrow-head.test.jsx +34 -0
  129. package/src/tools/shared/arrow-head.jsx +46 -0
  130. package/src/tools/shared/line/__tests__/__snapshots__/index.test.jsx.snap +360 -0
  131. package/src/tools/shared/line/__tests__/__snapshots__/line-path.test.jsx.snap +57 -0
  132. package/src/tools/shared/line/__tests__/__snapshots__/with-root-edge.test.jsx.snap +63 -0
  133. package/src/tools/shared/line/__tests__/index.test.jsx +247 -0
  134. package/src/tools/shared/line/__tests__/line-path.test.jsx +53 -0
  135. package/src/tools/shared/line/__tests__/with-root-edge.test.jsx +73 -0
  136. package/src/tools/shared/line/index.jsx +473 -0
  137. package/src/tools/shared/line/line-path.jsx +88 -0
  138. package/src/tools/shared/line/with-root-edge.jsx +97 -0
  139. package/src/tools/shared/point/__tests__/__snapshots__/arrow-point.test.jsx.snap +55 -0
  140. package/src/tools/shared/point/__tests__/__snapshots__/base-point.test.jsx.snap +43 -0
  141. package/src/tools/shared/point/__tests__/arrow-point.test.jsx +87 -0
  142. package/src/tools/shared/point/__tests__/base-point.test.jsx +84 -0
  143. package/src/tools/shared/point/arrow-point.jsx +60 -0
  144. package/src/tools/shared/point/arrow.jsx +40 -0
  145. package/src/tools/shared/point/base-point.jsx +86 -0
  146. package/src/tools/shared/point/index.jsx +60 -0
  147. package/src/tools/shared/styles.js +20 -0
  148. package/src/tools/shared/types.js +8 -0
  149. package/src/undo-redo.jsx +47 -0
  150. package/src/use-debounce.js +13 -0
  151. package/src/utils.js +234 -0
@@ -0,0 +1,307 @@
1
+ import React from 'react';
2
+ import { Axis } from '@vx/axis';
3
+ import { types } from '@pie-lib/plot';
4
+ import PropTypes from 'prop-types';
5
+ import Arrow from './arrow';
6
+ import { withStyles } from '@material-ui/core';
7
+ import { countWords, findLongestWord, amountToIncreaseWidth, getTickValues } from '../utils';
8
+ import { color, Readable } from '@pie-lib/render-ui';
9
+
10
+ export const AxisPropTypes = {
11
+ includeArrows: PropTypes.object,
12
+ graphProps: PropTypes.object,
13
+ };
14
+
15
+ const AxisDefaultProps = {
16
+ includeArrows: {
17
+ left: true,
18
+ right: true,
19
+ up: true,
20
+ down: true,
21
+ },
22
+ };
23
+
24
+ const axisStyles = (theme) => ({
25
+ line: {
26
+ stroke: '#8a92c0',
27
+ strokeWidth: 4,
28
+ },
29
+ arrow: {
30
+ fill: '#8a92c0',
31
+ },
32
+ tick: {
33
+ fill: color.defaults.BLACK,
34
+ '& > line': {
35
+ stroke: '#8a92c0',
36
+ },
37
+ },
38
+ labelFontSize: {
39
+ fontSize: theme.typography.fontSize,
40
+ },
41
+ axisLabelHolder: {
42
+ padding: 0,
43
+ margin: 0,
44
+ textAlign: 'center',
45
+ '* > *': {
46
+ margin: 0,
47
+ padding: 0,
48
+ },
49
+ fontSize: theme.typography.fontSize,
50
+ },
51
+ });
52
+
53
+ const tickLabelStyles = {
54
+ fontFamily: 'Roboto',
55
+ fontSize: '14px',
56
+ cursor: 'inherit',
57
+ };
58
+
59
+ export const sharedValues = (
60
+ firstNegativeX,
61
+ firstNegativeY,
62
+ distanceFromOriginToFirstNegativeX,
63
+ distanceFromOriginToFirstNegativeY,
64
+ deltaAllowance,
65
+ dy,
66
+ ) => {
67
+ let result = [];
68
+
69
+ if (
70
+ firstNegativeX === firstNegativeY &&
71
+ distanceFromOriginToFirstNegativeX - deltaAllowance < distanceFromOriginToFirstNegativeY &&
72
+ distanceFromOriginToFirstNegativeY < distanceFromOriginToFirstNegativeX + deltaAllowance &&
73
+ distanceFromOriginToFirstNegativeX - deltaAllowance < dy &&
74
+ dy < distanceFromOriginToFirstNegativeX + deltaAllowance
75
+ ) {
76
+ result.push(firstNegativeX);
77
+ }
78
+
79
+ return result;
80
+ };
81
+
82
+ export const firstNegativeValue = (interval) => (interval || []).find((element) => element < 0);
83
+
84
+ export class RawXAxis extends React.Component {
85
+ static propTypes = {
86
+ ...AxisPropTypes,
87
+ classes: PropTypes.object,
88
+ graphProps: types.GraphPropsType.isRequired,
89
+ };
90
+ static defaultProps = AxisDefaultProps;
91
+
92
+ render() {
93
+ const {
94
+ includeArrows,
95
+ classes,
96
+ graphProps,
97
+ columnTicksValues,
98
+ skipValues,
99
+ distanceFromOriginToFirstNegativeY,
100
+ dy,
101
+ } = this.props;
102
+ const { scale, domain, size, range } = graphProps || {};
103
+
104
+ // Having 0 as a number in columnTicksValues does not make 0 to show up
105
+ // so we use this trick, by defining it as a string:
106
+ const tickValues =
107
+ (domain.labelStep || range.labelStep) && domain.min <= 0 ? ['0', ...columnTicksValues] : columnTicksValues;
108
+ // However, the '0' has to be displayed only if other tick labels (y-axis or x-axis) are displayed
109
+
110
+ const labelProps = (label) => {
111
+ const y = skipValues && skipValues[0] === label ? distanceFromOriginToFirstNegativeY + 4 : dy;
112
+
113
+ return {
114
+ ...tickLabelStyles,
115
+ textAnchor: 'middle',
116
+ y: y,
117
+ dx: label === '0' ? -10 : 0,
118
+ dy: label === '0' ? -7 : 0,
119
+ };
120
+ };
121
+
122
+ const necessaryRows = countWords(domain.axisLabel);
123
+ const longestWord = findLongestWord(domain.axisLabel);
124
+ const necessaryWidth = amountToIncreaseWidth(longestWord) + 2;
125
+
126
+ return (
127
+ <React.Fragment>
128
+ <Axis
129
+ axisLineClassName={classes.line}
130
+ scale={scale.x}
131
+ top={scale.y(0)}
132
+ left={0}
133
+ label={domain.label}
134
+ rangePadding={8}
135
+ tickClassName={classes.tick}
136
+ tickFormat={(value) => value}
137
+ tickLabelProps={labelProps}
138
+ tickValues={tickValues}
139
+ />
140
+ {includeArrows && includeArrows.left && (
141
+ <Arrow direction="left" x={domain.min} y={0} className={classes.arrow} scale={scale} />
142
+ )}
143
+ {includeArrows && includeArrows.right && (
144
+ <Arrow direction="right" x={domain.max} y={0} className={classes.arrow} scale={scale} />
145
+ )}
146
+ {domain.axisLabel && (
147
+ <foreignObject x={size.width + 17} y={scale.y(0) - 9} width={necessaryWidth} height={20 * necessaryRows}>
148
+ <div dangerouslySetInnerHTML={{ __html: domain.axisLabel }} className={classes.labelFontSize} />
149
+ </foreignObject>
150
+ )}
151
+ </React.Fragment>
152
+ );
153
+ }
154
+ }
155
+
156
+ const XAxis = withStyles(axisStyles)(RawXAxis);
157
+
158
+ export class RawYAxis extends React.Component {
159
+ static propTypes = {
160
+ ...AxisPropTypes,
161
+ graphProps: types.GraphPropsType.isRequired,
162
+ };
163
+ static defaultProps = AxisDefaultProps;
164
+
165
+ render() {
166
+ const { classes, includeArrows, graphProps, skipValues, rowTickValues } = this.props;
167
+ const { scale, range, size } = graphProps || {};
168
+
169
+ const necessaryWidth = range.axisLabel ? amountToIncreaseWidth(range.axisLabel.length) : 0;
170
+
171
+ const customTickFormat = (value) => (skipValues && skipValues.indexOf(value) >= 0 ? '' : value);
172
+
173
+ return (
174
+ <React.Fragment>
175
+ <Axis
176
+ axisLineClassName={classes.line}
177
+ orientation={'left'}
178
+ scale={scale.y}
179
+ top={0}
180
+ height={size.height}
181
+ left={scale.x(0)}
182
+ label={range.label}
183
+ labelProps={{ 'data-pie-readable': false }}
184
+ rangePadding={8}
185
+ tickLength={10}
186
+ tickClassName={classes.tick}
187
+ tickFormat={customTickFormat}
188
+ tickLabelProps={(value) => {
189
+ let digits = value.toLocaleString().replace(/[.-]/g, '').length || 1;
190
+
191
+ return {
192
+ ...tickLabelStyles,
193
+ dy: 4,
194
+ dx: -10 - digits * 9,
195
+ 'data-pie-readable': false,
196
+ };
197
+ }}
198
+ hideZero={true}
199
+ tickTextAnchor={'bottom'}
200
+ tickValues={rowTickValues}
201
+ />
202
+
203
+ {includeArrows && includeArrows.down && (
204
+ <Arrow direction="down" x={0} y={range.min} className={classes.arrow} scale={scale} />
205
+ )}
206
+ {includeArrows && includeArrows.up && (
207
+ <Arrow direction="up" x={0} y={range.max} className={classes.arrow} scale={scale} />
208
+ )}
209
+ {range.axisLabel && (
210
+ <foreignObject x={scale.x(0) - necessaryWidth / 2} y={-33} width={necessaryWidth} height="20">
211
+ <Readable false>
212
+ <div dangerouslySetInnerHTML={{ __html: range.axisLabel }} className={classes.axisLabelHolder} />
213
+ </Readable>
214
+ </foreignObject>
215
+ )}
216
+ </React.Fragment>
217
+ );
218
+ }
219
+ }
220
+
221
+ const YAxis = withStyles(axisStyles)(RawYAxis);
222
+
223
+ export default class Axes extends React.Component {
224
+ static propTypes = {
225
+ ...AxisPropTypes,
226
+ classes: PropTypes.object,
227
+ graphProps: types.GraphPropsType.isRequired,
228
+ };
229
+ static defaultProps = AxisDefaultProps;
230
+
231
+ xValues = () => {
232
+ const { graphProps } = this.props;
233
+ const { scale, domain } = graphProps || {};
234
+
235
+ if (!domain || !scale) {
236
+ return;
237
+ }
238
+
239
+ const ticks = getTickValues({ ...domain, step: domain.labelStep });
240
+ const negative = firstNegativeValue(ticks);
241
+
242
+ return {
243
+ columnTicksValues: ticks,
244
+ firstNegativeX: negative,
245
+ distanceFromOriginToFirstNegativeX: Math.abs(scale.y(0) - scale.y(negative)),
246
+ };
247
+ };
248
+
249
+ yValues = () => {
250
+ const { graphProps } = this.props;
251
+ const { scale, range } = graphProps || {};
252
+
253
+ if (!range || !scale) {
254
+ return;
255
+ }
256
+
257
+ const ticks = getTickValues({ ...range, step: range.labelStep });
258
+ const negative = firstNegativeValue(ticks);
259
+
260
+ return {
261
+ rowTickValues: ticks,
262
+ firstNegativeY: negative,
263
+ distanceFromOriginToFirstNegativeY: Math.abs(scale.x(0) - scale.x(negative)),
264
+ };
265
+ };
266
+
267
+ render() {
268
+ const { graphProps } = this.props;
269
+ const { domain, range } = graphProps || {};
270
+ const { columnTicksValues, firstNegativeX, distanceFromOriginToFirstNegativeX } = this.xValues();
271
+ const { rowTickValues, firstNegativeY, distanceFromOriginToFirstNegativeY } = this.yValues();
272
+ const deltaAllowance = 6;
273
+ const dy = 25;
274
+
275
+ const skipValues = sharedValues(
276
+ firstNegativeX,
277
+ firstNegativeY,
278
+ distanceFromOriginToFirstNegativeX,
279
+ distanceFromOriginToFirstNegativeY,
280
+ deltaAllowance,
281
+ dy,
282
+ );
283
+
284
+ // each axis has to be displayed only if the domain & range include it
285
+ return (
286
+ <React.Fragment>
287
+ {range.min <= 0 ? (
288
+ <XAxis
289
+ {...this.props}
290
+ skipValues={skipValues}
291
+ columnTicksValues={columnTicksValues}
292
+ distanceFromOriginToFirstNegativeY={distanceFromOriginToFirstNegativeY}
293
+ dy={dy}
294
+ />
295
+ ) : null}
296
+ {domain.min <= 0 ? (
297
+ <YAxis
298
+ {...this.props}
299
+ skipValues={skipValues}
300
+ rowTickValues={rowTickValues}
301
+ distanceFromOriginToFirstNegativeX={distanceFromOriginToFirstNegativeX}
302
+ />
303
+ ) : null}
304
+ </React.Fragment>
305
+ );
306
+ }
307
+ }
@@ -0,0 +1,2 @@
1
+ import Axes, { AxisPropTypes } from './axes';
2
+ export { Axes, AxisPropTypes };
package/src/bg.jsx ADDED
@@ -0,0 +1,96 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { select, mouse } from 'd3-selection';
4
+ import { types, utils } from '@pie-lib/plot';
5
+ import { getTickValues, thinnerShapesNeeded } from './utils';
6
+
7
+ export default class Bg extends React.Component {
8
+ static propTypes = {
9
+ width: PropTypes.number.isRequired,
10
+ height: PropTypes.number.isRequired,
11
+ onClick: PropTypes.func.isRequired,
12
+ graphProps: types.GraphPropsType.isRequired,
13
+ };
14
+
15
+ static defaultProps = {};
16
+
17
+ componentDidMount() {
18
+ const rect = select(this.rect);
19
+
20
+ rect.on('click', this.onRectClick.bind(this, rect));
21
+ }
22
+
23
+ shouldComponentUpdate(nextProps) {
24
+ return (
25
+ !utils.isDomainRangeEqual(this.props.graphProps, nextProps.graphProps) ||
26
+ this.props.width !== nextProps.width ||
27
+ this.props.height !== nextProps.height
28
+ );
29
+ }
30
+
31
+ getRectPadding = () => {
32
+ const { graphProps } = this.props;
33
+
34
+ return thinnerShapesNeeded(graphProps) ? 6 : 10;
35
+ };
36
+
37
+ /**
38
+ * Note: we use d3 click + mouse to give us domain values directly.
39
+ * Saves us having to calculate them ourselves from a MouseEvent.
40
+ */
41
+ onRectClick = (rect) => {
42
+ const { onClick, graphProps } = this.props;
43
+ const { scale } = graphProps;
44
+
45
+ const padding = this.getRectPadding();
46
+ const coords = mouse(rect._groups[0][0]);
47
+
48
+ // decrease the padding from coordinates to indicate the correct point clicked
49
+ const x = scale.x.invert(coords[0] - padding);
50
+ const y = scale.y.invert(coords[1] - padding);
51
+
52
+ const rowTicks = getTickValues(graphProps.range);
53
+ const columnTicks = getTickValues(graphProps.domain);
54
+
55
+ const closest = (ticks, value) => {
56
+ return (
57
+ ticks.length &&
58
+ ticks.reduce((prev, curr) => {
59
+ const currentDistance = Math.abs(curr - value);
60
+ const previousDistance = Math.abs(prev - value);
61
+
62
+ return currentDistance <= previousDistance ? curr : prev;
63
+ })
64
+ );
65
+ };
66
+
67
+ let snapped = {};
68
+
69
+ if (columnTicks.indexOf(x) >= 0 && rowTicks.indexOf(y) >= 0) {
70
+ snapped.x = x;
71
+ snapped.y = y;
72
+ } else {
73
+ snapped.x = closest(columnTicks, x);
74
+ snapped.y = closest(rowTicks, y);
75
+ }
76
+
77
+ onClick(snapped);
78
+ };
79
+
80
+ render() {
81
+ const { width, height } = this.props;
82
+ const padding = this.getRectPadding();
83
+
84
+ // expand the size of clickable area so a small area outside the edges of the grid lines to be clickable
85
+ return (
86
+ <rect
87
+ ref={(rect) => (this.rect = rect)}
88
+ transform={`translate(-${padding}, -${padding})`}
89
+ fill="red"
90
+ fillOpacity="0.0"
91
+ width={width + padding * 2}
92
+ height={height + padding * 2}
93
+ />
94
+ );
95
+ }
96
+ }
@@ -0,0 +1,8 @@
1
+ export const addMark = () => ({
2
+ type: 'ADD_MARK',
3
+ });
4
+
5
+ export const changeMarks = (marks) => ({
6
+ type: 'CHANGE_MARKS',
7
+ marks,
8
+ });
@@ -0,0 +1,86 @@
1
+ import { connect } from 'react-redux';
2
+ import React from 'react';
3
+ import { Provider } from 'react-redux';
4
+ import { applyMiddleware, createStore } from 'redux';
5
+ import reducer from './reducer';
6
+ import { changeMarks } from './actions';
7
+ import PropTypes from 'prop-types';
8
+ import isEqual from 'lodash/isEqual';
9
+ import { ActionCreators } from 'redux-undo';
10
+ import GraphWithControls from '../graph-with-controls';
11
+ import { lastActionMiddleware, getLastAction } from './middleware';
12
+
13
+ const mapStateToProps = (s) => ({
14
+ marks: s.marks.present,
15
+ });
16
+
17
+ const mapDispatchToProps = (dispatch) => ({
18
+ onChangeMarks: (m) => dispatch(changeMarks(m)),
19
+ onUndo: () => dispatch(ActionCreators.undo()),
20
+ onRedo: () => dispatch(ActionCreators.redo()),
21
+ onReset: () => dispatch(changeMarks([])),
22
+ });
23
+
24
+ export const GraphContainer = connect(mapStateToProps, mapDispatchToProps)(GraphWithControls);
25
+
26
+ /**
27
+ * The graph component entry point with undo/redo
28
+ * Redux is an implementation detail, hide it in the react component.
29
+ */
30
+ class Root extends React.Component {
31
+ static propTypes = {
32
+ onChangeMarks: PropTypes.func,
33
+ marks: PropTypes.array,
34
+ };
35
+
36
+ constructor(props) {
37
+ super(props);
38
+
39
+ const r = reducer();
40
+ this.store = createStore(r, { marks: props.marks }, applyMiddleware(lastActionMiddleware));
41
+
42
+ this.store.subscribe(this.onStoreChange);
43
+ }
44
+
45
+ componentDidUpdate(prevProps) {
46
+ const { marks } = this.props;
47
+ const storeState = this.store.getState();
48
+
49
+ if (isEqual(storeState.marks.present, marks)) {
50
+ return;
51
+ }
52
+
53
+ if (!isEqual(prevProps.marks, marks)) {
54
+ this.store.dispatch(changeMarks(marks));
55
+ }
56
+ }
57
+
58
+ onStoreChange = () => {
59
+ const { marks, onChangeMarks } = this.props;
60
+ const storeState = this.store.getState();
61
+ const lastAction = getLastAction();
62
+ const isUndoOperation = lastAction.type.includes('UNDO') || lastAction.type.includes('REDO');
63
+
64
+ if (!isEqual(storeState.marks.present, marks)) {
65
+ onChangeMarks(storeState.marks.present, isUndoOperation);
66
+ }
67
+ };
68
+
69
+ render() {
70
+ // eslint-disable-next-line no-unused-vars
71
+ const { onChangeMarks, marks, ...rest } = this.props;
72
+ const correctnessSet = marks && marks.find((m) => m.correctness);
73
+
74
+ if (correctnessSet) {
75
+ return <GraphWithControls {...rest} marks={marks} disabled={correctnessSet} />;
76
+ }
77
+
78
+ return (
79
+ <Provider store={this.store}>
80
+ <GraphContainer {...rest} />
81
+ </Provider>
82
+ );
83
+ }
84
+ }
85
+
86
+ export default Root;
@@ -0,0 +1,14 @@
1
+ const marks = (state = [], action) => {
2
+ switch (action.type) {
3
+ case 'CHANGE_MARKS':
4
+ if (Array.isArray(action.marks)) {
5
+ return action.marks;
6
+ } else {
7
+ throw new Error('marks must be an array');
8
+ }
9
+ default:
10
+ return state;
11
+ }
12
+ };
13
+
14
+ export default marks;
@@ -0,0 +1,7 @@
1
+ let lastAction = null;
2
+ export const getLastAction = () => lastAction;
3
+
4
+ export const lastActionMiddleware = () => (next) => (action) => {
5
+ lastAction = action;
6
+ return next(action);
7
+ };
@@ -0,0 +1,5 @@
1
+ import { combineReducers } from 'redux';
2
+ import marks from './marks';
3
+ import undoable from 'redux-undo';
4
+
5
+ export default () => combineReducers({ marks: undoable(marks, { debug: false }) });
@@ -0,0 +1,73 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { types } from '@pie-lib/plot';
4
+ import { color } from '@pie-lib/render-ui';
5
+ import { withStyles } from '@material-ui/core/styles';
6
+ import InputBase from '@material-ui/core/InputBase';
7
+ import { roundNumber } from './utils';
8
+
9
+ const styles = (theme) => ({
10
+ input: {
11
+ fontFamily: theme.typography.fontFamily,
12
+ fontSize: theme.typography.fontSize,
13
+ borderRadius: '8px',
14
+ background: theme.palette.common.white,
15
+ color: color.defaults.BLACK,
16
+ },
17
+ inputLabel: {
18
+ padding: 0,
19
+ },
20
+ });
21
+
22
+ export const getLabelPosition = (graphProps, x, y, labelLength) => {
23
+ const { scale, domain, range } = graphProps;
24
+ // treat corner cases for maximum and minimum
25
+ const topShift = y === range.min ? 16 : y === range.max ? 0 : 8;
26
+ const leftShift = 10;
27
+ const rightEdge = scale.x(x) + labelLength + leftShift;
28
+
29
+ if (rightEdge >= scale.x(domain.max)) {
30
+ return {
31
+ left: scale.x(x) - leftShift - labelLength,
32
+ top: scale.y(y) - topShift,
33
+ };
34
+ }
35
+
36
+ return {
37
+ left: scale.x(x) + leftShift,
38
+ top: scale.y(y) - topShift,
39
+ };
40
+ };
41
+
42
+ export const CoordinatesLabel = ({ x, y, graphProps, classes }) => {
43
+ const label = `(${roundNumber(x)}, ${roundNumber(y)})`;
44
+ const labelLength = (label.length || 0) * 6;
45
+ const labelPosition = getLabelPosition(graphProps, x, y, labelLength);
46
+
47
+ const style = {
48
+ position: 'absolute',
49
+ pointerEvents: 'auto',
50
+ width: labelLength,
51
+ padding: 0,
52
+ ...labelPosition,
53
+ };
54
+
55
+ return (
56
+ <InputBase
57
+ style={style}
58
+ classes={{ input: classes.inputLabel }}
59
+ className={classes.input}
60
+ value={label}
61
+ inputProps={{ ariaLabel: 'naked' }}
62
+ />
63
+ );
64
+ };
65
+
66
+ CoordinatesLabel.propTypes = {
67
+ graphProps: types.GraphPropsType,
68
+ classes: PropTypes.object,
69
+ x: PropTypes.number,
70
+ y: PropTypes.number,
71
+ };
72
+
73
+ export default withStyles(styles)(CoordinatesLabel);