mirror of
https://github.com/gchq/CyberChef
synced 2025-03-13 21:36:56 +00:00
Ported heatmap and hex density chart ops
This commit is contained in:
parent
5bb8eb22ec
commit
da2d5674a5
6 changed files with 1362 additions and 914 deletions
414
package-lock.json
generated
414
package-lock.json
generated
|
@ -1631,7 +1631,7 @@
|
|||
},
|
||||
"array-equal": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
|
||||
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -1716,7 +1716,7 @@
|
|||
},
|
||||
"util": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -1864,7 +1864,7 @@
|
|||
},
|
||||
"axios": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
|
||||
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -2334,7 +2334,7 @@
|
|||
},
|
||||
"browserify-aes": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -2371,7 +2371,7 @@
|
|||
},
|
||||
"browserify-rsa": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
|
||||
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -2436,7 +2436,7 @@
|
|||
},
|
||||
"buffer": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
||||
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -2590,7 +2590,7 @@
|
|||
},
|
||||
"camelcase-keys": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
|
||||
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -2639,7 +2639,7 @@
|
|||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||
"requires": {
|
||||
"ansi-styles": "^2.2.1",
|
||||
|
@ -3172,7 +3172,7 @@
|
|||
},
|
||||
"create-hash": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -3185,7 +3185,7 @@
|
|||
},
|
||||
"create-hmac": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -3332,7 +3332,7 @@
|
|||
},
|
||||
"css-select": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -3440,6 +3440,266 @@
|
|||
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
|
||||
"dev": true
|
||||
},
|
||||
"d3": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/d3/-/d3-4.13.0.tgz",
|
||||
"integrity": "sha512-l8c4+0SldjVKLaE2WG++EQlqD7mh/dmQjvi2L2lKPadAVC+TbJC4ci7Uk9bRi+To0+ansgsS0iWfPjD7DBy+FQ==",
|
||||
"requires": {
|
||||
"d3-array": "1.2.1",
|
||||
"d3-axis": "1.0.8",
|
||||
"d3-brush": "1.0.4",
|
||||
"d3-chord": "1.0.4",
|
||||
"d3-collection": "1.0.4",
|
||||
"d3-color": "1.0.3",
|
||||
"d3-dispatch": "1.0.3",
|
||||
"d3-drag": "1.2.1",
|
||||
"d3-dsv": "1.0.8",
|
||||
"d3-ease": "1.0.3",
|
||||
"d3-force": "1.1.0",
|
||||
"d3-format": "1.2.2",
|
||||
"d3-geo": "1.9.1",
|
||||
"d3-hierarchy": "1.1.5",
|
||||
"d3-interpolate": "1.1.6",
|
||||
"d3-path": "1.0.5",
|
||||
"d3-polygon": "1.0.3",
|
||||
"d3-quadtree": "1.0.3",
|
||||
"d3-queue": "3.0.7",
|
||||
"d3-random": "1.1.0",
|
||||
"d3-request": "1.0.6",
|
||||
"d3-scale": "1.0.7",
|
||||
"d3-selection": "1.3.0",
|
||||
"d3-shape": "1.2.0",
|
||||
"d3-time": "1.0.8",
|
||||
"d3-time-format": "2.1.1",
|
||||
"d3-timer": "1.0.7",
|
||||
"d3-transition": "1.1.1",
|
||||
"d3-voronoi": "1.1.2",
|
||||
"d3-zoom": "1.7.1"
|
||||
}
|
||||
},
|
||||
"d3-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz",
|
||||
"integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw=="
|
||||
},
|
||||
"d3-axis": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.8.tgz",
|
||||
"integrity": "sha1-MacFoLU15ldZ3hQXOjGTMTfxjvo="
|
||||
},
|
||||
"d3-brush": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.4.tgz",
|
||||
"integrity": "sha1-AMLyOAGfJPbAoZSibUGhUw/+e8Q=",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-drag": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-selection": "1",
|
||||
"d3-transition": "1"
|
||||
}
|
||||
},
|
||||
"d3-chord": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.4.tgz",
|
||||
"integrity": "sha1-fexPC6iG9xP+ERxF92NBT290yiw=",
|
||||
"requires": {
|
||||
"d3-array": "1",
|
||||
"d3-path": "1"
|
||||
}
|
||||
},
|
||||
"d3-collection": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.4.tgz",
|
||||
"integrity": "sha1-NC39EoN8kJdPM/HMCnha6lcNzcI="
|
||||
},
|
||||
"d3-color": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.0.3.tgz",
|
||||
"integrity": "sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs="
|
||||
},
|
||||
"d3-dispatch": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz",
|
||||
"integrity": "sha1-RuFJHqqbWMNY/OW+TovtYm54cfg="
|
||||
},
|
||||
"d3-drag": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.1.tgz",
|
||||
"integrity": "sha512-Cg8/K2rTtzxzrb0fmnYOUeZHvwa4PHzwXOLZZPwtEs2SKLLKLXeYwZKBB+DlOxUvFmarOnmt//cU4+3US2lyyQ==",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-selection": "1"
|
||||
}
|
||||
},
|
||||
"d3-dsv": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.8.tgz",
|
||||
"integrity": "sha512-IVCJpQ+YGe3qu6odkPQI0KPqfxkhbP/oM1XhhE/DFiYmcXKfCRub4KXyiuehV1d4drjWVXHUWx4gHqhdZb6n/A==",
|
||||
"requires": {
|
||||
"commander": "2",
|
||||
"iconv-lite": "0.4",
|
||||
"rw": "1"
|
||||
}
|
||||
},
|
||||
"d3-ease": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz",
|
||||
"integrity": "sha1-aL+8NJM4o4DETYrMT7wzBKotjA4="
|
||||
},
|
||||
"d3-force": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.0.tgz",
|
||||
"integrity": "sha512-2HVQz3/VCQs0QeRNZTYb7GxoUCeb6bOzMp/cGcLa87awY9ZsPvXOGeZm0iaGBjXic6I1ysKwMn+g+5jSAdzwcg==",
|
||||
"requires": {
|
||||
"d3-collection": "1",
|
||||
"d3-dispatch": "1",
|
||||
"d3-quadtree": "1",
|
||||
"d3-timer": "1"
|
||||
}
|
||||
},
|
||||
"d3-format": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.2.2.tgz",
|
||||
"integrity": "sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw=="
|
||||
},
|
||||
"d3-geo": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.9.1.tgz",
|
||||
"integrity": "sha512-l9wL/cEQkyZQYXw3xbmLsH3eQ5ij+icNfo4r0GrLa5rOCZR/e/3am45IQ0FvQ5uMsv+77zBRunLc9ufTWSQYFA==",
|
||||
"requires": {
|
||||
"d3-array": "1"
|
||||
}
|
||||
},
|
||||
"d3-hexbin": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-hexbin/-/d3-hexbin-0.2.2.tgz",
|
||||
"integrity": "sha1-nFg32s/UcasFM3qeke8Qv8T5iDE="
|
||||
},
|
||||
"d3-hierarchy": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz",
|
||||
"integrity": "sha1-ochFxC+Eoga88cAcAQmOpN2qeiY="
|
||||
},
|
||||
"d3-interpolate": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.1.6.tgz",
|
||||
"integrity": "sha512-mOnv5a+pZzkNIHtw/V6I+w9Lqm9L5bG3OTXPM5A+QO0yyVMQ4W1uZhR+VOJmazaOZXri2ppbiZ5BUNWT0pFM9A==",
|
||||
"requires": {
|
||||
"d3-color": "1"
|
||||
}
|
||||
},
|
||||
"d3-path": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.5.tgz",
|
||||
"integrity": "sha1-JB6xhJvZ6egCHA0KeZ+KDo5EF2Q="
|
||||
},
|
||||
"d3-polygon": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.3.tgz",
|
||||
"integrity": "sha1-FoiOkCZGCTPysXllKtN4Ik04LGI="
|
||||
},
|
||||
"d3-quadtree": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz",
|
||||
"integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg="
|
||||
},
|
||||
"d3-queue": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz",
|
||||
"integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg="
|
||||
},
|
||||
"d3-random": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.0.tgz",
|
||||
"integrity": "sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM="
|
||||
},
|
||||
"d3-request": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-request/-/d3-request-1.0.6.tgz",
|
||||
"integrity": "sha512-FJj8ySY6GYuAJHZMaCQ83xEYE4KbkPkmxZ3Hu6zA1xxG2GD+z6P+Lyp+zjdsHf0xEbp2xcluDI50rCS855EQ6w==",
|
||||
"requires": {
|
||||
"d3-collection": "1",
|
||||
"d3-dispatch": "1",
|
||||
"d3-dsv": "1",
|
||||
"xmlhttprequest": "1"
|
||||
}
|
||||
},
|
||||
"d3-scale": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz",
|
||||
"integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==",
|
||||
"requires": {
|
||||
"d3-array": "^1.2.0",
|
||||
"d3-collection": "1",
|
||||
"d3-color": "1",
|
||||
"d3-format": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-time": "1",
|
||||
"d3-time-format": "2"
|
||||
}
|
||||
},
|
||||
"d3-selection": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.0.tgz",
|
||||
"integrity": "sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA=="
|
||||
},
|
||||
"d3-shape": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.0.tgz",
|
||||
"integrity": "sha1-RdAVOPBkuv0F6j1tLLdI/YxB93c=",
|
||||
"requires": {
|
||||
"d3-path": "1"
|
||||
}
|
||||
},
|
||||
"d3-time": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.8.tgz",
|
||||
"integrity": "sha512-YRZkNhphZh3KcnBfitvF3c6E0JOFGikHZ4YqD+Lzv83ZHn1/u6yGenRU1m+KAk9J1GnZMnKcrtfvSktlA1DXNQ=="
|
||||
},
|
||||
"d3-time-format": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.1.tgz",
|
||||
"integrity": "sha512-8kAkymq2WMfzW7e+s/IUNAtN/y3gZXGRrdGfo6R8NKPAA85UBTxZg5E61bR6nLwjPjj4d3zywSQe1CkYLPFyrw==",
|
||||
"requires": {
|
||||
"d3-time": "1"
|
||||
}
|
||||
},
|
||||
"d3-timer": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.7.tgz",
|
||||
"integrity": "sha512-vMZXR88XujmG/L5oB96NNKH5lCWwiLM/S2HyyAQLcjWJCloK5shxta4CwOFYLZoY3AWX73v8Lgv4cCAdWtRmOA=="
|
||||
},
|
||||
"d3-transition": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.1.tgz",
|
||||
"integrity": "sha512-xeg8oggyQ+y5eb4J13iDgKIjUcEfIOZs2BqV/eEmXm2twx80wTzJ4tB4vaZ5BKfz7XsI/DFmQL5me6O27/5ykQ==",
|
||||
"requires": {
|
||||
"d3-color": "1",
|
||||
"d3-dispatch": "1",
|
||||
"d3-ease": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-selection": "^1.1.0",
|
||||
"d3-timer": "1"
|
||||
}
|
||||
},
|
||||
"d3-voronoi": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz",
|
||||
"integrity": "sha1-Fodmfo8TotFYyAwUgMWinLDYlzw="
|
||||
},
|
||||
"d3-zoom": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.7.1.tgz",
|
||||
"integrity": "sha512-sZHQ55DGq5BZBFGnRshUT8tm2sfhPHFnOlmPbbwTkAoPeVdRTkB4Xsf9GCY0TSHrTD8PeJPZGmP/TpGicwJDJQ==",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-drag": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-selection": "1",
|
||||
"d3-transition": "1"
|
||||
}
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
|
@ -3700,7 +3960,7 @@
|
|||
},
|
||||
"diffie-hellman": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
||||
"integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -3764,7 +4024,7 @@
|
|||
"dependencies": {
|
||||
"domelementtype": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
|
||||
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -3969,7 +4229,7 @@
|
|||
},
|
||||
"entities": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
|
||||
"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -4392,7 +4652,7 @@
|
|||
},
|
||||
"eventemitter2": {
|
||||
"version": "0.4.14",
|
||||
"resolved": "http://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz",
|
||||
"integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -4404,7 +4664,7 @@
|
|||
},
|
||||
"events": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -4821,7 +5081,7 @@
|
|||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
|
||||
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -5057,7 +5317,7 @@
|
|||
},
|
||||
"fs-extra": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz",
|
||||
"integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -5726,7 +5986,7 @@
|
|||
},
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -5868,7 +6128,7 @@
|
|||
"dependencies": {
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -5945,7 +6205,7 @@
|
|||
},
|
||||
"grunt-cli": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz",
|
||||
"integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -5993,7 +6253,7 @@
|
|||
"dependencies": {
|
||||
"shelljs": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz",
|
||||
"integrity": "sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -6013,7 +6273,7 @@
|
|||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -6058,7 +6318,7 @@
|
|||
},
|
||||
"grunt-contrib-jshint": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "http://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz",
|
||||
"integrity": "sha1-Np2QmyWTxA6L55lAshNAhQx5Oaw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -6157,7 +6417,7 @@
|
|||
"dependencies": {
|
||||
"colors": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
|
||||
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -6221,7 +6481,7 @@
|
|||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -6538,7 +6798,7 @@
|
|||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
|
||||
"integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -6557,7 +6817,7 @@
|
|||
},
|
||||
"http-errors": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
||||
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -6607,7 +6867,7 @@
|
|||
},
|
||||
"http-proxy-middleware": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz",
|
||||
"integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -6689,7 +6949,6 @@
|
|||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
|
@ -7053,7 +7312,7 @@
|
|||
},
|
||||
"is-builtin-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
|
||||
"integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -7614,7 +7873,7 @@
|
|||
},
|
||||
"jsonfile": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
|
||||
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -7725,7 +7984,7 @@
|
|||
},
|
||||
"kew": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "http://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
|
||||
"integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -7844,7 +8103,7 @@
|
|||
},
|
||||
"load-json-file": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
||||
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -7857,7 +8116,7 @@
|
|||
"dependencies": {
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -8221,7 +8480,7 @@
|
|||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -8280,7 +8539,7 @@
|
|||
},
|
||||
"meow": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
||||
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -8501,7 +8760,7 @@
|
|||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
|
@ -8542,7 +8801,7 @@
|
|||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
|
@ -8711,7 +8970,7 @@
|
|||
},
|
||||
"ncp": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "http://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz",
|
||||
"integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -8810,7 +9069,7 @@
|
|||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -8993,7 +9252,7 @@
|
|||
"dependencies": {
|
||||
"colors": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
|
||||
"integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q="
|
||||
},
|
||||
"underscore": {
|
||||
|
@ -9287,13 +9546,13 @@
|
|||
},
|
||||
"os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
|
||||
"dev": true
|
||||
},
|
||||
"os-locale": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
|
||||
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -9302,7 +9561,7 @@
|
|||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -9526,7 +9785,7 @@
|
|||
},
|
||||
"parse-asn1": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
|
||||
"integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -9612,7 +9871,7 @@
|
|||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -9653,7 +9912,7 @@
|
|||
"dependencies": {
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -9836,7 +10095,7 @@
|
|||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -10207,7 +10466,7 @@
|
|||
},
|
||||
"progress": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
|
||||
"integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74="
|
||||
},
|
||||
"promise-inflight": {
|
||||
|
@ -10232,13 +10491,13 @@
|
|||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
|
||||
"integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=",
|
||||
"dev": true
|
||||
},
|
||||
"winston": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "http://registry.npmjs.org/winston/-/winston-2.1.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz",
|
||||
"integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -10253,7 +10512,7 @@
|
|||
"dependencies": {
|
||||
"colors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
|
||||
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -10476,7 +10735,7 @@
|
|||
"dependencies": {
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -10665,7 +10924,7 @@
|
|||
"dependencies": {
|
||||
"jsesc": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
|
||||
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
|
||||
"dev": true
|
||||
}
|
||||
|
@ -10716,7 +10975,7 @@
|
|||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz",
|
||||
"integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -10728,7 +10987,7 @@
|
|||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.34",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -10973,6 +11232,11 @@
|
|||
"aproba": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
"integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
|
||||
|
@ -10995,7 +11259,7 @@
|
|||
},
|
||||
"safe-regex": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
|
||||
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -11005,8 +11269,7 @@
|
|||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sanitize-html": {
|
||||
"version": "1.19.1",
|
||||
|
@ -11315,7 +11578,7 @@
|
|||
},
|
||||
"sha.js": {
|
||||
"version": "2.4.11",
|
||||
"resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -11359,7 +11622,7 @@
|
|||
},
|
||||
"shelljs": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
|
||||
"integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -12080,7 +12343,7 @@
|
|||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
|
@ -12097,7 +12360,7 @@
|
|||
},
|
||||
"strip-eof": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
|
||||
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -12190,7 +12453,7 @@
|
|||
},
|
||||
"tar": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
|
||||
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -12348,7 +12611,7 @@
|
|||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -13008,7 +13271,7 @@
|
|||
"dependencies": {
|
||||
"async": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
|
||||
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -13034,7 +13297,7 @@
|
|||
},
|
||||
"valid-data-url": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "http://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.6.tgz",
|
||||
"resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.6.tgz",
|
||||
"integrity": "sha512-FXg2qXMzfAhZc0y2HzELNfUeiOjPr+52hU1DNBWiJJ2luXD+dD1R9NA48Ug5aj0ibbxroeGDc/RJv6ThiGgkDw==",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -13050,7 +13313,7 @@
|
|||
},
|
||||
"validator": {
|
||||
"version": "9.4.1",
|
||||
"resolved": "http://registry.npmjs.org/validator/-/validator-9.4.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz",
|
||||
"integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -13582,7 +13845,7 @@
|
|||
},
|
||||
"webpack-node-externals": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "http://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz",
|
||||
"integrity": "sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg==",
|
||||
"dev": true
|
||||
},
|
||||
|
@ -13736,14 +13999,14 @@
|
|||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
|
||||
"integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"colors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
|
||||
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
|
@ -13776,7 +14039,7 @@
|
|||
},
|
||||
"wrap-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -13885,6 +14148,11 @@
|
|||
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
|
||||
"integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk="
|
||||
},
|
||||
"xmlhttprequest": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
|
||||
"integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw="
|
||||
},
|
||||
"xpath": {
|
||||
"version": "0.0.27",
|
||||
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz",
|
||||
|
|
177
src/core/lib/Charts.mjs
Normal file
177
src/core/lib/Charts.mjs
Normal file
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
* @author tlwr [toby@toby.codes] - Original
|
||||
* @author Matt C [matt@artemisbot.uk] - Conversion to new format
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
export const RECORD_DELIMITER_OPTIONS = ["Line feed", "CRLF"];
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
export const FIELD_DELIMITER_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Tab"];
|
||||
|
||||
|
||||
/**
|
||||
* Default from colour
|
||||
*
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
export const COLOURS = {
|
||||
min: "white",
|
||||
max: "black"
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded, length) {
|
||||
let headings;
|
||||
const values = [];
|
||||
|
||||
input
|
||||
.split(recordDelimiter)
|
||||
.forEach((row, rowIndex) => {
|
||||
const split = row.split(fieldDelimiter);
|
||||
|
||||
if (split.length !== length) throw new OperationError(`Each row must have length ${length}.`);
|
||||
|
||||
if (columnHeadingsAreIncluded && rowIndex === 0) {
|
||||
headings = split;
|
||||
} else {
|
||||
values.push(split);
|
||||
}
|
||||
});
|
||||
|
||||
return { headings, values};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a scatter plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
let { headings, values } = getValues(
|
||||
input,
|
||||
recordDelimiter, fieldDelimiter,
|
||||
columnHeadingsAreIncluded,
|
||||
2
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
headings = {x: headings[0], y: headings[1]};
|
||||
}
|
||||
|
||||
values = values.map(row => {
|
||||
const x = parseFloat(row[0], 10),
|
||||
y = parseFloat(row[1], 10);
|
||||
|
||||
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
|
||||
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
|
||||
|
||||
return [x, y];
|
||||
});
|
||||
|
||||
return { headings, values };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets values from input for a scatter plot with colour from the third column.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getScatterValuesWithColour(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
let { headings, values } = getValues(
|
||||
input,
|
||||
recordDelimiter, fieldDelimiter,
|
||||
columnHeadingsAreIncluded,
|
||||
3
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
headings = {x: headings[0], y: headings[1]};
|
||||
}
|
||||
|
||||
values = values.map(row => {
|
||||
const x = parseFloat(row[0], 10),
|
||||
y = parseFloat(row[1], 10),
|
||||
colour = row[2];
|
||||
|
||||
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
|
||||
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
|
||||
|
||||
return [x, y, colour];
|
||||
});
|
||||
|
||||
return { headings, values };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets values from input for a time series plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
const { values } = getValues(
|
||||
input,
|
||||
recordDelimiter, fieldDelimiter,
|
||||
false,
|
||||
3
|
||||
);
|
||||
|
||||
let xValues = new Set();
|
||||
const series = {};
|
||||
|
||||
values.forEach(row => {
|
||||
const serie = row[0],
|
||||
xVal = row[1],
|
||||
val = parseFloat(row[2], 10);
|
||||
|
||||
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
|
||||
|
||||
xValues.add(xVal);
|
||||
if (typeof series[serie] === "undefined") series[serie] = {};
|
||||
series[serie][xVal] = val;
|
||||
});
|
||||
|
||||
xValues = new Array(...xValues);
|
||||
|
||||
const seriesList = [];
|
||||
for (const seriesName in series) {
|
||||
const serie = series[seriesName];
|
||||
seriesList.push({name: seriesName, data: serie});
|
||||
}
|
||||
|
||||
return { xValues, series: seriesList };
|
||||
}
|
|
@ -1,841 +0,0 @@
|
|||
import * as d3 from "d3";
|
||||
import {hexbin as d3hexbin} from "d3-hexbin";
|
||||
import Utils from "../Utils.js";
|
||||
|
||||
/**
|
||||
* Charting operations.
|
||||
*
|
||||
* @author tlwr [toby@toby.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const Charts = {
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
RECORD_DELIMITER_OPTIONS: ["Line feed", "CRLF"],
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
FIELD_DELIMITER_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Tab"],
|
||||
|
||||
|
||||
/**
|
||||
* Default from colour
|
||||
*
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
COLOURS: {
|
||||
min: "white",
|
||||
max: "black",
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
_getValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded, length) {
|
||||
let headings;
|
||||
const values = [];
|
||||
|
||||
input
|
||||
.split(recordDelimiter)
|
||||
.forEach((row, rowIndex) => {
|
||||
let split = row.split(fieldDelimiter);
|
||||
|
||||
if (split.length !== length) throw `Each row must have length ${length}.`;
|
||||
|
||||
if (columnHeadingsAreIncluded && rowIndex === 0) {
|
||||
headings = split;
|
||||
} else {
|
||||
values.push(split);
|
||||
}
|
||||
});
|
||||
|
||||
return { headings, values};
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a scatter plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
_getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
let { headings, values } = Charts._getValues(
|
||||
input,
|
||||
recordDelimiter, fieldDelimiter,
|
||||
columnHeadingsAreIncluded,
|
||||
2
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
headings = {x: headings[0], y: headings[1]};
|
||||
}
|
||||
|
||||
values = values.map(row => {
|
||||
let x = parseFloat(row[0], 10),
|
||||
y = parseFloat(row[1], 10);
|
||||
|
||||
if (Number.isNaN(x)) throw "Values must be numbers in base 10.";
|
||||
if (Number.isNaN(y)) throw "Values must be numbers in base 10.";
|
||||
|
||||
return [x, y];
|
||||
});
|
||||
|
||||
return { headings, values };
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a scatter plot with colour from the third column.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
_getScatterValuesWithColour(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
let { headings, values } = Charts._getValues(
|
||||
input,
|
||||
recordDelimiter, fieldDelimiter,
|
||||
columnHeadingsAreIncluded,
|
||||
3
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
headings = {x: headings[0], y: headings[1]};
|
||||
}
|
||||
|
||||
values = values.map(row => {
|
||||
let x = parseFloat(row[0], 10),
|
||||
y = parseFloat(row[1], 10),
|
||||
colour = row[2];
|
||||
|
||||
if (Number.isNaN(x)) throw "Values must be numbers in base 10.";
|
||||
if (Number.isNaN(y)) throw "Values must be numbers in base 10.";
|
||||
|
||||
return [x, y, colour];
|
||||
});
|
||||
|
||||
return { headings, values };
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a time series plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
_getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
let { headings, values } = Charts._getValues(
|
||||
input,
|
||||
recordDelimiter, fieldDelimiter,
|
||||
false,
|
||||
3
|
||||
);
|
||||
|
||||
let xValues = new Set(),
|
||||
series = {};
|
||||
|
||||
values = values.forEach(row => {
|
||||
let serie = row[0],
|
||||
xVal = row[1],
|
||||
val = parseFloat(row[2], 10);
|
||||
|
||||
if (Number.isNaN(val)) throw "Values must be numbers in base 10.";
|
||||
|
||||
xValues.add(xVal);
|
||||
if (typeof series[serie] === "undefined") series[serie] = {};
|
||||
series[serie][xVal] = val;
|
||||
});
|
||||
|
||||
xValues = new Array(...xValues);
|
||||
|
||||
const seriesList = [];
|
||||
for (let seriesName in series) {
|
||||
let serie = series[seriesName];
|
||||
seriesList.push({name: seriesName, data: serie});
|
||||
}
|
||||
|
||||
return { xValues, series: seriesList };
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Hex Bin chart operation.
|
||||
*
|
||||
* @param {Object[]} - centres
|
||||
* @param {number} - radius
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
_getEmptyHexagons(centres, radius) {
|
||||
const emptyCentres = [];
|
||||
let boundingRect = [d3.extent(centres, d => d.x), d3.extent(centres, d => d.y)],
|
||||
indent = false,
|
||||
hexagonCenterToEdge = Math.cos(2 * Math.PI / 12) * radius,
|
||||
hexagonEdgeLength = Math.sin(2 * Math.PI / 12) * radius;
|
||||
|
||||
for (let y = boundingRect[1][0]; y <= boundingRect[1][1] + radius; y += hexagonEdgeLength + radius) {
|
||||
for (let x = boundingRect[0][0]; x <= boundingRect[0][1] + radius; x += 2 * hexagonCenterToEdge) {
|
||||
let cx = x,
|
||||
cy = y;
|
||||
|
||||
if (indent && x >= boundingRect[0][1]) break;
|
||||
if (indent) cx += hexagonCenterToEdge;
|
||||
|
||||
emptyCentres.push({x: cx, y: cy});
|
||||
}
|
||||
indent = !indent;
|
||||
}
|
||||
|
||||
return emptyCentres;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Hex Bin chart operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
runHexDensityChart: function (input, args) {
|
||||
const recordDelimiter = Utils.charRep[args[0]],
|
||||
fieldDelimiter = Utils.charRep[args[1]],
|
||||
packRadius = args[2],
|
||||
drawRadius = args[3],
|
||||
columnHeadingsAreIncluded = args[4],
|
||||
drawEdges = args[7],
|
||||
minColour = args[8],
|
||||
maxColour = args[9],
|
||||
drawEmptyHexagons = args[10],
|
||||
dimension = 500;
|
||||
|
||||
let xLabel = args[5],
|
||||
yLabel = args[6],
|
||||
{ headings, values } = Charts._getScatterValues(
|
||||
input,
|
||||
recordDelimiter,
|
||||
fieldDelimiter,
|
||||
columnHeadingsAreIncluded
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
xLabel = headings.x;
|
||||
yLabel = headings.y;
|
||||
}
|
||||
|
||||
let svg = document.createElement("svg");
|
||||
svg = d3.select(svg)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("viewBox", `0 0 ${dimension} ${dimension}`);
|
||||
|
||||
let margin = {
|
||||
top: 10,
|
||||
right: 0,
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
},
|
||||
width = dimension - margin.left - margin.right,
|
||||
height = dimension - margin.top - margin.bottom,
|
||||
marginedSpace = svg.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
let hexbin = d3hexbin()
|
||||
.radius(packRadius)
|
||||
.extent([0, 0], [width, height]);
|
||||
|
||||
let hexPoints = hexbin(values),
|
||||
maxCount = Math.max(...hexPoints.map(b => b.length));
|
||||
|
||||
let xExtent = d3.extent(hexPoints, d => d.x),
|
||||
yExtent = d3.extent(hexPoints, d => d.y);
|
||||
xExtent[0] -= 2 * packRadius;
|
||||
xExtent[1] += 3 * packRadius;
|
||||
yExtent[0] -= 2 * packRadius;
|
||||
yExtent[1] += 2 * packRadius;
|
||||
|
||||
let xAxis = d3.scaleLinear()
|
||||
.domain(xExtent)
|
||||
.range([0, width]);
|
||||
let yAxis = d3.scaleLinear()
|
||||
.domain(yExtent)
|
||||
.range([height, 0]);
|
||||
|
||||
let colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour))
|
||||
.domain([0, maxCount]);
|
||||
|
||||
marginedSpace.append("clipPath")
|
||||
.attr("id", "clip")
|
||||
.append("rect")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
if (drawEmptyHexagons) {
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "empty-hexagon")
|
||||
.selectAll("path")
|
||||
.data(Charts._getEmptyHexagons(hexPoints, packRadius))
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("d", d => {
|
||||
return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`;
|
||||
})
|
||||
.attr("fill", (d) => colour(0))
|
||||
.attr("stroke", drawEdges ? "black" : "none")
|
||||
.attr("stroke-width", drawEdges ? "0.5" : "none")
|
||||
.append("title")
|
||||
.text(d => {
|
||||
let count = 0,
|
||||
perc = 0,
|
||||
tooltip = `Count: ${count}\n
|
||||
Percentage: ${perc.toFixed(2)}%\n
|
||||
Center: ${d.x.toFixed(2)}, ${d.y.toFixed(2)}\n
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
return tooltip;
|
||||
});
|
||||
}
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "hexagon")
|
||||
.attr("clip-path", "url(#clip)")
|
||||
.selectAll("path")
|
||||
.data(hexPoints)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("d", d => {
|
||||
return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`;
|
||||
})
|
||||
.attr("fill", (d) => colour(d.length))
|
||||
.attr("stroke", drawEdges ? "black" : "none")
|
||||
.attr("stroke-width", drawEdges ? "0.5" : "none")
|
||||
.append("title")
|
||||
.text(d => {
|
||||
let count = d.length,
|
||||
perc = 100.0 * d.length / values.length,
|
||||
CX = d.x,
|
||||
CY = d.y,
|
||||
xMin = Math.min(...d.map(d => d[0])),
|
||||
xMax = Math.max(...d.map(d => d[0])),
|
||||
yMin = Math.min(...d.map(d => d[1])),
|
||||
yMax = Math.max(...d.map(d => d[1])),
|
||||
tooltip = `Count: ${count}\n
|
||||
Percentage: ${perc.toFixed(2)}%\n
|
||||
Center: ${CX.toFixed(2)}, ${CY.toFixed(2)}\n
|
||||
Min X: ${xMin.toFixed(2)}\n
|
||||
Max X: ${xMax.toFixed(2)}\n
|
||||
Min Y: ${yMin.toFixed(2)}\n
|
||||
Max Y: ${yMax.toFixed(2)}
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
return tooltip;
|
||||
});
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--y")
|
||||
.call(d3.axisLeft(yAxis).tickSizeOuter(-width));
|
||||
|
||||
svg.append("text")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", -margin.left)
|
||||
.attr("x", -(height / 2))
|
||||
.attr("dy", "1em")
|
||||
.style("text-anchor", "middle")
|
||||
.text(yLabel);
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--x")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.call(d3.axisBottom(xAxis).tickSizeOuter(-height));
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", width / 2)
|
||||
.attr("y", dimension)
|
||||
.style("text-anchor", "middle")
|
||||
.text(xLabel);
|
||||
|
||||
return svg._groups[0][0].outerHTML;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Packs a list of x, y coordinates into a number of bins for use in a heatmap.
|
||||
*
|
||||
* @param {Object[]} points
|
||||
* @param {number} number of vertical bins
|
||||
* @param {number} number of horizontal bins
|
||||
* @returns {Object[]} a list of bins (each bin is an Array) with x y coordinates, filled with the points
|
||||
*/
|
||||
_getHeatmapPacking(values, vBins, hBins) {
|
||||
const xBounds = d3.extent(values, d => d[0]),
|
||||
yBounds = d3.extent(values, d => d[1]),
|
||||
bins = [];
|
||||
|
||||
if (xBounds[0] === xBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum X coordinate.";
|
||||
if (yBounds[0] === yBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum Y coordinate.";
|
||||
|
||||
for (let y = 0; y < vBins; y++) {
|
||||
bins.push([]);
|
||||
for (let x = 0; x < hBins; x++) {
|
||||
let item = [];
|
||||
item.y = y;
|
||||
item.x = x;
|
||||
|
||||
bins[y].push(item);
|
||||
} // x
|
||||
} // y
|
||||
|
||||
let epsilon = 0.000000001; // This is to clamp values that are exactly the maximum;
|
||||
|
||||
values.forEach(v => {
|
||||
let fractionOfY = (v[1] - yBounds[0]) / ((yBounds[1] + epsilon) - yBounds[0]),
|
||||
fractionOfX = (v[0] - xBounds[0]) / ((xBounds[1] + epsilon) - xBounds[0]);
|
||||
let y = Math.floor(vBins * fractionOfY),
|
||||
x = Math.floor(hBins * fractionOfX);
|
||||
|
||||
bins[y][x].push({x: v[0], y: v[1]});
|
||||
});
|
||||
|
||||
return bins;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Heatmap chart operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
runHeatmapChart: function (input, args) {
|
||||
const recordDelimiter = Utils.charRep[args[0]],
|
||||
fieldDelimiter = Utils.charRep[args[1]],
|
||||
vBins = args[2],
|
||||
hBins = args[3],
|
||||
columnHeadingsAreIncluded = args[4],
|
||||
drawEdges = args[7],
|
||||
minColour = args[8],
|
||||
maxColour = args[9],
|
||||
dimension = 500;
|
||||
|
||||
if (vBins <= 0) throw "Number of vertical bins must be greater than 0";
|
||||
if (hBins <= 0) throw "Number of horizontal bins must be greater than 0";
|
||||
|
||||
let xLabel = args[5],
|
||||
yLabel = args[6],
|
||||
{ headings, values } = Charts._getScatterValues(
|
||||
input,
|
||||
recordDelimiter,
|
||||
fieldDelimiter,
|
||||
columnHeadingsAreIncluded
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
xLabel = headings.x;
|
||||
yLabel = headings.y;
|
||||
}
|
||||
|
||||
let svg = document.createElement("svg");
|
||||
svg = d3.select(svg)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("viewBox", `0 0 ${dimension} ${dimension}`);
|
||||
|
||||
let margin = {
|
||||
top: 10,
|
||||
right: 0,
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
},
|
||||
width = dimension - margin.left - margin.right,
|
||||
height = dimension - margin.top - margin.bottom,
|
||||
binWidth = width / hBins,
|
||||
binHeight = height/ vBins,
|
||||
marginedSpace = svg.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
let bins = Charts._getHeatmapPacking(values, vBins, hBins),
|
||||
maxCount = Math.max(...bins.map(row => {
|
||||
let lengths = row.map(cell => cell.length);
|
||||
return Math.max(...lengths);
|
||||
}));
|
||||
|
||||
let xExtent = d3.extent(values, d => d[0]),
|
||||
yExtent = d3.extent(values, d => d[1]);
|
||||
|
||||
let xAxis = d3.scaleLinear()
|
||||
.domain(xExtent)
|
||||
.range([0, width]);
|
||||
let yAxis = d3.scaleLinear()
|
||||
.domain(yExtent)
|
||||
.range([height, 0]);
|
||||
|
||||
let colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour))
|
||||
.domain([0, maxCount]);
|
||||
|
||||
marginedSpace.append("clipPath")
|
||||
.attr("id", "clip")
|
||||
.append("rect")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "bins")
|
||||
.attr("clip-path", "url(#clip)")
|
||||
.selectAll("g")
|
||||
.data(bins)
|
||||
.enter()
|
||||
.append("g")
|
||||
.selectAll("rect")
|
||||
.data(d => d)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("x", (d) => binWidth * d.x)
|
||||
.attr("y", (d) => (height - binHeight * (d.y + 1)))
|
||||
.attr("width", binWidth)
|
||||
.attr("height", binHeight)
|
||||
.attr("fill", (d) => colour(d.length))
|
||||
.attr("stroke", drawEdges ? "rgba(0, 0, 0, 0.5)" : "none")
|
||||
.attr("stroke-width", drawEdges ? "0.5" : "none")
|
||||
.append("title")
|
||||
.text(d => {
|
||||
let count = d.length,
|
||||
perc = 100.0 * d.length / values.length,
|
||||
tooltip = `Count: ${count}\n
|
||||
Percentage: ${perc.toFixed(2)}%\n
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
return tooltip;
|
||||
});
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--y")
|
||||
.call(d3.axisLeft(yAxis).tickSizeOuter(-width));
|
||||
|
||||
svg.append("text")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", -margin.left)
|
||||
.attr("x", -(height / 2))
|
||||
.attr("dy", "1em")
|
||||
.style("text-anchor", "middle")
|
||||
.text(yLabel);
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--x")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.call(d3.axisBottom(xAxis).tickSizeOuter(-height));
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", width / 2)
|
||||
.attr("y", dimension)
|
||||
.style("text-anchor", "middle")
|
||||
.text(xLabel);
|
||||
|
||||
return svg._groups[0][0].outerHTML;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Scatter chart operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
runScatterChart: function (input, args) {
|
||||
const recordDelimiter = Utils.charRep[args[0]],
|
||||
fieldDelimiter = Utils.charRep[args[1]],
|
||||
columnHeadingsAreIncluded = args[2],
|
||||
fillColour = args[5],
|
||||
radius = args[6],
|
||||
colourInInput = args[7],
|
||||
dimension = 500;
|
||||
|
||||
let xLabel = args[3],
|
||||
yLabel = args[4];
|
||||
|
||||
let dataFunction = colourInInput ? Charts._getScatterValuesWithColour : Charts._getScatterValues;
|
||||
|
||||
let { headings, values } = dataFunction(
|
||||
input,
|
||||
recordDelimiter,
|
||||
fieldDelimiter,
|
||||
columnHeadingsAreIncluded
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
xLabel = headings.x;
|
||||
yLabel = headings.y;
|
||||
}
|
||||
|
||||
let svg = document.createElement("svg");
|
||||
svg = d3.select(svg)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("viewBox", `0 0 ${dimension} ${dimension}`);
|
||||
|
||||
let margin = {
|
||||
top: 10,
|
||||
right: 0,
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
},
|
||||
width = dimension - margin.left - margin.right,
|
||||
height = dimension - margin.top - margin.bottom,
|
||||
marginedSpace = svg.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
let xExtent = d3.extent(values, d => d[0]),
|
||||
xDelta = xExtent[1] - xExtent[0],
|
||||
yExtent = d3.extent(values, d => d[1]),
|
||||
yDelta = yExtent[1] - yExtent[0],
|
||||
xAxis = d3.scaleLinear()
|
||||
.domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)])
|
||||
.range([0, width]),
|
||||
yAxis = d3.scaleLinear()
|
||||
.domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)])
|
||||
.range([height, 0]);
|
||||
|
||||
marginedSpace.append("clipPath")
|
||||
.attr("id", "clip")
|
||||
.append("rect")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "points")
|
||||
.attr("clip-path", "url(#clip)")
|
||||
.selectAll("circle")
|
||||
.data(values)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("cx", (d) => xAxis(d[0]))
|
||||
.attr("cy", (d) => yAxis(d[1]))
|
||||
.attr("r", d => radius)
|
||||
.attr("fill", d => {
|
||||
return colourInInput ? d[2] : fillColour;
|
||||
})
|
||||
.attr("stroke", "rgba(0, 0, 0, 0.5)")
|
||||
.attr("stroke-width", "0.5")
|
||||
.append("title")
|
||||
.text(d => {
|
||||
let x = d[0],
|
||||
y = d[1],
|
||||
tooltip = `X: ${x}\n
|
||||
Y: ${y}\n
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
return tooltip;
|
||||
});
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--y")
|
||||
.call(d3.axisLeft(yAxis).tickSizeOuter(-width));
|
||||
|
||||
svg.append("text")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", -margin.left)
|
||||
.attr("x", -(height / 2))
|
||||
.attr("dy", "1em")
|
||||
.style("text-anchor", "middle")
|
||||
.text(yLabel);
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--x")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.call(d3.axisBottom(xAxis).tickSizeOuter(-height));
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", width / 2)
|
||||
.attr("y", dimension)
|
||||
.style("text-anchor", "middle")
|
||||
.text(xLabel);
|
||||
|
||||
return svg._groups[0][0].outerHTML;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Series chart operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
runSeriesChart(input, args) {
|
||||
const recordDelimiter = Utils.charRep[args[0]],
|
||||
fieldDelimiter = Utils.charRep[args[1]],
|
||||
xLabel = args[2],
|
||||
pipRadius = args[3],
|
||||
seriesColours = args[4].split(","),
|
||||
svgWidth = 500,
|
||||
interSeriesPadding = 20,
|
||||
xAxisHeight = 50,
|
||||
seriesLabelWidth = 50,
|
||||
seriesHeight = 100,
|
||||
seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding;
|
||||
|
||||
let { xValues, series } = Charts._getSeriesValues(input, recordDelimiter, fieldDelimiter),
|
||||
allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight),
|
||||
svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding;
|
||||
|
||||
let svg = document.createElement("svg");
|
||||
svg = d3.select(svg)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
|
||||
|
||||
let xAxis = d3.scalePoint()
|
||||
.domain(xValues)
|
||||
.range([0, seriesWidth]);
|
||||
|
||||
svg.append("g")
|
||||
.attr("class", "axis axis--x")
|
||||
.attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`)
|
||||
.call(
|
||||
d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => {
|
||||
return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0;
|
||||
}))
|
||||
);
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", svgWidth / 2)
|
||||
.attr("y", xAxisHeight / 2)
|
||||
.style("text-anchor", "middle")
|
||||
.text(xLabel);
|
||||
|
||||
let tooltipText = {},
|
||||
tooltipAreaWidth = seriesWidth / xValues.length;
|
||||
|
||||
xValues.forEach(x => {
|
||||
let tooltip = [];
|
||||
|
||||
series.forEach(serie => {
|
||||
let y = serie.data[x];
|
||||
if (typeof y === "undefined") return;
|
||||
|
||||
tooltip.push(`${serie.name}: ${y}`);
|
||||
});
|
||||
|
||||
tooltipText[x] = tooltip.join("\n");
|
||||
});
|
||||
|
||||
let chartArea = svg.append("g")
|
||||
.attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`);
|
||||
|
||||
chartArea
|
||||
.append("g")
|
||||
.selectAll("rect")
|
||||
.data(xValues)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("x", x => {
|
||||
return xAxis(x) - (tooltipAreaWidth / 2);
|
||||
})
|
||||
.attr("y", 0)
|
||||
.attr("width", tooltipAreaWidth)
|
||||
.attr("height", allSeriesHeight)
|
||||
.attr("stroke", "none")
|
||||
.attr("fill", "transparent")
|
||||
.append("title")
|
||||
.text(x => {
|
||||
return `${x}\n
|
||||
--\n
|
||||
${tooltipText[x]}\n
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
});
|
||||
|
||||
let yAxesArea = svg.append("g")
|
||||
.attr("transform", `translate(0, ${xAxisHeight})`);
|
||||
|
||||
series.forEach((serie, seriesIndex) => {
|
||||
let yExtent = d3.extent(Object.values(serie.data)),
|
||||
yAxis = d3.scaleLinear()
|
||||
.domain(yExtent)
|
||||
.range([seriesHeight, 0]);
|
||||
|
||||
let seriesGroup = chartArea
|
||||
.append("g")
|
||||
.attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`);
|
||||
|
||||
let path = "";
|
||||
xValues.forEach((x, xIndex) => {
|
||||
let nextX = xValues[xIndex + 1],
|
||||
y = serie.data[x],
|
||||
nextY= serie.data[nextX];
|
||||
|
||||
if (typeof y === "undefined" || typeof nextY === "undefined") return;
|
||||
|
||||
x = xAxis(x); nextX = xAxis(nextX);
|
||||
y = yAxis(y); nextY = yAxis(nextY);
|
||||
|
||||
path += `M ${x} ${y} L ${nextX} ${nextY} z `;
|
||||
});
|
||||
|
||||
seriesGroup
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", seriesColours[seriesIndex % seriesColours.length])
|
||||
.attr("stroke-width", "1");
|
||||
|
||||
xValues.forEach(x => {
|
||||
let y = serie.data[x];
|
||||
if (typeof y === "undefined") return;
|
||||
|
||||
seriesGroup
|
||||
.append("circle")
|
||||
.attr("cx", xAxis(x))
|
||||
.attr("cy", yAxis(y))
|
||||
.attr("r", pipRadius)
|
||||
.attr("fill", seriesColours[seriesIndex % seriesColours.length])
|
||||
.append("title")
|
||||
.text(d => {
|
||||
return `${x}\n
|
||||
--\n
|
||||
${tooltipText[x]}\n
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
});
|
||||
});
|
||||
|
||||
yAxesArea
|
||||
.append("g")
|
||||
.attr("transform", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`)
|
||||
.attr("class", "axis axis--y")
|
||||
.call(d3.axisLeft(yAxis).ticks(5));
|
||||
|
||||
yAxesArea
|
||||
.append("g")
|
||||
.attr("transform", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`)
|
||||
.append("text")
|
||||
.style("text-anchor", "middle")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.text(serie.name);
|
||||
});
|
||||
|
||||
return svg._groups[0][0].outerHTML;
|
||||
},
|
||||
};
|
||||
|
||||
export default Charts;
|
260
src/core/operations/HeatmapChart.mjs
Normal file
260
src/core/operations/HeatmapChart.mjs
Normal file
|
@ -0,0 +1,260 @@
|
|||
/**
|
||||
* @author tlwr [toby@toby.codes]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import * as d3 from "d3";
|
||||
import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts";
|
||||
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import Utils from "../Utils";
|
||||
|
||||
/**
|
||||
* Heatmap chart operation
|
||||
*/
|
||||
class HeatmapChart extends Operation {
|
||||
|
||||
/**
|
||||
* HeatmapChart constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Heatmap chart";
|
||||
this.module = "Charts";
|
||||
this.description = "";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Record delimiter",
|
||||
type: "option",
|
||||
value: RECORD_DELIMITER_OPTIONS,
|
||||
},
|
||||
{
|
||||
name: "Field delimiter",
|
||||
type: "option",
|
||||
value: FIELD_DELIMITER_OPTIONS,
|
||||
},
|
||||
{
|
||||
name: "Number of vertical bins",
|
||||
type: "number",
|
||||
value: 25,
|
||||
},
|
||||
{
|
||||
name: "Number of horizontal bins",
|
||||
type: "number",
|
||||
value: 25,
|
||||
},
|
||||
{
|
||||
name: "Use column headers as labels",
|
||||
type: "boolean",
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: "X label",
|
||||
type: "string",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "Y label",
|
||||
type: "string",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "Draw bin edges",
|
||||
type: "boolean",
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: "Min colour value",
|
||||
type: "string",
|
||||
value: COLOURS.min,
|
||||
},
|
||||
{
|
||||
name: "Max colour value",
|
||||
type: "string",
|
||||
value: COLOURS.max,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
run(input, args) {
|
||||
const recordDelimiter = Utils.charRep[args[0]],
|
||||
fieldDelimiter = Utils.charRep[args[1]],
|
||||
vBins = args[2],
|
||||
hBins = args[3],
|
||||
columnHeadingsAreIncluded = args[4],
|
||||
drawEdges = args[7],
|
||||
minColour = args[8],
|
||||
maxColour = args[9],
|
||||
dimension = 500;
|
||||
|
||||
if (vBins <= 0) throw new OperationError("Number of vertical bins must be greater than 0");
|
||||
if (hBins <= 0) throw new OperationError("Number of horizontal bins must be greater than 0");
|
||||
|
||||
let xLabel = args[5],
|
||||
yLabel = args[6];
|
||||
const { headings, values } = getScatterValues(
|
||||
input,
|
||||
recordDelimiter,
|
||||
fieldDelimiter,
|
||||
columnHeadingsAreIncluded
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
xLabel = headings.x;
|
||||
yLabel = headings.y;
|
||||
}
|
||||
|
||||
let svg = document.createElement("svg");
|
||||
svg = d3.select(svg)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("viewBox", `0 0 ${dimension} ${dimension}`);
|
||||
|
||||
const margin = {
|
||||
top: 10,
|
||||
right: 0,
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
},
|
||||
width = dimension - margin.left - margin.right,
|
||||
height = dimension - margin.top - margin.bottom,
|
||||
binWidth = width / hBins,
|
||||
binHeight = height/ vBins,
|
||||
marginedSpace = svg.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
const bins = this.getHeatmapPacking(values, vBins, hBins),
|
||||
maxCount = Math.max(...bins.map(row => {
|
||||
const lengths = row.map(cell => cell.length);
|
||||
return Math.max(...lengths);
|
||||
}));
|
||||
|
||||
const xExtent = d3.extent(values, d => d[0]),
|
||||
yExtent = d3.extent(values, d => d[1]);
|
||||
|
||||
const xAxis = d3.scaleLinear()
|
||||
.domain(xExtent)
|
||||
.range([0, width]);
|
||||
const yAxis = d3.scaleLinear()
|
||||
.domain(yExtent)
|
||||
.range([height, 0]);
|
||||
|
||||
const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour))
|
||||
.domain([0, maxCount]);
|
||||
|
||||
marginedSpace.append("clipPath")
|
||||
.attr("id", "clip")
|
||||
.append("rect")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "bins")
|
||||
.attr("clip-path", "url(#clip)")
|
||||
.selectAll("g")
|
||||
.data(bins)
|
||||
.enter()
|
||||
.append("g")
|
||||
.selectAll("rect")
|
||||
.data(d => d)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("x", (d) => binWidth * d.x)
|
||||
.attr("y", (d) => (height - binHeight * (d.y + 1)))
|
||||
.attr("width", binWidth)
|
||||
.attr("height", binHeight)
|
||||
.attr("fill", (d) => colour(d.length))
|
||||
.attr("stroke", drawEdges ? "rgba(0, 0, 0, 0.5)" : "none")
|
||||
.attr("stroke-width", drawEdges ? "0.5" : "none")
|
||||
.append("title")
|
||||
.text(d => {
|
||||
let count = d.length,
|
||||
perc = 100.0 * d.length / values.length,
|
||||
tooltip = `Count: ${count}\n
|
||||
Percentage: ${perc.toFixed(2)}%\n
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
return tooltip;
|
||||
});
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--y")
|
||||
.call(d3.axisLeft(yAxis).tickSizeOuter(-width));
|
||||
|
||||
svg.append("text")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", -margin.left)
|
||||
.attr("x", -(height / 2))
|
||||
.attr("dy", "1em")
|
||||
.style("text-anchor", "middle")
|
||||
.text(yLabel);
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--x")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.call(d3.axisBottom(xAxis).tickSizeOuter(-height));
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", width / 2)
|
||||
.attr("y", dimension)
|
||||
.style("text-anchor", "middle")
|
||||
.text(xLabel);
|
||||
|
||||
return svg._groups[0][0].outerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Packs a list of x, y coordinates into a number of bins for use in a heatmap.
|
||||
*
|
||||
* @param {Object[]} points
|
||||
* @param {number} number of vertical bins
|
||||
* @param {number} number of horizontal bins
|
||||
* @returns {Object[]} a list of bins (each bin is an Array) with x y coordinates, filled with the points
|
||||
*/
|
||||
getHeatmapPacking(values, vBins, hBins) {
|
||||
const xBounds = d3.extent(values, d => d[0]),
|
||||
yBounds = d3.extent(values, d => d[1]),
|
||||
bins = [];
|
||||
|
||||
if (xBounds[0] === xBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum X coordinate.";
|
||||
if (yBounds[0] === yBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum Y coordinate.";
|
||||
|
||||
for (let y = 0; y < vBins; y++) {
|
||||
bins.push([]);
|
||||
for (let x = 0; x < hBins; x++) {
|
||||
const item = [];
|
||||
item.y = y;
|
||||
item.x = x;
|
||||
|
||||
bins[y].push(item);
|
||||
} // x
|
||||
} // y
|
||||
|
||||
const epsilon = 0.000000001; // This is to clamp values that are exactly the maximum;
|
||||
|
||||
values.forEach(v => {
|
||||
const fractionOfY = (v[1] - yBounds[0]) / ((yBounds[1] + epsilon) - yBounds[0]),
|
||||
fractionOfX = (v[0] - xBounds[0]) / ((xBounds[1] + epsilon) - xBounds[0]),
|
||||
y = Math.floor(vBins * fractionOfY),
|
||||
x = Math.floor(hBins * fractionOfX);
|
||||
|
||||
bins[y][x].push({x: v[0], y: v[1]});
|
||||
});
|
||||
|
||||
return bins;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default HeatmapChart;
|
287
src/core/operations/HexDensityChart.mjs
Normal file
287
src/core/operations/HexDensityChart.mjs
Normal file
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
* @author tlwr [toby@toby.codes]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import * as d3 from "d3";
|
||||
import * as d3hexbin from "d3-hexbin";
|
||||
import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts";
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
|
||||
/**
|
||||
* Hex Density chart operation
|
||||
*/
|
||||
class HexDensityChart extends Operation {
|
||||
|
||||
/**
|
||||
* HexDensityChart constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Hex Density chart";
|
||||
this.module = "Charts";
|
||||
this.description = "";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Record delimiter",
|
||||
type: "option",
|
||||
value: RECORD_DELIMITER_OPTIONS,
|
||||
},
|
||||
{
|
||||
name: "Field delimiter",
|
||||
type: "option",
|
||||
value: FIELD_DELIMITER_OPTIONS,
|
||||
},
|
||||
{
|
||||
name: "Pack radius",
|
||||
type: "number",
|
||||
value: 25,
|
||||
},
|
||||
{
|
||||
name: "Draw radius",
|
||||
type: "number",
|
||||
value: 15,
|
||||
},
|
||||
{
|
||||
name: "Use column headers as labels",
|
||||
type: "boolean",
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: "X label",
|
||||
type: "string",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "Y label",
|
||||
type: "string",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "Draw hexagon edges",
|
||||
type: "boolean",
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: "Min colour value",
|
||||
type: "string",
|
||||
value: COLOURS.min,
|
||||
},
|
||||
{
|
||||
name: "Max colour value",
|
||||
type: "string",
|
||||
value: COLOURS.max,
|
||||
},
|
||||
{
|
||||
name: "Draw empty hexagons within data boundaries",
|
||||
type: "boolean",
|
||||
value: false,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
run(input, args) {
|
||||
const recordDelimiter = Utils.charRep[args[0]],
|
||||
fieldDelimiter = Utils.charRep[args[1]],
|
||||
packRadius = args[2],
|
||||
drawRadius = args[3],
|
||||
columnHeadingsAreIncluded = args[4],
|
||||
drawEdges = args[7],
|
||||
minColour = args[8],
|
||||
maxColour = args[9],
|
||||
drawEmptyHexagons = args[10],
|
||||
dimension = 500;
|
||||
|
||||
let xLabel = args[5],
|
||||
yLabel = args[6];
|
||||
const { headings, values } = getScatterValues(
|
||||
input,
|
||||
recordDelimiter,
|
||||
fieldDelimiter,
|
||||
columnHeadingsAreIncluded
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
xLabel = headings.x;
|
||||
yLabel = headings.y;
|
||||
}
|
||||
|
||||
let svg = document.createElement("svg");
|
||||
svg = d3.select(svg)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("viewBox", `0 0 ${dimension} ${dimension}`);
|
||||
|
||||
const margin = {
|
||||
top: 10,
|
||||
right: 0,
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
},
|
||||
width = dimension - margin.left - margin.right,
|
||||
height = dimension - margin.top - margin.bottom,
|
||||
marginedSpace = svg.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
const hexbin = d3hexbin.hexbin()
|
||||
.radius(packRadius)
|
||||
.extent([0, 0], [width, height]);
|
||||
|
||||
const hexPoints = hexbin(values),
|
||||
maxCount = Math.max(...hexPoints.map(b => b.length));
|
||||
|
||||
const xExtent = d3.extent(hexPoints, d => d.x),
|
||||
yExtent = d3.extent(hexPoints, d => d.y);
|
||||
xExtent[0] -= 2 * packRadius;
|
||||
xExtent[1] += 3 * packRadius;
|
||||
yExtent[0] -= 2 * packRadius;
|
||||
yExtent[1] += 2 * packRadius;
|
||||
|
||||
const xAxis = d3.scaleLinear()
|
||||
.domain(xExtent)
|
||||
.range([0, width]);
|
||||
const yAxis = d3.scaleLinear()
|
||||
.domain(yExtent)
|
||||
.range([height, 0]);
|
||||
|
||||
const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour))
|
||||
.domain([0, maxCount]);
|
||||
|
||||
marginedSpace.append("clipPath")
|
||||
.attr("id", "clip")
|
||||
.append("rect")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
if (drawEmptyHexagons) {
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "empty-hexagon")
|
||||
.selectAll("path")
|
||||
.data(this.getEmptyHexagons(hexPoints, packRadius))
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("d", d => {
|
||||
return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`;
|
||||
})
|
||||
.attr("fill", (d) => colour(0))
|
||||
.attr("stroke", drawEdges ? "black" : "none")
|
||||
.attr("stroke-width", drawEdges ? "0.5" : "none")
|
||||
.append("title")
|
||||
.text(d => {
|
||||
const count = 0,
|
||||
perc = 0,
|
||||
tooltip = `Count: ${count}\n
|
||||
Percentage: ${perc.toFixed(2)}%\n
|
||||
Center: ${d.x.toFixed(2)}, ${d.y.toFixed(2)}\n
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
return tooltip;
|
||||
});
|
||||
}
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "hexagon")
|
||||
.attr("clip-path", "url(#clip)")
|
||||
.selectAll("path")
|
||||
.data(hexPoints)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("d", d => {
|
||||
return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`;
|
||||
})
|
||||
.attr("fill", (d) => colour(d.length))
|
||||
.attr("stroke", drawEdges ? "black" : "none")
|
||||
.attr("stroke-width", drawEdges ? "0.5" : "none")
|
||||
.append("title")
|
||||
.text(d => {
|
||||
const count = d.length,
|
||||
perc = 100.0 * d.length / values.length,
|
||||
CX = d.x,
|
||||
CY = d.y,
|
||||
xMin = Math.min(...d.map(d => d[0])),
|
||||
xMax = Math.max(...d.map(d => d[0])),
|
||||
yMin = Math.min(...d.map(d => d[1])),
|
||||
yMax = Math.max(...d.map(d => d[1])),
|
||||
tooltip = `Count: ${count}\n
|
||||
Percentage: ${perc.toFixed(2)}%\n
|
||||
Center: ${CX.toFixed(2)}, ${CY.toFixed(2)}\n
|
||||
Min X: ${xMin.toFixed(2)}\n
|
||||
Max X: ${xMax.toFixed(2)}\n
|
||||
Min Y: ${yMin.toFixed(2)}\n
|
||||
Max Y: ${yMax.toFixed(2)}
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
return tooltip;
|
||||
});
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--y")
|
||||
.call(d3.axisLeft(yAxis).tickSizeOuter(-width));
|
||||
|
||||
svg.append("text")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", -margin.left)
|
||||
.attr("x", -(height / 2))
|
||||
.attr("dy", "1em")
|
||||
.style("text-anchor", "middle")
|
||||
.text(yLabel);
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--x")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.call(d3.axisBottom(xAxis).tickSizeOuter(-height));
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", width / 2)
|
||||
.attr("y", dimension)
|
||||
.style("text-anchor", "middle")
|
||||
.text(xLabel);
|
||||
|
||||
return svg._groups[0][0].outerHTML;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hex Bin chart operation.
|
||||
*
|
||||
* @param {Object[]} - centres
|
||||
* @param {number} - radius
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
getEmptyHexagons(centres, radius) {
|
||||
const emptyCentres = [],
|
||||
boundingRect = [d3.extent(centres, d => d.x), d3.extent(centres, d => d.y)],
|
||||
hexagonCenterToEdge = Math.cos(2 * Math.PI / 12) * radius,
|
||||
hexagonEdgeLength = Math.sin(2 * Math.PI / 12) * radius;
|
||||
let indent = false;
|
||||
|
||||
for (let y = boundingRect[1][0]; y <= boundingRect[1][1] + radius; y += hexagonEdgeLength + radius) {
|
||||
for (let x = boundingRect[0][0]; x <= boundingRect[0][1] + radius; x += 2 * hexagonCenterToEdge) {
|
||||
let cx = x;
|
||||
const cy = y;
|
||||
|
||||
if (indent && x >= boundingRect[0][1]) break;
|
||||
if (indent) cx += hexagonCenterToEdge;
|
||||
|
||||
emptyCentres.push({x: cx, y: cy});
|
||||
}
|
||||
indent = !indent;
|
||||
}
|
||||
|
||||
return emptyCentres;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default HexDensityChart;
|
297
src/core/operations/legacy/Charts.js
Executable file
297
src/core/operations/legacy/Charts.js
Executable file
|
@ -0,0 +1,297 @@
|
|||
import * as d3 from "d3";
|
||||
import Utils from "../Utils.js";
|
||||
|
||||
/**
|
||||
* Charting operations.
|
||||
*
|
||||
* @author tlwr [toby@toby.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const Charts = {
|
||||
|
||||
|
||||
/**
|
||||
* Scatter chart operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
runScatterChart: function (input, args) {
|
||||
const recordDelimiter = Utils.charRep[args[0]],
|
||||
fieldDelimiter = Utils.charRep[args[1]],
|
||||
columnHeadingsAreIncluded = args[2],
|
||||
fillColour = args[5],
|
||||
radius = args[6],
|
||||
colourInInput = args[7],
|
||||
dimension = 500;
|
||||
|
||||
let xLabel = args[3],
|
||||
yLabel = args[4];
|
||||
|
||||
let dataFunction = colourInInput ? Charts._getScatterValuesWithColour : Charts._getScatterValues;
|
||||
|
||||
let { headings, values } = dataFunction(
|
||||
input,
|
||||
recordDelimiter,
|
||||
fieldDelimiter,
|
||||
columnHeadingsAreIncluded
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
xLabel = headings.x;
|
||||
yLabel = headings.y;
|
||||
}
|
||||
|
||||
let svg = document.createElement("svg");
|
||||
svg = d3.select(svg)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("viewBox", `0 0 ${dimension} ${dimension}`);
|
||||
|
||||
let margin = {
|
||||
top: 10,
|
||||
right: 0,
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
},
|
||||
width = dimension - margin.left - margin.right,
|
||||
height = dimension - margin.top - margin.bottom,
|
||||
marginedSpace = svg.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
let xExtent = d3.extent(values, d => d[0]),
|
||||
xDelta = xExtent[1] - xExtent[0],
|
||||
yExtent = d3.extent(values, d => d[1]),
|
||||
yDelta = yExtent[1] - yExtent[0],
|
||||
xAxis = d3.scaleLinear()
|
||||
.domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)])
|
||||
.range([0, width]),
|
||||
yAxis = d3.scaleLinear()
|
||||
.domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)])
|
||||
.range([height, 0]);
|
||||
|
||||
marginedSpace.append("clipPath")
|
||||
.attr("id", "clip")
|
||||
.append("rect")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "points")
|
||||
.attr("clip-path", "url(#clip)")
|
||||
.selectAll("circle")
|
||||
.data(values)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("cx", (d) => xAxis(d[0]))
|
||||
.attr("cy", (d) => yAxis(d[1]))
|
||||
.attr("r", d => radius)
|
||||
.attr("fill", d => {
|
||||
return colourInInput ? d[2] : fillColour;
|
||||
})
|
||||
.attr("stroke", "rgba(0, 0, 0, 0.5)")
|
||||
.attr("stroke-width", "0.5")
|
||||
.append("title")
|
||||
.text(d => {
|
||||
let x = d[0],
|
||||
y = d[1],
|
||||
tooltip = `X: ${x}\n
|
||||
Y: ${y}\n
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
return tooltip;
|
||||
});
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--y")
|
||||
.call(d3.axisLeft(yAxis).tickSizeOuter(-width));
|
||||
|
||||
svg.append("text")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", -margin.left)
|
||||
.attr("x", -(height / 2))
|
||||
.attr("dy", "1em")
|
||||
.style("text-anchor", "middle")
|
||||
.text(yLabel);
|
||||
|
||||
marginedSpace.append("g")
|
||||
.attr("class", "axis axis--x")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.call(d3.axisBottom(xAxis).tickSizeOuter(-height));
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", width / 2)
|
||||
.attr("y", dimension)
|
||||
.style("text-anchor", "middle")
|
||||
.text(xLabel);
|
||||
|
||||
return svg._groups[0][0].outerHTML;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Series chart operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
runSeriesChart(input, args) {
|
||||
const recordDelimiter = Utils.charRep[args[0]],
|
||||
fieldDelimiter = Utils.charRep[args[1]],
|
||||
xLabel = args[2],
|
||||
pipRadius = args[3],
|
||||
seriesColours = args[4].split(","),
|
||||
svgWidth = 500,
|
||||
interSeriesPadding = 20,
|
||||
xAxisHeight = 50,
|
||||
seriesLabelWidth = 50,
|
||||
seriesHeight = 100,
|
||||
seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding;
|
||||
|
||||
let { xValues, series } = Charts._getSeriesValues(input, recordDelimiter, fieldDelimiter),
|
||||
allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight),
|
||||
svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding;
|
||||
|
||||
let svg = document.createElement("svg");
|
||||
svg = d3.select(svg)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
|
||||
|
||||
let xAxis = d3.scalePoint()
|
||||
.domain(xValues)
|
||||
.range([0, seriesWidth]);
|
||||
|
||||
svg.append("g")
|
||||
.attr("class", "axis axis--x")
|
||||
.attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`)
|
||||
.call(
|
||||
d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => {
|
||||
return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0;
|
||||
}))
|
||||
);
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", svgWidth / 2)
|
||||
.attr("y", xAxisHeight / 2)
|
||||
.style("text-anchor", "middle")
|
||||
.text(xLabel);
|
||||
|
||||
let tooltipText = {},
|
||||
tooltipAreaWidth = seriesWidth / xValues.length;
|
||||
|
||||
xValues.forEach(x => {
|
||||
let tooltip = [];
|
||||
|
||||
series.forEach(serie => {
|
||||
let y = serie.data[x];
|
||||
if (typeof y === "undefined") return;
|
||||
|
||||
tooltip.push(`${serie.name}: ${y}`);
|
||||
});
|
||||
|
||||
tooltipText[x] = tooltip.join("\n");
|
||||
});
|
||||
|
||||
let chartArea = svg.append("g")
|
||||
.attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`);
|
||||
|
||||
chartArea
|
||||
.append("g")
|
||||
.selectAll("rect")
|
||||
.data(xValues)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("x", x => {
|
||||
return xAxis(x) - (tooltipAreaWidth / 2);
|
||||
})
|
||||
.attr("y", 0)
|
||||
.attr("width", tooltipAreaWidth)
|
||||
.attr("height", allSeriesHeight)
|
||||
.attr("stroke", "none")
|
||||
.attr("fill", "transparent")
|
||||
.append("title")
|
||||
.text(x => {
|
||||
return `${x}\n
|
||||
--\n
|
||||
${tooltipText[x]}\n
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
});
|
||||
|
||||
let yAxesArea = svg.append("g")
|
||||
.attr("transform", `translate(0, ${xAxisHeight})`);
|
||||
|
||||
series.forEach((serie, seriesIndex) => {
|
||||
let yExtent = d3.extent(Object.values(serie.data)),
|
||||
yAxis = d3.scaleLinear()
|
||||
.domain(yExtent)
|
||||
.range([seriesHeight, 0]);
|
||||
|
||||
let seriesGroup = chartArea
|
||||
.append("g")
|
||||
.attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`);
|
||||
|
||||
let path = "";
|
||||
xValues.forEach((x, xIndex) => {
|
||||
let nextX = xValues[xIndex + 1],
|
||||
y = serie.data[x],
|
||||
nextY= serie.data[nextX];
|
||||
|
||||
if (typeof y === "undefined" || typeof nextY === "undefined") return;
|
||||
|
||||
x = xAxis(x); nextX = xAxis(nextX);
|
||||
y = yAxis(y); nextY = yAxis(nextY);
|
||||
|
||||
path += `M ${x} ${y} L ${nextX} ${nextY} z `;
|
||||
});
|
||||
|
||||
seriesGroup
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", seriesColours[seriesIndex % seriesColours.length])
|
||||
.attr("stroke-width", "1");
|
||||
|
||||
xValues.forEach(x => {
|
||||
let y = serie.data[x];
|
||||
if (typeof y === "undefined") return;
|
||||
|
||||
seriesGroup
|
||||
.append("circle")
|
||||
.attr("cx", xAxis(x))
|
||||
.attr("cy", yAxis(y))
|
||||
.attr("r", pipRadius)
|
||||
.attr("fill", seriesColours[seriesIndex % seriesColours.length])
|
||||
.append("title")
|
||||
.text(d => {
|
||||
return `${x}\n
|
||||
--\n
|
||||
${tooltipText[x]}\n
|
||||
`.replace(/\s{2,}/g, "\n");
|
||||
});
|
||||
});
|
||||
|
||||
yAxesArea
|
||||
.append("g")
|
||||
.attr("transform", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`)
|
||||
.attr("class", "axis axis--y")
|
||||
.call(d3.axisLeft(yAxis).ticks(5));
|
||||
|
||||
yAxesArea
|
||||
.append("g")
|
||||
.attr("transform", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`)
|
||||
.append("text")
|
||||
.style("text-anchor", "middle")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.text(serie.name);
|
||||
});
|
||||
|
||||
return svg._groups[0][0].outerHTML;
|
||||
},
|
||||
};
|
||||
|
||||
export default Charts;
|
Loading…
Add table
Reference in a new issue