@windborne/grapher 1.0.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/.eslintrc.js +85 -0
- package/.idea/codeStyles/Project.xml +19 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/grapher.iml +12 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/0.bundle.js +2 -0
- package/0.bundle.js.map +1 -0
- package/1767282193a714f63082.module.wasm +0 -0
- package/537.bundle.js +2 -0
- package/537.bundle.js.map +1 -0
- package/831.bundle.js +2 -0
- package/831.bundle.js.map +1 -0
- package/bundle.js +2 -0
- package/bundle.js.map +1 -0
- package/package.json +75 -0
- package/readme.md +129 -0
- package/src/components/annotations.js +62 -0
- package/src/components/context_menu.js +73 -0
- package/src/components/draggable_points.js +114 -0
- package/src/components/graph_body.js +292 -0
- package/src/components/graph_title.js +16 -0
- package/src/components/options.js +111 -0
- package/src/components/percentile_button.js +72 -0
- package/src/components/range_graph.js +352 -0
- package/src/components/range_selection.js +175 -0
- package/src/components/range_selection_button.js +26 -0
- package/src/components/range_selection_button_base.js +51 -0
- package/src/components/series_key.js +235 -0
- package/src/components/series_key_axis_container.js +70 -0
- package/src/components/series_key_item.js +52 -0
- package/src/components/sidebar.js +76 -0
- package/src/components/tooltip.js +244 -0
- package/src/components/vertical_lines.js +70 -0
- package/src/components/x_axis.js +124 -0
- package/src/components/y_axis.js +239 -0
- package/src/eventable.js +65 -0
- package/src/grapher.js +367 -0
- package/src/grapher.scss +914 -0
- package/src/helpers/axis_sizes.js +2 -0
- package/src/helpers/binary_search.js +67 -0
- package/src/helpers/color_to_vector.js +35 -0
- package/src/helpers/colors.js +27 -0
- package/src/helpers/custom_prop_types.js +159 -0
- package/src/helpers/flatten_simple_data.js +81 -0
- package/src/helpers/format.js +233 -0
- package/src/helpers/generator_params_equal.js +10 -0
- package/src/helpers/name_for_series.js +16 -0
- package/src/helpers/place_grid.js +257 -0
- package/src/helpers/pyodide_ready.js +13 -0
- package/src/multigrapher.js +105 -0
- package/src/renderer/background.frag +7 -0
- package/src/renderer/background.vert +7 -0
- package/src/renderer/background_program.js +48 -0
- package/src/renderer/circle.frag +26 -0
- package/src/renderer/circle.vert +12 -0
- package/src/renderer/create_gl_program.js +36 -0
- package/src/renderer/draw_area.js +159 -0
- package/src/renderer/draw_background.js +15 -0
- package/src/renderer/draw_bars.js +80 -0
- package/src/renderer/draw_line.js +69 -0
- package/src/renderer/draw_zero_line.js +24 -0
- package/src/renderer/extract_vertices.js +137 -0
- package/src/renderer/graph_body_renderer.js +293 -0
- package/src/renderer/line.frag +51 -0
- package/src/renderer/line.vert +32 -0
- package/src/renderer/line_program.js +125 -0
- package/src/renderer/paths_from.js +72 -0
- package/src/renderer/scale_bounds.js +28 -0
- package/src/renderer/size_canvas.js +59 -0
- package/src/rust/Cargo.lock +233 -0
- package/src/rust/Cargo.toml +35 -0
- package/src/rust/pkg/grapher_rs.d.ts +42 -0
- package/src/rust/pkg/grapher_rs.js +351 -0
- package/src/rust/pkg/grapher_rs_bg.d.ts +11 -0
- package/src/rust/pkg/grapher_rs_bg.wasm +0 -0
- package/src/rust/pkg/index.js +342 -0
- package/src/rust/pkg/index_bg.wasm +0 -0
- package/src/rust/pkg/package.json +14 -0
- package/src/rust/src/extract_vertices.rs +83 -0
- package/src/rust/src/get_point_number.rs +50 -0
- package/src/rust/src/lib.rs +15 -0
- package/src/rust/src/selected_space_to_render_space.rs +131 -0
- package/src/state/average_loop_times.js +15 -0
- package/src/state/bound_calculator_from_selection.js +36 -0
- package/src/state/bound_calculators.js +41 -0
- package/src/state/calculate_annotations_state.js +59 -0
- package/src/state/calculate_data_bounds.js +104 -0
- package/src/state/calculate_tooltip_state.js +241 -0
- package/src/state/data_types.js +13 -0
- package/src/state/expand_bounds.js +58 -0
- package/src/state/find_matching_axis.js +31 -0
- package/src/state/get_default_bounds_calculator.js +15 -0
- package/src/state/hooks.js +164 -0
- package/src/state/infer_type.js +74 -0
- package/src/state/merge_bounds.js +64 -0
- package/src/state/multigraph_state_controller.js +334 -0
- package/src/state/selection_from_global_bounds.js +25 -0
- package/src/state/space_conversions/condense_data_space.js +115 -0
- package/src/state/space_conversions/data_space_to_selected_space.js +328 -0
- package/src/state/space_conversions/selected_space_to_background_space.js +144 -0
- package/src/state/space_conversions/selected_space_to_render_space.js +161 -0
- package/src/state/space_conversions/simple_series_to_data_space.js +229 -0
- package/src/state/state_controller.js +1770 -0
- package/src/state/sync_pool.js +101 -0
- package/test/setup.js +15 -0
- package/test/space_conversions/data_space_to_selected_space.test.js +434 -0
- package/webpack.dev.config.js +109 -0
- package/webpack.prod.config.js +60 -0
- package/webpack.test.config.js +59 -0
package/package.json
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
{
|
2
|
+
"name": "@windborne/grapher",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "Graphing library",
|
5
|
+
"main": "bundle.js",
|
6
|
+
"publishConfig": {},
|
7
|
+
"scripts": {
|
8
|
+
"lint-base": "eslint ./src ./examples ./test",
|
9
|
+
"lint": "npm run lint-base -- --fix",
|
10
|
+
"dev": "webpack-dev-server --config webpack.dev.config.js",
|
11
|
+
"build": "rm -f *.wasm && webpack --config webpack.prod.config.js && touch src/rust/pkg/.npmignore",
|
12
|
+
"analyze-size": "webpack --config webpack.prod.config.js --json | webpack-bundle-size-analyzer",
|
13
|
+
"test": "mocha-webpack --webpack-config webpack.test.config.js --require test/setup.js --recursive --glob \"*.test.js\" test",
|
14
|
+
"coverage": "NODE_ENV=coverage nyc --reporter=lcov --reporter=text npm run test"
|
15
|
+
},
|
16
|
+
"repository": {
|
17
|
+
"type": "git",
|
18
|
+
"url": "git+https://github.com/windborne/grapher.git"
|
19
|
+
},
|
20
|
+
"author": "",
|
21
|
+
"license": "UNLICENSED",
|
22
|
+
"bugs": {
|
23
|
+
"url": "https://github.com/windborne/grapher/issues"
|
24
|
+
},
|
25
|
+
"nyc": {
|
26
|
+
"include": [
|
27
|
+
"src/**/*.js"
|
28
|
+
],
|
29
|
+
"instrument": false,
|
30
|
+
"sourceMap": false
|
31
|
+
},
|
32
|
+
"browserslist": [
|
33
|
+
"last 2 Chrome versions"
|
34
|
+
],
|
35
|
+
"homepage": "https://github.com/windborne/grapher/readme.md",
|
36
|
+
"devDependencies": {
|
37
|
+
"@babel/core": "^7.7.5",
|
38
|
+
"@babel/polyfill": "^7.7.0",
|
39
|
+
"@babel/preset-env": "^7.7.6",
|
40
|
+
"@babel/preset-react": "^7.7.4",
|
41
|
+
"@wasm-tool/wasm-pack-plugin": "^1.2.0",
|
42
|
+
"babel-eslint": "^10.1.0",
|
43
|
+
"babel-loader": "^8.0.6",
|
44
|
+
"chai": "^4.2.0",
|
45
|
+
"css-loader": "^6.8.1",
|
46
|
+
"eslint": "^6.7.2",
|
47
|
+
"eslint-plugin-react": "^7.17.0",
|
48
|
+
"file-loader": "^6.2.0",
|
49
|
+
"html-webpack-plugin": "^5.6.0",
|
50
|
+
"istanbul-instrumenter-loader": "^3.0.1",
|
51
|
+
"jsdom": "^16.3.0",
|
52
|
+
"jsdom-global": "^3.0.2",
|
53
|
+
"kefir": "^3.8.6",
|
54
|
+
"mocha": "^7.2.0",
|
55
|
+
"mocha-webpack": "^2.0.0-beta.0",
|
56
|
+
"nyc": "^15.1.0",
|
57
|
+
"prop-types": "^15.7.2",
|
58
|
+
"react": "^16.12.0",
|
59
|
+
"react-dom": "^16.12.0",
|
60
|
+
"sass": "^1.68.0",
|
61
|
+
"sass-loader": "^13.3.2",
|
62
|
+
"sinon": "^9.0.2",
|
63
|
+
"sinon-chai": "^3.5.0",
|
64
|
+
"style-loader": "^3.3.3",
|
65
|
+
"util": "^0.12.5",
|
66
|
+
"webpack": "^5.9.1",
|
67
|
+
"webpack-cli": "^4.10.0",
|
68
|
+
"webpack-dev-server": "^4.15.2",
|
69
|
+
"webpack-glsl-loader": "^1.0.1",
|
70
|
+
"webpack-node-externals": "^2.5.0"
|
71
|
+
},
|
72
|
+
"peerDependencies": {
|
73
|
+
"react": ">= 16.8"
|
74
|
+
}
|
75
|
+
}
|
package/readme.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
|
2
|
+
# Grapher
|
3
|
+
Powerful, performant graphing.
|
4
|
+
|
5
|
+
Grapher is designed primarily for internal infrastructure with lots of data and lots of axes.
|
6
|
+
Key features include:
|
7
|
+
- Performance. Grapher can render a million points in 100ms and support new data coming in at hundreds of points per second without degraded performance.
|
8
|
+
- Async data sources. Grapher supports observables directly as well as generator functions to generate data on the fly
|
9
|
+
- Axis management. Series can be moved between axes and new axes created with ease.
|
10
|
+
- Long data ranges.
|
11
|
+
|
12
|
+
You _shouldn't_ use this package if:
|
13
|
+
- You need to support older browsers. While it may work in older browsers, no guarantees are made.
|
14
|
+
New features will be used as they become available without regard for maintaining legacy support.
|
15
|
+
- You don't want to add a react dependency (though the rendering engine may be used without react).
|
16
|
+
- Asset size is a big concern. Despite not having dependencies, this is a relatively heavy engine compared to alternatives.
|
17
|
+
|
18
|
+
## As a user
|
19
|
+
|
20
|
+
### Range selection
|
21
|
+
|
22
|
+
### Axes
|
23
|
+
|
24
|
+
### Tooltips
|
25
|
+
|
26
|
+
### Options
|
27
|
+
Percentile
|
28
|
+
|
29
|
+
Auto-scale Y
|
30
|
+
|
31
|
+
## As a developer
|
32
|
+
|
33
|
+
### Installing
|
34
|
+
|
35
|
+
The package is hosted on github, so things are marginally more tricky than usual.
|
36
|
+
Add an .npmrc with the following contents:
|
37
|
+
```
|
38
|
+
@windborne:registry=https://npm.pkg.github.com
|
39
|
+
registry=https://registry.npmjs.org
|
40
|
+
```
|
41
|
+
|
42
|
+
Then, `npm install @windborne/grapher`.
|
43
|
+
|
44
|
+
It also requires React version 16.8 or greater as a peer dependency
|
45
|
+
|
46
|
+
### Importing
|
47
|
+
The simplest option is to import it from the root.
|
48
|
+
```ecmascript 6
|
49
|
+
import Grapher from '@windborne/grapher';
|
50
|
+
```
|
51
|
+
|
52
|
+
This uses the webpacked version and allows you to not worry about any other dependencies.
|
53
|
+
However, since relative imports won't work, neither will the optional WASM extensions.
|
54
|
+
For most applications, WASM extensions won't be necessary, but if there are strong performance
|
55
|
+
requirements, you should consider them.
|
56
|
+
|
57
|
+
#### Importing with WASM extensions
|
58
|
+
To use the Rust WASM extensions, you should import the component itself.
|
59
|
+
```ecmascript 6
|
60
|
+
import Grapher from '@windborne/grapher/src/grapher';
|
61
|
+
```
|
62
|
+
|
63
|
+
However, this will require that you have the dependencies needed to build the whole project.
|
64
|
+
You can find a full list in the package.json devDependencies, but they can be summarized as follows:
|
65
|
+
- React and babel (include @babel/polyfill, @babel/preset-react, etc). You likely already have this in your project
|
66
|
+
- Sass support (eg node-sass, sass-loader)
|
67
|
+
- Webgl loader (webpack-glsl-loader)
|
68
|
+
|
69
|
+
### props
|
70
|
+
Like any react component, grapher is primarily configured via props.
|
71
|
+
Refer to the grapher proptypes for information
|
72
|
+
|
73
|
+
**series**. This sets the data for the graph and is the only property that is truly required.
|
74
|
+
See passing in data section for more details.
|
75
|
+
|
76
|
+
**webgl**. If true, will render with webgl rather than a 2d context.
|
77
|
+
This is more performant, but uses more resources.
|
78
|
+
|
79
|
+
**requireWASM**. If true, will wait until the WASM extensions are ready before it renders.
|
80
|
+
This can be useful when your app has expensive initialization.
|
81
|
+
|
82
|
+
**onAxisChange**. If passed in, this function will be called every time the axes change.
|
83
|
+
It will be called with an array of objects, where each object is a single series object with the `axis`
|
84
|
+
property set to {left, right}-{index}. This can be useful for saving state between reloads.
|
85
|
+
|
86
|
+
**onRenderTime**. If passed in, this function will be called every time the grapher renders.
|
87
|
+
It will be called with diagnostic information about how long rendering took.
|
88
|
+
|
89
|
+
**timingFrameCount**. This will set the number of frames for when the state controller's
|
90
|
+
`averageLoopTime` method is called.
|
91
|
+
|
92
|
+
**theme**. Sets the theme of grapher to either day or night.
|
93
|
+
You can also override any css property directly in a stylesheet.
|
94
|
+
|
95
|
+
**bodyHeight**. Sets the height of the graph body (ie excluding range graph, series controls, etc).
|
96
|
+
|
97
|
+
**height**. Sets the height of the entire graph.
|
98
|
+
|
99
|
+
**width**. Sets the width of the graph.
|
100
|
+
|
101
|
+
**showAxes**. Whether to show the axes on the graph.
|
102
|
+
|
103
|
+
**showRangeGraph**. Whether to show the smaller range graph below the main graph.
|
104
|
+
|
105
|
+
**showRangeSelectors**. Whether to show the top bar with range selection (eg last day button) and other options.
|
106
|
+
|
107
|
+
**showSeriesKey**. Whether to show the key of which series have which colors.
|
108
|
+
|
109
|
+
**showGrid**
|
110
|
+
|
111
|
+
**showTooltips**
|
112
|
+
|
113
|
+
**boundsSelectionEnabled**
|
114
|
+
|
115
|
+
**tooltipOptions**
|
116
|
+
|
117
|
+
**customBoundsSelectors**
|
118
|
+
|
119
|
+
### Usage from ruby
|
120
|
+
|
121
|
+
### Usage from python
|
122
|
+
|
123
|
+
## Developing
|
124
|
+
Other than an `npm install`, you'll need to install rust and [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/).
|
125
|
+
|
126
|
+
## License
|
127
|
+
All rights reserved.
|
128
|
+
|
129
|
+
This library uses icons from FontAwesome, licensed via https://fontawesome.com/license. No changes were made, nor does Font Awesome endorse this use.
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
|
4
|
+
export default class Annotations extends React.PureComponent {
|
5
|
+
|
6
|
+
render() {
|
7
|
+
const { bodyHeight, annotationState } = this.props;
|
8
|
+
const { annotations, elementWidth } = annotationState;
|
9
|
+
|
10
|
+
return (
|
11
|
+
<div className="grapher-annotations">
|
12
|
+
{
|
13
|
+
annotations.map(({ pixelX, width, content, lineOnly }, i) => {
|
14
|
+
if (lineOnly) {
|
15
|
+
return (
|
16
|
+
<div key={i} className="grapher-annotation" style={{ left: pixelX }}>
|
17
|
+
<div
|
18
|
+
className="annotation-marker"
|
19
|
+
style={{ width: width, height: bodyHeight }}
|
20
|
+
/>
|
21
|
+
</div>
|
22
|
+
);
|
23
|
+
}
|
24
|
+
|
25
|
+
const textStyle = {
|
26
|
+
top: 21*i
|
27
|
+
};
|
28
|
+
|
29
|
+
if (elementWidth - pixelX < content.length*5.5) {
|
30
|
+
textStyle.left = pixelX - elementWidth;
|
31
|
+
} else {
|
32
|
+
textStyle.left = 0;
|
33
|
+
}
|
34
|
+
|
35
|
+
return (
|
36
|
+
<div key={i} className="grapher-annotation" style={{ left: pixelX }}>
|
37
|
+
<div
|
38
|
+
className="annotation-marker"
|
39
|
+
style={{ width: width, height: bodyHeight }}
|
40
|
+
/>
|
41
|
+
|
42
|
+
<div className="annotation-text" style={textStyle}>
|
43
|
+
{content}
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
);
|
47
|
+
})
|
48
|
+
}
|
49
|
+
</div>
|
50
|
+
);
|
51
|
+
}
|
52
|
+
|
53
|
+
}
|
54
|
+
|
55
|
+
Annotations.propTypes = {
|
56
|
+
annotationState: PropTypes.shape({
|
57
|
+
annotations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
58
|
+
elementWidth: PropTypes.number.isRequired
|
59
|
+
}),
|
60
|
+
bodyHeight: PropTypes.number,
|
61
|
+
lineOnly: PropTypes.bool
|
62
|
+
};
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
|
4
|
+
export default class ContextMenu extends React.PureComponent {
|
5
|
+
constructor(props) {
|
6
|
+
super(props);
|
7
|
+
this.setTextRef = this.setTextRef.bind(this);
|
8
|
+
}
|
9
|
+
|
10
|
+
setTextRef(ref) {
|
11
|
+
this.textRef = ref;
|
12
|
+
|
13
|
+
if (this.props.contextMenu.showing && this.textRef) {
|
14
|
+
const range = document.createRange();
|
15
|
+
const selection = window.getSelection();
|
16
|
+
|
17
|
+
selection.removeAllRanges();
|
18
|
+
|
19
|
+
range.selectNodeContents(this.textRef);
|
20
|
+
selection.addRange(range);
|
21
|
+
this.textRef.focus();
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
formatDateTime(dateTimeStr) {
|
26
|
+
const [datePart, timePart] = dateTimeStr.split(', ');
|
27
|
+
|
28
|
+
const [month, day, year] = datePart.split('/');
|
29
|
+
const formattedDate = `${month.padStart(2, '0')}/${day.padStart(2, '0')}/${year}`;
|
30
|
+
|
31
|
+
const [time, period] = timePart.split(' ');
|
32
|
+
const [hours, minutes] = time.split(':');
|
33
|
+
const formattedHours = hours.padStart(2, '0');
|
34
|
+
const formattedTime = `${formattedHours}:${minutes} ${period}`;
|
35
|
+
|
36
|
+
return `${formattedDate} ${formattedTime}`;
|
37
|
+
}
|
38
|
+
|
39
|
+
render() {
|
40
|
+
const { x, y, showing, value } = this.props.contextMenu;
|
41
|
+
|
42
|
+
const style = { left: x, top: y, width: '150px'};
|
43
|
+
|
44
|
+
if (!showing || !value || value.toLocaleString() === 'Invalid Date') {
|
45
|
+
return null;
|
46
|
+
}
|
47
|
+
|
48
|
+
const displayValue = value instanceof Date ? this.formatDateTime(value.toLocaleString()) : value;
|
49
|
+
return (
|
50
|
+
<div className="grapher-context-menu" style={style}>
|
51
|
+
<div className="menu-item">
|
52
|
+
<div className="menu-text" autoFocus={true} ref={this.setTextRef}>
|
53
|
+
{displayValue}
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
);
|
58
|
+
}
|
59
|
+
|
60
|
+
}
|
61
|
+
|
62
|
+
ContextMenu.propTypes = {
|
63
|
+
contextMenu: PropTypes.shape({
|
64
|
+
x: PropTypes.number.isRequired,
|
65
|
+
y: PropTypes.number.isRequired,
|
66
|
+
showing: PropTypes.bool.isRequired,
|
67
|
+
value: PropTypes.oneOfType([
|
68
|
+
PropTypes.instanceOf(Date),
|
69
|
+
PropTypes.number,
|
70
|
+
PropTypes.object
|
71
|
+
])
|
72
|
+
}).isRequired
|
73
|
+
};
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import CustomPropTypes from '../helpers/custom_prop_types.js';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
import StateController from '../state/state_controller.js';
|
5
|
+
import {LINE_COLORS} from '../helpers/colors.js';
|
6
|
+
import {useAxisBounds, useSizing} from '../state/hooks.js';
|
7
|
+
|
8
|
+
export default React.memo(DraggablePoints);
|
9
|
+
|
10
|
+
function DraggablePoints({ stateController, draggablePoints, onPointDrag, onDraggablePointsDoubleClick }) {
|
11
|
+
const bounds = useAxisBounds(stateController)[0];
|
12
|
+
const sizing = useSizing(stateController);
|
13
|
+
|
14
|
+
if (!sizing) {
|
15
|
+
return null;
|
16
|
+
}
|
17
|
+
|
18
|
+
const onPointlessDoubleClick = onDraggablePointsDoubleClick && ((event) => {
|
19
|
+
const newXT = (event.clientX - sizing.boundingRect.left)/sizing.elementWidth;
|
20
|
+
const newYT = 1 - (event.clientY - sizing.boundingRect.top)/sizing.elementHeight;
|
21
|
+
|
22
|
+
onDraggablePointsDoubleClick(event, {
|
23
|
+
x: newXT*(bounds.maxX - bounds.minX) + bounds.minX,
|
24
|
+
y: newYT*(bounds.maxY - bounds.minY) + bounds.minY
|
25
|
+
});
|
26
|
+
});
|
27
|
+
|
28
|
+
return (
|
29
|
+
<div className="grapher-draggable-points" onDoubleClick={onPointlessDoubleClick}>
|
30
|
+
<svg width={sizing.elementWidth} height={sizing.elementHeight}>
|
31
|
+
{
|
32
|
+
draggablePoints.map((point, index) => {
|
33
|
+
const xT = (point.x - bounds.minX)/(bounds.maxX - bounds.minX);
|
34
|
+
const yT = (1 - (point.y - bounds.minY)/(bounds.maxY - bounds.minY));
|
35
|
+
|
36
|
+
if (xT < 0 || xT > 1 || yT < 0 || yT > 1) {
|
37
|
+
return null;
|
38
|
+
}
|
39
|
+
|
40
|
+
const pixelX = xT * sizing.elementWidth;
|
41
|
+
const pixelY = yT * sizing.elementHeight;
|
42
|
+
|
43
|
+
const radius = point.radius || 5;
|
44
|
+
const fillColor = point.fillColor || LINE_COLORS[0];
|
45
|
+
const strokeColor = point.strokeColor || 'white';
|
46
|
+
const strokeWidth = point.strokeWidth || 1;
|
47
|
+
|
48
|
+
const onMouseDown = (event) => {
|
49
|
+
event.stopPropagation();
|
50
|
+
|
51
|
+
if (!onPointDrag) {
|
52
|
+
return;
|
53
|
+
}
|
54
|
+
|
55
|
+
const xOffset = pixelX - event.clientX;
|
56
|
+
const yOffset = pixelY - event.clientY;
|
57
|
+
|
58
|
+
const onMouseMove = (moveEvent) => {
|
59
|
+
const newXT = (moveEvent.clientX + xOffset)/sizing.elementWidth;
|
60
|
+
const newYT = 1 - (moveEvent.clientY + yOffset)/sizing.elementHeight;
|
61
|
+
|
62
|
+
onPointDrag({
|
63
|
+
index,
|
64
|
+
point,
|
65
|
+
x: newXT*(bounds.maxX - bounds.minX) + bounds.minX,
|
66
|
+
y: newYT*(bounds.maxY - bounds.minY) + bounds.minY
|
67
|
+
}, moveEvent);
|
68
|
+
};
|
69
|
+
|
70
|
+
const onMouseUp = () => {
|
71
|
+
window.removeEventListener('mousemove', onMouseMove);
|
72
|
+
window.removeEventListener('mouseup', onMouseUp);
|
73
|
+
};
|
74
|
+
|
75
|
+
window.addEventListener('mousemove', onMouseMove);
|
76
|
+
window.addEventListener('mouseup', onMouseUp);
|
77
|
+
};
|
78
|
+
|
79
|
+
const onClick = point.onClick && ((event) => {
|
80
|
+
point.onClick(event, point);
|
81
|
+
});
|
82
|
+
|
83
|
+
const onDoubleClick = point.onDoubleClick && ((event) => {
|
84
|
+
event.stopPropagation();
|
85
|
+
point.onDoubleClick(event, point);
|
86
|
+
});
|
87
|
+
|
88
|
+
return (
|
89
|
+
<circle
|
90
|
+
key={index}
|
91
|
+
cx={pixelX}
|
92
|
+
cy={pixelY}
|
93
|
+
r={radius}
|
94
|
+
fill={fillColor}
|
95
|
+
stroke={strokeColor}
|
96
|
+
strokeWidth={strokeWidth}
|
97
|
+
onMouseDown={onMouseDown}
|
98
|
+
onClick={onClick}
|
99
|
+
onDoubleClick={onDoubleClick}
|
100
|
+
/>
|
101
|
+
);
|
102
|
+
})
|
103
|
+
}
|
104
|
+
</svg>
|
105
|
+
</div>
|
106
|
+
);
|
107
|
+
}
|
108
|
+
|
109
|
+
DraggablePoints.propTypes = {
|
110
|
+
stateController: PropTypes.instanceOf(StateController).isRequired,
|
111
|
+
draggablePoints: CustomPropTypes.DraggablePoints.isRequired,
|
112
|
+
onPointDrag: PropTypes.func,
|
113
|
+
onDraggablePointsDoubleClick: PropTypes.func
|
114
|
+
};
|