diff --git a/README.md b/README.md new file mode 100644 index 0000000..6e7085b --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# Layoutr +A graph layout web application + +## Installation + +After a git checkout, run the following to serve the application locally: + +``` +npm i +npm run serve +``` + +## Usage + +* Click "UPLOAD CSV OR JSON" to upload a file of the form: +``` +source,target +a,b +b,c +c,d +d,a +``` +for CSV and +``` +{ + "nodes": [ + { "id": "a" }, + { "id": "b" }, + { "id": "c" }, + { "id": "d" } + ], + "edges": [ + { + "source": "a", + "target": "b" + }, + { + "source": "b", + "target": "c" + }, + { + "source": "c", + "target": "d" + }, + { + "source": "d", + "target": "a" + } + ] +} +``` +for JSON + +* Click "Start Layout". +* Play with the controls! diff --git a/dist/index.html b/dist/index.html index e6c4c1d..fbfed9f 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1,30 +1,10 @@ Layoutr + - -
- +
diff --git a/package-lock.json b/package-lock.json index 5e59f60..dd04e61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,64 @@ "integrity": "sha512-Uy0PN4R5vgBUXFoJrKryf5aTk3kJ8Rv3PdlHjl6UaX+Cqp1QE0yPQ68MPXGrZOfG7gZVNDIJZYyot0B9ubXUrQ==", "dev": true }, + "@vue/component-compiler-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz", + "integrity": "sha512-IHjxt7LsOFYc0DkTncB7OXJL7UzwOLPPQCfEUNyxL2qt+tF12THV+EO33O1G2Uk4feMSWua3iD39Itszx0f0bw==", + "dev": true, + "requires": { + "consolidate": "^0.15.1", + "hash-sum": "^1.0.2", + "lru-cache": "^4.1.2", + "merge-source-map": "^1.1.0", + "postcss": "^7.0.14", + "postcss-selector-parser": "^5.0.0", + "prettier": "1.16.3", + "source-map": "~0.6.1", + "vue-template-es2015-compiler": "^1.9.0" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -987,6 +1045,15 @@ "date-now": "^0.1.4" } }, + "consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "dev": true, + "requires": { + "bluebird": "^3.1.1" + } + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -1435,6 +1502,12 @@ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", "dev": true }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", + "dev": true + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1642,6 +1715,12 @@ "minimalistic-crypto-utils": "^1.0.0" } }, + "email-addresses": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.0.3.tgz", + "integrity": "sha512-kUlSC06PVvvjlMRpNIl3kR1NRXLEe86VQ7N0bQeaCZb2g+InShCeHQp/JvyYNTugMnRN2NvJhHlc3q12MWbbpg==", + "dev": true + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -1987,6 +2066,33 @@ "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", "dev": true }, + "filename-reserved-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", + "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=", + "dev": true + }, + "filenamify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", + "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=", + "dev": true, + "requires": { + "filename-reserved-regex": "^1.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + } + }, + "filenamify-url": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz", + "integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=", + "dev": true, + "requires": { + "filenamify": "^1.0.0", + "humanize-url": "^1.0.0" + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -2130,6 +2236,17 @@ "readable-stream": "^2.0.0" } }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", @@ -2731,6 +2848,33 @@ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, + "gh-pages": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.0.1.tgz", + "integrity": "sha512-uFlk3bukljeiWKQ2XvPfjcSi/ou7IfoDf2p+Fj672saLAr8bnOdFVqI/JSgrSgInKpCg5BksxEwGUl++dbg8Dg==", + "dev": true, + "requires": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify-url": "^1.0.0", + "fs-extra": "^7.0.0", + "globby": "^6.1.0", + "graceful-fs": "^4.1.11", + "rimraf": "^2.6.2" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + } + } + }, "gl-mat3": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/gl-mat3/-/gl-mat3-2.0.0.tgz", @@ -2919,6 +3063,12 @@ "safe-buffer": "^5.0.1" } }, + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=", + "dev": true + }, "hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -2929,6 +3079,12 @@ "minimalistic-assert": "^1.0.1" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -3029,6 +3185,16 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "humanize-url": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz", + "integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=", + "dev": true, + "requires": { + "normalize-url": "^1.0.0", + "strip-url-auth": "^1.0.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3290,6 +3456,12 @@ "path-is-inside": "^1.0.2" } }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -3366,6 +3538,15 @@ "minimist": "^1.2.0" } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jszip": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.4.tgz", @@ -3530,11 +3711,6 @@ "object-visit": "^1.0.0" } }, - "materialize-css": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/materialize-css/-/materialize-css-1.0.0.tgz", - "integrity": "sha512-4/oecXl8y/1i8RDZvyvwAICyqwNoKU4or5uf8uoAd74k76KzZ0Llym4zhJ5lLNUskcqjO0AuMcvNyDkpz8Z6zw==" - }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -3579,6 +3755,23 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3866,6 +4059,18 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, "nosleep.js": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/nosleep.js/-/nosleep.js-0.7.0.tgz", @@ -4295,6 +4500,18 @@ "integrity": "sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==", "dev": true }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "prettier": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.3.tgz", + "integrity": "sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -4338,6 +4555,12 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -4397,6 +4620,16 @@ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", "dev": true }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -5024,6 +5257,15 @@ } } }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -5229,6 +5471,12 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -5264,6 +5512,21 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "strip-url-auth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz", + "integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=", + "dev": true + }, "style-loader": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", @@ -5437,6 +5700,15 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", "dev": true }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -5501,6 +5773,12 @@ "imurmurhash": "^0.1.4" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -5698,6 +5976,61 @@ } } }, + "vue": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz", + "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==" + }, + "vue-hot-reload-api": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz", + "integrity": "sha512-KmvZVtmM26BQOMK1rwUZsrqxEGeKiYSZGA7SNWE6uExx8UX/cj9hq2MRV/wWC3Cq6AoeDGk57rL9YMFRel/q+g==", + "dev": true + }, + "vue-loader": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.7.0.tgz", + "integrity": "sha512-x+NZ4RIthQOxcFclEcs8sXGEWqnZHodL2J9Vq+hUz+TDZzBaDIh1j3d9M2IUlTjtrHTZy4uMuRdTi8BGws7jLA==", + "dev": true, + "requires": { + "@vue/component-compiler-utils": "^2.5.1", + "hash-sum": "^1.0.2", + "loader-utils": "^1.1.0", + "vue-hot-reload-api": "^2.3.0", + "vue-style-loader": "^4.1.0" + } + }, + "vue-style-loader": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz", + "integrity": "sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ==", + "dev": true, + "requires": { + "hash-sum": "^1.0.2", + "loader-utils": "^1.0.2" + } + }, + "vue-template-compiler": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz", + "integrity": "sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, + "vue-template-es2015-compiler": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", + "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", + "dev": true + }, + "vuetify": { + "version": "1.5.16", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-1.5.16.tgz", + "integrity": "sha512-yBgOsfurKQkeS+l+rrTQZ2bFk0D9ezjHhkuVM5A/yVzcg62sY2nfYaq/H++uezBWC9WYFrp/5OmSocJQcWn9Qw==" + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", diff --git a/package.json b/package.json index 107396a..95a4e26 100644 --- a/package.json +++ b/package.json @@ -10,18 +10,25 @@ "build": "webpack", "watch": "webpack --watch", "serve": "webpack-dev-server --open", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "predeploy": "npm run build", + "deploy": "gh-pages -d dist" }, "author": "Jeff Baumes (jeff.baumes@kitware.com)", "license": "MIT", + "homepage": "https://kitware.github.io/layoutr", "dependencies": { "d3": "^5.9.7", "geojs": "^0.19.4", - "materialize-css": "^1.0.0" + "vue": "^2.6.10", + "vuetify": "^1.5.16" }, "devDependencies": { "css-loader": "^3.0.0", + "gh-pages": "^2.0.1", "style-loader": "^0.23.1", + "vue-loader": "^15.7.0", + "vue-template-compiler": "^2.6.10", "webpack": "^4.35.2", "webpack-cli": "^3.3.5", "webpack-dev-server": "^3.7.2", diff --git a/scripts/matnet-to-edgelist.js b/scripts/matnet-to-edgelist.js index 9fb746c..df7c278 100644 --- a/scripts/matnet-to-edgelist.js +++ b/scripts/matnet-to-edgelist.js @@ -1,5 +1,4 @@ const fs = require('fs'); -// const system = require('system'); fs.readFile(process.argv[2], 'utf-8', (err, data) => { graph = JSON.parse(data); diff --git a/scripts/matnet-to-json.js b/scripts/matnet-to-json.js new file mode 100644 index 0000000..317dbc9 --- /dev/null +++ b/scripts/matnet-to-json.js @@ -0,0 +1,10 @@ +const fs = require('fs'); + +fs.readFile(process.argv[2], 'utf-8', (err, data) => { + let graph = JSON.parse(data); + let out = { + nodes: Object.keys(graph.nodes).map(id => ({ ...graph.nodes[id], id })), + edges: graph.edges.map(e => ({ source: e[0], target: e[1] })), + } + console.log(JSON.stringify(out, null, 2)); +}); diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..829b1c8 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,477 @@ + + + + + diff --git a/src/force/periodicTable.js b/src/force/periodicTable.js new file mode 100644 index 0000000..200dc94 --- /dev/null +++ b/src/force/periodicTable.js @@ -0,0 +1,144 @@ +const table = [ + "H", "Hydrogen", "1.00794", 1, 1, + "He", "Helium", "4.002602", 18, 1, + "Li", "Lithium", "6.941", 1, 2, + "Be", "Beryllium", "9.012182", 2, 2, + "B", "Boron", "10.811", 13, 2, + "C", "Carbon", "12.0107", 14, 2, + "N", "Nitrogen", "14.0067", 15, 2, + "O", "Oxygen", "15.9994", 16, 2, + "F", "Fluorine", "18.9984032", 17, 2, + "Ne", "Neon", "20.1797", 18, 2, + "Na", "Sodium", "22.98976...", 1, 3, + "Mg", "Magnesium", "24.305", 2, 3, + "Al", "Aluminium", "26.9815386", 13, 3, + "Si", "Silicon", "28.0855", 14, 3, + "P", "Phosphorus", "30.973762", 15, 3, + "S", "Sulfur", "32.065", 16, 3, + "Cl", "Chlorine", "35.453", 17, 3, + "Ar", "Argon", "39.948", 18, 3, + "K", "Potassium", "39.948", 1, 4, + "Ca", "Calcium", "40.078", 2, 4, + "Sc", "Scandium", "44.955912", 3, 4, + "Ti", "Titanium", "47.867", 4, 4, + "V", "Vanadium", "50.9415", 5, 4, + "Cr", "Chromium", "51.9961", 6, 4, + "Mn", "Manganese", "54.938045", 7, 4, + "Fe", "Iron", "55.845", 8, 4, + "Co", "Cobalt", "58.933195", 9, 4, + "Ni", "Nickel", "58.6934", 10, 4, + "Cu", "Copper", "63.546", 11, 4, + "Zn", "Zinc", "65.38", 12, 4, + "Ga", "Gallium", "69.723", 13, 4, + "Ge", "Germanium", "72.63", 14, 4, + "As", "Arsenic", "74.9216", 15, 4, + "Se", "Selenium", "78.96", 16, 4, + "Br", "Bromine", "79.904", 17, 4, + "Kr", "Krypton", "83.798", 18, 4, + "Rb", "Rubidium", "85.4678", 1, 5, + "Sr", "Strontium", "87.62", 2, 5, + "Y", "Yttrium", "88.90585", 3, 5, + "Zr", "Zirconium", "91.224", 4, 5, + "Nb", "Niobium", "92.90628", 5, 5, + "Mo", "Molybdenum", "95.96", 6, 5, + "Tc", "Technetium", "(98)", 7, 5, + "Ru", "Ruthenium", "101.07", 8, 5, + "Rh", "Rhodium", "102.9055", 9, 5, + "Pd", "Palladium", "106.42", 10, 5, + "Ag", "Silver", "107.8682", 11, 5, + "Cd", "Cadmium", "112.411", 12, 5, + "In", "Indium", "114.818", 13, 5, + "Sn", "Tin", "118.71", 14, 5, + "Sb", "Antimony", "121.76", 15, 5, + "Te", "Tellurium", "127.6", 16, 5, + "I", "Iodine", "126.90447", 17, 5, + "Xe", "Xenon", "131.293", 18, 5, + "Cs", "Caesium", "132.9054", 1, 6, + "Ba", "Barium", "132.9054", 2, 6, + "La", "Lanthanum", "138.90547", 4, 9, + "Ce", "Cerium", "140.116", 5, 9, + "Pr", "Praseodymium", "140.90765", 6, 9, + "Nd", "Neodymium", "144.242", 7, 9, + "Pm", "Promethium", "(145)", 8, 9, + "Sm", "Samarium", "150.36", 9, 9, + "Eu", "Europium", "151.964", 10, 9, + "Gd", "Gadolinium", "157.25", 11, 9, + "Tb", "Terbium", "158.92535", 12, 9, + "Dy", "Dysprosium", "162.5", 13, 9, + "Ho", "Holmium", "164.93032", 14, 9, + "Er", "Erbium", "167.259", 15, 9, + "Tm", "Thulium", "168.93421", 16, 9, + "Yb", "Ytterbium", "173.054", 17, 9, + "Lu", "Lutetium", "174.9668", 18, 9, + "Hf", "Hafnium", "178.49", 4, 6, + "Ta", "Tantalum", "180.94788", 5, 6, + "W", "Tungsten", "183.84", 6, 6, + "Re", "Rhenium", "186.207", 7, 6, + "Os", "Osmium", "190.23", 8, 6, + "Ir", "Iridium", "192.217", 9, 6, + "Pt", "Platinum", "195.084", 10, 6, + "Au", "Gold", "196.966569", 11, 6, + "Hg", "Mercury", "200.59", 12, 6, + "Tl", "Thallium", "204.3833", 13, 6, + "Pb", "Lead", "207.2", 14, 6, + "Bi", "Bismuth", "208.9804", 15, 6, + "Po", "Polonium", "(209)", 16, 6, + "At", "Astatine", "(210)", 17, 6, + "Rn", "Radon", "(222)", 18, 6, + "Fr", "Francium", "(223)", 1, 7, + "Ra", "Radium", "(226)", 2, 7, + "Ac", "Actinium", "(227)", 4, 10, + "Th", "Thorium", "232.03806", 5, 10, + "Pa", "Protactinium", "231.0588", 6, 10, + "U", "Uranium", "238.02891", 7, 10, + "Np", "Neptunium", "(237)", 8, 10, + "Pu", "Plutonium", "(244)", 9, 10, + "Am", "Americium", "(243)", 10, 10, + "Cm", "Curium", "(247)", 11, 10, + "Bk", "Berkelium", "(247)", 12, 10, + "Cf", "Californium", "(251)", 13, 10, + "Es", "Einstenium", "(252)", 14, 10, + "Fm", "Fermium", "(257)", 15, 10, + "Md", "Mendelevium", "(258)", 16, 10, + "No", "Nobelium", "(259)", 17, 10, + "Lr", "Lawrencium", "(262)", 18, 10, + "Rf", "Rutherfordium", "(267)", 4, 7, + "Db", "Dubnium", "(268)", 5, 7, + "Sg", "Seaborgium", "(271)", 6, 7, + "Bh", "Bohrium", "(272)", 7, 7, + "Hs", "Hassium", "(270)", 8, 7, + "Mt", "Meitnerium", "(276)", 9, 7, + "Ds", "Darmstadium", "(281)", 10, 7, + "Rg", "Roentgenium", "(280)", 11, 7, + "Cn", "Copernicium", "(285)", 12, 7, + "Nh", "Nihonium", "(286)", 13, 7, + "Fl", "Flerovium", "(289)", 14, 7, + "Mc", "Moscovium", "(290)", 15, 7, + "Lv", "Livermorium", "(293)", 16, 7, + "Ts", "Tennessine", "(294)", 17, 7, + "Og", "Oganesson", "(294)", 18, 7 +]; + +let periodicTable = {} +table.forEach((d, i) => { + if (i % 5 === 0) { + periodicTable[d] = { + x: table[i + 3], + y: table[i + 4], + } + } +}) + +function periodicTableForce(alpha) { + const nodes = simulation.nodes(); + let k = alpha * 1; + for (var i = 0, n = nodes.length, node; i < n; ++i) { + node = nodes[i]; + if (periodicTable[node.id]) { + let tx = (periodicTable[node.id].x - 9) * 750; + let ty = (periodicTable[node.id].y - 6) * 750; + node.vx -= (node.x - tx) * k; + node.vy -= (node.y - ty) * k; + } + } +} diff --git a/src/index.css b/src/index.css deleted file mode 100644 index b79f19e..0000000 --- a/src/index.css +++ /dev/null @@ -1,37 +0,0 @@ -html,body,#map{ - width: 100%; - height: 100%; - padding: 0; - margin: 0; - overflow: hidden; -} -#tooltip { - margin-left: 0px; - margin-top: -20px; - height: 16px; - line-height: 16px; - padding: 2px 5px; - background: rgba(255, 255, 255, 0.75); - border-radius: 10px; - border-bottom-left-radius: 0; - border: 1px solid rgba(0, 0, 0, 0.75); - font-size: 12px; - color: black; -} -#tooltip.hidden { - display: none; -} -.slider-wrapper { - padding: 0px 24px; - /* Need to reset line-height since we're in a side nav */ - line-height: 1.6; -} -.slider-wrapper .slider-label { - line-height: 3; -} -.noUi-horizontal .noUi-handle, .noUi-vertical .noUi-handle { - background-color: #2196f3; -} -.noUi-target.noUi-horizontal .noUi-tooltip { - background-color: #2196f3; -} diff --git a/src/index.js b/src/index.js index 9952074..3b7cd8c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,222 +1,14 @@ -import 'materialize-css/dist/css/materialize.min.css'; -import 'materialize-css/dist/js/materialize.min.js'; -import 'materialize-css/extras/noUiSlider/nouislider.css'; -import noUiSlider from 'materialize-css/extras/noUiSlider/nouislider.min.js'; -import geo from 'geojs/geo.js'; +import 'vuetify/dist/vuetify.min.css'; -import LayoutWorker from 'worker-loader!./worker.js'; -import './index.css'; +import Vue from 'vue'; +import Vuetify from 'vuetify'; +import App from './App'; -let b = 20000; -let bounds = { - minx: -b, - maxx: b, - miny: -b, - maxy: b, -}; -let params = geo.util.pixelCoordinateParams( - '#map', bounds.maxx - bounds.minx, bounds.maxy - bounds.miny); +Vue.use(Vuetify); +Vue.config.productionTip = false; -// the utility function assumes top left is 0, 0. Move it to minx, miny. -params.map.maxBounds.left += bounds.minx; -params.map.maxBounds.top += bounds.miny; -params.map.maxBounds.right += bounds.minx; -params.map.maxBounds.bottom += bounds.miny; -params.map.center.x += bounds.minx; -params.map.center.y += bounds.miny; - -// inflate the bounds to add a border -const maxwh = Math.max(bounds.maxx - bounds.minx, bounds.maxy - bounds.miny); -params.map.maxBounds.left -= maxwh * 0.1; -params.map.maxBounds.top -= maxwh * 0.1; -params.map.maxBounds.right += maxwh * 0.1; -params.map.maxBounds.bottom += maxwh * 0.1; - -// allow zoomming in until 1 unit of space is 2^(value) bigger. -params.map.max += 3; -const map = geo.map(params.map); -const layer = map.createLayer('feature', {features: ['point', 'line']}); - -const uiLayer = map.createLayer('ui', {zIndex: 2}); -const tooltip = uiLayer.createWidget('dom', {position: {x: 0, y: 0}}); -const tooltipElem = tooltip.canvas(); -tooltipElem.setAttribute('id', 'tooltip'); -tooltipElem.classList.toggle('hidden', true); -tooltipElem.style['pointer-events'] = 'none'; - -map.draw(); - -let points; -let lines; -let graph; -let positions; -let nodeMap; -let radiusFactor = 2; - -var layoutWorker = new LayoutWorker(); -layoutWorker.onmessage = function(e) { - if (e.data.type === 'graph') { - graph = e.data.graph; - - nodeMap = {}; - graph.nodes.forEach((n, i) => nodeMap[n.id] = i); - lines = layer.createFeature('line').data(graph.edges.map(e => [nodeMap[e.source], nodeMap[e.target]])).style({ - position: nodeid => graph.nodes[nodeid], - width: 1, - strokeColor: 'black', - strokeOpacity: 0.01, - }); - lines.visible(false); - map.draw(); - - points = layer.createFeature('point', { - primitiveShape: 'triangle', - style: { - strokeColor: 'black', - fillColor: 'grey', - fillOpacity: 0.5, - strokeOpacity: 0.5, - radius: nodeid => Math.max(1, Math.pow(2, map.zoom()) * Math.sqrt(graph.nodes[nodeid].degree) * radiusFactor) - }, - position: nodeid => graph.nodes[nodeid] - }).data(Object.keys(graph.nodes)); - - map.geoOn(geo.event.zoom, () => { - points.modified().draw(); - }); - - map.draw(); - - points - .geoOn(geo.event.feature.mouseon, function (evt) { - const nodeid = evt.data, node = graph.nodes[nodeid]; - let text = node.id; - if (text) { - tooltip.position(evt.mouse.geo); - tooltipElem.innerText = text; - } - tooltipElem.classList.toggle('hidden', !text); - }) - .geoOn(geo.event.feature.mousemove, function (evt) { - tooltip.position(evt.mouse.geo); - }) - .geoOn(geo.event.feature.mouseoff, function (evt) { - tooltipElem.classList.toggle('hidden', true); - }); - } - else if (e.data.type === 'positions') { - positions = e.data.nodes; - points.position(nodeid => positions[nodeid]); - lines.position(nodeid => positions[nodeid]); - map.draw(); - } - else if (e.data.type === 'alpha') { - alpha.noUiSlider.set(e.data.value); - } -} - -document.getElementById('toggle-start').onclick = () => { - let mode = document.getElementById('toggle-start').innerText.toLowerCase().split(' ')[0]; - layoutWorker.postMessage({type: mode}); - if (mode === 'start') { - alpha.setAttribute('disabled', true); - } else { - alpha.removeAttribute('disabled'); - } - document.getElementById('toggle-start').innerText = (mode === 'start' ? 'Stop Layout': 'Start Layout'); -} - -document.getElementById('save').onclick = () => { - const nodesWithPositions = graph.nodes.map((n, i) => ({ - ...n, - ...positions[i], - })); - const saveGraph = { - nodes: nodesWithPositions, - edges: graph.edges, - } - var blob = new Blob([JSON.stringify(saveGraph, null, 2)], { - type : "data:text/json;charset=utf-8;" - }); - const dl = document.getElementById('download'); - dl.setAttribute("href", URL.createObjectURL(blob)); - dl.setAttribute("download", "scene.json"); - dl.click(); -} - -document.getElementById('init-upload-edge-list').onclick = () => { - const upload = document.getElementById('upload-edge-list'); - upload.click(); -} - -document.getElementById('upload-edge-list').onchange = () => { - var file = document.getElementById('upload-edge-list').files[0]; - if (file) { - var reader = new FileReader(); - reader.readAsText(file, "UTF-8"); - reader.onload = function (evt) { - layoutWorker.postMessage({type: 'loadEdgeList', text: evt.target.result}); - } - reader.onerror = function (evt) { - console.log('Error: ', evt); - } - } -} - -function fixedFormat(n) { - return { - to: function (value) { - return value.toFixed(n); - }, - from: function (value) { - return Number(value); - }, - }; -} - -let theta = document.getElementById('theta'); -noUiSlider.create(theta, { - start: 1.5, - step: 0.1, - range: {min: 0.5, max: 3.0}, - format: fixedFormat(1), -}); -theta.noUiSlider.on('update', () => { - layoutWorker.postMessage({ - type: 'theta', - value: theta.noUiSlider.get(), - }); -}); - -let alpha = document.getElementById('alpha'); -noUiSlider.create(alpha, { - start: 1.0, - step: 0.01, - range: {min: 0.0, max: 1.0}, - format: fixedFormat(2), -}); -alpha.noUiSlider.on('update', () => { - layoutWorker.postMessage({ - type: 'alpha', - value: alpha.noUiSlider.get(), - }); -}); - -let radiusFactorSlider = document.getElementById('radius-factor'); -noUiSlider.create(radiusFactorSlider, { - start: 2.0, - step: 0.1, - range: {min: 0.1, max: 10.0}, - format: fixedFormat(1), -}); -radiusFactorSlider.noUiSlider.on('update', () => { - radiusFactor = radiusFactorSlider.noUiSlider.get(); - if (points) { - points.modified(); - map.draw(); - } - layoutWorker.postMessage({ - type: 'radiusFactor', - value: radiusFactorSlider.noUiSlider.get(), - }); +new Vue({ + el: '#app', + components: { App }, + template: '', }); diff --git a/src/scales.js b/src/scales.js new file mode 100644 index 0000000..f01bd1e --- /dev/null +++ b/src/scales.js @@ -0,0 +1,26 @@ +let d3 = require('d3/dist/d3.js'); + +// generateScale() +// Create a linear scaling function for a numeric data field. +// Range will go from `min` to `max`, with invalid (non-numeric) values at `invalid`. +// If `area` is specified, range will be scaled such that every point on average fills `area` square units. +export function generateScale(arr, field, {area = null, min = -0.5, max = 0.5, invalid = 0.7}) { + const size = area ? Math.sqrt(arr.length * area) : 1; + const domain = d3.extent(arr, n => n[field]); + const scale = d3.scaleLinear().domain(domain).range([size * min, size * max]); + return n => { + const val = n[field]; + if (!isNaN(parseFloat(val)) && isFinite(val)) { + return scale(val); + } + return size * invalid; + } +} + +export function generateSizeScale(arr, field, size) { + if (field === 'None') { + return () => 250 * size; + } + const sizeScale = generateScale(arr, field, {min: 3, max: 500*500, invalid: 2}); + return d => Math.sqrt(sizeScale(d)) * size; +} diff --git a/src/worker.js b/src/worker.js index f1c8b06..a19ef5e 100644 --- a/src/worker.js +++ b/src/worker.js @@ -1,24 +1,31 @@ let d3 = require('d3/dist/d3.js'); +let scales = require('./scales.js'); -let radiusFactor = 2; +let size = 1; +let sizeField = 'degree'; +let linkStrength = 1; +let xField = 'degree'; +let yField = 'degree'; +let radialField = 'degree'; let linkStrengthFunctions = { - inverseMinDegree: link => 1 / Math.min(link.source.degree, link.target.degree), - inverseSumDegree: link => 1 / (link.source.degree + link.target.degree), - inverseSumSqrtDegree: link => 1 / (Math.sqrt(link.source.degree) + Math.sqrt(link.target.degree)), + inverseMinDegree: link => linkStrength / Math.min(link.source.degree, link.target.degree), + inverseSumDegree: link => linkStrength / (link.source.degree + link.target.degree), + inverseSumSqrtDegree: link => linkStrength / (Math.sqrt(link.source.degree) + Math.sqrt(link.target.degree)), }; let linkDistanceFunctions = { - sumSqrtDegree: link => (Math.sqrt(link.source.degree) + Math.sqrt(link.target.degree)) * radiusFactor, + sumSqrtDegree: link => (Math.sqrt(link.source.degree) + Math.sqrt(link.target.degree)) * size, }; let link = d3.forceLink().id(d => d.id).distance(linkDistanceFunctions.sumSqrtDegree).strength(linkStrengthFunctions.inverseMinDegree); let charge = d3.forceManyBody(); -let collide = d3.forceCollide().radius(d => Math.sqrt(d.degree) * radiusFactor); +let collide = d3.forceCollide(); +let center = d3.forceCenter(); +let x = d3.forceX(); +let y = d3.forceY(); +let radial = d3.forceRadial(); let simulation = d3.forceSimulation() - .force('link', link) - .force('charge', charge) - .force('collide', collide) .alphaMin(0) .alphaTarget(0) .stop(); @@ -41,6 +48,7 @@ loadGraph = function(graph) { graph.nodes.forEach(d => { nodeMap[d.id] = d; }); + graph.edges = graph.edges.filter(e => nodeMap[e.source] && nodeMap[e.target]); graph.edges.forEach(d => { nodeMap[d.source].degree += 1; nodeMap[d.target].degree += 1; @@ -51,38 +59,93 @@ loadGraph = function(graph) { .nodes(graph.nodes) .on('tick', tick); - postMessage({type: 'graph', graph: { - nodes: graph.nodes.map(n => ({id: n.id, degree: n.degree, x: n.x, y: n.y})), - edges: graph.edges, - }}); + postMessage({type: 'graph', graph}); + postMessage({type: 'positions', nodes: graph.nodes.map(n => ({x: n.x, y: n.y}))}); - simulation.force('link') - .links(graph.edges); + // Initialize data-dependent scales + collide.radius(scales.generateSizeScale(simulation.nodes(), sizeField, size)); + x.x(scales.generateScale(simulation.nodes(), xField, {area: 1000})); + y.y(scales.generateScale(simulation.nodes(), yField, {area: 1000})); + radial.radius(scales.generateScale( + simulation.nodes(), radialField, {area: 1000, min: 0.5, max: 1.5, invalid: 1.6}, + )); + + let oldLink = simulation.force('link'); + simulation.force('link', link); + link.links(graph.edges); + simulation.force('link', oldLink); } onmessage = function(e) { - if (e.data.type === 'stop') { - simulation.stop(); - } - else if (e.data.type === 'start') { - simulation.restart(); + if (e.data.type === 'layout') { + if (e.data.value) { + simulation.restart(); + } else { + simulation.stop(); + } } else if (e.data.type === 'loadEdgeList') { loadGraph({edges: d3.csvParse(e.data.text)}); } + else if (e.data.type === 'loadJSON') { + loadGraph(JSON.parse(e.data.text)); + } else if (e.data.type === 'theta') { charge.theta(e.data.value); } else if (e.data.type === 'alpha') { simulation.alpha(e.data.value); } - else if (e.data.type === 'radiusFactor') { - radiusFactor = e.data.value; - link.strength(linkStrengthFunctions.inverseMinDegree); - collide.radius(d => Math.sqrt(d.degree) * radiusFactor); + else if (e.data.type === 'size') { + size = e.data.value; + link.strength(link.strength()); + collide.radius(scales.generateSizeScale(simulation.nodes(), sizeField, size)); + } + else if (e.data.type === 'sizeField') { + sizeField = e.data.value; + collide.radius(scales.generateSizeScale(simulation.nodes(), sizeField, size)); + } + else if (e.data.type === 'linkStrength') { + simulation.force('link', e.data.value ? link : null); + linkStrength = e.data.value; + link.strength(link.strength()); + } + else if (e.data.type === 'chargeStrength') { + simulation.force('charge', e.data.value ? charge : null); + charge.strength(-e.data.value); + } + else if (e.data.type === 'collideStrength') { + simulation.force('collide', e.data.value ? collide : null); + collide.strength(e.data.value); + } + else if (e.data.type === 'center') { + simulation.force('center', e.data.value ? center : null); + } + else if (e.data.type === 'xStrength') { + simulation.force('x', e.data.value ? x : null); + x.strength(e.data.value); + } + else if (e.data.type === 'xField') { + xField = e.data.value; + x.x(scales.generateScale(simulation.nodes(), xField, {area: 1000})); + } + else if (e.data.type === 'yStrength') { + simulation.force('y', e.data.value ? y : null); + y.strength(e.data.value); + } + else if (e.data.type === 'yField') { + yField = e.data.value; + y.y(scales.generateScale(simulation.nodes(), yField, {min: 0.5, max: -0.5, area: 1000})); + } + else if (e.data.type === 'radialStrength') { + simulation.force('radial', e.data.value ? radial : null); + radial.strength(e.data.value); } - else if (e.data.type === 'collide') { - simulation.collide(e.data.enabled ? collide : null); + else if (e.data.type === 'radialField') { + radialField = e.data.value; + radial.radius(scales.generateScale( + simulation.nodes(), radialField, {area: 1000, min: 0.5, max: 1.5, invalid: 1.6}, + )); } else { throw Error(`Unknown message type '${e.data.type}'`); diff --git a/webpack.config.js b/webpack.config.js index c703b71..a23891e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,5 @@ const path = require('path'); +const VueLoaderPlugin = require('vue-loader/lib/plugin'); module.exports = { mode: 'development', @@ -8,14 +9,27 @@ module.exports = { }, module: { rules: [ + { + test: /\.vue$/, + loader: 'vue-loader', + }, { test: /\.css$/i, use: ['style-loader', 'css-loader'], }, ], }, + resolve: { + extensions: ['.js', '.vue'], + alias: { + 'vue$': 'vue/dist/vue.js', + } + }, output: { path: path.resolve(__dirname, 'dist'), - filename: 'bundle.js' + filename: 'bundle.js', }, + plugins: [ + new VueLoaderPlugin(), + ], };