├── src ├── paths │ ├── es6 │ │ ├── .babelrc │ │ ├── package.json │ │ ├── app.css │ │ ├── index.html │ │ └── demo.js │ ├── es5 │ │ ├── package.json │ │ ├── app.css │ │ ├── index.html │ │ └── demo.js │ ├── ts │ │ ├── babel.config.js │ │ ├── tsconfig.json │ │ ├── rollup.config.js │ │ ├── package.json │ │ ├── app.css │ │ ├── index.html │ │ └── demo.ts │ ├── meta.json │ └── app.css ├── active-filtering │ ├── es6 │ │ ├── .babelrc │ │ ├── package.json │ │ ├── index.html │ │ └── demo.js │ ├── ts │ │ ├── babel.config.js │ │ ├── tsconfig.json │ │ ├── rollup.config.js │ │ ├── package.json │ │ ├── index.html │ │ └── demo.ts │ ├── es5 │ │ ├── package.json │ │ ├── index.html │ │ └── demo.js │ ├── meta.json │ └── app.css ├── hierarchy-layout │ ├── es6 │ │ ├── .babelrc │ │ ├── package.json │ │ ├── index.html │ │ ├── kpitree.json │ │ └── demo.js │ └── app.css ├── segmented-connectors │ └── es6 │ │ ├── .babelrc │ │ ├── app.css │ │ ├── package.json │ │ ├── index.html │ │ └── demo.js ├── skeleton │ └── es5 │ │ ├── package.json │ │ ├── app.css │ │ ├── index.html │ │ └── app.js ├── groups │ ├── ts │ │ ├── babel.config.js │ │ ├── tsconfig.json │ │ ├── rollup.config.js │ │ ├── package.json │ │ ├── index.html │ │ └── demo.ts │ ├── meta.json │ └── app.css ├── layouts │ ├── ts │ │ ├── babel.config.js │ │ ├── tsconfig.json │ │ ├── rollup.config.js │ │ ├── package.json │ │ ├── index.html │ │ └── demo.ts │ ├── app.css │ ├── meta.json │ └── demo-support.js └── multiple │ ├── ts │ ├── babel.config.js │ ├── tsconfig.json │ ├── rollup.config.js │ ├── package.json │ ├── index.html │ └── demo.ts │ ├── meta.json │ └── app.css ├── .gitignore ├── README.md ├── scripts ├── build-common.js ├── build-vanilla.js └── gatlight.js ├── package.json └── index.html /src/paths/es6/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /src/active-filtering/es6/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /src/hierarchy-layout/es6/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /src/segmented-connectors/es6/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .npmrc 3 | package-lock.json 4 | _build 5 | dist 6 | .idea 7 | demonstrations-*.json 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository is now archived, and the demonstrations have been moved to https://github.com/jsplumb/jsplumbtoolkit-applications 2 | -------------------------------------------------------------------------------- /src/skeleton/es5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "6.0.0", 3 | "dependencies": { 4 | "@jsplumbtoolkit/browser-ui": "^6.0.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/paths/es5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "6.0.0", 3 | "dependencies": { 4 | "@jsplumbtoolkit/browser-ui": "^6.0.0", 5 | "jsplumbtoolkit-demo-support": "^1.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/groups/ts/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | 4 | const presets = [ 5 | [ "@babel/env", 6 | { 7 | "modules": false 8 | } 9 | ], 10 | "@babel/preset-typescript" 11 | ]; 12 | const plugins = [ 13 | "@babel/proposal-class-properties", 14 | "@babel/proposal-object-rest-spread" 15 | ]; 16 | 17 | return { 18 | presets, 19 | plugins 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/layouts/ts/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | 4 | const presets = [ 5 | [ "@babel/env", 6 | { 7 | "modules": false 8 | } 9 | ], 10 | "@babel/preset-typescript" 11 | ]; 12 | const plugins = [ 13 | "@babel/proposal-class-properties", 14 | "@babel/proposal-object-rest-spread" 15 | ]; 16 | 17 | return { 18 | presets, 19 | plugins 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/multiple/ts/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | 4 | const presets = [ 5 | [ "@babel/env", 6 | { 7 | "modules": false 8 | } 9 | ], 10 | "@babel/preset-typescript" 11 | ]; 12 | const plugins = [ 13 | "@babel/proposal-class-properties", 14 | "@babel/proposal-object-rest-spread" 15 | ]; 16 | 17 | return { 18 | presets, 19 | plugins 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/paths/ts/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | 4 | const presets = [ 5 | [ "@babel/env", 6 | { 7 | "modules": false 8 | } 9 | ], 10 | "@babel/preset-typescript" 11 | ]; 12 | const plugins = [ 13 | "@babel/proposal-class-properties", 14 | "@babel/proposal-object-rest-spread" 15 | ]; 16 | 17 | return { 18 | presets, 19 | plugins 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/active-filtering/ts/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | 4 | const presets = [ 5 | [ "@babel/env", 6 | { 7 | "modules": false 8 | } 9 | ], 10 | "@babel/preset-typescript" 11 | ]; 12 | const plugins = [ 13 | "@babel/proposal-class-properties", 14 | "@babel/proposal-object-rest-spread" 15 | ]; 16 | 17 | return { 18 | presets, 19 | plugins 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/groups/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "outDir": "_build", 6 | "moduleResolution": "node", 7 | "sourceMap": false, 8 | "declaration":true, 9 | "emitDecoratorMetadata": false, 10 | "experimentalDecorators": false, 11 | "lib": [ "esnext", "dom" ], 12 | "noImplicitAny": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "allowJs":false, 15 | "rootDir": ".", 16 | "resolveJsonModule": true, 17 | "esModuleInterop": true 18 | }, 19 | "include": ["./demo.ts"], 20 | "exclude":["node_modules", "**/*.d.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /src/paths/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "outDir": "_build", 6 | "moduleResolution": "node", 7 | "sourceMap": false, 8 | "declaration":true, 9 | "emitDecoratorMetadata": false, 10 | "experimentalDecorators": false, 11 | "lib": [ "esnext", "dom" ], 12 | "noImplicitAny": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "allowJs":false, 15 | "rootDir": ".", 16 | "resolveJsonModule": true, 17 | "esModuleInterop": true 18 | }, 19 | "include": ["./demo.ts"], 20 | "exclude":["node_modules", "**/*.d.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /src/layouts/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "outDir": "_build", 6 | "moduleResolution": "node", 7 | "sourceMap": false, 8 | "declaration":true, 9 | "emitDecoratorMetadata": false, 10 | "experimentalDecorators": false, 11 | "lib": [ "esnext", "dom" ], 12 | "noImplicitAny": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "allowJs":false, 15 | "rootDir": ".", 16 | "resolveJsonModule": true, 17 | "esModuleInterop": true 18 | }, 19 | "include": ["./demo.ts"], 20 | "exclude":["node_modules", "**/*.d.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /src/multiple/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "outDir": "_build", 6 | "moduleResolution": "node", 7 | "sourceMap": false, 8 | "declaration":true, 9 | "emitDecoratorMetadata": false, 10 | "experimentalDecorators": false, 11 | "lib": [ "esnext", "dom" ], 12 | "noImplicitAny": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "allowJs":false, 15 | "rootDir": ".", 16 | "resolveJsonModule": true, 17 | "esModuleInterop": true 18 | }, 19 | "include": ["./demo.ts"], 20 | "exclude":["node_modules", "**/*.d.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /src/active-filtering/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "outDir": "_build", 6 | "moduleResolution": "node", 7 | "sourceMap": false, 8 | "declaration":true, 9 | "emitDecoratorMetadata": false, 10 | "experimentalDecorators": false, 11 | "lib": [ "esnext", "dom" ], 12 | "noImplicitAny": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "allowJs":false, 15 | "rootDir": ".", 16 | "resolveJsonModule": true, 17 | "esModuleInterop": true 18 | }, 19 | "include": ["./demo.ts"], 20 | "exclude":["node_modules", "**/*.d.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /src/layouts/app.css: -------------------------------------------------------------------------------- 1 | #layout { 2 | margin-bottom:25px; 3 | height:35px; 4 | outline:none; 5 | } 6 | 7 | 8 | .description { 9 | display:flex; 10 | } 11 | 12 | .config { 13 | 14 | opacity: 0.8; 15 | z-index: 500; 16 | border-top-right-radius: 4px; 17 | display: flex; 18 | flex-direction: column; 19 | color: black; 20 | } 21 | 22 | 23 | .jtk-node { 24 | z-index:50; 25 | border:1px solid #ccc; 26 | background-color:white; 27 | display:flex; 28 | align-items: center; 29 | justify-content: center; 30 | border-radius:3px; 31 | } 32 | 33 | .jtk-node .add, .jtk-node .delete { 34 | position:absolute; 35 | top:3px; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/groups/ts/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import babel from '@rollup/plugin-babel'; 4 | 5 | const extensions = [ 6 | '.ts' 7 | ]; 8 | 9 | export default [ 10 | { 11 | input: './demo.ts', 12 | output: [ 13 | { 14 | name: 'Groups Demonstration', 15 | file: './_build/demo.umd.js', 16 | format: 'umd' 17 | } 18 | ], 19 | plugins: [ 20 | resolve({ extensions }), 21 | commonjs(), 22 | babel({ extensions, include: ['**/*'] }) 23 | ] 24 | } 25 | ]; 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/layouts/ts/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import babel from '@rollup/plugin-babel'; 4 | 5 | const extensions = [ 6 | '.ts' 7 | ]; 8 | 9 | export default [ 10 | { 11 | input: './demo.ts', 12 | output: [ 13 | { 14 | name: 'Groups Demonstration', 15 | file: './_build/demo.umd.js', 16 | format: 'umd' 17 | } 18 | ], 19 | plugins: [ 20 | resolve({ extensions }), 21 | commonjs(), 22 | babel({ extensions, include: ['**/*'] }) 23 | ] 24 | } 25 | ]; 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/paths/ts/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import babel from '@rollup/plugin-babel'; 4 | 5 | const extensions = [ 6 | '.ts' 7 | ]; 8 | 9 | export default [ 10 | { 11 | input: './demo.ts', 12 | output: [ 13 | { 14 | name: 'Groups Demonstration', 15 | file: './_build/demo.umd.js', 16 | format: 'umd' 17 | } 18 | ], 19 | plugins: [ 20 | resolve({ extensions }), 21 | commonjs(), 22 | babel({ extensions, include: ['**/*'] }) 23 | ] 24 | } 25 | ]; 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/active-filtering/ts/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import babel from '@rollup/plugin-babel'; 4 | 5 | const extensions = [ 6 | '.ts' 7 | ]; 8 | 9 | export default [ 10 | { 11 | input: './demo.ts', 12 | output: [ 13 | { 14 | name: 'ActiveFiltering', 15 | file: './_build/demo.umd.js', 16 | format: 'umd' 17 | } 18 | ], 19 | plugins: [ 20 | resolve({ extensions }), 21 | commonjs(), 22 | babel({ extensions, include: ['**/*'] }) 23 | ] 24 | } 25 | ]; 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/multiple/ts/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import babel from '@rollup/plugin-babel'; 4 | 5 | const extensions = [ 6 | '.ts' 7 | ]; 8 | 9 | export default [ 10 | { 11 | input: './demo.ts', 12 | output: [ 13 | { 14 | name: 'Groups Demonstration', 15 | file: './_build/demo.umd.js', 16 | format: 'umd' 17 | } 18 | ], 19 | plugins: [ 20 | resolve({ extensions }), 21 | commonjs(), 22 | babel({ extensions, include: ['**/*'] }) 23 | ] 24 | } 25 | ]; 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/segmented-connectors/es6/app.css: -------------------------------------------------------------------------------- 1 | body * { 2 | font-family:sans-serif; 3 | font-size:15px; 4 | } 5 | 6 | .jtk-node { 7 | border:1px solid #ccc; 8 | background-color:white; 9 | display:flex; 10 | align-items: center; 11 | justify-content: center; 12 | border-radius:3px; 13 | min-width:100px; 14 | padding:10px; 15 | width:150px; 16 | height:90px; 17 | z-index:10; 18 | } 19 | 20 | .jtk-connector path { 21 | stroke:#89bcde; 22 | stroke-width:2px; 23 | } 24 | 25 | .code { 26 | white-space: pre; 27 | background-color: white; 28 | margin: 0.5rem; 29 | border: 1px solid gray; 30 | border-radius: 5px; 31 | padding:0.5rem; 32 | } 33 | -------------------------------------------------------------------------------- /src/skeleton/es5/app.css: -------------------------------------------------------------------------------- 1 | .jtk-node { 2 | border:1px solid #ccc; 3 | background-color:white; 4 | display:flex; 5 | align-items: center; 6 | justify-content: center; 7 | border-radius:3px; 8 | min-width:100px; 9 | padding:10px; 10 | } 11 | 12 | .jtk-node .name { 13 | background-color: #3E7E9C; 14 | color: #f7ebca; 15 | cursor: move; 16 | font-size: 13px; 17 | line-height: 24px; 18 | padding: 6px; 19 | text-align: center; 20 | } 21 | 22 | .jtk-node .name span { 23 | cursor:pointer; 24 | } 25 | 26 | .jtk-connector path { 27 | stroke: #89bcde; 28 | } 29 | 30 | .jtk-endpoint circle { 31 | fill:#89bcde; 32 | } 33 | 34 | path.jtk-overlay { 35 | fill:#89bcde; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /scripts/build-common.js: -------------------------------------------------------------------------------- 1 | const cp = require('child_process') 2 | const g = require('./gatlight') 3 | 4 | const NODE_MODULES = "node_modules" 5 | 6 | function buildDemonstration(demoRoot) { 7 | console.log(`Building ${demoRoot}`) 8 | cp.execSync('npm i', { cwd: demoRoot, env: process.env, stdio: 'inherit' }); 9 | cp.execSync('npm run build', { cwd: demoRoot, env: process.env, stdio: 'inherit' }); 10 | } 11 | 12 | function listCandidates(rootDir) { 13 | return g.ls(rootDir, { 14 | filter:(candidate) => { 15 | return candidate !== NODE_MODULES && g.isDirectory(`${rootDir}/${candidate}`) 16 | } 17 | }) 18 | } 19 | 20 | exports.buildDemonstration = buildDemonstration; 21 | exports.listCandidates = listCandidates; 22 | -------------------------------------------------------------------------------- /src/layouts/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Layouts", 3 | "description": "Demonstration of various layouts supported by the Toolkit", 4 | "keywords": [ 5 | "layout", "force directed", "hierarchy", "balloon" 6 | ], 7 | "discussion": [ 8 | "Reapply Layout' resets the positions of the nodes. Note that the `ForceDirected` layout uses random positions to start and will not result in the same layout every time.", 9 | "Use the + button to add a new child node to some node.", 10 | "Click the 'Pencil' icon to enter 'select' mode, then select several nodes. Click the canvas to exit.", 11 | "Click the 'Home' icon to zoom out and see all the nodes.", 12 | "Use the undo/redo buttons to undo and redo deletion/addition operations" 13 | 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/multiple/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Multiple renderers", 3 | "description": "Demonstration of rendering a dataset to multiple renderers", 4 | "keywords": [ 5 | "layout", "multiple renderer", "multiple" 6 | ], 7 | "discussion": [ 8 | "Reapply Layout' resets the positions of the nodes. Note that the `ForceDirected` layout uses random positions to start and will not result in the same layout every time.", 9 | "Use the + button to add a new child node to some node.", 10 | "Click the 'Pencil' icon to enter 'select' mode, then select several nodes. Click the canvas to exit.", 11 | "Click the 'Home' icon to zoom out and see all the nodes.", 12 | "Use the undo/redo buttons to undo and redo deletion/addition operations" 13 | 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/groups/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Groups", 3 | "description": "Demonstration of the Toolkit's support for groups", 4 | "keywords": [ 5 | "drag", "filter", "ports", "edge", "target", "group" 6 | ], 7 | "discussion": [ 8 | "Drag new nodes/groups from the palette on the left onto the workspace. You can also drag nodes directly into groups", 9 | "Collapse/expand groups with the -/+ buttons", 10 | "Drag existing nodes into groups to add them.", 11 | "Nodes can be dragged out of group 1 but not out of group 2, due to the way each group is configured.", 12 | "Click the 'Pencil' icon to enter 'select' mode, then select several nodes. Click the canvas to exit.", 13 | "Click the 'Home' icon to zoom out and see all the nodes." 14 | 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/paths/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Path tracing", 3 | "description": "Demonstration of tracing paths between nodes", 4 | "keywords": [ 5 | "layout", "shortest path", "path", "route" 6 | ], 7 | "discussion": [ 8 | "This is a demonstration of the path tracing functionality", 9 | "Nodes here are positioned using a ForceDirected layout.", 10 | "Edges are directed in this demonstration - the arrow shows the direction.", 11 | "Edges cannot be traversed in the opposite direction to the way the arrow points", 12 | "Click a node to select it. Then click another node. The shortest path between the two nodes will be traversed with an overlay. If no path exists, an alert will show.", 13 | "Use the pause/play/cancel buttons to change the state of the path trace animation." 14 | 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/active-filtering/es5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "active-filtering", 3 | "version": "6.0.0", 4 | "description": "", 5 | "main": "demo.js", 6 | "scripts": { 7 | "build": "set -e;npx tsc;npx rollup -c rollup.config.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/jsplumb-toolkit-demonstrations/active-filtering.git" 12 | }, 13 | "author": "jsPlumb (https://jsplumbtoolkit.com)", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/jsplumb-toolkit-demonstrations/active-filtering/issues" 17 | }, 18 | "homepage": "https://github.com/jsplumb-toolkit-demonstrations/active-filtering#readme", 19 | "dependencies": { 20 | "@jsplumbtoolkit/browser-ui": "^6.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/groups/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "6.0.0", 3 | "scripts": { 4 | "build": "set -e;npx tsc;npx rollup -c rollup.config.js" 5 | }, 6 | "dependencies": { 7 | "@jsplumbtoolkit/browser-ui": "^6.0.0" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "14.17.3", 11 | "@types/estree": "1.0.0", 12 | "@babel/cli": "^7.4.4", 13 | "@babel/core": "^7.4.4", 14 | "@babel/plugin-proposal-class-properties": "^7.4.4", 15 | "@babel/plugin-proposal-object-rest-spread": "^7.4.4", 16 | "@babel/preset-env": "^7.4.4", 17 | "@babel/preset-typescript": "^7.3.3", 18 | "@rollup/plugin-babel": "^5.2.1", 19 | "@rollup/plugin-commonjs": "^15.0.0", 20 | "@rollup/plugin-node-resolve": "^9.0.0", 21 | "babel-loader": "^8.0.6", 22 | "rollup": "^2.26.11", 23 | "typescript": "^4.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/hierarchy-layout/app.css: -------------------------------------------------------------------------------- 1 | body * { 2 | font-family:sans-serif; 3 | font-size:15px; 4 | } 5 | 6 | .jtk-demo-canvas { 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | 11 | .jtk-demo-canvas > div { 12 | width: 100%; 13 | flex-basis: 500px; 14 | border: 1px solid; 15 | position:relative; 16 | } 17 | 18 | .label { 19 | position:absolute; 20 | left:1rem; 21 | top:1rem; 22 | } 23 | 24 | .jtk-node { 25 | width:120px; 26 | height:80px; 27 | outline:1px solid; 28 | padding:0.5rem; 29 | z-index:10; 30 | background-color: #edf7f7; 31 | } 32 | 33 | /* KPI tree example */ 34 | 35 | [data-type='strategy'] { 36 | border-left:4px solid green; 37 | } 38 | 39 | [data-type='theme'] { 40 | border-left:4px solid orange; 41 | } 42 | 43 | [data-type='tactic'] { 44 | border-left:4px solid purple; 45 | } 46 | 47 | [data-type='measure'] { 48 | border-left:4px solid blue; 49 | } 50 | -------------------------------------------------------------------------------- /src/layouts/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "6.0.0", 3 | "scripts": { 4 | "build": "npx tsc;npx rollup -c rollup.config.js" 5 | }, 6 | "dependencies": { 7 | "@jsplumbtoolkit/browser-ui": "^6.0.0", 8 | "jsplumbtoolkit-demo-support": "^1.0.0" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "14.17.3", 12 | "@types/estree": "1.0.0", 13 | "@babel/cli": "^7.4.4", 14 | "@babel/core": "^7.4.4", 15 | "@babel/plugin-proposal-class-properties": "^7.4.4", 16 | "@babel/plugin-proposal-object-rest-spread": "^7.4.4", 17 | "@babel/preset-env": "^7.4.4", 18 | "@babel/preset-typescript": "^7.3.3", 19 | "@rollup/plugin-babel": "^5.2.1", 20 | "@rollup/plugin-commonjs": "^15.0.0", 21 | "@rollup/plugin-node-resolve": "^9.0.0", 22 | "babel-loader": "^8.0.6", 23 | "rollup": "^2.26.11", 24 | "typescript": "^4.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/multiple/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "6.0.0", 3 | "scripts": { 4 | "build": "npx tsc;npx rollup -c rollup.config.js" 5 | }, 6 | "dependencies": { 7 | "@jsplumbtoolkit/browser-ui": "^6.0.0", 8 | "jsplumbtoolkit-demo-support": "^1.0.0" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "14.17.3", 12 | "@types/estree":"1.0.0", 13 | "@babel/cli": "^7.4.4", 14 | "@babel/core": "^7.4.4", 15 | "@babel/plugin-proposal-class-properties": "^7.4.4", 16 | "@babel/plugin-proposal-object-rest-spread": "^7.4.4", 17 | "@babel/preset-env": "^7.4.4", 18 | "@babel/preset-typescript": "^7.3.3", 19 | "@rollup/plugin-babel": "^5.2.1", 20 | "@rollup/plugin-commonjs": "^15.0.0", 21 | "@rollup/plugin-node-resolve": "^9.0.0", 22 | "babel-loader": "^8.0.6", 23 | "rollup": "^2.26.11", 24 | "typescript": "^4.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/multiple/app.css: -------------------------------------------------------------------------------- 1 | 2 | #demo-grid { 3 | justify-content: center; 4 | width: 100%; 5 | overflow: auto; 6 | display:flex; 7 | flex-wrap:wrap; 8 | 9 | } 10 | 11 | .multiple-demo { 12 | outline: 1px solid #eee; 13 | height:450px; 14 | width:450px; 15 | margin:25px; 16 | } 17 | 18 | 19 | 20 | 21 | 22 | .jtk-demo-canvas strong { 23 | position: absolute; 24 | left: 25px; 25 | top: 55px; 26 | color: #424040; 27 | } 28 | 29 | .jtk-node .add, .jtk-node .delete { 30 | position:absolute; 31 | top:3px; 32 | } 33 | 34 | 35 | .jtk-node { 36 | border:1px solid #ccc; 37 | background-color:white; 38 | display:flex; 39 | align-items: center; 40 | justify-content: center; 41 | border-radius:3px; 42 | } 43 | 44 | 45 | .jtk-connector path { 46 | stroke-width: 1; 47 | stroke: #89bcde; 48 | } 49 | 50 | .jtk-connector.jtk-hover path { 51 | stroke-width: 2; 52 | stroke: #FF6600; 53 | } 54 | -------------------------------------------------------------------------------- /src/paths/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "6.0.0", 3 | "scripts": { 4 | "build": "(npx tsc || true) && npx rollup -c rollup.config.js" 5 | }, 6 | "dependencies": { 7 | "@jsplumbtoolkit/browser-ui": "^6.0.0", 8 | "jsplumbtoolkit-demo-support": "^1.0.0" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "14.17.3", 12 | "@types/estree": "1.0.0", 13 | "@babel/cli": "^7.4.4", 14 | "@babel/core": "^7.4.4", 15 | "@babel/plugin-proposal-class-properties": "^7.4.4", 16 | "@babel/plugin-proposal-object-rest-spread": "^7.4.4", 17 | "@babel/preset-env": "^7.4.4", 18 | "@babel/preset-typescript": "^7.3.3", 19 | "@rollup/plugin-babel": "^5.2.1", 20 | "@rollup/plugin-commonjs": "^15.0.0", 21 | "@rollup/plugin-node-resolve": "^9.0.0", 22 | "babel-loader": "^8.0.6", 23 | "rollup": "^2.26.11", 24 | "typescript": "^4.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/active-filtering/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Active Filtering", 3 | "description": "Provides an example of how to use the active filtering mechanism to restrict drag targets", 4 | "keywords": [ 5 | "drag", "filter", "constrain", "edge", "target" 6 | ], 7 | "discussion": [ 8 | "The Active filtering plugin provides a way to filter connection targets when a drag begins.", 9 | "When an activeFiltering plugin is set on a Surface and a beforeConnect function is supplied to the Toolkit's constructor, that function is called before a new connection is dragged, for every possible target for the current source.", 10 | "Whenever the beforeConnect method returns false, the corresponding target object (a Node, Group or Port) is disabled, and the user will not be able to drop the connection onto it.", 11 | "Each Node contains a set of entries which each contain the name of two types of animal. Entries are deemed connectable if one or more animals from the source entry are matched in a given target." 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/active-filtering/app.css: -------------------------------------------------------------------------------- 1 | 2 | .connectivity-node { 3 | border:1px solid #ccc; 4 | width:250px; 5 | text-align: center; 6 | transition:outline 0.5s; 7 | background-color:white; 8 | } 9 | 10 | .hl { 11 | outline:12px solid #ffc06c; 12 | } 13 | 14 | .connectivity-node > div > div{ 15 | text-align: left; 16 | list-style-type:none; 17 | border-bottom: 1px solid #CCC; 18 | border-top: 1px solid #CCC; 19 | margin-bottom:10px; 20 | padding:10px; 21 | background-color: #edf7f7; 22 | } 23 | 24 | .jtk-node { 25 | align-items: stretch; 26 | flex-direction: column; 27 | } 28 | 29 | .jtk-drag-active { 30 | transition: outline 0.05s; 31 | } 32 | 33 | .jtk-drag-hover { 34 | outline:3px solid #2ba5f7; 35 | } 36 | 37 | [data-jtk-enabled='false'] { 38 | color:#999; 39 | opacity:0.3; 40 | outline:none; 41 | } 42 | 43 | .jtk-connector path { 44 | stroke: #2ba5f7; 45 | } 46 | 47 | .jtk-endpoint circle { 48 | fill:#2ba5f7; 49 | } 50 | 51 | .jtk-demo-dataset { 52 | font-size:12px; 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsplumbtoolkit-demonstrations", 3 | "version": "1.0.0", 4 | "description": "A set of demonstrations for the Toolkit edition. Previously these were all in separate repositories in the jsplumb-toolkit-demonstrations organisation. These demonstrations require a 6.x version of the Toolkit.", 5 | "main": "index.js", 6 | "scripts": { 7 | "init": "npm i;npm run build:vanilla", 8 | "build": "node ./scripts/build-vanilla.js", 9 | "serve": "npx http-server" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/jsplumb/jsplumbtoolkit-demonstrations.git" 14 | }, 15 | "author": "jsPlumb (https://jsplumbtoolkit.com/)", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/jsplumb/jsplumbtoolkit-demonstrations/issues" 19 | }, 20 | "homepage": "https://github.com/jsplumb/jsplumbtoolkit-demonstrations#readme", 21 | "dependencies": { 22 | "@jsplumbtoolkit/browser-ui": "6.1.1", 23 | "@types/estree": "1.0.0", 24 | "http-server": "^14.1.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/active-filtering/es6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "active-filtering", 3 | "version": "6.0.0", 4 | "description": "", 5 | "main": "demo.js", 6 | "scripts": { 7 | "build-es6": "./node_modules/.bin/esbuild demo.js --target=es2016 --bundle --format=iife --outfile=_build/bundle.js", 8 | "transpile-es5": "npx babel _build/bundle.js -o _build/bundle-es5.js", 9 | "build": "npm run build-es6;npm run transpile-es5" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/jsplumb-toolkit-demonstrations/active-filtering.git" 14 | }, 15 | "author": "jsPlumb (https://jsplumbtoolkit.com)", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/jsplumb-toolkit-demonstrations/active-filtering/issues" 19 | }, 20 | "homepage": "https://github.com/jsplumb-toolkit-demonstrations/active-filtering#readme", 21 | "dependencies": { 22 | "@jsplumbtoolkit/browser-ui": "^6.0.0" 23 | }, 24 | "devDependencies": { 25 | "babel-cli": "^6.26.0", 26 | "babel-preset-env": "^1.7.0", 27 | "esbuild": "0.17.18" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/hierarchy-layout/es6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hierarchy-layout", 3 | "version": "6.0.0", 4 | "description": "", 5 | "main": "demo.js", 6 | "scripts": { 7 | "build-es6": "./node_modules/.bin/esbuild demo.js --target=es2016 --bundle --format=iife --outfile=_build/bundle.js", 8 | "transpile-es5": "npx babel _build/bundle.js -o _build/bundle-es5.js", 9 | "build": "npm run build-es6;npm run transpile-es5" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/jsplumb-toolkit-demonstrations/active-filtering.git" 14 | }, 15 | "author": "jsPlumb (https://jsplumbtoolkit.com)", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/jsplumb-toolkit-demonstrations/active-filtering/issues" 19 | }, 20 | "homepage": "https://github.com/jsplumb-toolkit-demonstrations/active-filtering#readme", 21 | "dependencies": { 22 | "@jsplumbtoolkit/browser-ui": "^6.0.0" 23 | }, 24 | "devDependencies": { 25 | "babel-cli": "^6.26.0", 26 | "babel-preset-env": "^1.7.0", 27 | "esbuild": "0.17.18" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/segmented-connectors/es6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "segmented-connectors", 3 | "version": "6.5.0", 4 | "description": "", 5 | "main": "demo.js", 6 | "scripts": { 7 | "build-es6": "./node_modules/.bin/esbuild demo.js --target=es2016 --bundle --format=iife --outfile=_build/bundle.js", 8 | "transpile-es5": "npx babel _build/bundle.js -o _build/bundle-es5.js", 9 | "build": "npm run build-es6;npm run transpile-es5" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/jsplumb/jsplumbtoolkit-demonstrations.git" 14 | }, 15 | "author": "jsPlumb (https://jsplumbtoolkit.com)", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/jsplumb/jsplumbtoolkit-demonstrations/issues" 19 | }, 20 | "homepage": "https://github.com/jsplumb/jsplumbtoolkit-demonstrations#readme", 21 | "dependencies": { 22 | "@jsplumbtoolkit/browser-ui": "^6.5.0", 23 | "jsplumbtoolkit-demo-support": "^1.0.0" 24 | 25 | }, 26 | "devDependencies": { 27 | "babel-cli": "^6.26.0", 28 | "babel-preset-env": "^1.7.0", 29 | "esbuild": "0.17.18" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/paths/es6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "active-filtering", 3 | "version": "6.0.0", 4 | "description": "", 5 | "main": "demo.js", 6 | "scripts": { 7 | "build-es6": "./node_modules/.bin/esbuild demo.js --target=es2016 --bundle --format=iife --outfile=_build/bundle.js", 8 | "transpile-es5": "npx babel _build/bundle.js -o _build/bundle-es5.js", 9 | "build": "npm run build-es6;npm run transpile-es5" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/jsplumb-toolkit-demonstrations/active-filtering.git" 14 | }, 15 | "author": "jsPlumb (https://jsplumbtoolkit.com)", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/jsplumb-toolkit-demonstrations/active-filtering/issues" 19 | }, 20 | "homepage": "https://github.com/jsplumb-toolkit-demonstrations/active-filtering#readme", 21 | "dependencies": { 22 | "@jsplumbtoolkit/browser-ui": "^6.0.0", 23 | "jsplumbtoolkit-demo-support": "^1.0.0" 24 | 25 | }, 26 | "devDependencies": { 27 | "babel-cli": "^6.26.0", 28 | "babel-preset-env": "^1.7.0", 29 | "esbuild": "0.17.18" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /scripts/build-vanilla.js: -------------------------------------------------------------------------------- 1 | /** 2 | Builds all of the vanilla demonstrations. For demonstrations that have multiple source versions, 3 | all of the versions are built. 4 | 5 | */ 6 | 7 | const g = require('./gatlight') 8 | const cp = require('child_process') 9 | const common = require('./build-common') 10 | 11 | const VANILLA_ROOT = './src' 12 | 13 | const vanillaDemonstrations = common.listCandidates(VANILLA_ROOT) 14 | const results = {} 15 | 16 | vanillaDemonstrations.forEach(v => { 17 | _maybeBuildES5(v) 18 | _maybeBuildES6_TS(v, "es6") 19 | _maybeBuildES6_TS(v, "ts") 20 | }) 21 | 22 | g.write("./demonstrations-vanilla.json", JSON.stringify(results)) 23 | 24 | function _maybeBuildES5(demoId) { 25 | const demoRoot = `${VANILLA_ROOT}/${demoId}` 26 | const es5Root = `${demoRoot}/es5` 27 | if (g.exists(es5Root)) { 28 | console.log(`Building ES5 version of ${demoId}`) 29 | cp.execSync('npm i', { cwd: es5Root, env: process.env, stdio: 'inherit' }); 30 | results[demoId] = "es5" 31 | } 32 | } 33 | 34 | function _maybeBuildES6_TS(demoId, version) { 35 | const demoRoot = `${VANILLA_ROOT}/${demoId}` 36 | const root = `${demoRoot}/${version}` 37 | if (g.exists(root)) { 38 | common.buildDemonstration(root) 39 | results[demoId] = version 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/active-filtering/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "active-filtering", 3 | "version": "6.0.0", 4 | "description": "", 5 | "main": "demo.js", 6 | "scripts": { 7 | "build": "set -e;npx tsc;npx rollup -c rollup.config.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/jsplumb-toolkit-demonstrations/active-filtering.git" 12 | }, 13 | "author": "jsPlumb (https://jsplumbtoolkit.com)", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/jsplumb-toolkit-demonstrations/active-filtering/issues" 17 | }, 18 | "homepage": "https://github.com/jsplumb-toolkit-demonstrations/active-filtering#readme", 19 | "dependencies": { 20 | "@jsplumbtoolkit/browser-ui": "^6.0.0" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "14.17.3", 24 | "@types/estree": "1.0.0", 25 | "@babel/cli": "^7.4.4", 26 | "@babel/core": "^7.4.4", 27 | "@babel/plugin-proposal-class-properties": "^7.4.4", 28 | "@babel/plugin-proposal-object-rest-spread": "^7.4.4", 29 | "@babel/preset-env": "^7.4.4", 30 | "@babel/preset-typescript": "^7.3.3", 31 | "@rollup/plugin-babel": "^5.2.1", 32 | "@rollup/plugin-commonjs": "^15.0.0", 33 | "@rollup/plugin-node-resolve": "^9.0.0", 34 | "babel-loader": "^8.0.6", 35 | "rollup": "^2.26.11", 36 | "typescript": "^4.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/paths/app.css: -------------------------------------------------------------------------------- 1 | 2 | .jtk-node { 3 | border:1px solid #ccc; 4 | background-color:white; 5 | display:flex; 6 | align-items: center; 7 | justify-content: center; 8 | border-radius:3px; 9 | min-width:100px; 10 | padding:10px; 11 | } 12 | 13 | .jtk-animate-source { 14 | border: 4px solid yellow; 15 | } 16 | 17 | /** applied to a node that a path is traversing on its way to another node */ 18 | .jtk-animate-node-traversing { 19 | border: 4px solid orangered; 20 | } 21 | 22 | .jtk-animate-edge-traversable path { 23 | stroke: green; 24 | stroke-width: 2px; 25 | } 26 | 27 | .jtk-animate-edge-traversed path { 28 | stroke: purple; 29 | } 30 | 31 | /** applied to an edge that an overlay is traversing */ 32 | .jtk-animate-edge-traversing path { 33 | stroke:orangered; 34 | stroke-width:2; 35 | } 36 | 37 | 38 | 39 | /* -------------- transport controls -------------------------------------*/ 40 | 41 | .transport { 42 | font-style: normal; 43 | font-size: 14px !important; 44 | } 45 | 46 | [action='play'] { 47 | margin-left:20px; 48 | } 49 | 50 | [action='play']:after { 51 | font-style: normal; 52 | content:"►"; 53 | } 54 | 55 | [action='pause']:after { 56 | content:"||"; 57 | } 58 | 59 | [action='cancel']:after { 60 | content:"✖"; 61 | } 62 | 63 | .controls[state='stopped'] .transport { 64 | background-color:grey !important; 65 | color:#CCC !important; 66 | } 67 | 68 | .controls[state='playing'] .transport[action='play'] { 69 | background-color:grey !important; 70 | color:#CCC !important; 71 | } 72 | 73 | .controls[state='paused'] .transport[action='pause'] { 74 | background-color:grey !important; 75 | color:#CCC !important; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/paths/es5/app.css: -------------------------------------------------------------------------------- 1 | 2 | .jtk-node { 3 | border:1px solid #ccc; 4 | background-color:white; 5 | display:flex; 6 | align-items: center; 7 | justify-content: center; 8 | border-radius:3px; 9 | min-width:100px; 10 | padding:10px; 11 | } 12 | 13 | .jtk-connector path { 14 | stroke:#89bcde; 15 | stroke-width:1px; 16 | } 17 | 18 | .jtk-animate-source { 19 | border: 4px solid yellow; 20 | } 21 | 22 | /** applied to a node that a path is traversing on its way to another node */ 23 | .jtk-animate-node-traversing { 24 | border: 4px solid orangered; 25 | } 26 | 27 | .jtk-animate-edge-traversable path { 28 | stroke: green; 29 | stroke-width: 2px; 30 | } 31 | 32 | .jtk-animate-edge-traversed path { 33 | stroke: purple; 34 | } 35 | 36 | /** applied to an edge that an overlay is traversing */ 37 | .jtk-animate-edge-traversing path { 38 | stroke:orangered; 39 | stroke-width:2; 40 | } 41 | 42 | 43 | 44 | /* -------------- transport controls -------------------------------------*/ 45 | 46 | .transport { 47 | font-style: normal; 48 | font-size: 14px !important; 49 | } 50 | 51 | [action='play'] { 52 | margin-left:20px; 53 | } 54 | 55 | [action='play']:after { 56 | font-style: normal; 57 | content:"►"; 58 | } 59 | 60 | [action='pause']:after { 61 | content:"||"; 62 | } 63 | 64 | [action='cancel']:after { 65 | content:"✖"; 66 | } 67 | 68 | .controls[state='stopped'] .transport { 69 | background-color:grey !important; 70 | color:#CCC !important; 71 | } 72 | 73 | .controls[state='playing'] .transport[action='play'] { 74 | background-color:grey !important; 75 | color:#CCC !important; 76 | } 77 | 78 | .controls[state='paused'] .transport[action='pause'] { 79 | background-color:grey !important; 80 | color:#CCC !important; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/paths/es6/app.css: -------------------------------------------------------------------------------- 1 | 2 | .jtk-node { 3 | border:1px solid #ccc; 4 | background-color:white; 5 | display:flex; 6 | align-items: center; 7 | justify-content: center; 8 | border-radius:3px; 9 | min-width:100px; 10 | padding:10px; 11 | } 12 | 13 | .jtk-connector path { 14 | stroke:#89bcde; 15 | stroke-width:1px; 16 | } 17 | 18 | .jtk-animate-source { 19 | border: 4px solid yellow; 20 | } 21 | 22 | /** applied to a node that a path is traversing on its way to another node */ 23 | .jtk-animate-node-traversing { 24 | border: 4px solid orangered; 25 | } 26 | 27 | .jtk-animate-edge-traversable path { 28 | stroke: green; 29 | stroke-width: 2px; 30 | } 31 | 32 | .jtk-animate-edge-traversed path { 33 | stroke: purple; 34 | } 35 | 36 | /** applied to an edge that an overlay is traversing */ 37 | .jtk-animate-edge-traversing path { 38 | stroke:orangered; 39 | stroke-width:2; 40 | } 41 | 42 | 43 | 44 | /* -------------- transport controls -------------------------------------*/ 45 | 46 | .transport { 47 | font-style: normal; 48 | font-size: 14px !important; 49 | } 50 | 51 | [action='play'] { 52 | margin-left:20px; 53 | } 54 | 55 | [action='play']:after { 56 | font-style: normal; 57 | content:"►"; 58 | } 59 | 60 | [action='pause']:after { 61 | content:"||"; 62 | } 63 | 64 | [action='cancel']:after { 65 | content:"✖"; 66 | } 67 | 68 | .controls[state='stopped'] .transport { 69 | background-color:grey !important; 70 | color:#CCC !important; 71 | } 72 | 73 | .controls[state='playing'] .transport[action='play'] { 74 | background-color:grey !important; 75 | color:#CCC !important; 76 | } 77 | 78 | .controls[state='paused'] .transport[action='pause'] { 79 | background-color:grey !important; 80 | color:#CCC !important; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/paths/ts/app.css: -------------------------------------------------------------------------------- 1 | 2 | .jtk-node { 3 | border:1px solid #ccc; 4 | background-color:white; 5 | display:flex; 6 | align-items: center; 7 | justify-content: center; 8 | border-radius:3px; 9 | min-width:100px; 10 | padding:10px; 11 | } 12 | 13 | .jtk-connector path { 14 | stroke:#89bcde; 15 | stroke-width:1px; 16 | } 17 | 18 | .jtk-animate-source { 19 | border: 4px solid yellow; 20 | } 21 | 22 | /** applied to a node that a path is traversing on its way to another node */ 23 | .jtk-animate-node-traversing { 24 | border: 4px solid orangered; 25 | } 26 | 27 | .jtk-animate-edge-traversable path { 28 | stroke: green; 29 | stroke-width: 2px; 30 | } 31 | 32 | .jtk-animate-edge-traversed path { 33 | stroke: purple; 34 | } 35 | 36 | /** applied to an edge that an overlay is traversing */ 37 | .jtk-animate-edge-traversing path { 38 | stroke:orangered; 39 | stroke-width:2; 40 | } 41 | 42 | 43 | 44 | /* -------------- transport controls -------------------------------------*/ 45 | 46 | .transport { 47 | font-style: normal; 48 | font-size: 14px !important; 49 | } 50 | 51 | [action='play'] { 52 | margin-left:20px; 53 | } 54 | 55 | [action='play']:after { 56 | font-style: normal; 57 | content:"►"; 58 | } 59 | 60 | [action='pause']:after { 61 | content:"||"; 62 | } 63 | 64 | [action='cancel']:after { 65 | content:"✖"; 66 | } 67 | 68 | .controls[state='stopped'] .transport { 69 | background-color:grey !important; 70 | color:#CCC !important; 71 | } 72 | 73 | .controls[state='playing'] .transport[action='play'] { 74 | background-color:grey !important; 75 | color:#CCC !important; 76 | } 77 | 78 | .controls[state='paused'] .transport[action='pause'] { 79 | background-color:grey !important; 80 | color:#CCC !important; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/skeleton/es5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Skeleton 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 |
23 | 24 |
25 | 26 |
27 |
28 |
29 |
30 |

