@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,263 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { withStyles } from '@material-ui/core/styles';
4
+ import classNames from 'classnames';
5
+ import uniq from 'lodash/uniq';
6
+ import isString from 'lodash/isString';
7
+ import { color } from '@pie-lib/render-ui';
8
+ import ToolMenu from './tool-menu';
9
+ import Graph, { graphPropTypes } from './graph';
10
+ import UndoRedo from './undo-redo';
11
+ import { allTools, toolsArr } from './tools';
12
+ import { ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary, Typography } from '@material-ui/core';
13
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
14
+
15
+ export const setToolbarAvailability = (toolbarTools) =>
16
+ toolsArr.map((tA) => ({ ...tA, toolbar: !!toolbarTools.find((t) => t === tA.type) })) || [];
17
+
18
+ export const toolIsAvailable = (tools, currentTool) =>
19
+ currentTool && tools && (tools.find((tool) => tool.type === currentTool.type) || {}).toolbar;
20
+
21
+ export const getAvailableTool = (tools) => tools.find((tool) => tool.toolbar);
22
+
23
+ export const filterByValidToolTypes = (backgroundMarks) =>
24
+ backgroundMarks.filter((bM) => !!allTools.find((tool) => tool === bM.type));
25
+
26
+ export const filterByVisibleToolTypes = (toolbarTools, marks) =>
27
+ marks.filter((bM) => !!toolbarTools.find((tool) => tool === bM.type));
28
+
29
+ const getDefaultCurrentTool = (toolType) => toolsArr.find((tool) => tool.type === toolType) || null;
30
+
31
+ const Collapsible = ({ classes, children, title }) => (
32
+ <ExpansionPanel elevation={0} className={classes.expansionPanel} disabledGutters={true} square={true}>
33
+ <ExpansionPanelSummary
34
+ classes={{
35
+ root: classes.summaryRoot,
36
+ content: classes.summaryContent,
37
+ }}
38
+ expandIcon={<ExpandMoreIcon />}
39
+ >
40
+ <Typography variant="subheading">{title}</Typography>
41
+ </ExpansionPanelSummary>
42
+ <ExpansionPanelDetails className={classes.details}>{children}</ExpansionPanelDetails>
43
+ </ExpansionPanel>
44
+ );
45
+
46
+ Collapsible.propTypes = {
47
+ classes: PropTypes.object,
48
+ children: PropTypes.array,
49
+ title: PropTypes.string,
50
+ };
51
+
52
+ export class GraphWithControls extends React.Component {
53
+ static propTypes = {
54
+ ...graphPropTypes,
55
+ onUndo: PropTypes.func,
56
+ onRedo: PropTypes.func,
57
+ onReset: PropTypes.func,
58
+ toolbarTools: PropTypes.arrayOf(PropTypes.string), // array of tool types that have to be displayed in the toolbar, same shape as 'allTools'
59
+ language: PropTypes.string,
60
+ };
61
+
62
+ static defaultProps = {
63
+ collapsibleToolbar: false,
64
+ collapsibleToolbarTitle: '',
65
+ disabledLabels: false,
66
+ disabledTitle: false,
67
+ showLabels: true,
68
+ showTitle: true,
69
+ toolbarTools: [],
70
+ };
71
+
72
+ constructor(props) {
73
+ super(props);
74
+
75
+ this.state = {
76
+ currentTool: getDefaultCurrentTool(props.defaultTool),
77
+ labelModeEnabled: false,
78
+ };
79
+ }
80
+
81
+ componentDidUpdate(prevProps) {
82
+ const { defaultTool } = this.props;
83
+
84
+ if (prevProps.defaultTool !== defaultTool) {
85
+ const currentTool = getDefaultCurrentTool(defaultTool);
86
+
87
+ this.setState({ currentTool });
88
+ }
89
+ }
90
+
91
+ changeCurrentTool = (tool, tools) => this.setState({ currentTool: tools.find((t) => t.type === tool) });
92
+
93
+ toggleLabelMode = () => this.setState((state) => ({ labelModeEnabled: !state.labelModeEnabled }));
94
+
95
+ render() {
96
+ let { currentTool, labelModeEnabled } = this.state;
97
+ const {
98
+ axesSettings,
99
+ classes,
100
+ className,
101
+ coordinatesOnHover,
102
+ collapsibleToolbar,
103
+ collapsibleToolbarTitle,
104
+ disabled,
105
+ disabledLabels,
106
+ disabledTitle,
107
+ domain,
108
+ labels,
109
+ labelsPlaceholders,
110
+ onChangeLabels,
111
+ onChangeMarks,
112
+ onChangeTitle,
113
+ onUndo,
114
+ onRedo,
115
+ range,
116
+ size,
117
+ showLabels,
118
+ showPixelGuides,
119
+ showTitle,
120
+ title,
121
+ titlePlaceholder,
122
+ language,
123
+ disableToolbar = false,
124
+ gssLineData,
125
+ onChangeGssLineData,
126
+ onSolutionSetSelected,
127
+ onCustomReset,
128
+ } = this.props;
129
+ let { backgroundMarks, marks, toolbarTools } = this.props;
130
+
131
+ // make sure only valid tool types are kept (string) and without duplicates
132
+ toolbarTools = uniq(toolbarTools || []).filter((tT) => !!isString(tT)) || [];
133
+
134
+ // keep only the backgroundMarks that have valid types
135
+ backgroundMarks = filterByValidToolTypes(backgroundMarks || []);
136
+
137
+ // keep only the marks that have types which appear in toolbar
138
+ marks = filterByVisibleToolTypes(toolbarTools, marks || []);
139
+
140
+ if (gssLineData && gssLineData.lineA && marks[0] && marks[0].type === 'line')
141
+ marks[0].fill = gssLineData.lineA.lineType;
142
+ if (gssLineData && gssLineData.lineB && marks[1] && marks[1].type === 'line')
143
+ marks[1].fill = gssLineData.lineB.lineType;
144
+
145
+ const tools = setToolbarAvailability(toolbarTools);
146
+
147
+ // set current tool if there's no current tool or if the existing one is no longer available
148
+ if (!currentTool || !toolIsAvailable(tools, currentTool)) {
149
+ currentTool = getAvailableTool(tools);
150
+ }
151
+
152
+ const gssActions = gssLineData && (
153
+ <React.Fragment>
154
+ <ToolMenu
155
+ numberOfLines={gssLineData.numberOfLines}
156
+ gssLineData={gssLineData}
157
+ onChange={onChangeGssLineData}
158
+ disabled={!!disabled}
159
+ language={language}
160
+ />
161
+ {!disabled && (
162
+ <UndoRedo
163
+ className={classes.undoRedoOuterDiv}
164
+ onUndo={onUndo}
165
+ onRedo={onRedo}
166
+ onReset={onCustomReset}
167
+ language={language}
168
+ />
169
+ )}
170
+ </React.Fragment>
171
+ );
172
+
173
+ return (
174
+ <div className={classNames(classes.graphWithControls, className)}>
175
+ {!disableToolbar && (
176
+ <div className={classes.controls}>
177
+ {collapsibleToolbar ? (
178
+ <Collapsible classes={classes} title={collapsibleToolbarTitle}>
179
+ {gssActions}
180
+ </Collapsible>
181
+ ) : (
182
+ gssActions
183
+ )}
184
+ </div>
185
+ )}
186
+
187
+ <div ref={(r) => (this.labelNode = r)} />
188
+
189
+ <Graph
190
+ axesSettings={axesSettings}
191
+ backgroundMarks={backgroundMarks}
192
+ coordinatesOnHover={coordinatesOnHover}
193
+ currentTool={currentTool}
194
+ disabledLabels={disabledLabels}
195
+ disabledTitle={disabledTitle}
196
+ domain={domain}
197
+ labels={labels}
198
+ labelModeEnabled={labelModeEnabled}
199
+ labelsPlaceholders={labelsPlaceholders}
200
+ marks={marks}
201
+ onChangeMarks={!disabled ? onChangeMarks : undefined}
202
+ onChangeLabels={onChangeLabels}
203
+ onChangeTitle={onChangeTitle}
204
+ range={range}
205
+ size={size}
206
+ showLabels={showLabels}
207
+ showPixelGuides={showPixelGuides}
208
+ showTitle={showTitle}
209
+ title={title}
210
+ titlePlaceholder={titlePlaceholder}
211
+ tools={tools}
212
+ gssLineData={gssLineData}
213
+ onSolutionSetSelected={onSolutionSetSelected}
214
+ disabled={!!disabled}
215
+ />
216
+ </div>
217
+ );
218
+ }
219
+ }
220
+
221
+ const styles = (theme) => ({
222
+ graphWithControls: {
223
+ display: 'flex',
224
+ flexDirection: 'column',
225
+ width: 'min-content',
226
+ },
227
+ controls: {
228
+ display: 'flex',
229
+ justifyContent: 'space-between',
230
+ padding: 'calc(1.25rem - 12px) calc(1.25rem - 12px) 1.25rem',
231
+ color: color.text(),
232
+ backgroundColor: '#9FA8DA',
233
+ '& button': {
234
+ fontSize: '0.875rem',
235
+ padding: '0.25rem .3rem',
236
+ width: '5rem',
237
+ },
238
+ },
239
+ expansionPanel: {
240
+ backgroundColor: color.primaryLight(),
241
+ width: '100%',
242
+ },
243
+ summaryRoot: {
244
+ padding: `0 ${theme.spacing.unit}px`,
245
+ minHeight: '32px !important',
246
+ },
247
+ summaryContent: {
248
+ margin: '4px 0 !important',
249
+ },
250
+ details: {
251
+ padding: 0,
252
+ marginTop: theme.spacing.unit,
253
+ display: 'flex',
254
+ justifyContent: 'space-between',
255
+ },
256
+ undoRedoOuterDiv: {
257
+ display: 'flex',
258
+ flexDirection: 'column',
259
+ marginTop: '.5rem',
260
+ },
261
+ });
262
+
263
+ export default withStyles(styles)(GraphWithControls);
package/src/graph.jsx ADDED
@@ -0,0 +1,334 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import isEqual from 'lodash/isEqual';
4
+ import cloneDeep from 'lodash/cloneDeep';
5
+ import { Root, types, createGraphProps } from '@pie-lib/plot';
6
+ import debug from 'debug';
7
+
8
+ import { Axes, AxisPropTypes } from './axis';
9
+ import Grid from './grid';
10
+ import { LabelType } from './labels';
11
+ import Bg from './bg';
12
+ import { isDuplicatedMark, areArraysOfObjectsEqual } from './utils';
13
+
14
+ const log = debug('pie-lib:graphing-solution-set:graph');
15
+
16
+ export const graphPropTypes = {
17
+ axesSettings: PropTypes.shape(AxisPropTypes),
18
+ backgroundMarks: PropTypes.array,
19
+ className: PropTypes.string,
20
+ collapsibleToolbar: PropTypes.bool,
21
+ collapsibleToolbarTitle: PropTypes.string,
22
+ disabledLabels: PropTypes.bool,
23
+ disabledTitle: PropTypes.bool,
24
+ domain: types.DomainType,
25
+ labels: PropTypes.shape(LabelType),
26
+ labelModeEnabled: PropTypes.bool,
27
+ coordinatesOnHover: PropTypes.bool,
28
+ marks: PropTypes.array,
29
+ onChangeLabels: PropTypes.func,
30
+ onChangeMarks: PropTypes.func,
31
+ onChangeTitle: PropTypes.func,
32
+ range: types.DomainType,
33
+ size: PropTypes.shape({
34
+ width: PropTypes.number.isRequired,
35
+ height: PropTypes.number.isRequired,
36
+ }),
37
+ showLabels: PropTypes.bool,
38
+ showPixelGuides: PropTypes.bool,
39
+ showTitle: PropTypes.bool,
40
+ title: PropTypes.string,
41
+ tools: PropTypes.array,
42
+ };
43
+
44
+ const getMaskSize = (size) => ({
45
+ x: -23,
46
+ y: -23,
47
+ width: size.width + 46,
48
+ height: size.height + 46,
49
+ });
50
+
51
+ export const removeBuildingToolIfCurrentToolDiffers = ({ marks, currentTool }) => {
52
+ const buildingMark = marks.filter((m) => m.building)[0];
53
+ let newMarks = cloneDeep(marks);
54
+
55
+ if (buildingMark && currentTool && buildingMark.type !== currentTool.type) {
56
+ const index = newMarks.findIndex((m) => isEqual(m, buildingMark));
57
+
58
+ if (index >= 0) {
59
+ newMarks.splice(index, 1);
60
+ }
61
+ }
62
+
63
+ return newMarks;
64
+ };
65
+
66
+ export class Graph extends React.Component {
67
+ constructor(props) {
68
+ super(props);
69
+ this.maskUid = this.generateMaskId();
70
+ }
71
+
72
+ static propTypes = {
73
+ ...graphPropTypes,
74
+ currentTool: PropTypes.object,
75
+ };
76
+
77
+ static defaultProps = {
78
+ onChangeMarks: () => {},
79
+ disabledLabels: false,
80
+ disabledTitle: false,
81
+ };
82
+
83
+ state = {};
84
+
85
+ generateMaskId() {
86
+ return 'graph-' + (Math.random() * 10000).toFixed();
87
+ }
88
+
89
+ componentDidMount = () => this.setState({ labelNode: this.labelNode });
90
+
91
+ changeMark = (oldMark, newMark) => {
92
+ const { onChangeMarks, marks, gssLineData } = this.props;
93
+
94
+ if (gssLineData?.selectedTool === 'solutionSet') {
95
+ return;
96
+ }
97
+
98
+ let newMarks = cloneDeep(marks);
99
+
100
+ const index = newMarks.findIndex((m) => isEqual(m, oldMark));
101
+
102
+ if (index >= 0 && !isDuplicatedMark(newMark, marks, oldMark)) {
103
+ newMarks.splice(index, 1, newMark);
104
+
105
+ onChangeMarks(newMarks);
106
+ }
107
+ };
108
+
109
+ completeMark = (markData) => {
110
+ const { currentTool, marks } = this.props;
111
+ const buildingMark = marks.filter((m) => m.building)[0];
112
+
113
+ if (!buildingMark || !currentTool) return;
114
+
115
+ const updatedMark = currentTool.complete(buildingMark, markData);
116
+
117
+ this.updateMarks(buildingMark, updatedMark);
118
+ };
119
+
120
+ updateMarks = (existing, update, addIfMissing = false) => {
121
+ const { onChangeMarks, marks } = this.props;
122
+ let newMarks = cloneDeep(marks);
123
+
124
+ if (!update || (!update.building && isDuplicatedMark(update, marks))) {
125
+ return;
126
+ }
127
+
128
+ const index = newMarks.findIndex((m) => isEqual(m, existing));
129
+
130
+ if (index >= 0) {
131
+ newMarks.splice(index, 1, update);
132
+
133
+ onChangeMarks(newMarks);
134
+ } else if (addIfMissing) {
135
+ onChangeMarks([...newMarks, update]);
136
+ }
137
+ };
138
+
139
+ getComponent = (mark) => {
140
+ if (!mark) return null;
141
+
142
+ const tool = (this.props.tools || []).find((t) => t.type === mark.type);
143
+
144
+ return (tool && tool.Component) || null;
145
+ };
146
+
147
+ onBgClick = (point) => {
148
+ const { x, y } = point || {};
149
+ const { labelModeEnabled, currentTool, marks, gssLineData, onSolutionSetSelected } = this.props;
150
+ if (gssLineData) {
151
+ if (gssLineData.selectedTool === 'solutionSet') {
152
+ onSolutionSetSelected(point);
153
+ return;
154
+ }
155
+ if (
156
+ gssLineData.numberOfLines === 1 &&
157
+ gssLineData.selectedTool === 'lineA' &&
158
+ marks.length === 1 &&
159
+ !marks[0].building
160
+ ) {
161
+ return;
162
+ }
163
+ if (gssLineData.numberOfLines === 2 && gssLineData.selectedTool === 'lineA' && marks.length === 2) {
164
+ return;
165
+ }
166
+ if (
167
+ gssLineData.numberOfLines === 2 &&
168
+ gssLineData.selectedTool === 'lineA' &&
169
+ marks.length === 1 &&
170
+ !marks[0].building
171
+ ) {
172
+ return;
173
+ }
174
+ if (
175
+ gssLineData.numberOfLines === 2 &&
176
+ gssLineData.selectedTool === 'lineB' &&
177
+ marks.length === 2 &&
178
+ !marks[1].building
179
+ ) {
180
+ return;
181
+ }
182
+ }
183
+ if (labelModeEnabled || !currentTool || [null, undefined].includes(x) || [null, undefined].includes(y)) {
184
+ return;
185
+ }
186
+
187
+ log('[onBgClick] x,y: ', x, y);
188
+
189
+ const buildingMark = marks.filter((m) => m.building)[0];
190
+ let updatedMark;
191
+
192
+ // if the building mark has a different type, we just replace it
193
+ if (buildingMark && currentTool && buildingMark.type === currentTool.type) {
194
+ updatedMark = currentTool.addPoint({ x, y }, { ...buildingMark });
195
+ } else {
196
+ updatedMark = currentTool.addPoint({ x, y }, undefined);
197
+ }
198
+
199
+ this.updateMarks(buildingMark, updatedMark, true);
200
+ };
201
+
202
+ render() {
203
+ const {
204
+ axesSettings,
205
+ currentTool,
206
+ coordinatesOnHover,
207
+ size,
208
+ disabledLabels,
209
+ disabledTitle,
210
+ domain,
211
+ backgroundMarks,
212
+ range,
213
+ title,
214
+ labels,
215
+ labelModeEnabled,
216
+ labelsPlaceholders,
217
+ showLabels,
218
+ showPixelGuides,
219
+ showTitle,
220
+ titlePlaceholder,
221
+ onChangeLabels,
222
+ onChangeTitle,
223
+ mathMlOptions = {},
224
+ gssLineData,
225
+ disabled,
226
+ } = this.props;
227
+ let { marks } = this.props;
228
+
229
+ const graphProps = createGraphProps(domain, range, size, () => this.rootNode);
230
+
231
+ const maskSize = getMaskSize(size);
232
+ let common = { graphProps, labelModeEnabled, gssLineData };
233
+
234
+ marks = removeBuildingToolIfCurrentToolDiffers({ marks: marks || [], currentTool });
235
+ let solutionSet = marks.filter((mark) => mark.type === 'polygon');
236
+ if (gssLineData && gssLineData.selectedTool === 'solutionSet') {
237
+ gssLineData.sections.forEach((section) => {
238
+ if (solutionSet.length === 0 || !areArraysOfObjectsEqual(section, solutionSet[0].points)) {
239
+ let polygon = {
240
+ type: 'polygon',
241
+ building: false,
242
+ closed: true,
243
+ points: section,
244
+ isSolution: false,
245
+ };
246
+ if (!disabled) marks.push(polygon);
247
+ }
248
+ });
249
+ }
250
+ //Adjusted layering of added svg elements so that polygons will be added first and lines will be added second
251
+ let newMarks = [];
252
+ newMarks.push(...marks.filter((mark) => mark.type === 'polygon'));
253
+ newMarks.push(...marks.filter((mark) => mark.type === 'line'));
254
+
255
+ return (
256
+ <Root
257
+ rootRef={(r) => (this.rootNode = r)}
258
+ disabledTitle={disabledTitle}
259
+ disabledLabels={disabledLabels}
260
+ labels={labels}
261
+ labelsPlaceholders={labelsPlaceholders || {}}
262
+ showPixelGuides={showPixelGuides}
263
+ showLabels={showLabels}
264
+ showTitle={showTitle}
265
+ title={title}
266
+ titlePlaceholder={titlePlaceholder}
267
+ onChangeTitle={onChangeTitle}
268
+ onChangeLabels={onChangeLabels}
269
+ mathMlOptions={mathMlOptions}
270
+ {...common}
271
+ >
272
+ <g
273
+ transform={
274
+ domain && domain.padding && domain.range ? `translate(${domain.padding}, ${range.padding})` : undefined
275
+ }
276
+ >
277
+ <Grid {...common} />
278
+ <Axes {...axesSettings} {...common} />
279
+ <Bg {...size} onClick={this.onBgClick} {...common} />
280
+ <mask id={`${this.maskUid}`}>
281
+ <rect {...maskSize} fill="white" /> {/* TODO hardcoded color */}
282
+ </mask>
283
+
284
+ <g id="marks" mask={`url('#${this.maskUid}')`}>
285
+ {(backgroundMarks || []).map((m, index) => {
286
+ const Component = this.getComponent(m);
287
+ const markType = m.type;
288
+
289
+ return (
290
+ <Component
291
+ key={`${markType}-${index}-bg`}
292
+ mark={{ ...m, disabled: true, isBackground: true }}
293
+ labelNode={this.state.labelNode}
294
+ onClick={this.onBgClick}
295
+ {...common}
296
+ />
297
+ );
298
+ })}
299
+
300
+ {newMarks.map((m, index) => {
301
+ const Component = this.getComponent(m);
302
+ const markType = m.type;
303
+
304
+ return (
305
+ <Component
306
+ key={`${markType}-${index}`}
307
+ mark={m}
308
+ coordinatesOnHover={coordinatesOnHover}
309
+ onChange={this.changeMark}
310
+ onComplete={this.completeMark}
311
+ onClick={this.onBgClick}
312
+ onDragStart={this.startDrag}
313
+ onDragStop={this.stopDrag}
314
+ labelNode={this.state.labelNode}
315
+ isToolActive={currentTool && markType === currentTool.type}
316
+ {...common}
317
+ />
318
+ );
319
+ })}
320
+ <foreignObject
321
+ ref={(labelNode) => (this.labelNode = labelNode)}
322
+ x="0"
323
+ y="0"
324
+ {...size}
325
+ style={{ pointerEvents: 'none', fontSize: '14px' }}
326
+ />
327
+ </g>
328
+ </g>
329
+ </Root>
330
+ );
331
+ }
332
+ }
333
+
334
+ export default Graph;