@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.
- package/CHANGELOG.json +1 -0
- package/CHANGELOG.md +16 -0
- package/LICENSE.md +5 -0
- package/NEXT.CHANGELOG.json +1 -0
- package/lib/__tests__/graph-with-controls.test.js +191 -0
- package/lib/__tests__/graph.test.js +290 -0
- package/lib/__tests__/grid.test.js +40 -0
- package/lib/__tests__/labels.test.js +59 -0
- package/lib/__tests__/mark-label.test.js +154 -0
- package/lib/__tests__/toggle-bar.test.js +54 -0
- package/lib/__tests__/tool-menu.test.js +43 -0
- package/lib/__tests__/undo-redo.test.js +42 -0
- package/lib/__tests__/use-debounce.test.js +28 -0
- package/lib/__tests__/utils.js +72 -0
- package/lib/__tests__/utils.test.js +133 -0
- package/lib/axis/__tests__/arrow.test.js +68 -0
- package/lib/axis/__tests__/axes.test.js +214 -0
- package/lib/axis/arrow.js +115 -0
- package/lib/axis/axes.js +415 -0
- package/lib/axis/index.js +26 -0
- package/lib/bg.js +139 -0
- package/lib/container/actions.js +24 -0
- package/lib/container/index.js +166 -0
- package/lib/container/marks.js +27 -0
- package/lib/container/middleware.js +25 -0
- package/lib/container/reducer.js +25 -0
- package/lib/coordinates-label.js +109 -0
- package/lib/graph-with-controls.js +372 -0
- package/lib/graph.js +419 -0
- package/lib/grid-setup.js +462 -0
- package/lib/grid.js +176 -0
- package/lib/index.js +51 -0
- package/lib/labels.js +299 -0
- package/lib/mark-label.js +208 -0
- package/lib/toggle-bar.js +336 -0
- package/lib/tool-menu.js +325 -0
- package/lib/tools/index.js +29 -0
- package/lib/tools/line/__tests__/component.test.js +56 -0
- package/lib/tools/line/component.js +106 -0
- package/lib/tools/line/index.js +16 -0
- package/lib/tools/polygon/__tests__/component.test.js +245 -0
- package/lib/tools/polygon/__tests__/index.test.js +95 -0
- package/lib/tools/polygon/__tests__/line.test.js +43 -0
- package/lib/tools/polygon/__tests__/polygon.test.js +73 -0
- package/lib/tools/polygon/component.js +457 -0
- package/lib/tools/polygon/index.js +106 -0
- package/lib/tools/polygon/line.js +151 -0
- package/lib/tools/polygon/polygon.js +171 -0
- package/lib/tools/shared/__tests__/arrow-head.test.js +62 -0
- package/lib/tools/shared/arrow-head.js +75 -0
- package/lib/tools/shared/line/__tests__/index.test.js +291 -0
- package/lib/tools/shared/line/__tests__/line-path.test.js +78 -0
- package/lib/tools/shared/line/__tests__/with-root-edge.test.js +122 -0
- package/lib/tools/shared/line/index.js +637 -0
- package/lib/tools/shared/line/line-path.js +145 -0
- package/lib/tools/shared/line/with-root-edge.js +155 -0
- package/lib/tools/shared/point/__tests__/arrow-point.test.js +137 -0
- package/lib/tools/shared/point/__tests__/base-point.test.js +134 -0
- package/lib/tools/shared/point/arrow-point.js +113 -0
- package/lib/tools/shared/point/arrow.js +96 -0
- package/lib/tools/shared/point/base-point.js +151 -0
- package/lib/tools/shared/point/index.js +94 -0
- package/lib/tools/shared/styles.js +49 -0
- package/lib/tools/shared/types.js +19 -0
- package/lib/undo-redo.js +107 -0
- package/lib/use-debounce.js +32 -0
- package/lib/utils.js +314 -0
- package/package.json +50 -0
- package/src/__tests__/__snapshots__/graph-with-controls.test.jsx.snap +114 -0
- package/src/__tests__/__snapshots__/graph.test.jsx.snap +213 -0
- package/src/__tests__/__snapshots__/grid.test.jsx.snap +54 -0
- package/src/__tests__/__snapshots__/labels.test.jsx.snap +30 -0
- package/src/__tests__/__snapshots__/mark-label.test.jsx.snap +37 -0
- package/src/__tests__/__snapshots__/toggle-bar.test.jsx.snap +7 -0
- package/src/__tests__/__snapshots__/tool-menu.test.jsx.snap +35 -0
- package/src/__tests__/__snapshots__/undo-redo.test.jsx.snap +15 -0
- package/src/__tests__/graph-with-controls.test.jsx +131 -0
- package/src/__tests__/graph.test.jsx +230 -0
- package/src/__tests__/grid.test.jsx +20 -0
- package/src/__tests__/labels.test.jsx +38 -0
- package/src/__tests__/mark-label.test.jsx +68 -0
- package/src/__tests__/toggle-bar.test.jsx +36 -0
- package/src/__tests__/tool-menu.test.jsx +29 -0
- package/src/__tests__/undo-redo.test.jsx +25 -0
- package/src/__tests__/use-debounce.test.js +21 -0
- package/src/__tests__/utils.js +38 -0
- package/src/__tests__/utils.test.js +151 -0
- package/src/axis/__tests__/__snapshots__/arrow.test.jsx.snap +33 -0
- package/src/axis/__tests__/__snapshots__/axes.test.jsx.snap +122 -0
- package/src/axis/__tests__/arrow.test.jsx +39 -0
- package/src/axis/__tests__/axes.test.jsx +220 -0
- package/src/axis/arrow.jsx +62 -0
- package/src/axis/axes.jsx +307 -0
- package/src/axis/index.js +2 -0
- package/src/bg.jsx +96 -0
- package/src/container/actions.js +8 -0
- package/src/container/index.jsx +86 -0
- package/src/container/marks.js +14 -0
- package/src/container/middleware.js +7 -0
- package/src/container/reducer.js +5 -0
- package/src/coordinates-label.jsx +73 -0
- package/src/graph-with-controls.jsx +263 -0
- package/src/graph.jsx +334 -0
- package/src/grid-setup.jsx +427 -0
- package/src/grid.jsx +135 -0
- package/src/index.js +7 -0
- package/src/labels.jsx +214 -0
- package/src/mark-label.jsx +136 -0
- package/src/toggle-bar.jsx +242 -0
- package/src/tool-menu.jsx +294 -0
- package/src/tools/index.js +8 -0
- package/src/tools/line/__tests__/__snapshots__/component.test.jsx.snap +20 -0
- package/src/tools/line/__tests__/component.test.jsx +36 -0
- package/src/tools/line/component.jsx +77 -0
- package/src/tools/line/index.js +4 -0
- package/src/tools/polygon/__tests__/__snapshots__/component.test.jsx.snap +94 -0
- package/src/tools/polygon/__tests__/__snapshots__/line.test.jsx.snap +44 -0
- package/src/tools/polygon/__tests__/__snapshots__/polygon.test.jsx.snap +53 -0
- package/src/tools/polygon/__tests__/component.test.jsx +214 -0
- package/src/tools/polygon/__tests__/index.test.js +65 -0
- package/src/tools/polygon/__tests__/line.test.jsx +25 -0
- package/src/tools/polygon/__tests__/polygon.test.jsx +44 -0
- package/src/tools/polygon/component.jsx +336 -0
- package/src/tools/polygon/index.js +52 -0
- package/src/tools/polygon/line.jsx +78 -0
- package/src/tools/polygon/polygon.jsx +101 -0
- package/src/tools/shared/__tests__/__snapshots__/arrow-head.test.jsx.snap +32 -0
- package/src/tools/shared/__tests__/arrow-head.test.jsx +34 -0
- package/src/tools/shared/arrow-head.jsx +46 -0
- package/src/tools/shared/line/__tests__/__snapshots__/index.test.jsx.snap +360 -0
- package/src/tools/shared/line/__tests__/__snapshots__/line-path.test.jsx.snap +57 -0
- package/src/tools/shared/line/__tests__/__snapshots__/with-root-edge.test.jsx.snap +63 -0
- package/src/tools/shared/line/__tests__/index.test.jsx +247 -0
- package/src/tools/shared/line/__tests__/line-path.test.jsx +53 -0
- package/src/tools/shared/line/__tests__/with-root-edge.test.jsx +73 -0
- package/src/tools/shared/line/index.jsx +473 -0
- package/src/tools/shared/line/line-path.jsx +88 -0
- package/src/tools/shared/line/with-root-edge.jsx +97 -0
- package/src/tools/shared/point/__tests__/__snapshots__/arrow-point.test.jsx.snap +55 -0
- package/src/tools/shared/point/__tests__/__snapshots__/base-point.test.jsx.snap +43 -0
- package/src/tools/shared/point/__tests__/arrow-point.test.jsx +87 -0
- package/src/tools/shared/point/__tests__/base-point.test.jsx +84 -0
- package/src/tools/shared/point/arrow-point.jsx +60 -0
- package/src/tools/shared/point/arrow.jsx +40 -0
- package/src/tools/shared/point/base-point.jsx +86 -0
- package/src/tools/shared/point/index.jsx +60 -0
- package/src/tools/shared/styles.js +20 -0
- package/src/tools/shared/types.js +8 -0
- package/src/undo-redo.jsx +47 -0
- package/src/use-debounce.js +13 -0
- 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
|
+
}
|
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,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,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);
|