@pie-lib/graphing 2.6.1 → 2.7.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.
@@ -10,6 +10,13 @@ import ToolMenu from './tool-menu';
10
10
  import Graph, { graphPropTypes } from './graph';
11
11
  import UndoRedo from './undo-redo';
12
12
  import { allTools, toolsArr } from './tools';
13
+ import {
14
+ ExpansionPanel,
15
+ ExpansionPanelDetails,
16
+ ExpansionPanelSummary,
17
+ Typography
18
+ } from '@material-ui/core';
19
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
13
20
 
14
21
  export const setToolbarAvailability = toolbarTools =>
15
22
  toolsArr.map(tA => ({ ...tA, toolbar: !!toolbarTools.find(t => t === tA.type) })) || [];
@@ -25,6 +32,28 @@ export const filterByValidToolTypes = backgroundMarks =>
25
32
  export const filterByVisibleToolTypes = (toolbarTools, marks) =>
26
33
  marks.filter(bM => !!toolbarTools.find(tool => tool === bM.type));
27
34
 
35
+ const getDefaultCurrentTool = toolType => toolsArr.find(tool => tool.type === toolType) || null;
36
+
37
+ const Collapsible = ({ classes, children, title }) => (
38
+ <ExpansionPanel
39
+ elevation={0}
40
+ className={classes.expansionPanel}
41
+ disabledGutters={true}
42
+ square={true}
43
+ >
44
+ <ExpansionPanelSummary
45
+ classes={{
46
+ root: classes.summaryRoot,
47
+ content: classes.summaryContent
48
+ }}
49
+ expandIcon={<ExpandMoreIcon />}
50
+ >
51
+ <Typography variant="subheading">{title}</Typography>
52
+ </ExpansionPanelSummary>
53
+ <ExpansionPanelDetails className={classes.details}>{children}</ExpansionPanelDetails>
54
+ </ExpansionPanel>
55
+ );
56
+
28
57
  export class GraphWithControls extends React.Component {
29
58
  static propTypes = {
30
59
  ...graphPropTypes,
@@ -34,9 +63,30 @@ export class GraphWithControls extends React.Component {
34
63
  toolbarTools: PropTypes.arrayOf(PropTypes.string) // array of tool types that have to be displayed in the toolbar, same shape as 'allTools'
35
64
  };
36
65
 
37
- static defaultProps = { toolbarTools: [] };
66
+ static defaultProps = {
67
+ collapsibleToolbar: false,
68
+ collapsibleToolbarTitle: '',
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;
38
83
 
39
- state = { currentTool: null, labelModeEnabled: false };
84
+ if (prevProps.defaultTool !== defaultTool) {
85
+ const currentTool = getDefaultCurrentTool(defaultTool);
86
+
87
+ this.setState({ currentTool });
88
+ }
89
+ }
40
90
 
41
91
  changeCurrentTool = (tool, tools) =>
42
92
  this.setState({ currentTool: tools.find(t => t.type === tool) });
@@ -45,12 +95,13 @@ export class GraphWithControls extends React.Component {
45
95
 
46
96
  render() {
47
97
  let { currentTool, labelModeEnabled } = this.state;
48
-
49
98
  const {
50
99
  axesSettings,
51
100
  classes,
52
101
  className,
53
102
  coordinatesOnHover,
103
+ collapsibleToolbar,
104
+ collapsibleToolbarTitle,
54
105
  disabled,
55
106
  domain,
56
107
  labels,
@@ -62,7 +113,6 @@ export class GraphWithControls extends React.Component {
62
113
  size,
63
114
  title
64
115
  } = this.props;
65
-
66
116
  let { backgroundMarks, marks, toolbarTools } = this.props;
67
117
 
68
118
  // make sure only valid tool types are kept (string) and without duplicates
@@ -81,19 +131,31 @@ export class GraphWithControls extends React.Component {
81
131
  currentTool = getAvailableTool(tools);
82
132
  }
83
133
 
134
+ const graphActions = (
135
+ <React.Fragment>
136
+ <ToolMenu
137
+ currentToolType={currentTool && currentTool.type}
138
+ disabled={!!disabled}
139
+ labelModeEnabled={labelModeEnabled}
140
+ onChange={tool => this.changeCurrentTool(tool, tools)}
141
+ onToggleLabelMode={this.toggleLabelMode}
142
+ toolbarTools={toolbarTools}
143
+ />
144
+
145
+ {!disabled && <UndoRedo onUndo={onUndo} onRedo={onRedo} onReset={onReset} />}
146
+ </React.Fragment>
147
+ );
148
+
84
149
  return (
85
150
  <div className={classNames(classes.graphWithControls, className)}>
86
151
  <div className={classes.controls}>
87
- <ToolMenu
88
- currentToolType={currentTool && currentTool.type}
89
- disabled={!!disabled}
90
- labelModeEnabled={labelModeEnabled}
91
- onChange={tool => this.changeCurrentTool(tool, tools)}
92
- onToggleLabelMode={this.toggleLabelMode}
93
- toolbarTools={toolbarTools}
94
- />
95
-
96
- {!disabled && <UndoRedo onUndo={onUndo} onRedo={onRedo} onReset={onReset} />}
152
+ {collapsibleToolbar ? (
153
+ <Collapsible classes={classes} title={collapsibleToolbarTitle}>
154
+ {graphActions}
155
+ </Collapsible>
156
+ ) : (
157
+ graphActions
158
+ )}
97
159
  </div>
98
160
 
99
161
  <div ref={r => (this.labelNode = r)} />
@@ -134,6 +196,20 @@ const styles = theme => ({
134
196
  '& button': {
135
197
  fontSize: theme.typography.fontSize
136
198
  }
199
+ },
200
+ expansionPanel: {
201
+ backgroundColor: color.primaryLight()
202
+ },
203
+ summaryRoot: {
204
+ padding: `0 ${theme.spacing.unit}px`,
205
+ minHeight: '32px !important'
206
+ },
207
+ summaryContent: {
208
+ margin: '4px 0 !important'
209
+ },
210
+ details: {
211
+ padding: 0,
212
+ marginTop: theme.spacing.unit
137
213
  }
138
214
  });
139
215
 
package/src/graph.jsx CHANGED
@@ -18,6 +18,8 @@ export const graphPropTypes = {
18
18
  axesSettings: PropTypes.shape(AxisPropTypes),
19
19
  backgroundMarks: PropTypes.array,
20
20
  className: PropTypes.string,
21
+ collapsibleToolbar: PropTypes.bool,
22
+ collapsibleToolbarTitle: PropTypes.string,
21
23
  domain: types.DomainType,
22
24
  labels: PropTypes.shape(LabelType),
23
25
  labelModeEnabled: PropTypes.bool,
@@ -0,0 +1,427 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { color, InputContainer } from '@pie-lib/render-ui';
4
+ import { withStyles } from '@material-ui/core/styles';
5
+ import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
6
+ import Typography from '@material-ui/core/Typography';
7
+ import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
8
+ import ExpansionPanel from '@material-ui/core/ExpansionPanel';
9
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
10
+ import { NumberTextFieldCustom, Toggle } from '@pie-lib/config-ui';
11
+ import EditableHTML from '@pie-lib/editable-html';
12
+
13
+ const GridConfig = props => {
14
+ const {
15
+ classes,
16
+ disabled,
17
+ displayedFields,
18
+ labelValue,
19
+ labelValues,
20
+ gridValue,
21
+ gridValues,
22
+ onChange
23
+ } = props;
24
+ const { labelStep = {}, step = {} } = displayedFields;
25
+
26
+ return (
27
+ <div className={classes.columnView}>
28
+ {step && step.enabled && (
29
+ <NumberTextFieldCustom
30
+ className={classes.mediumTextField}
31
+ label={step.label || ''}
32
+ value={gridValue}
33
+ customValues={gridValues}
34
+ variant="outlined"
35
+ disabled={disabled}
36
+ onChange={(e, v) => onChange('step', v)}
37
+ />
38
+ )}
39
+ {labelStep && labelStep.enabled && (
40
+ <NumberTextFieldCustom
41
+ className={classes.mediumTextField}
42
+ label={labelStep.label || ''}
43
+ value={labelValue}
44
+ customValues={labelValues}
45
+ variant="outlined"
46
+ disabled={disabled}
47
+ onChange={(e, v) => onChange('labelStep', v)}
48
+ />
49
+ )}
50
+ </div>
51
+ );
52
+ };
53
+
54
+ const AxisConfig = props => {
55
+ const {
56
+ classes,
57
+ disabled,
58
+ displayedFields,
59
+ displayHeader,
60
+ label,
61
+ maxValue,
62
+ minValue,
63
+ onChange,
64
+ type
65
+ } = props;
66
+ const { axisLabel = {}, min = {}, max = {} } = displayedFields;
67
+ const activePlugins = [
68
+ 'bold',
69
+ 'italic',
70
+ 'underline',
71
+ 'strikethrough'
72
+ // 'languageCharacters'
73
+ ];
74
+
75
+ return (
76
+ <div className={classes.columnView}>
77
+ {displayHeader && (
78
+ <Typography variant="subtitle2">
79
+ <i>{type === 'domain' ? 'x' : 'y'}</i>
80
+ -axis
81
+ </Typography>
82
+ )}
83
+ {min && min.enabled && (
84
+ <NumberTextFieldCustom
85
+ className={classes.mediumTextField}
86
+ label={min.label || ''}
87
+ value={minValue}
88
+ min={-10000}
89
+ max={maxValue - 0.01}
90
+ variant="outlined"
91
+ disabled={disabled}
92
+ onChange={(e, v) => onChange('min', v)}
93
+ />
94
+ )}
95
+ {max && max.enabled && (
96
+ <NumberTextFieldCustom
97
+ className={classes.mediumTextField}
98
+ label={max.label || ''}
99
+ value={maxValue}
100
+ min={minValue + 0.01}
101
+ max={10000}
102
+ variant="outlined"
103
+ disabled={disabled}
104
+ onChange={(e, v) => onChange('max', v)}
105
+ />
106
+ )}
107
+ {axisLabel && axisLabel.enabled && (
108
+ <InputContainer label={axisLabel.label || ''} className={classes.mediumTextField}>
109
+ <EditableHTML
110
+ className={classes.axisLabel}
111
+ onChange={value => onChange('axisLabel', value)}
112
+ markup={label || ''}
113
+ charactersLimit={5}
114
+ activePlugins={activePlugins}
115
+ />
116
+ </InputContainer>
117
+ )}
118
+ </div>
119
+ );
120
+ };
121
+
122
+ const GridSetup = props => {
123
+ const {
124
+ classes,
125
+ domain,
126
+ displayedFields = {},
127
+ gridValues = {},
128
+ includeAxes,
129
+ labelValues = {},
130
+ onChange,
131
+ range,
132
+ size,
133
+ sizeConstraints,
134
+ standardGrid
135
+ } = props;
136
+ const gridProps = { min: 2, max: 41 };
137
+ const {
138
+ axisLabel = {},
139
+ dimensionsEnabled,
140
+ includeAxesEnabled,
141
+ labelStep = {},
142
+ min = {},
143
+ max = {},
144
+ standardGridEnabled,
145
+ step = {}
146
+ } = displayedFields || {};
147
+ const displayAxisType =
148
+ min.enabled || max.enabled || axisLabel.enabled || step.enabled || labelStep.enabled;
149
+ const gridConfigFields = { step, labelStep };
150
+ const axisConfigFields = { min, max, axisLabel };
151
+
152
+ const onIncludeAxes = includeAxes => {
153
+ const noAxesConfig = type => {
154
+ const axis = type === 'domain' ? domain : range;
155
+
156
+ return {
157
+ min: 1,
158
+ max: axis.max < gridProps.min || axis.max > gridProps.max ? 16 : axis.max,
159
+ step: 1,
160
+ labelStep: 0
161
+ };
162
+ };
163
+
164
+ const updatedRange = {
165
+ ...range,
166
+ ...(includeAxes ? { labelStep: 1 } : noAxesConfig('range'))
167
+ };
168
+ const updatedDomain = {
169
+ ...domain,
170
+ ...(includeAxes ? { labelStep: 1 } : noAxesConfig('domain'))
171
+ };
172
+
173
+ onChange({ includeAxes, range: updatedRange, domain: updatedDomain });
174
+ };
175
+
176
+ const onStandardGridChanged = value => {
177
+ onChange({
178
+ standardGrid: value,
179
+ range: {
180
+ ...domain,
181
+ axisLabel: range.axisLabel
182
+ },
183
+ graph: {
184
+ ...size,
185
+ height: size.width
186
+ }
187
+ });
188
+ };
189
+
190
+ const onSizeChanged = (key, value) => {
191
+ const graph = { ...size, [key]: value };
192
+
193
+ if (standardGrid) {
194
+ graph.height = value;
195
+ }
196
+
197
+ onChange({ graph });
198
+ };
199
+
200
+ const onDomainChanged = (key, value) => {
201
+ domain[key] = value;
202
+
203
+ if (standardGrid && key !== 'axisLabel') {
204
+ range[key] = value;
205
+ }
206
+
207
+ onChange({ domain, range });
208
+ };
209
+
210
+ const onRangeChanged = (key, value) => {
211
+ range[key] = value;
212
+
213
+ onChange({ range });
214
+ };
215
+
216
+ const axesConfig = (
217
+ <React.Fragment>
218
+ <div className={classes.rowView}>
219
+ <AxisConfig
220
+ classes={classes}
221
+ displayedFields={axisConfigFields}
222
+ displayHeader={displayAxisType}
223
+ type="domain"
224
+ minValue={domain.min}
225
+ maxValue={domain.max}
226
+ label={domain.axisLabel}
227
+ includeAxes={includeAxes}
228
+ onChange={onDomainChanged}
229
+ />
230
+ <AxisConfig
231
+ classes={classes}
232
+ displayedFields={axisConfigFields}
233
+ displayHeader={displayAxisType}
234
+ type="range"
235
+ minValue={range.min}
236
+ maxValue={range.max}
237
+ label={range.axisLabel}
238
+ disabled={standardGrid}
239
+ includeAxes={includeAxes}
240
+ onChange={onRangeChanged}
241
+ />
242
+ </div>
243
+ {(min.enabled || max.enabled) && (
244
+ <Typography className={classes.text}>
245
+ If you want the axis to be visible, use a zero or negative Min Value, and a positive Max
246
+ Value
247
+ </Typography>
248
+ )}
249
+ {(step.enabled || labelStep.enabled) && (
250
+ <div className={classes.rowView}>
251
+ <GridConfig
252
+ classes={classes}
253
+ displayedFields={gridConfigFields}
254
+ gridValue={domain.step}
255
+ labelValue={domain.labelStep}
256
+ gridValues={gridValues.domain || []}
257
+ labelValues={labelValues.domain || []}
258
+ onChange={onDomainChanged}
259
+ />
260
+ <GridConfig
261
+ classes={classes}
262
+ disabled={standardGrid}
263
+ displayedFields={gridConfigFields}
264
+ gridValue={range.step}
265
+ labelValue={range.labelStep}
266
+ gridValues={gridValues.range || []}
267
+ labelValues={labelValues.range || []}
268
+ onChange={onRangeChanged}
269
+ />
270
+ </div>
271
+ )}
272
+ {labelStep.enabled && (
273
+ <Typography className={classes.text}>
274
+ For unnumbered gridlines, enter a label interval of 0
275
+ </Typography>
276
+ )}
277
+ </React.Fragment>
278
+ );
279
+
280
+ const gridlinesConfig = max.enabled ? (
281
+ <div className={classes.columnView}>
282
+ <NumberTextFieldCustom
283
+ className={classes.largeTextField}
284
+ label="Number of Horizontal Gridlines"
285
+ value={domain.max}
286
+ min={!includeAxes && gridProps.min}
287
+ max={!includeAxes && gridProps.max}
288
+ variant="outlined"
289
+ onChange={(e, v) => onDomainChanged('max', v)}
290
+ />
291
+ <NumberTextFieldCustom
292
+ className={classes.largeTextField}
293
+ label="Number of Vertical Gridlines"
294
+ value={range.max}
295
+ min={!includeAxes && gridProps.min}
296
+ max={!includeAxes && gridProps.max}
297
+ variant="outlined"
298
+ disabled={standardGrid}
299
+ onChange={(e, v) => onRangeChanged('max', v)}
300
+ />
301
+ </div>
302
+ ) : null;
303
+
304
+ return (
305
+ <div className={classes.wrapper}>
306
+ <ExpansionPanel>
307
+ <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
308
+ <Typography variant="subtitle1">Customize Grid Setup</Typography>
309
+ </ExpansionPanelSummary>
310
+ <ExpansionPanelDetails>
311
+ <div className={classes.content}>
312
+ {includeAxesEnabled && (
313
+ <Toggle
314
+ label="Include axes and labels?"
315
+ toggle={onIncludeAxes}
316
+ checked={includeAxes}
317
+ />
318
+ )}
319
+ {standardGridEnabled && (
320
+ <Toggle
321
+ label="Constrain to standard coordinate grid?"
322
+ toggle={onStandardGridChanged}
323
+ checked={standardGrid}
324
+ />
325
+ )}
326
+ {includeAxes ? axesConfig : gridlinesConfig}
327
+ {dimensionsEnabled && (
328
+ <div className={classes.dimensions}>
329
+ <div>
330
+ <Typography>Dimensions(px)</Typography>
331
+ <Typography className={classes.disabled}>
332
+ Min {sizeConstraints.min}, Max {sizeConstraints.max}
333
+ </Typography>
334
+ </div>
335
+ <NumberTextFieldCustom
336
+ className={classes.textField}
337
+ label="Width"
338
+ value={size.width}
339
+ min={sizeConstraints.min}
340
+ max={sizeConstraints.max}
341
+ step={sizeConstraints.step}
342
+ variant="outlined"
343
+ onChange={(e, v) => onSizeChanged('width', v)}
344
+ />
345
+ <NumberTextFieldCustom
346
+ className={classes.textField}
347
+ label="Height"
348
+ value={size.height}
349
+ min={sizeConstraints.min}
350
+ max={sizeConstraints.max}
351
+ step={sizeConstraints.step}
352
+ variant="outlined"
353
+ disabled={standardGrid}
354
+ onChange={(e, v) => onSizeChanged('height', v)}
355
+ />
356
+ </div>
357
+ )}
358
+ </div>
359
+ </ExpansionPanelDetails>
360
+ </ExpansionPanel>
361
+ </div>
362
+ );
363
+ };
364
+
365
+ GridSetup.propTypes = {
366
+ classes: PropTypes.object,
367
+ domain: PropTypes.object,
368
+ displayedFields: PropTypes.object,
369
+ gridValues: PropTypes.object,
370
+ includeAxes: PropTypes.bool,
371
+ labelValues: PropTypes.object,
372
+ onChange: PropTypes.function,
373
+ range: PropTypes.object,
374
+ size: PropTypes.object,
375
+ sizeConstraints: PropTypes.object,
376
+ standardGrid: PropTypes.bool
377
+ };
378
+
379
+ const styles = theme => ({
380
+ wrapper: {
381
+ width: '450px'
382
+ },
383
+ content: {
384
+ display: 'flex',
385
+ flexDirection: 'column',
386
+ width: '100%'
387
+ },
388
+ columnView: {
389
+ display: 'flex',
390
+ flexDirection: 'column',
391
+ alignItems: 'center'
392
+ },
393
+ rowView: {
394
+ display: 'flex',
395
+ justifyContent: 'space-around',
396
+ alignItems: 'center'
397
+ },
398
+ textField: {
399
+ width: '130px',
400
+ margin: `${theme.spacing.unit}px ${theme.spacing.unit / 2}px`
401
+ },
402
+ mediumTextField: {
403
+ width: '160px',
404
+ margin: `${theme.spacing.unit}px ${theme.spacing.unit / 2}px`
405
+ },
406
+ largeTextField: {
407
+ width: '230px',
408
+ margin: `${theme.spacing.unit}px ${theme.spacing.unit / 2}px`
409
+ },
410
+ text: {
411
+ fontStyle: 'italic',
412
+ margin: `${theme.spacing.unit}px 0`
413
+ },
414
+ dimensions: {
415
+ display: 'flex',
416
+ justifyContent: 'space-between',
417
+ alignItems: 'center'
418
+ },
419
+ disabled: {
420
+ color: color.disabled()
421
+ },
422
+ axisLabel: {
423
+ paddingTop: theme.spacing.unit * 2
424
+ }
425
+ });
426
+
427
+ export default withStyles(styles)(GridSetup);
package/src/grid.jsx CHANGED
@@ -14,13 +14,6 @@ export class Grid extends React.Component {
14
14
  graphProps: types.GraphPropsType.isRequired
15
15
  };
16
16
 
17
- shouldComponentUpdate(nextProps) {
18
- const { graphProps } = this.props;
19
- const { graphProps: nextGraphProps } = nextProps;
20
-
21
- return !utils.isDomainRangeEqual(graphProps, nextGraphProps);
22
- }
23
-
24
17
  getAdditionalGridProps = (rowTickValues, columnTickValues) => {
25
18
  const {
26
19
  graphProps: {
package/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import Graph from './graph';
2
2
  import GraphContainer from './container';
3
+ import GridSetup from './grid-setup';
3
4
  import * as tools from './tools';
4
5
  import ToolMenu from './tool-menu';
5
6
 
6
- export { Graph, GraphContainer, ToolMenu, tools };
7
+ export { Graph, GraphContainer, GridSetup, ToolMenu, tools };