Skip to content

Commit 6deebb5

Browse files
authored
Feature/sandbox (#4)
* Simplified file structure. Add sandbox * Add webpack config file for generating dists * WIP: Add react-jsonschema-form to interact with graph properties. * Add componentWillReceiveProps to Graph component. 🎉 Functional sandbox! 🎉 * Atempt to add some style on sanbox. Add bootstrap cdn on index page :fancy: * Fix node color highlight 🐛 * Fix zoom limits update * Refine behavior for static graph config property * Some refinemnets on method should component update in Node * Refactor sandbox remove utils code to separate file * Add task dist with npm run all to generate sandbox and rd3g dist * Automatically generate graph configs when submiting form * Fix on drag node static graph logic * Add semantic stroke width property * Add styles to sanbox. Add generate config and reset config buttons
1 parent 85fd46c commit 6deebb5

File tree

25 files changed

+1279
-189
lines changed

25 files changed

+1279
-189
lines changed

.gitignore

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,11 @@ typings/
6161
# dotenv environment variables file
6262
.env
6363

64-
# PROJECT configurations
65-
dist/
66-
6764
# End of https://www.gitignore.io/api/node
6865

69-
70-
src/mock/mock.js
71-
src/mock/mock.xl.js
66+
# PROJECT configurations
67+
dist/
7268
DOCUMENTATION.md
7369
docs
70+
sandbox/rd3g.sandbox.bundle.js
71+
sandbox/rd3g.sandbox.bundle.js.map

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,11 @@ export default {
4444
}
4545
};
4646
```
47+
48+
## TODOs
49+
This are some ideas to further development:
50+
- Expose a graph property **background-color** that is applied to the svg graph container.
51+
52+
## Contributions
53+
Contributions are welcome fell free to submit new features or simply grab something from
54+
the above TODO list.

package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@
1818
"babel-preset-es2015": "6.16.0",
1919
"babel-preset-react": "6.16.0",
2020
"babel-preset-stage-0": "6.16.0",
21+
"css-loader": "^0.28.0",
2122
"documentation": "4.0.0-beta.18",
23+
"eslint": "3.18.0",
2224
"eslint-config-recommended": "1.5.0",
2325
"eslint-plugin-promise": "3.5.0",
2426
"eslint-plugin-standard": "2.1.1",
25-
"eslint": "3.18.0",
2627
"html-webpack-plugin": "2.28.0",
28+
"npm-run-all": "4.0.2",
2729
"react-dom": "15.4.2",
30+
"react-jsonschema-form": "0.46.0",
2831
"react-router-dom": "4.0.0",
32+
"style-loader": "^0.16.1",
2933
"webpack": "2.3.2",
3034
"webpack-dev-server": "2.4.2"
3135
},
@@ -45,8 +49,10 @@
4549
"visualization"
4650
],
4751
"scripts": {
48-
"dev": "node_modules/.bin/webpack-dev-server -d --content-base src --inline --hot --port 3002",
49-
"dist": "webpack -p",
52+
"dev": "node_modules/.bin/webpack-dev-server -d --content-base sandbox --inline --hot --port 3002",
53+
"dist": "node_modules/.bin/npm-run-all --parallel dist:*",
54+
"dist:rd3g": "webpack --config webpack.config.dist.js -p --display-modules",
55+
"dist:sandbox": "webpack --config webpack.config.js -p --display-modules",
5056
"docs": "node_modules/documentation/bin/documentation.js build src/**/*.js -f html -o docs && node_modules/documentation/bin/documentation.js build src/**/*.js -f md > DOCUMENTATION.md",
5157
"lint": "node_modules/eslint/bin/eslint.js --config=.eslintrc.js \"src/**/*.js\"",
5258
"test": "echo \"Error: no test specified\" && exit 1"

sandbox/Sandbox.js

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import React from 'react';
2+
3+
import Form from 'react-jsonschema-form';
4+
5+
import './styles.css';
6+
7+
import defaultConfig from '../src/components/Graph/config';
8+
import { Graph } from '../src';
9+
import mock from './miserables';
10+
import Utils from './utils';
11+
import ReactD3GraphUtils from '../src/utils';
12+
13+
export default class Sandbox extends React.Component {
14+
constructor(props) {
15+
super(props);
16+
17+
const schemaProps = Utils.generateFormSchema(defaultConfig, '', {});
18+
19+
const schema = {
20+
type: 'object',
21+
properties: schemaProps
22+
};
23+
24+
const uiSchema = {
25+
height: {'ui:readonly': 'true'},
26+
width: {'ui:readonly': 'true'}
27+
};
28+
29+
this.uiSchema = uiSchema;
30+
31+
this.state = {
32+
config: defaultConfig,
33+
generatedConfig: {},
34+
schema
35+
};
36+
}
37+
38+
onClickNode = (id) => window.alert(`clicked node ${id}`);
39+
40+
onClickLink = (source, target) => window.alert(`clicked link between ${source} and ${target}`);
41+
42+
onMouseOverNode = () => {
43+
// Do something with the node identifier ...
44+
}
45+
46+
onMouseOutNode = () => {
47+
// Do something with the node identifier ...
48+
}
49+
50+
pauseGraphSimulation = () => this.refs.graph.pauseSimulation();
51+
52+
restartGraphSimulation = () => this.refs.graph.restartSimulation();
53+
54+
resetNodesPositions = () => this.refs.graph.resetNodesPositions();
55+
56+
_buildGraphConfig = (data) => {
57+
let config = {};
58+
let schemaPropsValues = {};
59+
60+
for(let k of Object.keys(data.formData)) {
61+
// Set value mapping correctly for config object of react-d3-graph
62+
Utils.setValue(config, k, data.formData[k]);
63+
// Set new values for schema of jsonform
64+
schemaPropsValues[k] = {};
65+
schemaPropsValues[k]['default'] = data.formData[k]
66+
}
67+
68+
return {config, schemaPropsValues};
69+
}
70+
71+
refreshGraph = (data) => {
72+
const {config, schemaPropsValues} = this._buildGraphConfig(data);
73+
74+
this.state.schema.properties = ReactD3GraphUtils.merge(this.state.schema.properties, schemaPropsValues);
75+
76+
this.setState({
77+
config
78+
});
79+
}
80+
81+
// Generate graph configuration file ready to use!
82+
onSubmit = (data) => {
83+
const {config, schemaPropsValues} = this._buildGraphConfig(data);
84+
85+
this.setState({
86+
generatedConfig: config
87+
});
88+
}
89+
90+
onClickSubmit = () => {
91+
// Hack for allow submit button to live outside jsonform
92+
document.body.querySelector('.invisible-button').click();
93+
}
94+
95+
resetGraphConfig = () => {
96+
const schemaProps = Utils.generateFormSchema(defaultConfig, '', {});
97+
98+
const schema = {
99+
type: 'object',
100+
properties: schemaProps
101+
};
102+
103+
this.setState({
104+
config: defaultConfig,
105+
schema
106+
});
107+
}
108+
109+
render() {
110+
const graphProps = {
111+
id: 'graph',
112+
data: mock,
113+
config: this.state.config,
114+
onClickNode: this.onClickNode,
115+
onClickLink: this.onClickLink,
116+
onMouseOverNode: this.onMouseOverNode,
117+
onMouseOutNode: this.onMouseOutNode
118+
};
119+
120+
const btnStyle = {
121+
cursor: this.state.config.staticGraph ? 'not-allowed' : 'pointer'
122+
};
123+
124+
return (
125+
<div className='container'>
126+
<div className='container__graph'>
127+
<button onClick={this.restartGraphSimulation} className='btn btn-default' style={btnStyle} disabled={this.state.config.staticGraph}>▶️</button>
128+
<button onClick={this.pauseGraphSimulation} className='btn btn-default' style={btnStyle} disabled={this.state.config.staticGraph}>⏸️</button>
129+
<button onClick={this.resetNodesPositions} className='btn btn-default' style={btnStyle} disabled={this.state.config.staticGraph}>Unstick nodes</button>
130+
<Graph ref='graph' {...graphProps}/>
131+
</div>
132+
<div className='container__form'>
133+
<h4>Graph configurations</h4>
134+
<Form className='form-wrapper'
135+
schema={this.state.schema}
136+
uiSchema={this.uiSchema}
137+
onChange={this.refreshGraph}
138+
onSubmit={this.onSubmit}>
139+
<button className='invisible-button' type='submit'></button>
140+
</Form>
141+
<button className='submit-button btn btn-primary' onClick={this.onClickSubmit}>Generate config</button>
142+
<button className='reset-button btn btn-danger' onClick={this.resetGraphConfig}>Reset config</button>
143+
</div>
144+
<div className='container__graph-config'>
145+
<h4>Your config</h4>
146+
<JSONContainer data={this.state.generatedConfig} />
147+
</div>
148+
<div className='container__graph-data'>
149+
<h4>Graph data</h4>
150+
<JSONContainer data={mock} />
151+
</div>
152+
</div>
153+
);
154+
}
155+
}
156+
157+
class JSONContainer extends React.Component {
158+
shouldComponentUpdate(nextProps, nextState) {
159+
return JSON.stringify(nextProps.data) !== JSON.stringify(this.props.data);
160+
}
161+
162+
render() {
163+
return (
164+
<pre className='json-data-container'>{JSON.stringify(this.props.data, null, 2)}</pre>
165+
);
166+
}
167+
}

sandbox/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>react-d3-graph</title>
6+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
7+
</head>
8+
9+
<body>
10+
<div id="app"></div>
11+
<script src="rd3g.sandbox.bundle.js"></script>
12+
</body>
13+
</html>

src/js/app.js renamed to sandbox/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import {
55
Route
66
} from 'react-router-dom';
77

8-
import Demo from './components/Demo';
8+
import Sandbox from './Sandbox';
99

1010
const app = document.getElementById('app');
1111

1212
ReactDOM.render(
1313
<Router>
1414
<div>
15-
<Route path='/' component={Demo}/>
15+
<Route path='/' component={Sandbox}/>
1616
</div>
17-
</Router>
17+
</Router>
1818
, app);

sandbox/styles.css

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
* {box-sizing: border-box;}
2+
.container {
3+
margin: 4px;
4+
overflow: hidden;
5+
position: fixed;
6+
}
7+
8+
.container > div {
9+
padding: 1em;
10+
}.container {
11+
display: grid;
12+
grid-template-columns: repeat(3, 2fr);
13+
grid-auto-rows: minmax(100px, auto);
14+
}
15+
16+
.container__graph {
17+
grid-column: 1 / 5;
18+
grid-row: 1 / 2;
19+
20+
border: 1px dashed black;
21+
}
22+
23+
.container__graph-data {
24+
grid-column: 1 / 2;
25+
grid-row: 2 / 3;
26+
}
27+
28+
.container__graph-config {
29+
grid-column: 2 / 5;
30+
grid-row: 2 / 3;
31+
}
32+
33+
.container__form {
34+
grid-column: 5/ 6;
35+
grid-row: 1 / 4;
36+
}
37+
38+
.form-wrapper {
39+
overflow-y: scroll;
40+
max-height: 600px;
41+
}
42+
43+
.json-data-container {
44+
max-height: 250px;
45+
overflow: scroll;
46+
}
47+
48+
.submit-button {
49+
margin-top: 22px;
50+
}
51+
52+
.reset-button {
53+
margin-top: 22px;
54+
margin-left: 8px;
55+
}
56+
57+
.invisible-button {
58+
background: transparent;
59+
border: none !important;
60+
font-size:0;
61+
}

sandbox/utils.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
function setValue(obj, access, value) {
2+
if (typeof(access) == 'string') {
3+
access = access.split('.');
4+
}
5+
6+
// Check for non existence of root property before advancing
7+
if (!obj[access[0]]) {
8+
obj[access[0]] = {};
9+
}
10+
11+
access.length > 1 ? setValue(obj[access.shift()],access,value) : obj[access[0]] = value;
12+
}
13+
14+
/**
15+
This two functions generate the react-jsonschema-form
16+
schema from some passed graph configuration.
17+
*/
18+
function formMap(k,v) {
19+
return {
20+
title: k,
21+
type: typeof v,
22+
default: v
23+
};
24+
}
25+
26+
function generateFormSchema(o, rootSpreadProp, accum={}) {
27+
for(let k of Object.keys(o)) {
28+
const kk = rootSpreadProp ? `${rootSpreadProp}.${k}` : k;
29+
typeof o[k] === 'object' ? generateFormSchema(o[kk], kk, accum) : accum[kk] = formMap(kk, o[k]);
30+
}
31+
32+
return accum;
33+
}
34+
35+
export default {
36+
setValue,
37+
generateFormSchema
38+
};

0 commit comments

Comments
 (0)