31 | A basic skeleton demonstrating rendering of nodes and edges. 32 |

33 |
    34 |
  • Nodes are positioned using the ForceDirected layout.
  • 35 |
  • On load, the view is zoomed such that all nodes are visible.
  • 36 |
37 |
38 | 39 | 40 |
41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/skeleton/es5/app.js: -------------------------------------------------------------------------------- 1 | jsPlumbToolkit.ready(function() { 2 | 3 | // prepare some data 4 | var data = { 5 | nodes:[ 6 | { id:"1", label:"jsPlumb" }, 7 | { id:"2", label:"Toolkit" }, 8 | { id:"3", label:"Hello" }, 9 | { id:"4", label:"World" } 10 | ], 11 | edges:[ 12 | { source:"1", target:"2" }, 13 | { source:"2", target:"3" }, 14 | { source:"3", target:"4" }, 15 | { source:"4", target:"1" } 16 | ] 17 | }; 18 | 19 | // get a new instance of the Toolkit 20 | var toolkit = jsPlumbToolkit.newInstance(); 21 | 22 | var mainElement = document.querySelector(".jtk-demo-main"), 23 | canvasElement = mainElement.querySelector(".jtk-demo-canvas"), 24 | miniviewElement = mainElement.querySelector(".miniview"); 25 | 26 | // render it to the div with id 'canvas' and add a miniview. Instruct the surface to zoom out after data loading 27 | // so that all nodes are visible. Provide some basic jsPlumb paint defaults. 28 | var renderer = toolkit.render(canvasElement, { 29 | plugins:[ 30 | { 31 | type:"miniview", 32 | options:{ 33 | container:miniviewElement 34 | } 35 | } 36 | ], 37 | layout:{ 38 | type:"ForceDirected" 39 | }, 40 | zoomToFit:true, 41 | view:{ 42 | nodes:{ 43 | "default":{ 44 | templateId:"tmplNode" 45 | } 46 | }, 47 | edges:{ 48 | "default":{ 49 | overlays:[ 50 | {type: "Arrow", options:{ location:1, width:7, length:7 }} 51 | ] 52 | } 53 | } 54 | }, 55 | defaults:{ 56 | anchor:"Continuous", 57 | endpoints:[{type:"Dot", options:{ radius:3 }}, "Blank" ] 58 | } 59 | 60 | }); 61 | 62 | // load the data. 63 | toolkit.load({ 64 | data:data 65 | }); 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /src/hierarchy-layout/es6/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Active filtering demonstration 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |
20 |
Vertical alignment, parent relative placement
21 |
22 |
23 |
Horizontal alignment, parent relative placement
24 |
25 |
26 |
Vertical alignment, centered placement
27 |
28 |
29 | 30 |
31 | Back to demo list 32 |
33 |

Hierarchy Layout

34 |

35 | This layout renders nodes in a hierarchy. It is a newer version of the original `Hierarchical` layout, which is now deprecated. 36 |

37 |

For a full discussion of this layout, see the Toolkit documentation

38 |
39 | 40 |
41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/hierarchy-layout/es6/kpitree.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes":[ 3 | { 4 | "id":"strategy", 5 | "label":"Strategic goal", 6 | "type": "strategy" 7 | }, 8 | { 9 | "id":"theme1", 10 | "label":"Theme 1", 11 | "type": "theme" 12 | }, 13 | { 14 | "id":"theme2", 15 | "label":"Theme 2", 16 | "type": "theme" 17 | }, 18 | { 19 | "id":"theme3", 20 | "label":"Theme 3", 21 | "type": "theme" 22 | }, 23 | { 24 | "id":"theme4", 25 | "label":"Theme 4", 26 | "type": "theme" 27 | }, 28 | { 29 | "id":"tactic1", 30 | "label":"Tactic 1", 31 | "type": "tactic" 32 | }, 33 | { 34 | "id":"tactic2", 35 | "label":"Tactic 2", 36 | "type": "tactic" 37 | }, 38 | { 39 | "id":"tactic3", 40 | "label":"Tactic 3", 41 | "type": "tactic" 42 | }, 43 | { 44 | "id":"measure1", 45 | "label":"Measure 1", 46 | "type": "measure" 47 | }, 48 | { 49 | "id":"measure2", 50 | "label":"Measure 2", 51 | "type": "measure" 52 | }, 53 | { 54 | "id":"measure3", 55 | "label":"Measure 3", 56 | "type": "measure" 57 | }, 58 | { 59 | "id":"measure4", 60 | "label":"Measure 4", 61 | "type": "measure" 62 | }, 63 | { 64 | "id":"measure5", 65 | "label":"Measure 5", 66 | "type": "measure" 67 | } 68 | ], 69 | "edges": [ 70 | { "source": "strategy", "target": "theme1" }, 71 | { "source": "strategy", "target": "theme2" }, 72 | { "source": "strategy", "target": "theme3" }, 73 | { "source": "strategy", "target": "theme4" }, 74 | { "source": "theme2", "target": "tactic1" }, 75 | { "source": "theme2", "target": "tactic2" }, 76 | { "source": "theme2", "target": "tactic3" }, 77 | { "source": "tactic1", "target": "measure1" }, 78 | { "source": "tactic1", "target": "measure2" }, 79 | { "source": "tactic2", "target": "measure2" }, 80 | { "source": "tactic3", "target": "measure2" }, 81 | { "source": "tactic3", "target": "measure3" }, 82 | { "source": "tactic3", "target": "measure4" }, 83 | { "source": "tactic3", "target": "measure5" }, 84 | { "source": "measure4", "target": "measure5"} 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /src/hierarchy-layout/es6/demo.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | DEFAULT, AnchorLocations, 4 | BlankEndpoint, 5 | ready, 6 | newInstance,StraightConnector, 7 | HierarchyLayout 8 | } from "@jsplumbtoolkit/browser-ui" 9 | 10 | ready(() =>{ 11 | 12 | const toolkit = newInstance(); 13 | 14 | const mainElement = document.querySelector(".jtk-demo-main"), 15 | canvas1Element = mainElement.querySelector("#canvas1"), 16 | canvas2Element = mainElement.querySelector("#canvas2"), 17 | canvas3Element = mainElement.querySelector("#canvas3") 18 | 19 | const view = () => ({ 20 | nodes: { 21 | [DEFAULT]: { 22 | template: `
{{label}}
` 23 | } 24 | }, 25 | edges: { 26 | [DEFAULT]: { 27 | connector: StraightConnector.type, 28 | endpoint: BlankEndpoint.type 29 | } 30 | } 31 | }); 32 | 33 | toolkit.render(canvas1Element, { 34 | zoomToFit: true, 35 | view: view(), 36 | layout: { 37 | type: HierarchyLayout.type, 38 | options:{ 39 | axis:"vertical" 40 | } 41 | }, 42 | elementsDraggable:false, 43 | consumeRightClick:false, 44 | defaults:{ 45 | anchor: { type:AnchorLocations.Continuous, options:{ faces:["left", "right"]} } 46 | } 47 | }) 48 | 49 | toolkit.render(canvas2Element, { 50 | zoomToFit: true, 51 | view: view(), 52 | layout: { 53 | type: HierarchyLayout.type, 54 | options:{ 55 | axis:"horizontal" 56 | } 57 | }, 58 | elementsDraggable:false, 59 | consumeRightClick:false, 60 | defaults:{ 61 | anchor: { type:AnchorLocations.Continuous, options:{ faces:["bottom", "top"]} } 62 | } 63 | }) 64 | 65 | toolkit.render(canvas3Element, { 66 | zoomToFit: true, 67 | view: view(), 68 | layout: { 69 | type: HierarchyLayout.type, 70 | options:{ 71 | axis:"horizontal", 72 | placementStrategy:"center" 73 | } 74 | }, 75 | elementsDraggable:false, 76 | consumeRightClick:false, 77 | defaults:{ 78 | anchor: { type:AnchorLocations.Continuous, options:{ faces:["bottom", "top"]} } 79 | } 80 | }) 81 | 82 | toolkit.load({ 83 | url:'./kpitree.json' 84 | }) 85 | 86 | }) 87 | -------------------------------------------------------------------------------- /src/paths/ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Path Tracing Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 |
21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 |
34 | Back to demo list 35 |

36 | This is a demonstration of the path tracing functionality. Nodes here are positioned randomly 37 | and then moved as if they were connected with springs to each other. 38 |

39 |

40 | Click a node to select it. Then click another node. The shortest path between the two nodes 41 | will be traversed with an overlay. If no path exists, an alert will show. 42 |

43 |

Use the pause/play/cancel buttons to change the state of the path trace animation.

44 | 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/paths/es6/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Path Tracing Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 |
21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 |
34 | Back to demo list 35 |

36 | This is a demonstration of the path tracing functionality. Nodes here are positioned randomly 37 | and then moved as if they were connected with springs to each other. 38 |

39 |

40 | Click a node to select it. Then click another node. The shortest path between the two nodes 41 | will be traversed with an overlay. If no path exists, an alert will show. 42 |

43 |

Use the pause/play/cancel buttons to change the state of the path trace animation.

44 | 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/paths/es5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Path Tracing Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 |
21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 |
34 | Back to demo list 35 |

36 | This is a demonstration of the path tracing functionality. Nodes here are positioned randomly 37 | and then moved as if they were connected with springs to each other. 38 |

39 |

40 | Click a node to select it. Then click another node. The shortest path between the two nodes 41 | will be traversed with an overlay. If no path exists, an alert will show. 42 |

43 |

Use the pause/play/cancel buttons to change the state of the path trace animation.

44 | 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/segmented-connectors/es6/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Path Tracing Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 |
22 | 23 |
24 | 25 |
26 | Back to demo list 27 |

Segmented Connectors

28 | This connector type consists of a set of straight line segments, with the option to smooth the segments into a series of Bezier curves. 29 |
30 |

Editing paths

31 | The editor for this connector type supports three operations: 32 |
    33 |
  1. Segment end points can be dragged around
  2. 34 |
  3. The scissors icon cuts a segment into two.
  4. 35 |
  5. The trashcan icon deletes a segment. This icon is not shown when the connector has only one segment.
  6. 36 |
37 |
38 |

Smoothing

39 | Smoothing can be switched on via the `smooth` flag: 40 |
41 | connector:{ 42 | type:SegmentedConnector.type, 43 | options:{ 44 | smooth:true 45 | } 46 | } 47 |
48 | 49 | Note: this will reset paths and node positions. 50 |
51 |

Editing smooth paths

52 | When smoothing is switched on, the drag handles mark the control points of the Bezier curves. 53 | 54 |

55 | For a full discussion of this connector, see the documentation. 56 |

57 | 58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/groups/app.css: -------------------------------------------------------------------------------- 1 | .jtk-node { 2 | border:1px solid #ccc; 3 | background-color:white; 4 | display:flex; 5 | align-items: center; 6 | justify-content: center; 7 | border-radius:3px; 8 | min-width:100px; 9 | padding:10px; 10 | z-index:11; 11 | } 12 | 13 | .jtk-group { 14 | border:2px solid #9e9e9e; 15 | z-index: 10; 16 | min-width: 250px; 17 | min-height: 250px; 18 | } 19 | 20 | .jtk-group.jtk-drag-hover, .jtk-node.jtk-drag-hover { 21 | outline:2px solid red; 22 | } 23 | 24 | .group-title { 25 | margin:0; 26 | width:100%; 27 | display:flex; 28 | align-items: center; 29 | background-color:#9e9e9e; 30 | color:white; 31 | font-size: 13px; 32 | letter-spacing: 4px; 33 | text-transform: uppercase; 34 | text-indent:7px; 35 | padding:7px; 36 | box-sizing: border-box; 37 | } 38 | 39 | .group-title button { 40 | 41 | 42 | cursor:pointer; 43 | color: black; 44 | background-color: transparent; 45 | outline: none; 46 | width: 20px; 47 | height: 19px; 48 | display: flex; 49 | align-items: center; 50 | justify-content: center; 51 | border-radius: 50%; 52 | padding-bottom: 3px; 53 | margin-left:5px; 54 | border:1px solid transparent; 55 | 56 | } 57 | 58 | .group-title button:hover { 59 | background-color:#f7f7f7; 60 | } 61 | 62 | .group-title .expand { 63 | margin-left:auto; 64 | } 65 | 66 | .group-title .expand:after { 67 | content:"-"; 68 | } 69 | 70 | .group-title .group-delete { 71 | right:45px; 72 | } 73 | 74 | .group-title .group-delete:after { 75 | content:"x"; 76 | } 77 | 78 | .group-connect { 79 | position:absolute; 80 | bottom:10px; 81 | left:10px; 82 | width:20px; 83 | height:20px; 84 | border-radius: 50%; 85 | display: flex; 86 | align-items: center; 87 | justify-content: center; 88 | cursor: pointer; 89 | } 90 | 91 | .connect { 92 | cursor:pointer; 93 | } 94 | 95 | .jtk-group { 96 | background-color: #f7f7f7; 97 | } 98 | 99 | .jtk-group.jtk-group-collapsed { 100 | height:40px; 101 | min-height:0; 102 | } 103 | 104 | .jtk-group.jtk-group-collapsed .group-title .expand:after { 105 | content:"+"; 106 | } 107 | 108 | .jtk-group [jtk-group-content] { 109 | min-height:210px; 110 | margin:5px; 111 | width: auto ; 112 | } 113 | 114 | .jtk-group.jtk-group-collapsed [jtk-group-content] { 115 | display:none; 116 | min-height: 0; 117 | } 118 | 119 | .jtk-connector { 120 | z-index:12; 121 | } 122 | 123 | .jtk-node .add, .jtk-node .delete { 124 | position:absolute; 125 | top:3px; 126 | } 127 | 128 | 129 | .jtk-surface.jtk-drag-drop-active { 130 | outline:6px dotted #4ea758 !important; 131 | } 132 | 133 | .jtk-surface.jtk-drag-drop-hover { 134 | outline: 6px solid #f7a45d !important; 135 | } 136 | 137 | [data-jtk-managed].jtk-drag-drop-hover { 138 | outline: 6px solid #f7a45d !important; 139 | } 140 | -------------------------------------------------------------------------------- /src/active-filtering/es6/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Active filtering demonstration 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 28 | 29 |
30 | 31 |
32 | 33 |
34 | 35 | 36 | 37 | 38 |
39 | 40 |
41 |
42 | 43 |
44 | Back to demo list 45 |
46 |

Active Filtering

47 |

The Active filtering plugin provides a way to filter connection targets when a drag begins.

48 |

49 | When an activeFiltering plugin is set on a Surface and a beforeConnect function is supplied to the Toolkit's 50 | constructor, that function is called before a new connection is dragged, for every possible target for 51 | the current source. 52 |

53 |

54 | Whenever the beforeConnect method returns false, the corresponding target object (a Node, Group or Port) is disabled, and the 55 | user will not be able to drop the connection onto it.

56 |

57 | Each Node contains a set of entries which each contain the name of two types of animal. Entries are 58 | deemed connectable if one or more animals from the source entry are matched in a given target. 59 |

60 |
61 | 62 |
63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/active-filtering/ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Active filtering demonstration 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 28 | 29 |
30 | 31 |
32 | 33 |
34 | 35 | 36 | 37 | 38 |
39 | 40 |
41 |
42 | 43 |
44 | Back to demo list 45 |
46 |

Active Filtering

47 |

The Active filtering plugin provides a way to filter connection targets when a drag begins.

48 |

49 | When an activeFiltering plugin is set on a Surface and a beforeConnect function is supplied to the Toolkit's 50 | constructor, that function is called before a new connection is dragged, for every possible target for 51 | the current source. 52 |

53 |

54 | Whenever the beforeConnect method returns false, the corresponding target object (a Node, Group or Port) is disabled, and the 55 | user will not be able to drop the connection onto it.

56 |

57 | Each Node contains a set of entries which each contain the name of two types of animal. Entries are 58 | deemed connectable if one or more animals from the source entry are matched in a given target. 59 |

60 |
61 | 62 |
63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/active-filtering/es5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Active filtering demonstration 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 28 | 29 |
30 | 31 |
32 | 33 |
34 | 35 | 36 | 37 | 38 |
39 | 40 |
41 |
42 | 43 |
44 | Back to demo list 45 |
46 |

Active Filtering

47 |

The Active filtering plugin provides a way to filter connection targets when a drag begins.

48 |

49 | When an activeFiltering plugin is set on a Surface and a beforeConnect function is supplied to the Toolkit's 50 | constructor, that function is called before a new connection is dragged, for every possible target for 51 | the current source. 52 |

53 |

54 | Whenever the beforeConnect method returns false, the corresponding target object (a Node, Group or Port) is disabled, and the 55 | user will not be able to drop the connection onto it.

56 |

57 | Each Node contains a set of entries which each contain the name of two types of animal. Entries are 58 | deemed connectable if one or more animals from the source entry are matched in a given target. 59 |

60 |
61 | 62 |
63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/groups/ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Groups 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 36 | 37 |
38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 |
51 |
52 | 53 |
54 | 62 |
63 |

64 | This is a demonstration of the Groups functionality. 65 |

66 |
    67 |
  • Drag new Nodes/Groups from the palette on the left onto the workspace. You can drag Nodes directly into Groups.
  • 68 |
  • Collapse/Expand Groups with the -/+ buttons
  • 69 |
  • Drag existing Nodes into Groups to add them.
  • 70 |
  • Nodes can be dragged out of Group 1 but not out of Group 2
  • 71 |
  • Click the 'Pencil' icon to enter 'select' mode, then select several nodes. Click the canvas to exit.
  • 72 |
  • Click the 'Home' icon to zoom out and see all the nodes.
  • 73 |
74 |
75 |
76 | 77 | 78 | 79 |
80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit Demonstrations 5 | 6 | 7 | 8 | 9 | 20 |
jsPlumb Toolkit Demonstrations
21 |
22 |
23 |
24 | 25 |
Repository configuration
26 |

27 | You'll need to follow the instructions on this page: 28 | https://docs.jsplumbtoolkit.com/toolkit/6.x/lib/npm-repository to setup your 29 | local NPM for access to jsPlumb's repository. 30 |

31 | 32 |
Initializing
33 |

34 | If you're seeing this page in a web server then you've run npm i already. If you 35 | ran npm run init then you'll also be able to access the demonstrations. 36 | Otherwise you'll want to run a build. 37 |

38 | 39 |
Building
40 | 41 |

42 | npm run build 43 |

44 |

This will build all of the demonstrations. For demonstrations for which there is more than one 45 | version, we build the Typescript version

46 | 47 |
48 | 49 |
50 |
51 |
Demonstrations
52 |
53 | No demonstrations have been built! See instructions on the left hand side of this page. 54 |
55 |
56 | 57 |
58 |
59 |
60 | 61 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/segmented-connectors/es6/demo.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | ready, 4 | newInstance, 5 | AbsoluteLayout, 6 | SegmentedConnector, 7 | EdgePathEditor, 8 | EVENT_TAP, 9 | initializeSegmentedConnectorEditors, 10 | AnchorLocations, 11 | BlankEndpoint, 12 | EVENT_CANVAS_CLICK, 13 | ArrowOverlay 14 | } from "@jsplumbtoolkit/browser-ui" 15 | 16 | initializeSegmentedConnectorEditors() 17 | 18 | ready(() => { 19 | 20 | const data = { 21 | nodes:[ 22 | { id:"1", left:-100, top:0 }, 23 | { id:"2", left:350, top:0 }, 24 | { id:"3", left:350, top:450 }, 25 | { id:"4", left:0, top:350 } 26 | ], 27 | edges:[ 28 | { 29 | source:"1", 30 | target:"2", 31 | geometry:{ 32 | "segments":[ 33 | {"x":237, "y":200 }, 34 | {"x":197,"y":-7} 35 | ], 36 | "source":{ 37 | "curX":72, 38 | "curY":56, 39 | "x":1, 40 | "y":0.5, 41 | "ox":1, 42 | "oy":0 43 | }, 44 | "target":{ 45 | "curX":350, 46 | "curY":56, 47 | "x":0, 48 | "y":0.5, 49 | "ox":-1, 50 | "oy":0 51 | } 52 | } 53 | }, 54 | { source:"2", target:"3" }, 55 | { source:"3", target:"4" }, 56 | { source:"4", target:"1" } 57 | ] 58 | } 59 | 60 | const renderOptions = { 61 | zoomToFit:true, 62 | layout:{ 63 | type:AbsoluteLayout.type 64 | }, 65 | defaults:{ 66 | connector:{ 67 | type:SegmentedConnector.type 68 | }, 69 | anchor:AnchorLocations.AutoDefault, 70 | endpoint:BlankEndpoint.type 71 | }, 72 | view:{ 73 | nodes:{ 74 | default:{ 75 | template:`
76 | {{id}} 77 |
78 |
` 79 | 80 | } 81 | }, 82 | edges:{ 83 | default:{ 84 | events:{ 85 | [EVENT_TAP]:(p) => pathEditor.startEditing(p.edge) 86 | }, 87 | overlays:[ 88 | { 89 | type:ArrowOverlay.type, 90 | options:{ 91 | location:1 92 | } 93 | } 94 | ] 95 | } 96 | } 97 | }, 98 | events:{ 99 | [EVENT_CANVAS_CLICK]:() => pathEditor.stopEditing() 100 | } 101 | 102 | } 103 | 104 | // get a jsPlumbToolkit instance. 105 | const toolkit = newInstance() 106 | 107 | const mainElement = document.querySelector("#jtk-demo-connectors"), 108 | canvasElement = mainElement.querySelector(".jtk-demo-canvas") 109 | 110 | window.t = toolkit 111 | 112 | let surface = toolkit.render(canvasElement, renderOptions) 113 | let pathEditor = new EdgePathEditor(surface) 114 | 115 | function load() { 116 | // load the dataset and then start editing the first edge after load. 117 | toolkit.load({ 118 | data, 119 | onload:() => { 120 | pathEditor.startEditing(toolkit.getAllEdges()[0]) 121 | } 122 | }) 123 | } 124 | 125 | load() 126 | 127 | 128 | /* 129 | TOGGLING SMOOTH CONNECTORS 130 | 131 | This block of code toggle the smooth connectors on/off - it updates the render options and then clears the Toolkit, destroys 132 | the current surface and re-renders. 133 | 134 | */ 135 | let smooth = false 136 | window.toggleConnectorSmoothing = () => { 137 | smooth = !smooth 138 | renderOptions.defaults.connector = { 139 | type:SegmentedConnector.type, 140 | options:{ 141 | smooth 142 | } 143 | } 144 | toolkit.clear() 145 | surface.destroy() 146 | surface = toolkit.render(canvasElement, renderOptions) 147 | pathEditor = new EdgePathEditor(surface) 148 | // load the dataset and then start editing the first edge after load. 149 | load() 150 | 151 | } 152 | 153 | }) 154 | 155 | -------------------------------------------------------------------------------- /src/multiple/ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - Multiple Renderers Demonstration 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 |
24 | 25 |
26 | 27 |
28 |
29 | Hierarchy Layout 30 |
31 | 32 | 33 | 34 |
35 |
36 |
37 |
38 | Circular Layout 39 |
40 | 41 | 42 | 43 |
44 |
45 |
46 |
47 | Force Directed Layout 48 |
49 | 50 | 51 | 52 |
53 |
54 |
55 |
56 | Balloon Layout 57 |
58 | 59 | 60 | 61 |
62 |
63 |
64 |
65 |
66 | 67 |
68 | 69 |

70 | In this demonstration we render the same instance of the Toolkit to four different Surfaces, each with its own layout. 71 |

72 |
    73 |
  • Use the X button to delete nodes. Each of the Surfaces will update in response to this action.
  • 74 |
  • Use the + button to add a new child node to some node. Each of the Surfaces will update in response to this action.
  • 75 |
  • Click the 'Pencil' icon to enter 'select' mode, then select several nodes. Click the canvas to exit.
  • 76 |
  • Click the 'Home' icon to zoom out and see all the nodes in each Surface.
  • 77 |
78 |
79 | 80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/multiple/ts/demo.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EVENT_TAP, 3 | EVENT_CLICK, 4 | EVENT_CANVAS_CLICK, 5 | EVENT_SURFACE_MODE_CHANGED, 6 | SurfaceMode, 7 | DotEndpoint, 8 | AnchorLocations, 9 | ready, 10 | newInstance, 11 | MiniviewPlugin, 12 | StateMachineConnector, 13 | ObjectInfo, Node, 14 | HierarchicalLayout, 15 | BalloonLayout, 16 | CircularLayout, 17 | ForceDirectedLayout, 18 | LassoPlugin, HierarchyLayout 19 | } from "@jsplumbtoolkit/browser-ui" 20 | 21 | import {randomHierarchy, randomNode} from "jsplumbtoolkit-demo-support" 22 | 23 | ready(() =>{ 24 | 25 | // get a new jsPlumb Toolkit instance to use. 26 | const toolkit = newInstance() 27 | // make a random hierarchy 28 | const hierarchy = randomHierarchy(3, 3) 29 | 30 | const controls = document.querySelector(".controls") 31 | 32 | // 33 | // create one renderer 34 | // 35 | function render (id:string, layoutParams:any) { 36 | const selector = "#demo-" + id; 37 | const surface = toolkit.render(document.querySelector(selector), { 38 | layout: layoutParams, 39 | events:{ 40 | [EVENT_SURFACE_MODE_CHANGED] :(mode:SurfaceMode) => { 41 | surface.removeClass(controls.querySelectorAll(selector + " [mode]"), "selected-mode"); 42 | surface.addClass(controls.querySelectorAll(selector + " [mode='" + mode + "']"), "selected-mode"); 43 | }, 44 | [EVENT_CANVAS_CLICK]: () => { 45 | toolkit.clearSelection() 46 | } 47 | }, 48 | plugins:[ 49 | { 50 | type:MiniviewPlugin.type, 51 | options:{ 52 | container:document.getElementById("miniview-" + id) 53 | } 54 | }, 55 | LassoPlugin.type 56 | ], 57 | defaults: { 58 | anchor:AnchorLocations.Continuous, 59 | connector: { type:StateMachineConnector.type, options:{ curviness: 10 } }, 60 | endpoints: [ 61 | { type:DotEndpoint.type, options:{ radius: 2 } }, 62 | { type:DotEndpoint.type, options:{ radius: 2 } } 63 | ], 64 | endpointStyle: { fill: "#89bcde" }, 65 | endpointHoverStyle: { fill: "#FF6600" } 66 | }, 67 | zoomToFit: true, 68 | consumeRightClick: false, 69 | dragOptions: { 70 | filter: ".delete *, .add *, .delete, .add" 71 | } 72 | }); 73 | 74 | // bind event listeners to the mode buttons 75 | surface.on(document.querySelector(selector), EVENT_TAP, "[mode]", (e:Event) => { 76 | surface.setMode((e.target as any).getAttribute("mode")) 77 | }) 78 | 79 | // on home button tap, zoom content to fit. 80 | surface.on(document.querySelector(selector), EVENT_TAP, "[reset]", (e:Event) => { 81 | toolkit.clearSelection() 82 | surface.zoomToFit() 83 | }) 84 | 85 | surface.bindModelEvent(EVENT_TAP, ".delete", (event:Event, target:HTMLElement, info:ObjectInfo) => { 86 | const selection = toolkit.selectDescendants(info.obj, true) 87 | toolkit.remove(selection) 88 | }) 89 | 90 | surface.bindModelEvent(EVENT_TAP, ".add", (event:Event, target:HTMLElement, info:ObjectInfo) => { 91 | // get a random node. 92 | const n = randomNode("node") 93 | // add the node to the toolkit 94 | const newNode = toolkit.addNode(n) 95 | // and add an edge for it from the current node. 96 | toolkit.addEdge({source: info.obj, target: newNode}) 97 | }) 98 | } 99 | 100 | // 101 | // renderer specs. keys are ids, values are layout params. 102 | // 103 | const rendererSpecs = { 104 | "hierarchy":{ 105 | type: HierarchyLayout.type, 106 | axis: "horizontal", 107 | padding: [60, 60] 108 | }, 109 | "circular":{ 110 | type: CircularLayout.type, 111 | padding: 30 112 | }, 113 | "force-directed":{ 114 | type:ForceDirectedLayout.type, 115 | absoluteBacked:false 116 | }, 117 | "balloon":{ 118 | type:BalloonLayout.type 119 | } 120 | } 121 | 122 | // render each one. 123 | for (let id in rendererSpecs) { 124 | render(id, rendererSpecs[id]) 125 | } 126 | 127 | // load the data 128 | toolkit.load({data: hierarchy}) 129 | 130 | document.querySelector("#btnRegenerate").addEventListener(EVENT_CLICK, () => { 131 | toolkit.clear(); 132 | toolkit.load({ 133 | data:randomHierarchy(3) 134 | }) 135 | }) 136 | }) 137 | 138 | -------------------------------------------------------------------------------- /src/layouts/ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsPlumb Toolkit - layouts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 | 33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 |
41 | 67 |
68 |
 69 | {
 70 |   type: "Hierarchical",
 71 |   orientation: "horizontal",
 72 |     padding: [100, 60]
 73 | }
 74 |             
75 |
76 |
77 | 78 | 79 |
80 | 81 |
    82 |
  • 'Reapply Layout' resets the positions of the nodes. Note that the Spring layout uses random positions to start and will not result in the same layout every time.
  • 83 |
  • Use the + button to add a new child node to some node.
  • 84 |
  • Click the 'Pencil' icon to enter 'select' mode, then select several nodes. Click the canvas to exit.
  • 85 |
  • Click the 'Home' icon to zoom out and see all the nodes.
  • 86 |
  • Use the undo/redo buttons to undo and redo deletion/addition operations
  • 87 |
88 | 89 |
90 | 91 | 92 |
93 | 94 |
95 | 96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/layouts/demo-support.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is just a collection of utility methods used by the jsPlumb Toolkit demos to get random datasets for 3 | * demo purposes. 4 | */ 5 | (function () { 6 | 7 | window.jsPlumbToolkitDemoSupport = { 8 | // return a random hierarchy that contains at least 10 nodes (if possible. if maxDepth * maxBreadth is less 9 | // than 10, though, then we use that value for our desired amount). The hierarchy also contains positioning 10 | // information for use with an absolute layout, but most layouts ignore these values. 11 | randomHierarchy: function (maxDepth, maxBreadth, maxWidth, maxHeight) { 12 | maxDepth = maxDepth || 6; 13 | maxBreadth = maxBreadth || 3; 14 | maxWidth = maxWidth || 600; 15 | maxHeight = maxHeight || 400; 16 | var l = 0, t = 0; 17 | if (maxBreadth < 1) maxBreadth = 1; 18 | 19 | var _do = function () { 20 | var h = [ ], c = 0, pad = 80, 21 | _node = function (depth) { 22 | c++; 23 | var id = "w" + c, name = "" + c, 24 | childCount = Math.floor(Math.random() * maxBreadth) + 1, 25 | wh = jsPlumbToolkitDemoSupport.randomSize(80, 60), 26 | n = { id: id, name: name, w: wh[0], h: wh[1], left:l, top:t }, 27 | children = []; 28 | 29 | l += wh[0] + pad; 30 | if (l > maxWidth) { 31 | l = 0; 32 | t += wh[1] + pad; 33 | } 34 | 35 | if (depth < maxDepth) { 36 | for (var i = 0; i < childCount; i++) { 37 | children.push(_node(depth + 1)); 38 | } 39 | } 40 | 41 | h.unshift([n, children]); 42 | return id; 43 | }; 44 | _node(0); 45 | return h; 46 | }, 47 | _h = null; 48 | 49 | var maxNodesDesired = Math.min(maxDepth * maxBreadth, 10); 50 | while ((_h = _do()).length < maxNodesDesired); 51 | 52 | var out = { nodes: [], edges: [] }; 53 | for (var i = 0; i < _h.length; i++) { 54 | out.nodes.push(_h[i][0]); 55 | for (var j = 0; j < _h[i][1].length; j++) { 56 | out.edges.push({source: _h[i][0].id, target: _h[i][1][j], data:{id:jsPlumbUtil.uuid().slice(25)}}); 57 | } 58 | } 59 | 60 | return out; 61 | }, 62 | 63 | // randomizes the two given dimensions by up to +/- 20% 64 | randomSize: function (refWidth, refHeight, factor) { 65 | factor = factor || 0.3; 66 | var rx = Math.floor(factor - (Math.random() * (factor * 2 * refWidth))), 67 | ry = Math.floor(factor - (Math.random() * (factor * 2 * refHeight))); 68 | 69 | return [ refWidth + rx, refHeight + ry ]; 70 | }, 71 | 72 | // gets a random position within the given bounds 73 | randomLocation : function(w, h) { 74 | return [ 75 | Math.floor(Math.random() * w), 76 | Math.floor(Math.random() * h) 77 | ]; 78 | }, 79 | 80 | randomNode: function (name) { 81 | var wh = jsPlumbToolkitDemoSupport.randomSize(80, 60), 82 | xy = jsPlumbToolkitDemoSupport.randomLocation(800, 600), 83 | id = jsPlumbUtil.uuid(), 84 | name = name || id.substring(0, 3); 85 | 86 | return { id: id, name: name, w: wh[0], h: wh[1], left:xy[0], top:xy[1] }; 87 | }, 88 | 89 | randomGraph:function(minimum, maximum) { 90 | var nodeCount = minimum + Math.floor(Math.random() * (maximum - minimum)); 91 | var maybe = function() { return (Math.random() >= 0.5); }; 92 | var data = { nodes:[], edges:[] }, edgeCache = {}, edgeCount = 0; 93 | for (var i = 0; i < nodeCount; i++) { 94 | var sourceId = "" + (i+1); 95 | data.nodes.push({id:sourceId, name:sourceId}); 96 | for (var j = 0; j < nodeCount; j++) { 97 | if (i != j) { 98 | var targetId = "" + (j+1), 99 | key = sourceId +"_"+ targetId, 100 | keyInv = targetId +"_"+ sourceId; 101 | 102 | if (edgeCount < (nodeCount * 2) && !edgeCache[key] && !edgeCache[keyInv] && maybe()) { 103 | var directed = maybe(); 104 | data.edges.push({ 105 | source:sourceId, 106 | target: targetId, 107 | directed:directed, 108 | data:{type:directed? null : "bidirectional" } 109 | }); 110 | edgeCache[key] = true; 111 | edgeCache[keyInv] = true; 112 | edgeCount++; 113 | } 114 | } 115 | } 116 | } 117 | return data; 118 | } 119 | }; 120 | 121 | })(); -------------------------------------------------------------------------------- /src/paths/es5/demo.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | jsPlumbToolkit.ready(function() { 4 | 5 | var data = jsPlumbToolkitDemoSupport.randomGraph(5, 10) 6 | 7 | // get a jsPlumbToolkit instance. 8 | var toolkit = jsPlumbToolkit.newInstance() 9 | 10 | var mainElement = document.querySelector("#jtk-demo-paths"), 11 | canvasElement = mainElement.querySelector(".jtk-demo-canvas"), 12 | miniviewElement = mainElement.querySelector(".miniview"), 13 | controls = document.querySelector(".controls"); 14 | 15 | // path traversal. 16 | var source = null 17 | 18 | var transport = null 19 | var animator 20 | 21 | // define the view. we use the template inferencing mechanism to 22 | // determine that all nodes will be drawn using the template `jtk-template-default`, 23 | // but we supply some information about edges. Note the overlays: on the default edge, 24 | // which means on every Edge, we have an arrow at location 1. On 'bidirectional' edges 25 | // we have an arrow at location 0 also. Two of our edges - [1-4] and [6-2] are marked 26 | // as being `directed:false` (for the graph to use) and `type:"bidirectional"` (for the 27 | // renderer to use). 28 | var view = { 29 | edges: { 30 | "default": { 31 | overlays: [ 32 | {type:"Arrow", options:{ fill: "#89bcde", width: 10, length: 10, location:1 } } 33 | ] 34 | }, 35 | "bidirectional":{ 36 | overlays: [ 37 | {type:"Arrow", options:{ fill: "#89bcde", width: 10, length: 10, location:0, direction:-1 } } 38 | ] 39 | } 40 | }, 41 | nodes:{ 42 | "default":{ 43 | events: { 44 | "tap":function(params) { 45 | // on node click... 46 | if (source == null) { 47 | //... either set the current path source. here we also add a class 48 | // so you can see its selected. 49 | source = params 50 | renderer.addClass(source.el, "jtk-animate-source") 51 | } 52 | else { 53 | 54 | if (transport != null) { 55 | transport.cancel() 56 | } 57 | 58 | // ...or trace a path from the current source to the clicked node. 59 | transport = animator.tracePath({ 60 | source:source.obj, 61 | target:params.obj, 62 | overlay:{ 63 | type:"Diamond", 64 | options:{ 65 | width:15, 66 | length:15, 67 | fill: "#89bcde" 68 | } 69 | }, 70 | options: { 71 | speed: 120 72 | }, 73 | listener: stateChange 74 | }) 75 | // cleanup the source for the next one. 76 | renderer.removeClass(source.el, "jtk-animate-source") 77 | source = null 78 | 79 | if (transport == null) { 80 | alert("No path found!") 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | // load the data, 90 | toolkit.load({type: "json", data: data}) 91 | 92 | // and then render it to "demo" with a "Spring" (force directed) layout. 93 | // supply it with some defaults for jsPlumb 94 | var renderer = toolkit.render(canvasElement, { 95 | view:view, 96 | layout: { 97 | type: "ForceDirected" 98 | }, 99 | plugins:[ 100 | { 101 | type:"miniview", 102 | options:{ 103 | container:miniviewElement 104 | } 105 | }, 106 | { 107 | type:"lasso", 108 | options:{ 109 | filter: ".controls, .controls *, .miniview, .miniview *" 110 | } 111 | } 112 | ], 113 | dragOptions: { 114 | filter: ".delete *, .add *" 115 | }, 116 | events: { 117 | "canvasClick": function(e) { 118 | toolkit.clearSelection() 119 | }, 120 | "modeChanged": function(mode) { 121 | renderer.removeClass(controls.querySelectorAll("[mode]"), "selected-mode"); 122 | renderer.addClass(controls.querySelectorAll("[mode='" + mode + "']"), "selected-mode"); 123 | } 124 | }, 125 | defaults: { 126 | anchor:"Continuous", 127 | connector: { type:"Straight", options:{ cssClass: "connectorClass", hoverClass: "connectorHoverClass" } }, 128 | endpoint: "Blank" 129 | }, 130 | consumeRightClick:false 131 | }) 132 | 133 | // get an animator instance to use 134 | animator = new jsPlumbToolkit.SurfaceAnimator(renderer) 135 | 136 | // pan mode/select mode 137 | renderer.on(controls, "tap", "[mode]", function(e) { 138 | renderer.setMode((e.target).getAttribute("mode")) 139 | }) 140 | 141 | // on home button click, zoom content to fit. 142 | renderer.on(controls, "tap", "[reset]", function(e) { 143 | toolkit.clearSelection() 144 | renderer.zoomToFit() 145 | }) 146 | 147 | // transport controls 148 | 149 | function stateChange(state) { 150 | controls.setAttribute("state", state) 151 | if (state === "stopped") { 152 | transport = null 153 | } 154 | } 155 | 156 | renderer.on(controls.querySelectorAll(".transport"), 'tap', function(e) { 157 | const action = (e.target).getAttribute("action") 158 | if (transport != null) { 159 | transport[action]() 160 | } 161 | }) 162 | 163 | }) 164 | 165 | -------------------------------------------------------------------------------- /src/active-filtering/es5/demo.js: -------------------------------------------------------------------------------- 1 | jsPlumbToolkit.ready(function() { 2 | 3 | 4 | var CLASS_SELECTED_MODE = "selected-mode" 5 | var SELECTOR_SELECTED_MODE = "." + CLASS_SELECTED_MODE 6 | var CLASS_HIGHLIGHT = "hl" 7 | 8 | var toolkit = jsPlumbToolkit.newInstance({ 9 | portDataProperty:"items", 10 | beforeConnect:function(source, target) { 11 | // ignore node->node connections; our UI is not configured to produce them. we could catch it and 12 | // return false, though, which would ensure that nodes could not be connected programmatically. 13 | if (jsPlumbToolkit.isPort(source) && jsPlumbToolkit.isPort(target)) { 14 | 15 | // cannot create loopback connections 16 | if (source === target) { 17 | return false 18 | } 19 | 20 | // cannot connect to Ports on the same Node as the Edge source 21 | if (source.getParent() === target.getParent()) { 22 | return false 23 | } 24 | 25 | const sourceData = source.data.entries, 26 | targetData = target.data.entries 27 | 28 | // attempt to match animals 29 | for (let i = 0; i < sourceData.length; i++) { 30 | if (targetData.indexOf(sourceData[i]) !== -1) { 31 | return true 32 | } 33 | } 34 | return false 35 | } 36 | } 37 | }); 38 | 39 | const mainElement = document.querySelector("#jtk-demo-connectivity"), 40 | canvasElement = mainElement.querySelector(".jtk-demo-canvas"), 41 | miniviewElement = mainElement.querySelector(".miniview") 42 | 43 | // ----------------------- this code is the random node generator. it's just for this demo -------------------------------------- 44 | 45 | var words = [ "CAT", "DOG", "COW", "HORSE", "DUCK", "HEN" ] 46 | 47 | var randomPort = function(index) { 48 | const out = [], map = {} 49 | function _one() { 50 | let a, done = false 51 | while (!done) { 52 | a = words[Math.floor(Math.random() * words.length)] 53 | done = map[a] !== true 54 | map[a] = true 55 | } 56 | return a 57 | } 58 | out.push(_one()) 59 | out.push(_one()) 60 | return { entries:out, index:index, id:jsPlumbToolkit.uuid() } 61 | }; 62 | 63 | var newNode = function() { 64 | const groupCount = Math.floor(Math.random() * 3) + 1, 65 | data = { 66 | id:jsPlumbToolkit.uuid(), 67 | items:[] 68 | } 69 | 70 | for (var i = 0; i < groupCount; i++) { 71 | data.items.push(randomPort(i)) 72 | } 73 | 74 | return toolkit.addNode(data) 75 | }; 76 | 77 | // ---------------------------- / end random node generator --------------------------------------------- 78 | 79 | // initial dataset consists of 5 random nodes. 80 | var nodeCount = 5; 81 | for (let i = 0; i < nodeCount;i++) { 82 | newNode() 83 | } 84 | 85 | var view = { 86 | nodes: { 87 | "default": { 88 | templateId: "tmplNode" 89 | } 90 | }, 91 | edges: { 92 | "default": { 93 | connector: { type:jsPlumbToolkit.StateMachineConnector.type, options:{ curviness: 10 } }, 94 | endpoint: { type:jsPlumbToolkit.DotEndpoint.type, options:{ radius: 10 } }, 95 | anchor: { type:jsPlumbToolkit.AnchorLocations.Continuous, options:{ faces:["left", "right"]} } 96 | } 97 | } 98 | }; 99 | 100 | var renderer = toolkit.render(canvasElement, { 101 | zoomToFit: true, 102 | view: view, 103 | layout: { 104 | type: jsPlumbToolkit.ForceDirectedLayout.type 105 | }, 106 | plugins:[ 107 | { 108 | type:jsPlumbToolkit.MiniviewPlugin.type, 109 | options:{ 110 | container:miniviewElement 111 | } 112 | }, 113 | jsPlumbToolkit.ActiveFilteringPlugin.type, 114 | { 115 | type:jsPlumbToolkit.LassoPlugin.type, 116 | options:{lassoFilter: ".controls, .controls *, .miniview, .miniview *"} 117 | } 118 | ], 119 | events: { 120 | "canvasClick": function(e) { 121 | toolkit.clearSelection() 122 | }, 123 | "modeChanged": function(mode) { 124 | renderer.removeClass(document.querySelector(SELECTOR_SELECTED_MODE), CLASS_SELECTED_MODE) 125 | renderer.addClass(document.querySelector("[mode='" + mode + "']"), CLASS_SELECTED_MODE) 126 | } 127 | }, 128 | consumeRightClick:false, 129 | // disable dragging from anywhere in the individual animal elements (drag can only be done via the header) 130 | dragOptions:{ 131 | filter:"[data-jtk-port], [data-jtk-port] *" 132 | }, 133 | templateMacros:{ 134 | id:function(data) { return data.id.substring(0, 5) }, 135 | entryNames:function(data) { return data.entries.join(' ') } 136 | } 137 | }) 138 | 139 | // pan mode/select mode 140 | renderer.on(mainElement, jsPlumbToolkit.EVENT_CLICK, "[mode]", function(e, el) { 141 | renderer.setMode(el.getAttribute("mode")) 142 | }) 143 | 144 | // on home button tap, zoom content to fit. 145 | renderer.on(mainElement, jsPlumbToolkit.EVENT_CLICK, "[reset]", function() { 146 | toolkit.clearSelection() 147 | renderer.zoomToFit() 148 | }) 149 | 150 | // 151 | // assign a class to a new node which brings the user's attention to it. then a little while later, 152 | // take it off. 153 | // 154 | function flash(el) { 155 | renderer.addClass(el, CLASS_HIGHLIGHT) 156 | setTimeout(function() { 157 | renderer.removeClass(el, CLASS_HIGHLIGHT) 158 | }, 1950) 159 | } 160 | 161 | // on add node button, add a new node, zoom the display, flash the new element. 162 | renderer.on(mainElement, jsPlumbToolkit.EVENT_CLICK, "[add]", () => { 163 | const node = newNode() 164 | renderer.zoomToFit() 165 | flash(renderer.getRenderedElement(node)) 166 | }); 167 | 168 | }) 169 | -------------------------------------------------------------------------------- /src/active-filtering/es6/demo.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | EVENT_CLICK, 4 | EVENT_CANVAS_CLICK, 5 | EVENT_SURFACE_MODE_CHANGED, 6 | DEFAULT, AnchorLocations, 7 | DotEndpoint, 8 | ready, 9 | newInstance, 10 | isPort, 11 | uuid, 12 | ForceDirectedLayout, 13 | MiniviewPlugin, 14 | ActiveFilteringPlugin, 15 | LassoPlugin, StateMachineConnector 16 | } from "@jsplumbtoolkit/browser-ui" 17 | 18 | const CLASS_SELECTED_MODE = "selected-mode" 19 | const SELECTOR_SELECTED_MODE = "." + CLASS_SELECTED_MODE 20 | const CLASS_HIGHLIGHT = "hl" 21 | 22 | ready(() =>{ 23 | 24 | const toolkit = newInstance({ 25 | portDataProperty:"items", 26 | beforeConnect:(source, target) => { 27 | // ignore node->node connections; our UI is not configured to produce them. we could catch it and 28 | // return false, though, which would ensure that nodes could not be connected programmatically. 29 | if (isPort(source) && isPort(target)) { 30 | 31 | // cannot create loopback connections 32 | if (source === target) { 33 | return false 34 | } 35 | 36 | // cannot connect to Ports on the same Node as the Edge source 37 | if (source.getParent() === target.getParent()) { 38 | return false 39 | } 40 | 41 | const sourceData = source.data.entries, 42 | targetData = target.data.entries 43 | 44 | // attempt to match animals 45 | for (let i = 0; i < sourceData.length; i++) { 46 | if (targetData.indexOf(sourceData[i]) !== -1) { 47 | return true 48 | } 49 | } 50 | return false 51 | } 52 | } 53 | }); 54 | 55 | const mainElement = document.querySelector("#jtk-demo-connectivity"), 56 | canvasElement = mainElement.querySelector(".jtk-demo-canvas"), 57 | miniviewElement = mainElement.querySelector(".miniview") 58 | 59 | // ----------------------- this code is the random node generator. it's just for this demo -------------------------------------- 60 | 61 | const words = [ "CAT", "DOG", "COW", "HORSE", "DUCK", "HEN" ] 62 | 63 | const randomPort = (index) => { 64 | const out = [], map = {} 65 | function _one() { 66 | let a, done = false 67 | while (!done) { 68 | a = words[Math.floor(Math.random() * words.length)] 69 | done = map[a] !== true 70 | map[a] = true 71 | } 72 | return a 73 | } 74 | out.push(_one()) 75 | out.push(_one()) 76 | return { entries:out, index:index, id:uuid() } 77 | }; 78 | 79 | const newNode = () => { 80 | const groupCount = Math.floor(Math.random() * 3) + 1, 81 | data = { 82 | id:uuid(), 83 | items:[] 84 | } 85 | 86 | for (let i = 0; i < groupCount; i++) { 87 | data.items.push(randomPort(i)) 88 | } 89 | 90 | return toolkit.addNode(data) 91 | } 92 | 93 | // ---------------------------- / end random node generator --------------------------------------------- 94 | 95 | // initial dataset consists of 5 random nodes. 96 | const nodeCount = 5; 97 | for (let i = 0; i < nodeCount;i++) { 98 | newNode() 99 | } 100 | 101 | const view = { 102 | nodes: { 103 | [DEFAULT]: { 104 | templateId: "tmplNode" 105 | } 106 | }, 107 | edges: { 108 | [DEFAULT]: { 109 | connector: { type:StateMachineConnector.type, options:{ curviness: 10 } }, 110 | endpoint: { type:DotEndpoint.type, options:{ radius: 10 } }, 111 | anchor: { type:AnchorLocations.Continuous, options:{ faces:["left", "right"]} } 112 | } 113 | } 114 | }; 115 | 116 | const renderer = toolkit.render(canvasElement, { 117 | zoomToFit: true, 118 | view: view, 119 | layout: { 120 | type: ForceDirectedLayout.type 121 | }, 122 | plugins:[ 123 | { 124 | type:MiniviewPlugin.type, 125 | options:{ 126 | container:miniviewElement 127 | } 128 | }, 129 | ActiveFilteringPlugin.type, 130 | { 131 | type:LassoPlugin.type, 132 | options:{lassoFilter: ".controls, .controls *, .miniview, .miniview *"} 133 | } 134 | ], 135 | events: { 136 | [EVENT_CANVAS_CLICK]: (e) => { 137 | toolkit.clearSelection() 138 | }, 139 | [EVENT_SURFACE_MODE_CHANGED]: (mode) => { 140 | renderer.removeClass(document.querySelector(SELECTOR_SELECTED_MODE), CLASS_SELECTED_MODE) 141 | renderer.addClass(document.querySelector("[mode='" + mode + "']"), CLASS_SELECTED_MODE) 142 | } 143 | }, 144 | consumeRightClick:false, 145 | // disable dragging from anywhere in the individual animal elements (drag can only be done via the header) 146 | dragOptions:{ 147 | filter:"[data-jtk-port], [data-jtk-port] *" 148 | }, 149 | templateMacros:{ 150 | id:(data) => data.id.substring(0, 5), 151 | entryNames:(data) => data.entries.join(' ') 152 | } 153 | }) 154 | 155 | // pan mode/select mode 156 | renderer.on(mainElement, EVENT_CLICK, "[mode]", (e, el) => { 157 | renderer.setMode(el.getAttribute("mode")) 158 | }) 159 | 160 | // on home button tap, zoom content to fit. 161 | renderer.on(mainElement, EVENT_CLICK, "[reset]", () => { 162 | toolkit.clearSelection() 163 | renderer.zoomToFit() 164 | }) 165 | 166 | // 167 | // assign a class to a new node which brings the user's attention to it. then a little while later, 168 | // take it off. 169 | // 170 | function flash(el) { 171 | renderer.addClass(el, CLASS_HIGHLIGHT) 172 | setTimeout(function() { 173 | renderer.removeClass(el, CLASS_HIGHLIGHT) 174 | }, 1950) 175 | } 176 | 177 | // on add node button, add a new node, zoom the display, flash the new element. 178 | renderer.on(mainElement, EVENT_CLICK, "[add]", () => { 179 | const node = newNode() 180 | renderer.zoomToFit() 181 | flash(renderer.getRenderedElement(node)) 182 | }); 183 | }) 184 | -------------------------------------------------------------------------------- /src/active-filtering/ts/demo.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | SurfaceViewOptions, 4 | EVENT_CLICK, 5 | SurfaceMode, 6 | EVENT_CANVAS_CLICK, 7 | EVENT_SURFACE_MODE_CHANGED, 8 | DEFAULT, AnchorLocations, 9 | DotEndpoint, 10 | ready, 11 | newInstance, 12 | BrowserUI, 13 | Surface, 14 | isPort, 15 | Vertex, 16 | uuid, 17 | ObjectData, 18 | ForceDirectedLayout, 19 | MiniviewPlugin, 20 | ActiveFilteringPlugin, 21 | LassoPlugin, StateMachineConnector 22 | } from "@jsplumbtoolkit/browser-ui" 23 | 24 | const CLASS_SELECTED_MODE = "selected-mode" 25 | const SELECTOR_SELECTED_MODE = "." + CLASS_SELECTED_MODE 26 | const CLASS_HIGHLIGHT = "hl" 27 | 28 | ready(() =>{ 29 | 30 | const toolkit:BrowserUI = newInstance({ 31 | portDataProperty:"items", 32 | beforeConnect:(source:Vertex, target:Vertex) => { 33 | // ignore node->node connections; our UI is not configured to produce them. we could catch it and 34 | // return false, though, which would ensure that nodes could not be connected programmatically. 35 | if (isPort(source) && isPort(target)) { 36 | 37 | // cannot create loopback connections 38 | if (source === target) { 39 | return false 40 | } 41 | 42 | // cannot connect to Ports on the same Node as the Edge source 43 | if (source.getParent() === target.getParent()) { 44 | return false 45 | } 46 | 47 | const sourceData = source.data.entries, 48 | targetData = target.data.entries 49 | 50 | // attempt to match animals 51 | for (let i = 0; i < sourceData.length; i++) { 52 | if (targetData.indexOf(sourceData[i]) !== -1) { 53 | return true 54 | } 55 | } 56 | return false 57 | } 58 | } 59 | }); 60 | 61 | const mainElement = document.querySelector("#jtk-demo-connectivity"), 62 | canvasElement = mainElement.querySelector(".jtk-demo-canvas"), 63 | miniviewElement = mainElement.querySelector(".miniview") 64 | 65 | // ----------------------- this code is the random node generator. it's just for this demo -------------------------------------- 66 | 67 | const words = [ "CAT", "DOG", "COW", "HORSE", "DUCK", "HEN" ] 68 | 69 | const randomPort = (index:number) => { 70 | const out = [], map = {} 71 | function _one() { 72 | let a, done = false 73 | while (!done) { 74 | a = words[Math.floor(Math.random() * words.length)] 75 | done = map[a] !== true 76 | map[a] = true 77 | } 78 | return a 79 | } 80 | out.push(_one()) 81 | out.push(_one()) 82 | return { entries:out, index:index, id:uuid() } 83 | }; 84 | 85 | const newNode = () => { 86 | const groupCount = Math.floor(Math.random() * 3) + 1, 87 | data:any = { 88 | id:uuid(), 89 | items:[] 90 | } 91 | 92 | for (let i = 0; i < groupCount; i++) { 93 | data.items.push(randomPort(i)) 94 | } 95 | 96 | return toolkit.addNode(data) 97 | } 98 | 99 | // ---------------------------- / end random node generator --------------------------------------------- 100 | 101 | // initial dataset consists of 5 random nodes. 102 | const nodeCount = 5; 103 | for (let i = 0; i < nodeCount;i++) { 104 | newNode() 105 | } 106 | 107 | const view:SurfaceViewOptions = { 108 | nodes: { 109 | [DEFAULT]: { 110 | templateId: "tmplNode" 111 | } 112 | }, 113 | edges: { 114 | [DEFAULT]: { 115 | connector: { type:StateMachineConnector.type, options:{ curviness: 10 } }, 116 | endpoint: { type:DotEndpoint.type, options:{ radius: 10 } }, 117 | anchor: { type:AnchorLocations.Continuous, options:{ faces:["left", "right"]} } 118 | } 119 | } 120 | }; 121 | 122 | const renderer:Surface = toolkit.render(canvasElement, { 123 | zoomToFit: true, 124 | view: view, 125 | layout: { 126 | type: ForceDirectedLayout.type 127 | }, 128 | plugins:[ 129 | { 130 | type:MiniviewPlugin.type, 131 | options:{ 132 | container:miniviewElement 133 | } 134 | }, 135 | ActiveFilteringPlugin.type, 136 | { 137 | type:LassoPlugin.type, 138 | options:{lassoFilter: ".controls, .controls *, .miniview, .miniview *"} 139 | } 140 | ], 141 | events: { 142 | [EVENT_CANVAS_CLICK]: (e:Event) => { 143 | toolkit.clearSelection() 144 | }, 145 | [EVENT_SURFACE_MODE_CHANGED]: (mode:string) => { 146 | renderer.removeClass(document.querySelector(SELECTOR_SELECTED_MODE), CLASS_SELECTED_MODE) 147 | renderer.addClass(document.querySelector("[mode='" + mode + "']"), CLASS_SELECTED_MODE) 148 | } 149 | }, 150 | consumeRightClick:false, 151 | // disable dragging from anywhere in the individual animal elements (drag can only be done via the header) 152 | dragOptions:{ 153 | filter:"[data-jtk-port], [data-jtk-port] *" 154 | }, 155 | templateMacros:{ 156 | id:(data:ObjectData) => data.id.substring(0, 5), 157 | entryNames:(data:ObjectData) => data.entries.join(' ') 158 | } 159 | }) 160 | 161 | // pan mode/select mode 162 | renderer.on(mainElement, EVENT_CLICK, "[mode]", (e:Event, el:HTMLElement) => { 163 | renderer.setMode(el.getAttribute("mode") as SurfaceMode) 164 | }) 165 | 166 | // on home button tap, zoom content to fit. 167 | renderer.on(mainElement, EVENT_CLICK, "[reset]", () => { 168 | toolkit.clearSelection() 169 | renderer.zoomToFit() 170 | }) 171 | 172 | // 173 | // assign a class to a new node which brings the user's attention to it. then a little while later, 174 | // take it off. 175 | // 176 | function flash(el:Element) { 177 | renderer.addClass(el, CLASS_HIGHLIGHT) 178 | setTimeout(function() { 179 | renderer.removeClass(el, CLASS_HIGHLIGHT) 180 | }, 1950) 181 | } 182 | 183 | // on add node button, add a new node, zoom the display, flash the new element. 184 | renderer.on(mainElement, EVENT_CLICK, "[add]", () => { 185 | const node = newNode() 186 | renderer.zoomToFit() 187 | flash(renderer.getRenderedElement(node)) 188 | }); 189 | }) 190 | -------------------------------------------------------------------------------- /src/paths/es6/demo.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | EVENT_TAP, 4 | EVENT_CANVAS_CLICK, 5 | EVENT_SURFACE_MODE_CHANGED, 6 | SurfaceMode, 7 | DiamondOverlay, 8 | BlankEndpoint, 9 | StraightConnector, 10 | ArrowOverlay, 11 | DEFAULT, 12 | ready, 13 | newInstance, 14 | ForceDirectedLayout, 15 | MiniviewPlugin, 16 | LassoPlugin, 17 | PathTransport, SurfaceAnimator} from "@jsplumbtoolkit/browser-ui" 18 | 19 | import { randomGraph } from "jsplumbtoolkit-demo-support" 20 | 21 | ready(() => { 22 | 23 | const data = randomGraph(5, 10) 24 | 25 | // get a jsPlumbToolkit instance. 26 | const toolkit = newInstance() 27 | 28 | const mainElement = document.querySelector("#jtk-demo-paths"), 29 | canvasElement = mainElement.querySelector(".jtk-demo-canvas"), 30 | miniviewElement = mainElement.querySelector(".miniview"), 31 | controls = document.querySelector(".controls"); 32 | 33 | // path traversal. 34 | let source = null 35 | 36 | let transport = null 37 | let animator 38 | 39 | // define the view. we use the template inferencing mechanism to 40 | // determine that all nodes will be drawn using the template `jtk-template-default`, 41 | // but we supply some information about edges. Note the overlays: on the default edge, 42 | // which means on every Edge, we have an arrow at location 1. On 'bidirectional' edges 43 | // we have an arrow at location 0 also. Two of our edges - [1-4] and [6-2] are marked 44 | // as being `directed:false` (for the graph to use) and `type:"bidirectional"` (for the 45 | // renderer to use). 46 | const view = { 47 | edges: { 48 | [DEFAULT]: { 49 | //paintStyle: { lineWidth: 1, stroke: '#89bcde' }, 50 | overlays: [ 51 | {type:ArrowOverlay.type, options:{ fill: "#89bcde", width: 10, length: 10, location:1 } } 52 | ] 53 | }, 54 | "bidirectional":{ 55 | //parent:"default", 56 | overlays: [ 57 | {type:ArrowOverlay.type, options:{ fill: "#89bcde", width: 10, length: 10, location:0, direction:-1 } } 58 | ] 59 | } 60 | }, 61 | nodes:{ 62 | [DEFAULT]:{ 63 | events: { 64 | [EVENT_TAP]:(params) => { 65 | // on node click... 66 | if (source == null) { 67 | //... either set the current path source. here we also add a class 68 | // so you can see its selected. 69 | source = params 70 | renderer.addClass(source.el, "jtk-animate-source") 71 | } 72 | else { 73 | 74 | if (transport != null) { 75 | transport.cancel() 76 | } 77 | 78 | // ...or trace a path from the current source to the clicked node. 79 | transport = animator.tracePath({ 80 | source:source.obj, 81 | target:params.obj, 82 | overlay:{ 83 | type:DiamondOverlay.type, 84 | options:{ 85 | width:15, 86 | length:15, 87 | fill: "#89bcde" 88 | } 89 | }, 90 | options: { 91 | speed: 120 92 | }, 93 | listener: stateChange 94 | }) 95 | // cleanup the source for the next one. 96 | renderer.removeClass(source.el, "jtk-animate-source") 97 | source = null 98 | 99 | if (transport == null) { 100 | alert("No path found!") 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | // load the data, 110 | toolkit.load({type: "json", data: data}) 111 | 112 | // and then render it to "demo" with a "Spring" (force directed) layout. 113 | // supply it with some defaults for jsPlumb 114 | const renderer = toolkit.render(canvasElement, { 115 | view:view, 116 | layout: { 117 | type: ForceDirectedLayout.type, 118 | options: { 119 | padding: {x: 30, y: 30} 120 | } 121 | }, 122 | plugins:[ 123 | { 124 | type:MiniviewPlugin.type, 125 | options:{ 126 | container:miniviewElement 127 | } 128 | }, 129 | { 130 | type:LassoPlugin.type, 131 | options:{ 132 | filter: ".controls, .controls *, .miniview, .miniview *" 133 | } 134 | } 135 | ], 136 | dragOptions: { 137 | filter: ".delete *, .add *" 138 | }, 139 | events: { 140 | [EVENT_CANVAS_CLICK]: (e) => { 141 | toolkit.clearSelection() 142 | }, 143 | [EVENT_SURFACE_MODE_CHANGED]: (mode) => { 144 | renderer.removeClass(controls.querySelectorAll("[mode]"), "selected-mode"); 145 | renderer.addClass(controls.querySelectorAll("[mode='" + mode + "']"), "selected-mode"); 146 | } 147 | }, 148 | defaults: { 149 | anchor:"Continuous", 150 | connector: { type:StraightConnector.type, options:{ cssClass: "connectorClass", hoverClass: "connectorHoverClass" } }, 151 | endpoint: BlankEndpoint.type 152 | }, 153 | consumeRightClick:false 154 | }) 155 | 156 | // get an animator instance to use 157 | animator = new SurfaceAnimator(renderer) 158 | 159 | // pan mode/select mode 160 | renderer.on(controls, EVENT_TAP, "[mode]", (e) => { 161 | renderer.setMode(e.target.getAttribute("mode")) 162 | }) 163 | 164 | // on home button click, zoom content to fit. 165 | renderer.on(controls, EVENT_TAP, "[reset]", (e) => { 166 | toolkit.clearSelection() 167 | renderer.zoomToFit() 168 | }) 169 | 170 | // transport controls 171 | 172 | function stateChange(state) { 173 | controls.setAttribute("state", state) 174 | if (state === "stopped") { 175 | transport = null 176 | } 177 | } 178 | 179 | renderer.on(controls.querySelectorAll(".transport"), EVENT_TAP, (e) => { 180 | const action = e.target.getAttribute("action") 181 | if (transport != null) { 182 | transport[action]() 183 | } 184 | }) 185 | 186 | }) 187 | 188 | -------------------------------------------------------------------------------- /src/paths/ts/demo.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | EVENT_TAP, 4 | EVENT_CANVAS_CLICK, 5 | EVENT_SURFACE_MODE_CHANGED, 6 | SurfaceMode, 7 | DiamondOverlay, 8 | BlankEndpoint, 9 | StraightConnector, 10 | ArrowOverlay, 11 | DEFAULT, 12 | ready, 13 | newInstance, 14 | ForceDirectedLayout, 15 | MiniviewPlugin, 16 | LassoPlugin, 17 | PathTransport, SurfaceAnimator} from "@jsplumbtoolkit/browser-ui" 18 | 19 | import { randomGraph } from "jsplumbtoolkit-demo-support" 20 | 21 | ready(() => { 22 | 23 | const data = randomGraph(5, 10) 24 | 25 | // get a jsPlumbToolkit instance. 26 | const toolkit = newInstance() 27 | 28 | const mainElement = document.querySelector("#jtk-demo-paths"), 29 | canvasElement = mainElement.querySelector(".jtk-demo-canvas"), 30 | miniviewElement = mainElement.querySelector(".miniview"), 31 | controls = document.querySelector(".controls"); 32 | 33 | // path traversal. 34 | let source:any = null 35 | 36 | let transport:PathTransport = null 37 | let animator:SurfaceAnimator 38 | 39 | // define the view. we use the template inferencing mechanism to 40 | // determine that all nodes will be drawn using the template `jtk-template-default`, 41 | // but we supply some information about edges. Note the overlays: on the default edge, 42 | // which means on every Edge, we have an arrow at location 1. On 'bidirectional' edges 43 | // we have an arrow at location 0 also. Two of our edges - [1-4] and [6-2] are marked 44 | // as being `directed:false` (for the graph to use) and `type:"bidirectional"` (for the 45 | // renderer to use). 46 | const view = { 47 | edges: { 48 | [DEFAULT]: { 49 | //paintStyle: { lineWidth: 1, stroke: '#89bcde' }, 50 | overlays: [ 51 | {type:ArrowOverlay.type, options:{ fill: "#89bcde", width: 10, length: 10, location:1 } } 52 | ] 53 | }, 54 | "bidirectional":{ 55 | //parent:"default", 56 | overlays: [ 57 | {type:ArrowOverlay.type, options:{ fill: "#89bcde", width: 10, length: 10, location:0, direction:-1 } } 58 | ] 59 | } 60 | }, 61 | nodes:{ 62 | [DEFAULT]:{ 63 | events: { 64 | [EVENT_TAP]:(params:any) => { 65 | // on node click... 66 | if (source == null) { 67 | //... either set the current path source. here we also add a class 68 | // so you can see its selected. 69 | source = params 70 | renderer.addClass(source.el, "jtk-animate-source") 71 | } 72 | else { 73 | 74 | if (transport != null) { 75 | transport.cancel() 76 | } 77 | 78 | // ...or trace a path from the current source to the clicked node. 79 | transport = animator.tracePath({ 80 | source:source.obj, 81 | target:params.obj, 82 | overlay:{ 83 | type:DiamondOverlay.type, 84 | options:{ 85 | width:15, 86 | length:15, 87 | fill: "#89bcde" 88 | } 89 | }, 90 | options: { 91 | speed: 120 92 | }, 93 | listener: stateChange 94 | }) 95 | // cleanup the source for the next one. 96 | renderer.removeClass(source.el, "jtk-animate-source") 97 | source = null 98 | 99 | if (transport == null) { 100 | alert("No path found!") 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | // load the data, 110 | toolkit.load({type: "json", data: data}) 111 | 112 | // and then render it to "demo" with a "Spring" (force directed) layout. 113 | // supply it with some defaults for jsPlumb 114 | const renderer = toolkit.render(canvasElement, { 115 | view:view, 116 | layout: { 117 | type: ForceDirectedLayout.type, 118 | options: { 119 | padding: {x: 30, y: 30} 120 | } 121 | }, 122 | plugins:[ 123 | { 124 | type:MiniviewPlugin.type, 125 | options:{ 126 | container:miniviewElement 127 | } 128 | }, 129 | { 130 | type:LassoPlugin.type, 131 | options:{ 132 | filter: ".controls, .controls *, .miniview, .miniview *" 133 | } 134 | } 135 | ], 136 | dragOptions: { 137 | filter: ".delete *, .add *" 138 | }, 139 | events: { 140 | [EVENT_CANVAS_CLICK]: (e:Event) => { 141 | toolkit.clearSelection() 142 | }, 143 | [EVENT_SURFACE_MODE_CHANGED]: (mode:SurfaceMode) => { 144 | renderer.removeClass(controls.querySelectorAll("[mode]"), "selected-mode"); 145 | renderer.addClass(controls.querySelectorAll("[mode='" + mode + "']"), "selected-mode"); 146 | } 147 | }, 148 | defaults: { 149 | anchor:"Continuous", 150 | connector: { type:StraightConnector.type, options:{ cssClass: "connectorClass", hoverClass: "connectorHoverClass" } }, 151 | endpoint: BlankEndpoint.type 152 | }, 153 | consumeRightClick:false 154 | }) 155 | 156 | // get an animator instance to use 157 | animator = new SurfaceAnimator(renderer) 158 | 159 | // pan mode/select mode 160 | renderer.on(controls, EVENT_TAP, "[mode]", (e:Event) => { 161 | renderer.setMode((e.target as any).getAttribute("mode")) 162 | }) 163 | 164 | // on home button click, zoom content to fit. 165 | renderer.on(controls, EVENT_TAP, "[reset]", (e:Event) => { 166 | toolkit.clearSelection() 167 | renderer.zoomToFit() 168 | }) 169 | 170 | // transport controls 171 | 172 | function stateChange(state:string) { 173 | controls.setAttribute("state", state) 174 | if (state === "stopped") { 175 | transport = null 176 | } 177 | } 178 | 179 | renderer.on(controls.querySelectorAll(".transport"), EVENT_TAP, (e:Event) => { 180 | const action = (e.target as any).getAttribute("action") 181 | if (transport != null) { 182 | transport[action]() 183 | } 184 | }) 185 | 186 | }) 187 | 188 | -------------------------------------------------------------------------------- /scripts/gatlight.js: -------------------------------------------------------------------------------- 1 | /** 2 | * File ops. 3 | */ 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | 7 | const ANYTHING_PATTERN = "**/*"; 8 | 9 | function PatternFilter(pattern, isDirectory) { 10 | 11 | return (candidate) => { 12 | //console.log(pattern, candidate) 13 | return true; 14 | } 15 | } 16 | 17 | function ExtensionFilter(extension) { 18 | return (candidate) => candidate.endsWith("." + extension) 19 | } 20 | 21 | function isDirectory(path) { 22 | return fs.lstatSync(path).isDirectory() 23 | } 24 | 25 | function createFilter(options) { 26 | options = options || {}; 27 | return options.filter || (options.extension ? new ExtensionFilter(options.extension) : new PatternFilter(options.pattern || ANYTHING_PATTERN)); 28 | } 29 | 30 | /** 31 | * clean files from a directory, by default recursing into (and removing) subdirectories. 32 | * @param directory 33 | * @param options 34 | */ 35 | function clean (directory, options) { 36 | 37 | if (fs.existsSync(directory)) { 38 | 39 | options = options || {}; 40 | let filter = createFilter(options); 41 | let recurse = options.recurse !== false; 42 | 43 | let _one = (dirPath, alsoRemoveDirectory) => { 44 | 45 | fs.readdirSync(dirPath).forEach(file => { 46 | let p = path.join(dirPath, file); 47 | 48 | if (isDirectory(p)) { 49 | if (recurse && filter(file, true)) { 50 | _one(p, true); 51 | } 52 | } else { 53 | if (filter(file, false)) { 54 | fs.unlinkSync(p); 55 | } 56 | } 57 | }); 58 | 59 | if (alsoRemoveDirectory) { 60 | fs.rmdirSync(dirPath) 61 | } 62 | }; 63 | 64 | _one(directory); 65 | 66 | if (options.andRemove) { 67 | fs.rmdirSync(directory); 68 | } 69 | } 70 | 71 | } 72 | 73 | function rmdir(directory) { 74 | clean(directory, {andRemove:true}); 75 | } 76 | 77 | function exists(file) { 78 | return fs.existsSync(file); 79 | } 80 | 81 | function copyDirectory (input, output, options) { 82 | 83 | options = options || {}; 84 | let recurse = options.recurse !== false; 85 | let includeHidden = options.hidden === true; 86 | 87 | if (fs.existsSync(output)) { 88 | clean(output, { andRemove:true }); 89 | } 90 | 91 | let filter = createFilter(options); 92 | 93 | let _one = (inputDir, outputDir) => { 94 | 95 | fs.mkdirSync(outputDir); 96 | 97 | fs.readdirSync(inputDir).forEach(file => { 98 | 99 | if (filter(file)) { 100 | 101 | let inputPath = path.join(inputDir, file); 102 | if (isDirectory(inputPath)) { 103 | if (recurse) { 104 | _one(inputPath, path.join(outputDir, file)); 105 | } 106 | } else { 107 | if (includeHidden === true || file.indexOf(".") !== 0) { 108 | let outputPath = path.join(outputDir, file); 109 | console.log("gatlight: copying : " + inputPath + " to " + outputPath); 110 | try { 111 | fs.copyFileSync(inputPath, outputPath); 112 | } catch (e) { 113 | console.log(e) 114 | } 115 | } else { 116 | console.log("gatlight: excluding " + inputPath + " due to a dot") 117 | } 118 | } 119 | } else { 120 | console.log("Filtered " + file + " from copy"); 121 | } 122 | 123 | }); 124 | }; 125 | 126 | _one(input, output); 127 | 128 | 129 | } 130 | 131 | function copy (source, target, options) { 132 | 133 | 134 | options = options || {}; 135 | let filter = createFilter(options); 136 | 137 | // for now this is just a straight single file copy 138 | console.log("gatlight: copying : " + source + " to " + target); 139 | fs.copyFileSync(source, target); 140 | } 141 | 142 | function copyAll (sourceDirectory, targetDirectory, options) { 143 | 144 | ls(sourceDirectory, options).forEach((f) => { 145 | fs.copyFileSync(sourceDirectory + "/" + f, targetDirectory + "/" + f); 146 | }) 147 | } 148 | 149 | function copyAllRecursively (sourceDirectory, targetDirectory, options) { 150 | 151 | options = options || {} 152 | let filter = createFilter(options); 153 | const entries = fs.readdirSync(sourceDirectory) 154 | entries.forEach((f) => { 155 | const src = `${sourceDirectory}/${f}` 156 | const target = `${targetDirectory}/${f}` 157 | if (isDirectory(src)) { 158 | mkdir(target, true) 159 | copyAllRecursively(src, target, options) 160 | } else { 161 | if (filter(f)) { 162 | fs.copyFileSync(src, target); 163 | } 164 | } 165 | }) 166 | } 167 | 168 | function mkdir(target, silently) { 169 | 170 | if (Array.isArray(target)) { 171 | target = target.join("/"); 172 | } 173 | 174 | try { 175 | fs.mkdirSync(target); 176 | } 177 | catch (e) { 178 | if (!silently) { 179 | console.log("Could not make directory `" + target + "` - perhaps it exists already. Not failing.") 180 | } 181 | } 182 | } 183 | 184 | function mkdirs(target) { 185 | const components = target.split("/"); 186 | const stub = components.slice(0,1); 187 | mkdir(stub, true); 188 | for (let i = 1; i < components.length; i++) { 189 | stub.push(components[i]); 190 | mkdir(stub.join("/"), true); 191 | } 192 | } 193 | 194 | /** 195 | * Make a directory and then clean it - because if it existed already it could have stuff in it. 196 | * @param target 197 | */ 198 | function mkdirClean(target) { 199 | mkdir(target); 200 | clean(target); 201 | 202 | } 203 | 204 | function ls(directory, options) { 205 | 206 | options = options || {}; 207 | let filter = createFilter(options); 208 | 209 | return fs.readdirSync(directory).filter(filter); 210 | } 211 | 212 | function lsDirectories(directory) { 213 | return ls(directory, { 214 | filter:(candidate) => isDirectory(`${directory}/${candidate}`) 215 | }) 216 | } 217 | 218 | function lsAll(directories, options) { 219 | 220 | options = options || {}; 221 | let filter = createFilter(options); 222 | let out = []; 223 | for (let i = 0; i < directories.length; i++) { 224 | out.push.apply(out, fs.readdirSync(directories[i]).filter(filter).map(f => [f,directories[i]])); 225 | } 226 | 227 | return out; 228 | 229 | } 230 | 231 | function read(fileName, options) { 232 | return fs.readFileSync(fileName, options); 233 | } 234 | 235 | function readString(fileName, encoding) { 236 | return read(fileName, encoding || "UTF-8"); 237 | } 238 | 239 | function write(fileName, content) { 240 | fs.writeFileSync(fileName, content); 241 | } 242 | 243 | function remove(fileName, failOnError) { 244 | try { 245 | fs.unlinkSync(fileName); 246 | } 247 | catch (e) { 248 | if (failOnError) { 249 | throw e; 250 | } 251 | } 252 | } 253 | 254 | function uuid() { 255 | return ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 256 | let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); 257 | return v.toString(16); 258 | })); 259 | } 260 | 261 | exports.copyDirectory = copyDirectory; 262 | exports.clean = clean; 263 | exports.copy = copy; 264 | exports.copyAll = copyAll; 265 | exports.copyAllRecursively = copyAllRecursively; 266 | exports.mkdir = mkdir; 267 | exports.mkdirs = mkdirs; 268 | exports.mkdirClean = mkdirClean; 269 | exports.rmdir = rmdir; 270 | exports.ls = ls; 271 | exports.lsDirectories = lsDirectories; 272 | exports.lsAll = lsAll; 273 | exports.suffixFilter = function(suffix) { return function(file) { return file.endsWith("." + suffix); } } 274 | exports.read = read; 275 | exports.readString = readString; 276 | exports.write = write; 277 | exports.remove = remove; 278 | exports.rm = remove; 279 | exports.uuid = uuid; 280 | exports.exists = exists; 281 | exports.isDirectory = isDirectory; 282 | 283 | -------------------------------------------------------------------------------- /src/layouts/ts/demo.ts: -------------------------------------------------------------------------------- 1 | import {randomHierarchy, randomNode} from "jsplumbtoolkit-demo-support" 2 | import { 3 | StateMachineConnector, 4 | HierarchicalLayout, 5 | UndoRedoUpdateParams, 6 | EVENT_UNDOREDO_UPDATE, 7 | ObjectInfo, 8 | Node, 9 | MiniviewPlugin, 10 | ForceDirectedLayout, 11 | CircularLayout, 12 | EndpointSpec, TRUE, FALSE, AnchorLocations, DEFAULT, BlankEndpoint, DotEndpoint, EVENT_TAP, EVENT_CLICK, EVENT_CANVAS_CLICK, ready, newInstance} from "@jsplumbtoolkit/browser-ui" 13 | 14 | ready(function () { 15 | 16 | const toolkit = newInstance({ 17 | beforeStartDetach:() => { return false } 18 | }) 19 | 20 | const controls = document.querySelector(".controls") 21 | 22 | const view = { 23 | nodes: { 24 | [DEFAULT]: { 25 | templateId: "tmplNode" 26 | } 27 | }, 28 | edges: { 29 | [DEFAULT]: { 30 | connector: { type:StateMachineConnector.type, options:{ curviness: 10 } }, 31 | paintStyle: { strokeWidth: 2, stroke: '#89bcde' }, 32 | endpoints: [ { type:DotEndpoint.type, options:{ radius: 4 } }, BlankEndpoint.type ] as [EndpointSpec, EndpointSpec] 33 | } 34 | } 35 | } 36 | 37 | const mainElement = document.querySelector("#jtk-demo-layouts"), 38 | canvasElement = mainElement.querySelector(".jtk-demo-canvas"), 39 | miniviewElement = mainElement.querySelector(".miniview"), 40 | layoutSelector = document.querySelector("#layout") 41 | 42 | // make a random hierarchy and store how many nodes there are; we will use this when we add new nodes. 43 | const hierarchy = randomHierarchy(3) 44 | 45 | toolkit.load({type: "json", data: hierarchy}) 46 | 47 | const renderer = toolkit.render(canvasElement, { 48 | zoomToFit: true, 49 | view: view, 50 | layout: { 51 | type: HierarchicalLayout.type 52 | }, 53 | events: { 54 | [EVENT_CANVAS_CLICK] :(e:Event) => { 55 | toolkit.clearSelection() 56 | } 57 | }, 58 | defaults: { 59 | anchor: AnchorLocations.Center, 60 | endpointStyle: { fill: "gray" }, 61 | endpointHoverStyle: { fill: "#FF6600" } 62 | }, 63 | refreshLayoutOnEdgeConnect:true, 64 | elementsDraggable:false, 65 | plugins:[ 66 | { 67 | type:MiniviewPlugin.type, 68 | options:{ 69 | container:miniviewElement 70 | } 71 | } 72 | ], 73 | storePositionsInModel:false // by default this is true, and useful. We set it to false in this demonstration to ensure that each layout 74 | // is operating without any previous placement values (the Absolute layout, for instance would just use 75 | // values from the model which had been generated by a previous layout, so you'd see no difference). 76 | }) 77 | 78 | renderer.on(controls, EVENT_TAP, "[undo]", () => { 79 | toolkit.undo() 80 | }) 81 | 82 | renderer.on(controls, EVENT_TAP, "[redo]", () => { 83 | toolkit.redo() 84 | }) 85 | 86 | toolkit.bind(EVENT_UNDOREDO_UPDATE, (state:UndoRedoUpdateParams) => { 87 | controls.setAttribute("can-undo", state.undoCount > 0 ? TRUE : FALSE) 88 | controls.setAttribute("can-redo", state.redoCount > 0 ? TRUE : FALSE) 89 | }) 90 | 91 | // 92 | // use event delegation to attach event handlers to 93 | // remove buttons. This callback finds the related Node and 94 | // then tells the toolkit to delete it. 95 | // 96 | renderer.bindModelEvent(EVENT_TAP, ".delete", (event:Event, target:HTMLElement, info:ObjectInfo) => { 97 | const selection = toolkit.selectDescendants(info.obj, true) 98 | toolkit.transaction(function() { 99 | toolkit.remove(selection) 100 | }) 101 | }) 102 | 103 | // 104 | // use event delegation to attach event handlers to 105 | // add buttons. This callback adds an edge from the given node 106 | // to a newly created node, and then the layout is refreshed. 107 | // 108 | renderer.bindModelEvent(EVENT_TAP, ".add", (event:Event, target:HTMLElement, info:ObjectInfo) => { 109 | // get a random node. 110 | const n = randomNode("node") 111 | 112 | // start a transaction so that the new node and the edge to the existing node are wrapped into a single 113 | // unit of work, and can be undone/redone together. 114 | toolkit.transaction(() => { 115 | // add the node to the toolkit 116 | const newNode = toolkit.addNode(n); 117 | // and add an edge for it from the current node. 118 | toolkit.addEdge({source: info.obj, target: newNode}) 119 | }) 120 | }) 121 | 122 | // on home button tap, zoom content to fit. 123 | renderer.on(mainElement, EVENT_TAP, "[reset]", () => { 124 | renderer.zoomToFit() 125 | }) 126 | 127 | const layoutParams = { 128 | [ForceDirectedLayout.type]:{ 129 | absoluteBacked:false 130 | }, 131 | [HierarchicalLayout.type]:{ 132 | orientation: "horizontal", 133 | padding: {x:100, y:60} 134 | }, 135 | "HierarchicalCompressed":{ 136 | orientation: "horizontal", 137 | padding: {x:30,y:30}, 138 | spacing:"compress" 139 | }, 140 | "HierarchicalAlignStart":{ 141 | orientation: "horizontal", 142 | padding: {x:100, y:60}, 143 | align:"start" 144 | }, 145 | "HierarchicalAlignEnd":{ 146 | orientation: "horizontal", 147 | padding: {x:100, y:60}, 148 | align:"end" 149 | }, 150 | 151 | "HierarchicalVertical":{ 152 | orientation: "vertical", 153 | padding: {x:160, y:60} 154 | }, 155 | 156 | "HierarchicalVerticalAlignStart":{ 157 | orientation: "vertical", 158 | padding: {x:160, y:60}, 159 | align:"start" 160 | }, 161 | "HierarchicalVerticalAlignEnd":{ 162 | orientation: "vertical", 163 | padding: {x:160, y:60}, 164 | align:"end" 165 | }, 166 | 167 | "HierarchicalInverted":{ 168 | orientation: "horizontal", 169 | padding: {x:160, y:60}, 170 | invert:true 171 | }, 172 | "HierarchicalInvertedAlignStart":{ 173 | orientation: "horizontal", 174 | padding: {x:160, y:60}, 175 | invert:true, 176 | align:"start" 177 | }, 178 | "HierarchicalInvertedAlignEnd":{ 179 | orientation: "horizontal", 180 | padding: {x:160, y:60}, 181 | invert:true, 182 | align:"end" 183 | }, 184 | 185 | "HierarchicalVerticalInverted":{ 186 | orientation: "vertical", 187 | padding: {x:160, y:60}, 188 | invert:true 189 | }, 190 | "HierarchicalVerticalInvertedAlignStart":{ 191 | orientation: "vertical", 192 | padding: {x:160, y:60}, 193 | invert:true, 194 | align:"start" 195 | }, 196 | "HierarchicalVerticalInvertedAlignEnd":{ 197 | orientation: "vertical", 198 | padding: {x:160, y:60}, 199 | invert:true, 200 | align:"end" 201 | }, 202 | [CircularLayout.type]:{ 203 | padding:{x:15, y:15} 204 | }, 205 | "CircularCenteredRoot":{ 206 | padding:{x:15, y:15}, 207 | centerRoot:true 208 | } 209 | }; 210 | 211 | // change layout when user picks one from the drop down. 212 | renderer.on(layoutSelector, "change", (e:Event) => { 213 | const sel = (e.target as HTMLSelectElement), 214 | opt = sel.options[sel.selectedIndex], 215 | id = opt.value, 216 | extra = opt.getAttribute("extra") || "", 217 | paramKey = id + extra, 218 | params = layoutParams[paramKey] || {}, 219 | lp = { 220 | type:id, 221 | options:params 222 | } 223 | 224 | renderer.setLayout(lp) 225 | renderer.zoomToFit() 226 | 227 | // JSON cast to any here because the .d.ts has overloaded versions of `stringify` 228 | document.querySelector(".config pre").innerHTML = (JSON as any).stringify(lp, 2, 2) 229 | }) 230 | 231 | renderer.on(document.querySelector("#btnRegenerate"), EVENT_CLICK, () => { 232 | toolkit.clear() 233 | toolkit.load({ 234 | data:randomHierarchy(3) 235 | }) 236 | }) 237 | 238 | renderer.on(document.querySelector("#btnRelayout"), EVENT_CLICK, () => { 239 | renderer.relayout() 240 | }) 241 | }) 242 | -------------------------------------------------------------------------------- /src/groups/ts/demo.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | SurfaceViewOptions, 4 | EVENT_TAP, 5 | EVENT_CLICK, 6 | EVENT_SURFACE_MODE_CHANGED, 7 | EVENT_CANVAS_CLICK, 8 | BlankEndpoint, 9 | ArrowOverlay, 10 | AnchorLocations, 11 | DEFAULT, 12 | ready, 13 | newInstance, 14 | Group, 15 | Node, 16 | ObjectInfo, 17 | EVENT_GROUP_ADDED, 18 | AbsoluteLayout, 19 | EVENT_UNDOREDO_UPDATE, 20 | UndoRedoUpdateParams, 21 | createSurfaceDropManager, 22 | ForceDirectedLayout, 23 | MiniviewPlugin, 24 | StateMachineConnector, 25 | LassoPlugin} from "@jsplumbtoolkit/browser-ui" 26 | 27 | ready(() => { 28 | 29 | 30 | // jsPlumbToolkit code. 31 | 32 | // 1. declare some JSON data for the graph. This syntax is a JSON equivalent of GraphML. 33 | const data = { 34 | "groups":[ 35 | {"id":"one", "title":"Group 1", "left":100, top:50 }, 36 | {"id":"two", "title":"Group 2", "left":750, top:250, type:"constrained" }, 37 | {"id":"three", "title":"Nested Group", "left":50, "top":50, "group":"two" } 38 | ], 39 | "nodes": [ 40 | { "id": "window1", "name": "1", "left": 10, "top": 20, group:"one" }, 41 | { "id": "window2", "name": "2", "left": 140, "top": 50, group:"one" }, 42 | { "id": "window3", "name": "3", "left": 450, "top": 50 }, 43 | { "id": "window4", "name": "4", "left": 110, "top": 370 }, 44 | { "id": "window5", "name": "5", "left": 140, "top": 150, group:"one" }, 45 | { "id": "window6", "name": "6", "left": 450, "top": 50, group:"two" }, 46 | { "id": "window7", "name": "7", "left": 50, "top": 450 } 47 | ], 48 | "edges": [ 49 | { source:"window3", target:"one"}, 50 | { source:"window3", target:"window4"}, 51 | { source:"one", target:"two"}, 52 | { source:"window5", target:"window6"}, 53 | { source:"window1", target:"window2"}, 54 | { source:"window1", target:"window5"} 55 | ] 56 | }; 57 | 58 | /** 59 | * issues with groups: 60 | * 61 | * - surely 'revert' is the opposite of orphan, right? what is the point of setting one of these to false and the other to true? 62 | * - setting `constrain:true` would mean none of the other flags - revert, orphan, autoSize, had any point, right? 63 | * - when you drag a child out of the bounds of group 1, it just resizes the group, instead of orphaning the child. NB this only applies if you drag it 64 | * in a positive direction in either axis. if you drag it out of the top/left of the element it does get orphaned, but the group still resizes as if the node 65 | * is still a child. ALSO in this case, the miniview DOES repaint itself wtaf. 66 | * - autosize doesnt work when it should shrink 67 | * - the miniview doesnt respond to size change events that resulted from an auto size 68 | * - there should be a type of 'constrain' where the user can still drag an element out of the bounds of the group, but the element is not 69 | * orphaned, the group is resized instead. that is kind of how group one is erroneously working now. I think it's a combination of autoSize:true and 70 | * not having set `orphan:true` 71 | */ 72 | 73 | const view:SurfaceViewOptions = { 74 | nodes: { 75 | [DEFAULT]: { 76 | templateId: "tmplNode", 77 | events: { 78 | [EVENT_TAP]: (params:{node:Node}) => { 79 | toolkit.toggleSelection(params.node); 80 | } 81 | } 82 | } 83 | }, 84 | groups:{ 85 | [DEFAULT]:{ 86 | templateId:"tmplGroup", 87 | endpoint:BlankEndpoint.type, 88 | anchor:AnchorLocations.Continuous, 89 | revert:false, 90 | orphan:true, 91 | constrain:false, 92 | autoSize:true, 93 | layout:{ 94 | type:AbsoluteLayout.type 95 | }, 96 | events:{ 97 | [EVENT_CLICK]:function(){ 98 | console.log(arguments) 99 | } 100 | } 101 | }, 102 | constrained:{ 103 | parent:DEFAULT, 104 | constrain:true 105 | } 106 | }, 107 | edges:{ 108 | [DEFAULT]:{ 109 | events:{ 110 | [EVENT_CLICK]:function() { 111 | console.log(arguments) 112 | }, 113 | "mouseover":function() { console.log("mouseover"); } 114 | } 115 | } 116 | } 117 | }; 118 | 119 | // Get an instance of the BrowserUIVanilla Toolkit. provide a groupFactory; when you drag a Group on to the Surface we 120 | // set an appropriate title for the new Group. Provide a nodeFactory. 121 | const toolkit = newInstance({ 122 | groupFactory:(type:string, data:Record, callback:Function) => { 123 | data.title = "Group " + (toolkit.getGroupCount() + 1) 124 | callback(data) 125 | return true 126 | }, 127 | nodeFactory:(type:string, data:Record, callback:Function) => { 128 | data.name = (toolkit.getNodeCount() + 1) 129 | callback(data) 130 | return true 131 | } 132 | }) 133 | 134 | // get the various dom elements 135 | const mainElement = document.querySelector(".jtk-demo-main"), 136 | canvasElement = mainElement.querySelector(".jtk-demo-canvas"), 137 | miniviewElement = mainElement.querySelector(".miniview"); 138 | 139 | // 140 | // Render the toolkit to `canvasElement`. For 2.x users upgrading to 5.x, not that `container` is now passed as a separate 141 | // argument, outside of the rest of the render options, whereas in 2.x it used to be one of the render options. 142 | // 143 | const renderer = toolkit.render(canvasElement, { 144 | view: view, 145 | layout: { 146 | type: ForceDirectedLayout.type, 147 | options: { 148 | absoluteBacked: true 149 | } 150 | }, 151 | // FOR people coming from 2.x versions of the Toolkit, this key used to be `jsPlumb`. 152 | defaults: { 153 | anchor:AnchorLocations.Continuous, 154 | endpoint: BlankEndpoint.type, 155 | connector: { type:StateMachineConnector.type, options:{ cssClass: "connectorClass", hoverClass: "connectorHoverClass" } }, 156 | paintStyle: { strokeWidth: 1, stroke: '#89bcde' }, 157 | hoverPaintStyle: { stroke: "orange" }, 158 | connectionOverlays: [ 159 | { type:ArrowOverlay.type, options:{ fill: "#09098e", width: 10, length: 10, location: 1 } } 160 | ] 161 | }, 162 | plugins:[ 163 | { 164 | type:MiniviewPlugin.type, 165 | options:{ 166 | container:miniviewElement 167 | } 168 | }, 169 | LassoPlugin.type 170 | ], 171 | dragOptions: { 172 | filter: ".delete *, .group-connect *, .delete" 173 | }, 174 | magnetize:{ 175 | afterDrag:true, 176 | afterGroupExpand:true 177 | }, 178 | events: { 179 | [EVENT_CANVAS_CLICK]: (e:MouseEvent) => { 180 | toolkit.clearSelection() 181 | }, 182 | [EVENT_SURFACE_MODE_CHANGED]: (mode:string) => { 183 | renderer.removeClass(document.querySelector("[mode]"), "selected-mode"); 184 | renderer.addClass(document.querySelector("[mode='" + mode + "']"), "selected-mode"); 185 | }, 186 | [EVENT_GROUP_ADDED]:(group:Group) => { 187 | console.log("New group " + group.id + " added") 188 | } 189 | }, 190 | consumeRightClick:false, 191 | zoomToFit:true 192 | }); 193 | 194 | // load the data. 195 | toolkit.load({type: "json", data: data}); 196 | 197 | // pan mode/select mode 198 | const controls = document.querySelector(".controls") 199 | renderer.on(controls, EVENT_TAP, "[mode]", function () { 200 | renderer.setMode(this.getAttribute("mode")); 201 | }); 202 | 203 | // 204 | // on home button tap, zoom content to fit. Note here we use `on` to bind an event, as we're just binding to a DOM 205 | // element that is not part of our dataset. Compare this with `bindModelEvent` below. 206 | // 207 | renderer.on(controls, EVENT_TAP, "[reset]", function () { 208 | toolkit.clearSelection(); 209 | renderer.zoomToFit(); 210 | }) 211 | 212 | toolkit.bind(EVENT_UNDOREDO_UPDATE, (state:UndoRedoUpdateParams) => { 213 | controls.setAttribute("can-undo", state.undoCount > 0 ? "true" : "false") 214 | controls.setAttribute("can-redo", state.redoCount > 0 ? "true" : "false") 215 | }) 216 | 217 | renderer.on(controls, EVENT_TAP, "[undo]", () => { 218 | toolkit.undo() 219 | }) 220 | 221 | renderer.on(controls, EVENT_TAP, "[redo]", () => { 222 | toolkit.redo() 223 | }) 224 | 225 | // 226 | // Attach event handlers to 'delete' buttons. Note here the method `bindModelEvent`, which binds an event handler to some 227 | // named event on each of the vertices in the dataset. The callback is given the original event, the specific DOM element on 228 | // which the event occurred, and details about the model object on which the event occurred. 229 | // 230 | renderer.bindModelEvent(EVENT_TAP, ".delete", (event: Event, eventTarget: HTMLElement, info: ObjectInfo) =>{ 231 | toolkit.removeNode(info.obj) 232 | }) 233 | 234 | // 235 | // listen for group expand/collapse 236 | // 237 | renderer.bindModelEvent(EVENT_TAP, ".group-title .expand", (event: Event, eventTarget: HTMLElement, info: ObjectInfo) => { 238 | if (info.obj) { 239 | renderer.toggleGroup(info.obj) 240 | } 241 | }) 242 | 243 | // 244 | // listen for clicks on group delete buttons 245 | // 246 | renderer.bindModelEvent(EVENT_TAP, ".group-delete", (event: Event, eventTarget: HTMLElement, info: ObjectInfo) => { 247 | toolkit.removeGroup(info.obj, true) 248 | }) 249 | 250 | // 251 | // Here, we are registering elements that we will want to drop onto the workspace and have 252 | // the toolkit recognise them as new nodes 253 | // 254 | createSurfaceDropManager({ 255 | surface:renderer, 256 | source:document.querySelector(".node-palette"), 257 | selector:"[data-node-type]", 258 | dataGenerator:(e:Element) => { 259 | return { 260 | type:"default" 261 | }; 262 | } 263 | }) 264 | }) 265 | 266 | --------------------------------------------------------------------------------