Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/improve code structure #35

Merged
merged 11 commits into from
Nov 11, 2017
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"author": "Daniel Caldas",
"license": "MIT",
"scripts": {
"check": "npm run lint && npm run test",
"check": "npm run docs:lint && npm run lint && npm run test",
"dev": "node_modules/.bin/webpack-dev-server -d --content-base sandbox --inline --hot --port 3002",
"dist": "npm run check && node_modules/.bin/npm-run-all --parallel dist:*",
"dist:rd3g": "rm -rf dist/ && webpack --config webpack.config.dist.js -p --display-modules",
Expand Down
2 changes: 1 addition & 1 deletion sandbox/Sandbox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Form from 'react-jsonschema-form';

import './styles.css';

import defaultConfig from '../src/components/Graph/config';
import defaultConfig from '../src/components/graph/config';
import { Graph } from '../src';
import data from './data';
import Utils from './utils';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
* highlighted. If the value is set to **1** the selected node and his 1st degree connections will be highlighted. If
* the value is set to **2** the selected node will be highlighted as well as the 1st and 2nd common degree connections.
* @param {number} [highlightOpacity=1] - this value is used to highlight nodes in the network. The lower
* the value the more the less highlighted nodes will be visible (related to **highlightBehavior**).
* the value the more the less highlighted nodes will be visible (related to *highlightBehavior*).
* @param {number} [maxZoom=8] - max zoom that can be performed against the graph.
* @param {number} [minZoom=0.1] - min zoom that can be performed against the graph.
* @param {boolean} [panAndZoom=false] - 🚅🚅🚅 pan and zoom effect when performing zoom in the graph,
Expand All @@ -48,7 +48,7 @@
* from the given nodes positions by rd3g), no coordinates will be calculated by rd3g or subjacent d3 modules.
* @param {number} [width=800] - the width of the (svg) area where the graph will be rendered.
* <br/>
* @param {Object} node node object is explained in next section.
* @param {Object} node node object is explained in next section. ⬇️
* <h2 id="node-section">Node level configurations</h2>
* @param {string} [node.color='#d3d3d3'] - 🔍🔍🔍 this is the color that will be applied to the node if no **color property**
* is found inside the node itself (yes **you can pass a property 'color' inside the node and that color will override the
Expand Down Expand Up @@ -78,15 +78,15 @@
* - 'triangle'
* - 'wye'
*
* **[note]** react-d3-graph will map this values to d3 symbols ({@link https://github.com/d3/d3-shape#symbols})
* **[note]** react-d3-graph will map this values to [d3 symbols](https://github.com/d3/d3-shape#symbols)
* @param {string} [node.highlightColor='SAME'] - color for all highlighted nodes (use string 'SAME' if you
* want the node to keep its color in highlighted state).
* @param {number} [node.highlightFontSize=10] - node.fontSize in highlighted state.
* @param {string} [node.highlightFontWeight='normal'] - node.fontWeight in highlighted state.
* @param {string} [node.highlightStrokeColor='SAME'] - node.strokeColor in highlighted state.
* @param {number} [node.highlightStrokeWidth=1.5] - node.strokeWidth in highlighted state.
* @param {number} [node.highlightFontSize=10] - fontSize in highlighted state.
* @param {string} [node.highlightFontWeight='normal'] - fontWeight in highlighted state.
* @param {string} [node.highlightStrokeColor='SAME'] - strokeColor in highlighted state.
* @param {number} [node.highlightStrokeWidth=1.5] - strokeWidth in highlighted state.
* <br/>
* @param {Object} link link object is explained in the next section.
* @param {Object} link link object is explained in the next section. ⬇️
* <h2>Link level configurations</h2>
* @param {string} [link.color='#d3d3d3'] - the color for links.
* @param {number} [link.opacity=1] - the default opacity value for links.
Expand All @@ -97,7 +97,7 @@
* strokeWidth += (linkValue * strokeWidth) / 10;
* ```
* @param {number} [link.strokeWidth=1.5] - strokeWidth for all links.
* @param {string} [link.highlightColor='#d3d3d3'] - links color in highlight state.
* @param {string} [link.highlightColor='#d3d3d3'] - links' color in highlight state.
*
* @example
* // A simple config that uses some properties
Expand Down
12 changes: 3 additions & 9 deletions src/components/Graph/const.js → src/components/graph/const.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import CONST from '../../const';

export default {
COORDS_SEPARATOR: ',',
FORCE_IDEAL_STRENGTH: -100, // @TODO: Expose as configurable,
Expand All @@ -10,13 +12,5 @@ export default {
},
LINK_CLASS_NAME: 'link',
NODE_CLASS_NAME: 'node',
SYMBOLS: { // FIXME: Repeated SYMBOLS constant
CIRCLE: 'circle',
CROSS: 'cross',
DIAMOND: 'diamond',
SQUARE: 'square',
STAR: 'star',
TRIANGLE: 'triangle',
WYE: 'wye'
}
...CONST
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
* @description
* Offers a series of methods that isolate logic of Graph component.
*/
/**
* @typedef {Object} Link
* @property {string} source - the node id of the source in the link.
* @property {string} target - the node id of the target in the link.
* @memberof Graph/helper
*/
/**
* @typedef {Object} Node
* @property {string} id - the id of the node.
* @memberof Graph/helper
*/
import React from 'react';

import {
Expand All @@ -13,9 +24,12 @@ import {
} from 'd3-force';

import CONST from './const';
import DEFAULT_CONFIG from './config';
import ERRORS from '../../err';

import Link from '../Link/';
import Node from '../Node/';
import Link from '../link/';
import Node from '../node/';
import utils from '../../utils';

/**
* Build some Link properties based on given parameters.
Expand Down Expand Up @@ -285,6 +299,59 @@ function createForceSimulation(width, height) {
.force('y', fry);
}

/**
* Incapsulates common procedures to initialize graph.
* @param {Object} props - Graph component props, object that holds data, id and config.
* @param {Object} props.data - Data object holds links (array of **Link**) and nodes (array of **Node**).
* @param {string} props.id - the graph id.
* @param {Object} props.config - same as {@link #buildGraph|config in buildGraph}.
* @param {Object} state - Graph component current state (same format as returned object on this function).
* @returns a fully (re)initialized graph state object.
* @memberof Graph/helper
*/
function initializeGraphState({data, id, config}, state) {
let graph;

validateGraphData(data);

if (state && state.nodes && state.links && state.nodeIndexMapping) {
// absorve existent positining
graph = {
nodes: data.nodes.map(n => Object.assign({}, n, state.nodes[n.id])),
links: {}
};
} else {
graph = {
nodes: data.nodes.map(n => Object.assign({}, n)),
links: {}
};
}

graph.links = data.links.map(l => Object.assign({}, l));

let newConfig = Object.assign({}, utils.merge(DEFAULT_CONFIG, config || {}));
let {nodes, nodeIndexMapping} = initializeNodes(graph.nodes);
let links = initializeLinks(graph.links); // Matrix of graph connections
const {nodes: d3Nodes, links: d3Links} = graph;
const formatedId = id.replace(/ /g, '_');
const simulation = createForceSimulation(newConfig.width, newConfig.height);

return {
id: formatedId,
config: newConfig,
nodeIndexMapping,
links,
d3Links,
nodes,
d3Nodes,
highlightedNode: '',
simulation,
newGraphElements: false,
configUpdated: false,
transform: 1
};
}

/**
* Receives a matrix of the graph with the links source and target as concrete node instances and it transforms it
* in a lightweight matrix containing only links with source and target being strings representative of some node id
Expand Down Expand Up @@ -345,9 +412,27 @@ function initializeNodes(graphNodes) {
return { nodes, nodeIndexMapping };
}

/**
* Some integraty validations on links and nodes structure. If some validation fails the function will
* throw an error.
* @param {Object} data - Same as {@link #initializeGraphState|data in initializeGraphState}.
* @memberof Graph/helper
*/
function validateGraphData(data) {
data.links.forEach(l => {
if (!data.nodes.find(n => n.id === l.source)) {
utils.throwErr(this.constructor.name, `${ERRORS.INVALID_LINKS} - ${l.source} is not a valid node id`);
}
if (!data.nodes.find(n => n.id === l.target)) {
utils.throwErr(this.constructor.name, `${ERRORS.INVALID_LINKS} - ${l.target} is not a valid node id`);
}
});
}

export default {
buildGraph,
createForceSimulation,
initializeGraphState,
initializeLinks,
initializeNodes
};
72 changes: 3 additions & 69 deletions src/components/Graph/index.jsx → src/components/graph/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export default class Graph extends React.Component {

/**
* This method resets all nodes fixed positions by deleting the properties fx (fixed x)
* and fy (fixed y). Next a simulation is triggered in order to force nodes to go back
* and fy (fixed y). Following this, a simulation is triggered in order to force nodes to go back
* to their original positions (or at least new positions according to the d3 force parameters).
*/
resetNodesPositions = () => {
Expand All @@ -216,72 +216,6 @@ export default class Graph extends React.Component {
*/
restartSimulation = () => !this.state.config.staticGraph && this.state.simulation.restart();

/**
* Some integraty validations on links and nodes structure.
* @param {Object} data
*/
_validateGraphData(data) {
// @TODO: Move function to helper.jsx
data.links.forEach(l => {
if (!data.nodes.find(n => n.id === l.source)) {
utils.throwErr(this.constructor.name, `${ERRORS.INVALID_LINKS} - ${l.source} is not a valid node id`);
}
if (!data.nodes.find(n => n.id === l.target)) {
utils.throwErr(this.constructor.name, `${ERRORS.INVALID_LINKS} - ${l.target} is not a valid node id`);
}
});
}

/**
* Incapsulates common procedures to initialize graph.
* @param {Object} data
* @param {Array.<Object>} data.nodes - nodes of the graph to be created.
* @param {Array.<Object>} data.links - links that connect data.nodes.
* @returns {Object}
*/
_initializeGraphState(data) {
let graph;

this._validateGraphData(data);

if (this.state && this.state.nodes && this.state.links && this.state.nodeIndexMapping) {
// absorve existent positining
graph = {
nodes: data.nodes.map(n => Object.assign({}, n, this.state.nodes[n.id])),
links: {}
};
} else {
graph = {
nodes: data.nodes.map(n => Object.assign({}, n)),
links: {}
};
}

graph.links = data.links.map(l => Object.assign({}, l));

let config = Object.assign({}, utils.merge(DEFAULT_CONFIG, this.props.config || {}));
let {nodes, nodeIndexMapping} = graphHelper.initializeNodes(graph.nodes);
let links = graphHelper.initializeLinks(graph.links); // Matrix of graph connections
const {nodes: d3Nodes, links: d3Links} = graph;
const id = this.props.id.replace(/ /g, '_');
const simulation = graphHelper.createForceSimulation(config.width, config.height);

return {
id,
config,
nodeIndexMapping,
links,
d3Links,
nodes,
d3Nodes,
highlightedNode: '',
simulation,
newGraphElements: false,
configUpdated: false,
transform: 1
};
}

/**
* Sets d3 tick function and configures other d3 stuff such as forces and drag events.
*/
Expand Down Expand Up @@ -310,7 +244,7 @@ export default class Graph extends React.Component {
utils.throwErr(this.constructor.name, ERRORS.GRAPH_NO_ID_PROP);
}

this.state = this._initializeGraphState(this.props.data);
this.state = graphHelper.initializeGraphState(this.props, this.state);
}

componentWillReceiveProps(nextProps) {
Expand All @@ -322,7 +256,7 @@ export default class Graph extends React.Component {
}

const configUpdated = !utils.isDeepEqual(nextProps.config, this.state.config);
const state = newGraphElements ? this._initializeGraphState(nextProps.data) : this.state;
const state = newGraphElements ? graphHelper.initializeGraphState(nextProps, this.state) : this.state;
const config = configUpdated ? utils.merge(DEFAULT_CONFIG, nextProps.config || {}) : this.state.config;

// In order to properly update graph data we need to pause eventual d3 ongoing animations
Expand Down
File renamed without changes.
13 changes: 13 additions & 0 deletions src/components/node/const.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import CONFIG from '../graph/config';
import CONST from '../../const';

export default {
ARC: {
START_ANGLE: 0,
END_ANGLE: 2 * Math.PI
},
DEFAULT_NODE_SIZE: CONFIG.node.size,
NODE_LABEL_DX: '.90em',
NODE_LABEL_DY: '.35em',
...CONST
};
File renamed without changes.
File renamed without changes.
12 changes: 5 additions & 7 deletions src/components/Node/const.js → src/const.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
/**
* @ignore
* These are common keywords used across rd3g, thus being placed in a more abstract level
* in the tree directory.
*/
export default {
ARC: {
START_ANGLE: 0,
END_ANGLE: 2 * Math.PI
},
DEFAULT_NODE_SIZE: 80, // FIXME: This value should come from ../Graph/config.js
NODE_LABEL_DX: '.90em',
NODE_LABEL_DY: '.35em',
SYMBOLS: {
CIRCLE: 'circle',
CROSS: 'cross',
Expand Down
6 changes: 3 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Graph from './components/Graph';
import Node from './components/Node';
import Link from './components/Link';
import Graph from './components/graph';
import Node from './components/node';
import Link from './components/link';

export {
Graph,
Expand Down
2 changes: 1 addition & 1 deletion test/Graph.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import renderer from 'react-test-renderer';

import Graph from '../src/components/Graph';
import Graph from '../src/components/graph';
import graphMock from './graph.mock.js';

describe('Graph Component', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/Link.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import renderer from 'react-test-renderer';

import Link from '../src/components/Link';
import Link from '../src/components/link';

describe('Link Component', () => {
let that = {};
Expand Down
2 changes: 1 addition & 1 deletion test/Node.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import renderer from 'react-test-renderer';

import Node from '../src/components/Node';
import Node from '../src/components/node';

describe('Node Component', () => {
let that = {};
Expand Down