diff options
18 files changed, 1416 insertions, 519 deletions
diff --git a/package-lock.json b/package-lock.json index 3b9cda8bb..0a340b73a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1173,6 +1173,50 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, + "@types/d3": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "@types/d3-array": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", + "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", + "dev": true + }, "@types/d3-axis": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-2.1.3.tgz", @@ -1181,11 +1225,138 @@ "@types/d3-selection": "^2" } }, + "@types/d3-brush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz", + "integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==", + "dev": true + }, "@types/d3-color": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-2.0.3.tgz", "integrity": "sha512-+0EtEjBfKEDtH9Rk3u3kLOUXM5F+iZK+WvASPb0MhIZl8J8NUvGeZRwKCXl+P3HkYx5TdU4YtcibpqHkSR9n7w==" }, + "@types/d3-contour": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz", + "integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "dev": true + }, + "@types/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==", + "dev": true + }, + "@types/d3-drag": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz", + "integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-dsv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz", + "integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==", + "dev": true + }, + "@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "dev": true + }, + "@types/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==", + "dev": true, + "requires": { + "@types/d3-dsv": "*" + } + }, + "@types/d3-force": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz", + "integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==", + "dev": true + }, + "@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "dev": true + }, + "@types/d3-geo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz", + "integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, + "@types/d3-hierarchy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz", + "integrity": "sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==", + "dev": true + }, + "@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dev": true, + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "dev": true + }, + "@types/d3-polygon": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", + "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "dev": true + }, + "@types/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "dev": true + }, + "@types/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "dev": true + }, "@types/d3-scale": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz", @@ -1194,16 +1365,62 @@ "@types/d3-time": "^2" } }, + "@types/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "dev": true + }, "@types/d3-selection": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-2.0.1.tgz", "integrity": "sha512-3mhtPnGE+c71rl/T5HMy+ykg7migAZ4T6gzU0HxpgBFKcasBrSnwRbYV1/UZR6o5fkpySxhWxAhd7yhjj8jL7g==" }, + "@types/d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==", + "dev": true, + "requires": { + "@types/d3-path": "*" + } + }, "@types/d3-time": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.1.tgz", "integrity": "sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg==" }, + "@types/d3-time-format": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", + "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "dev": true + }, + "@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "dev": true + }, + "@types/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-Sv4qEI9uq3bnZwlOANvYK853zvpdKEm1yz9rcc8ZTsxvRklcs9Fx4YFuGA3gXoQN/c/1T6QkVNjhaRO/cWj94g==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-zoom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", + "integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==", + "dev": true, + "requires": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "@types/dom-speech-recognition": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/@types/dom-speech-recognition/-/dom-speech-recognition-0.0.1.tgz", @@ -2455,7 +2672,7 @@ "textarea-caret": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.0.2.tgz", - "integrity": "sha512-gRzeti2YS4did7UJnPQ47wrjD+vp+CJIe9zbsu0bJ987d8QVLvLNG9757rqiQTIy4hGIeFauTTJt5Xkn51UkXg==" + "integrity": "sha1-82DEhpmqGr9xhoCkOjGoUGZcLK8=" } } }, @@ -2554,7 +2771,7 @@ "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, "agent-base": { "version": "6.0.2", @@ -3346,7 +3563,7 @@ "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, "balanced-match": { "version": "1.0.2", @@ -3411,7 +3628,7 @@ "base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==" + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" }, "base64-js": { "version": "1.5.1", @@ -4000,6 +4217,11 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chart.js": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.0.tgz", + "integrity": "sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg==" + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -4333,7 +4555,7 @@ "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==" + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" }, "component-emitter": { "version": "1.3.0", @@ -4343,7 +4565,7 @@ "component-inherit": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==" + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" }, "compress-brotli": { "version": "1.3.8", @@ -4992,6 +5214,11 @@ "postcss-value-parser": "^3.3.0" } }, + "css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" + }, "css-vendor": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", @@ -5051,7 +5278,7 @@ "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==" + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=" }, "cyclist": { "version": "1.0.1", @@ -5059,6 +5286,180 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "d3": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.6.1.tgz", + "integrity": "sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw==", + "requires": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "dependencies": { + "d3-array": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==" + }, + "d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + }, + "d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" + }, + "d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + } + }, + "d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" + }, + "d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" + }, + "d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "requires": { + "d3-color": "1 - 3" + } + }, + "d3-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", + "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==" + }, + "d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "requires": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + } + }, + "d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + }, + "d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", + "requires": { + "d3-path": "1 - 3" + } + }, + "d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", + "requires": { + "d3-array": "2 - 3" + } + }, + "d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "requires": { + "d3-time": "1 - 3" + } + }, + "d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" + }, + "d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "requires": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + } + } + } + }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -5072,11 +5473,76 @@ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-2.1.0.tgz", "integrity": "sha512-z/G2TQMyuf0X3qP+Mh+2PimoJD41VOCjViJzT0BHeL/+JQAofkiWZbWxlwFGb1N8EN+Cl/CW+MUKbVzr1689Cw==" }, + "d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "dependencies": { + "d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + }, + "d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "requires": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + } + } + } + }, + "d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "requires": { + "d3-path": "1 - 3" + } + }, "d3-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" }, + "d3-contour": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", + "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", + "requires": { + "d3-array": "^3.2.0" + }, + "dependencies": { + "d3-array": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "requires": { + "internmap": "1 - 2" + } + } + } + }, + "d3-delaunay": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "requires": { + "delaunator": "5" + } + }, "d3-dispatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz", @@ -5091,16 +5557,64 @@ "d3-selection": "2" } }, + "d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "requires": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + } + } + }, "d3-ease": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz", "integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==" }, + "d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "requires": { + "d3-dsv": "1 - 3" + } + }, + "d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + } + }, "d3-format": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" }, + "d3-geo": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", + "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", + "requires": { + "d3-array": "2.5.0 - 3" + } + }, + "d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==" + }, "d3-interpolate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", @@ -5114,6 +5628,21 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" }, + "d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==" + }, + "d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" + }, + "d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==" + }, "d3-scale": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", @@ -5126,6 +5655,15 @@ "d3-time-format": "2 - 3" } }, + "d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "requires": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + } + }, "d3-selection": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz", @@ -5265,6 +5803,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -5468,6 +6011,14 @@ } } }, + "delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "requires": { + "robust-predicates": "^3.0.0" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6069,7 +6620,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "ws": { "version": "7.4.6", @@ -6208,6 +6759,28 @@ "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.61", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", + "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", + "dev": true, + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6219,6 +6792,7 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { + "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -7471,8 +8045,7 @@ "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "events": { "version": "3.3.0", @@ -7923,6 +8496,11 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "fast-equals": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz", + "integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==" + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -8416,7 +8994,7 @@ "fs-extra": { "version": "0.26.7", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", - "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", + "integrity": "sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==", "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^2.1.0", @@ -9054,14 +9632,14 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" } } }, "has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" }, "has-flag": { "version": "3.0.0", @@ -9557,7 +10135,7 @@ "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" }, "inflight": { "version": "1.0.6", @@ -10600,7 +11178,7 @@ "jsonfile": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", "requires": { "graceful-fs": "^4.1.6" } @@ -11019,7 +11597,7 @@ "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "lodash.isplainobject": { "version": "4.0.6", @@ -11270,7 +11848,7 @@ "mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", - "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==", + "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=", "requires": { "jquery": "^1.12.3" }, @@ -11278,7 +11856,7 @@ "jquery": { "version": "1.12.4", "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz", - "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg==" + "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw=" } } }, @@ -12788,7 +13366,7 @@ "dependencies": { "ansi-regex": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "is-fullwidth-code-point": { @@ -14287,7 +14865,7 @@ }, "minimist": { "version": "1.2.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minizlib": { @@ -14336,7 +14914,7 @@ "dependencies": { "minimist": { "version": "1.2.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" } } @@ -15709,7 +16287,7 @@ "dependencies": { "ansi-regex": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "is-fullwidth-code-point": { @@ -17506,6 +18084,11 @@ "use-memo-one": "^1.1.1" } }, + "react-chartjs-2": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.3.0.tgz", + "integrity": "sha512-zWnW3ReDlSC8PNiZKNqu8J/CHBOsVvx1AB4PicFbEAMUf/rpgO0/8ykRSPqS00kBUjP9d8lzfq1gFdLwYFWC8w==" + }, "react-color": { "version": "2.19.3", "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", @@ -17814,6 +18397,14 @@ "resolved": "https://registry.npmjs.org/react-resizable-rotatable-draggable/-/react-resizable-rotatable-draggable-0.2.0.tgz", "integrity": "sha512-F8TPx3z7/AcmRViySbYV3LpUWXFpHlGAmKmNcYMgPlS+h1eYFazRG3xYS8Z6e48hWY1EcCny/YNrwRNUrap8CQ==" }, + "react-resize-detector": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz", + "integrity": "sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw==", + "requires": { + "lodash": "^4.17.21" + } + }, "react-reveal": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/react-reveal/-/react-reveal-1.2.2.tgz", @@ -17874,6 +18465,28 @@ } } }, + "react-smooth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.1.tgz", + "integrity": "sha512-Own9TA0GPPf3as4vSwFhDouVfXP15ie/wIHklhyKBH5AN6NFtdk0UpHBnonV11BtqDkAWlt40MOUc+5srmW7NA==", + "requires": { + "fast-equals": "^2.0.0", + "react-transition-group": "2.9.0" + }, + "dependencies": { + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } + } + }, "react-table": { "version": "6.11.5", "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.5.tgz", @@ -18048,6 +18661,32 @@ "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=" }, + "recharts": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.1.12.tgz", + "integrity": "sha512-dAzEuc9AjM+IF0A33QzEdBEUnyGKJcGUPa0MYm0vd38P3WouQjrj2egBrCNInE7ZcQwN+z3MoT7Rw03u8nP9HA==", + "requires": { + "classnames": "^2.2.5", + "d3-interpolate": "^2.0.0", + "d3-scale": "^3.0.0", + "d3-shape": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.19", + "react-is": "^16.10.2", + "react-resize-detector": "^7.1.2", + "react-smooth": "^2.0.1", + "recharts-scale": "^0.4.4", + "reduce-css-calc": "^2.1.8" + } + }, + "recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "requires": { + "decimal.js-light": "^2.4.1" + } + }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -18083,6 +18722,15 @@ "strip-indent": "^1.0.1" } }, + "reduce-css-calc": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", + "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", + "requires": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + } + }, "reduce-flatten": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", @@ -18492,6 +19140,11 @@ "glob": "^7.1.3" } }, + "robust-predicates": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + }, "rope-sequence": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.3.tgz", @@ -18528,6 +19181,11 @@ } } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -19338,7 +19996,7 @@ "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==" }, "debug": { "version": "4.1.1", @@ -19351,7 +20009,7 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" } } }, @@ -20203,7 +20861,7 @@ "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==" + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" }, "to-fast-properties": { "version": "2.0.0", @@ -20715,6 +21373,12 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -21650,7 +22314,7 @@ "resolve-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==", "dev": true, "requires": { "resolve-from": "^3.0.0" @@ -21659,7 +22323,7 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", "dev": true }, "semver": { @@ -22200,7 +22864,7 @@ "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==" + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" }, "yn": { "version": "2.0.0", diff --git a/package.json b/package.json index 2e3ef0a07..4a61f09a9 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@types/connect-flash": "0.0.34", "@types/cookie-parser": "^1.4.2", "@types/cookie-session": "^2.0.44", + "@types/d3": "^7.4.0", "@types/dotenv": "^6.1.1", "@types/exif": "^0.6.3", "@types/express": "^4.17.13", @@ -66,17 +67,17 @@ "@types/prosemirror-view": "^1.23.1", "@types/rc-switch": "^1.9.2", "@types/react": "^18.0.15", - "@types/react-icons": "^3.0.0", - "@types/react-reconciler": "^0.26.4", - "@types/react-transition-group": "^4.4.5", "@types/react-autosuggest": "^9.3.14", "@types/react-color": "^2.17.6", "@types/react-datepicker": "^3.1.8", "@types/react-dom": "^18.0.6", "@types/react-grid-layout": "^1.3.2", + "@types/react-icons": "^3.0.0", "@types/react-measure": "^2.0.8", + "@types/react-reconciler": "^0.26.4", "@types/react-select": "^3.1.2", "@types/react-table": "^6.8.9", + "@types/react-transition-group": "^4.4.5", "@types/request": "^2.48.8", "@types/request-promise": "^4.1.48", "@types/rimraf": "^2.0.5", @@ -169,6 +170,7 @@ "browser-assert": "^1.2.1", "bson": "^4.6.1", "canvas": "^2.9.3", + "chart.js": "^3.8.0", "child_process": "^1.0.2", "chrome": "^0.1.0", "class-transformer": "^0.2.0", @@ -179,6 +181,7 @@ "cookie-parser": "^1.4.6", "cookie-session": "^2.0.0", "cors": "^2.8.5", + "d3": "^7.6.1", "depcheck": "^0.9.2", "equation-editor-react": "github:bobzel/equation-editor-react#useLocally", "exif": "^0.6.0", @@ -264,6 +267,7 @@ "react-audio-waveform": "0.0.5", "react-autosuggest": "^9.4.3", "react-beautiful-dnd": "^13.1.0", + "react-chartjs-2": "^4.3.0", "react-color": "^2.19.3", "react-compound-slider": "^2.5.0", "react-datepicker": "^3.8.0", @@ -281,6 +285,7 @@ "react-table": "^6.11.5", "react-transition-group": "^4.4.2", "readline": "^1.3.0", + "recharts": "^2.1.12", "request": "^2.88.2", "request-promise": "^4.2.6", "reveal.js": "^4.3.0", diff --git a/src/.DS_Store b/src/.DS_Store Binary files differindex 4751acf44..69407d6c2 100644 --- a/src/.DS_Store +++ b/src/.DS_Store diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx deleted file mode 100644 index df4c8f937..000000000 --- a/src/client/views/nodes/DataViz.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { ViewBoxBaseComponent } from '../DocComponent'; -import './DataViz.scss'; -import { FieldView, FieldViewProps } from './FieldView'; - -@observer -export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(DataVizBox, fieldKey); - } - - render() { - return ( - <div> - <div>Hi</div> - </div> - ); - } -} diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx new file mode 100644 index 000000000..c229e5471 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -0,0 +1,144 @@ +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc } from '../../../../fields/Doc'; +import { action, computed, observable } from 'mobx'; +import { NumCast, StrCast } from '../../../../fields/Types'; +import { LineChart } from './components/LineChart'; +import { Chart, ChartData } from './ChartInterface'; + +export interface ChartBoxProps { + rootDoc: Doc; + dataDoc: Doc; + pairs: { x: number; y: number }[]; + setChartBox: (chartBox: ChartBox) => void; + getAnchor: () => Doc; +} + +export interface DataPoint { + x: number; + y: number; +} + +@observer +export class ChartBox extends React.Component<ChartBoxProps> { + @observable private _chartData: ChartData | undefined = undefined; + private currChart: Chart | undefined; + + @computed get currView() { + if (this.props.rootDoc._dataVizView) { + return StrCast(this.props.rootDoc._currChartView); + } else { + return 'table'; + } + } + + constructor(props: any) { + super(props); + if (!this.props.rootDoc._currChartView) { + this.props.rootDoc._currChartView = 'bar'; + } + } + + setCurrChart = (chart: Chart) => { + this.currChart = chart; + }; + + @action + generateChartData() { + if (this.props.rootDoc._chartData) { + this._chartData = JSON.parse(StrCast(this.props.rootDoc._chartData)); + return; + } + + const data: ChartData = { + xLabel: '', + yLabel: '', + data: [[]], + }; + + if (this.props.pairs && this.props.pairs.length > 0) { + data.xLabel = 'x'; + data.yLabel = 'y'; + this.props.pairs.forEach(pair => { + // TODO: nda - add actual support for multiple sets of data + data.data[0].push({ x: pair.x, y: pair.y }); + }); + } + + this._chartData = data; + this.props.rootDoc._chartData = JSON.stringify(data); + } + + componentDidMount = () => { + this.generateChartData(); + // setting timeout to allow rerender cycle of the actual chart tof inish + // setTimeout(() => { + this.props.setChartBox(this); + // }); + // this.props.setChartBox(this); + }; + + @action + onClickChangeChart = (e: React.MouseEvent<HTMLButtonElement>) => { + e.preventDefault(); + e.stopPropagation(); + console.log(e.currentTarget.value); + this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase(); + }; + + scrollFocus(doc: Doc, smooth: boolean): number | undefined { + // if x and y exist on this + // then highlight/focus the x and y position (datapoint) + // highlight for a few seconds and then remove (set a timer to unhighlight after a period of time, + // to be consistent, use 5 seconds and highlight color is orange) + // if x and y don't exist => link to whole doc => dash will take care of focusing on the entire doc + // return value is how long this action takes + // undefined => immediately, 0 => introduces a timeout + // number in ms => won't do any of the focus or call the automated highlighting thing until this timeout expires + // in this case probably number in ms doesn't matter + + // for now could just update the currSelected in linechart to be what doc.x and doc.y + + if (this.currChart && doc.x && doc.y) { + // update curr selected + this.currChart.setCurrSelected(Number(doc.x), Number(doc.y)); + } + return 0; + } + + _getAnchor() { + return this.currChart?._getAnchor(); + } + + render() { + if (this.props.pairs && this._chartData) { + let width = NumCast(this.props.rootDoc._width); + width = width * 0.7; + let height = NumCast(this.props.rootDoc._height); + height = height * 0.7; + console.log(width, height); + const margin = { top: 50, right: 50, bottom: 50, left: 50 }; + return ( + <div> + <div> + {/*{this.props.rootDoc._currChartView == 'line' ? ( + <Line ref={this._chartRef} options={this.options} data={this._chartJsData} onClick={e => this.onClick(e)} /> + ) : ( + <Bar ref={this._chartRef} options={this.options} data={this._chartJsData} onClick={e => this.onClick(e)} /> + )} */} + {/* {this.reLineChart} */} + <LineChart margin={margin} width={width} height={height} chartData={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} /> + </div> + <button onClick={e => this.onClickChangeChart(e)} value="line"> + Line + </button> + <button onClick={e => this.onClickChangeChart(e)} value="bar"> + Bar + </button> + </div> + ); + } else { + return <div></div>; + } + } +} diff --git a/src/client/views/nodes/DataVizBox/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts new file mode 100644 index 000000000..494242ac5 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/ChartInterface.ts @@ -0,0 +1,36 @@ +import { Doc } from '../../../../fields/Doc'; +import { DataPoint } from './ChartBox'; +import { LineChart } from './components/LineChart'; + +export interface Chart { + tooltipContent: (data: DataPoint) => string; + drawChart: () => void; + height: number; + width: number; + // TODO: nda - probably want to get rid of this to keep charts more generic + _getAnchor: () => Doc; + setCurrSelected: (x: number, y: number) => void; +} + +export interface ChartProps { + chartData: ChartData; + width: number; + height: number; + dataDoc: Doc; + fieldKey: string; + // returns linechart component but should be generic chart + setCurrChart: (chart: Chart) => void; + getAnchor: () => Doc; + margin: { + top: number; + right: number; + bottom: number; + left: number; + }; +} + +export interface ChartData { + xLabel: string; + yLabel: string; + data: DataPoint[][]; +} diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 592723ee9..db677ff61 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,90 +1,170 @@ -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { StrCast } from "../../../../fields/Types"; -import { ViewBoxBaseComponent } from "../../DocComponent"; -import { FieldViewProps, FieldView } from "../FieldView"; -import "./DataVizBox.scss"; -import { HistogramBox } from "./HistogramBox"; -import { TableBox } from "./TableBox"; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc } from '../../../../fields/Doc'; +import { Cast, StrCast } from '../../../../fields/Types'; +import { CsvField } from '../../../../fields/URLField'; +import { Docs } from '../../../documents/Documents'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; +import { FieldViewProps, FieldView } from '../FieldView'; +import { ChartBox } from './ChartBox'; +import './DataVizBox.scss'; +import { TableBox } from './components/TableBox'; enum DataVizView { - TABLE = "table", - HISTOGRAM= "histogram" + TABLE = 'table', + HISTOGRAM = 'histogram', } - @observer -export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() { - @observable private pairs: {x: number, y:number}[] = [{x: 1, y:2}]; +export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { + // says we have an object and any string + // 2 ways of doing it + // @observable private pairs: { [key: string]: number | string | undefined }[] = []; + // @observable private pairs: { [key: string]: FieldResult }[] = []; + @observable private pairs: { x: number; y: number }[] = []; + private _chartBox: ChartBox | undefined; + // // another way would be store a schema that defines the type of data we are expecting from an imported doc + + // method1() { + // this.pairs[0].x = 3; + // } + + // method() { + // // this.pairs[0].x = 3; + // // go through the pairs + // const x = this.pairs[0].x; + // if (typeof x == 'number') { + // let x1 = Number(x); + // // let x1 = NumCast(x); + // } + // } + + // could use field result + // [key: string]: FieldResult; + // instead of numeric x,y in there, // TODO: nda - make this use enum values instead // @observable private currView: DataVizView = DataVizView.TABLE; + + // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops + + setChartBox = (chartBox: ChartBox) => { + this._chartBox = chartBox; + }; + @computed get currView() { if (this.rootDoc._dataVizView) { return StrCast(this.rootDoc._dataVizView); } else { - return "table"; + return 'table'; } } + scrollFocus = (doc: Doc, smooth: boolean) => { + // reconstruct the view based on the anchor -> + // keep track of the datavizbox view and the type of chart we're rendering + + // use dataview to restore part of it and then pass on the rest to the chartbox + + // pseudocode + return this._chartBox?.scrollFocus(doc, smooth); + }; + + getAnchor = () => { + // TODO: nda - look into DocumentButtonBar and DocumentLinksButton + + /* + if nothing selected: + return this.rootDoc + this part does not specify view type (doesn't change type when following the link) + + // this slightly diff than the stuff below: + below part returns an anchor that specifies the viewtype for the whole doc and nothing else + + */ + + // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element) + const anchor = + // this._COMPONENTNAME._getAnchor() ?? + this._chartBox?._getAnchor() ?? + Docs.Create.TextanchorDocument({ + // when we clear selection -> we should have it so chartBox getAnchor returns undefined + // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker) + /*put in some options*/ + }); + + anchor.dataView = this.currView; + + this.addDocument(anchor); + return anchor; + // have some other function in code that + }; constructor(props: any) { super(props); if (!this.rootDoc._dataVizView) { // TODO: nda - this might not always want to default to "table" - this.rootDoc._dataVizView = "table"; + this.rootDoc._dataVizView = 'table'; } } - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataVizBox, fieldKey); } - - @action - private createPairs() { - const xVals: number[] = [0, 1, 2, 3, 4, 5]; - // const yVals: number[] = [10, 20, 30, 40, 50, 60]; - const yVals: number[] = [1, 2, 3, 4, 5, 6]; - let pairs: { - x: number, - y:number - }[] = []; - if (xVals.length != yVals.length) return pairs; - for (let i = 0; i < xVals.length; i++) { - pairs.push({x: xVals[i], y: yVals[i]}); - } - this.pairs = pairs; - return pairs; + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DataVizBox, fieldKey); } @computed get selectView() { - switch(this.currView) { - case "table": - return (<TableBox pairs={this.pairs} />) - case "histogram": - return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>) + switch (this.currView) { + case 'table': + return <TableBox pairs={this.pairs} />; + case 'histogram': + return <ChartBox getAnchor={this.getAnchor} setChartBox={this.setChartBox} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />; + // case "histogram": + // return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>) } } @computed get pairVals() { - return this.createPairs(); + return fetch('/csvData?uri=' + this.dataUrl?.url.href).then(res => res.json()); + } + + @computed get dataUrl() { + return Cast(this.dataDoc[this.fieldKey], CsvField); } componentDidMount() { - this.createPairs(); + this.props.setContentView?.(this); + // this.createPairs(); + this.fetchData(); + } + + fetchData() { + const uri = this.dataUrl?.url.href; + fetch('/csvData?uri=' + uri).then(res => + res.json().then( + action(res => { + this.pairs = res; + }) + ) + ); } // handle changing the view using a button @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) { e.preventDefault(); e.stopPropagation(); - this.rootDoc._dataVizView = this.currView == "table" ? "histogram" : "table"; + this.rootDoc._dataVizView = this.currView == 'table' ? 'histogram' : 'table'; } render() { + if (this.pairs.length == 0) { + return <div>Loading...</div>; + } + return ( <div className="dataViz"> - <button onClick={(e) => this.changeViewHandler(e)}>Change View</button> + <button onClick={e => this.changeViewHandler(e)}>Change View</button> {this.selectView} </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/DrawHelper.ts b/src/client/views/nodes/DataVizBox/DrawHelper.ts deleted file mode 100644 index 595cecebf..000000000 --- a/src/client/views/nodes/DataVizBox/DrawHelper.ts +++ /dev/null @@ -1,247 +0,0 @@ -export class PIXIPoint { - public get x() { return this.coords[0]; } - public get y() { return this.coords[1]; } - public set x(value: number) { this.coords[0] = value; } - public set y(value: number) { this.coords[1] = value; } - public coords: number[] = [0, 0]; - constructor(x: number, y: number) { - this.coords[0] = x; - this.coords[1] = y; - } -} - -export class PIXIRectangle { - public x: number; - public y: number; - public width: number; - public height: number; - public get left() { return this.x; } - public get right() { return this.x + this.width; } - public get top() { return this.y; } - public get bottom() { return this.top + this.height; } - public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); } - constructor(x: number, y: number, width: number, height: number) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } -} - -export class MathUtil { - - public static EPSILON: number = 0.001; - - public static Sign(value: number): number { - return value >= 0 ? 1 : -1; - } - - public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { - if (inline) { - p1.x += p2.x; - p1.y += p2.y; - return p1; - } - else { - return new PIXIPoint(p1.x + p2.x, p1.y + p2.y); - } - } - - public static Perp(p1: PIXIPoint): PIXIPoint { - return new PIXIPoint(-p1.y, p1.x); - } - - public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint { - if (inline) { - p1.x /= by; - p1.y /= by; - return p1; - } - else { - return new PIXIPoint(p1.x / by, p1.y / by); - } - } - - public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) { - if (inline) { - p1.x *= by; - p1.y *= by; - return p1; - } - else { - return new PIXIPoint(p1.x * by, p1.y * by); - } - } - - public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { - if (inline) { - p1.x -= p2.x; - p1.y -= p2.y; - return p1; - } - else { - return new PIXIPoint(p1.x - p2.x, p1.y - p2.y); - } - } - - public static Area(rect: PIXIRectangle): number { - return rect.width * rect.height; - } - - public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) { - // Return minimum distance between line segment vw and point p - var l2 = MathUtil.DistSquared(v, w); // i.e. |w-v|^2 - avoid a sqrt - if (l2 === 0.0) return MathUtil.Dist(p, v); // v === w case - // Consider the line extending the segment, parameterized as v + t (w - v). - // We find projection of point p onto the line. - // It falls where t = [(p-v) . (w-v)] / |w-v|^2 - // We clamp t from [0,1] to handle points outside the segment vw. - var dot = MathUtil.Dot( - MathUtil.SubtractPoint(p, v), - MathUtil.SubtractPoint(w, v)) / l2; - var t = Math.max(0, Math.min(1, dot)); - // Projection falls on the segment - var projection = MathUtil.AddPoint(v, - MathUtil.MultiplyConstant( - MathUtil.SubtractPoint(w, v), t)); - return MathUtil.Dist(p, projection); - } - - public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined { - var a1 = pe1.y - ps1.y; - var b1 = ps1.x - pe1.x; - - var a2 = pe2.y - ps2.y; - var b2 = ps2.x - pe2.x; - - var delta = a1 * b2 - a2 * b1; - if (delta === 0) { - return undefined; - } - var c2 = a2 * ps2.x + b2 * ps2.y; - var c1 = a1 * ps1.x + b1 * ps1.y; - var invdelta = 1 / delta; - return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta); - } - - public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean { - if (p.x < rect.left - this.EPSILON) { - return false; - } - if (p.x > rect.right + this.EPSILON) { - return false; - } - if (p.y < rect.top - this.EPSILON) { - return false; - } - if (p.y > rect.bottom + this.EPSILON) { - return false; - } - - return true; - } - - public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array<PIXIPoint> { - var r1 = new PIXIPoint(rect.left, rect.top); - var r2 = new PIXIPoint(rect.right, rect.top); - var r3 = new PIXIPoint(rect.right, rect.bottom); - var r4 = new PIXIPoint(rect.left, rect.bottom); - var ret = new Array<PIXIPoint>(); - var dist = this.Dist(lineFrom, lineTo); - var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - return ret; - } - - public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle { - const left = Math.max(rect1.x, rect2.x); - const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width); - const top = Math.max(rect1.y, rect2.y); - const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height); - return new PIXIRectangle(left, top, right - left, bottom - top); - } - - public static Dist(p1: PIXIPoint, p2: PIXIPoint): number { - return Math.sqrt(MathUtil.DistSquared(p1, p2)); - } - - public static Dot(p1: PIXIPoint, p2: PIXIPoint): number { - return p1.x * p2.x + p1.y * p2.y; - } - - public static Normalize(p1: PIXIPoint) { - var d = this.Length(p1); - return new PIXIPoint(p1.x / d, p1.y / d); - } - - public static Length(p1: PIXIPoint): number { - return Math.sqrt(p1.x * p1.x + p1.y * p1.y); - } - - public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number { - const a = p1.x - p2.x; - const b = p1.y - p2.y; - return (a * a + b * b); - } - - public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean { - return !(r2.x > r1.x + r1.width || - r2.x + r2.width < r1.x || - r2.y > r1.y + r1.height || - r2.y + r2.height < r1.y); - } - - public static ArgMin(temp: number[]): number { - let index = 0; - let value = temp[0]; - for (let i = 1; i < temp.length; i++) { - if (temp[i] < value) { - value = temp[i]; - index = i; - } - } - return index; - } - - public static ArgMax(temp: number[]): number { - let index = 0; - let value = temp[0]; - for (let i = 1; i < temp.length; i++) { - if (temp[i] > value) { - value = temp[i]; - index = i; - } - } - return index; - } - - public static Combinations<T>(chars: T[]) { - let result = new Array<T>(); - let f = (prefix: any, chars: any) => { - for (let i = 0; i < chars.length; i++) { - result.push(prefix.concat(chars[i])); - f(prefix.concat(chars[i]), chars.slice(i + 1)); - } - }; - f([], chars); - return result; - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.scss b/src/client/views/nodes/DataVizBox/HistogramBox.scss deleted file mode 100644 index 5aac9dc77..000000000 --- a/src/client/views/nodes/DataVizBox/HistogramBox.scss +++ /dev/null @@ -1,18 +0,0 @@ -// change the stroke color of line-svg class -.svgLine { - position: absolute; - background: darkGray; - stroke: #000; - stroke-width: 1px; - width:100%; - height:100%; - opacity: 0.4; -} - -.svgContainer { - position: absolute; - top:0; - left:0; - width:100%; - height: 100%; -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.tsx b/src/client/views/nodes/DataVizBox/HistogramBox.tsx deleted file mode 100644 index 00dc2ef46..000000000 --- a/src/client/views/nodes/DataVizBox/HistogramBox.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc } from "../../../../fields/Doc"; -import { NumCast } from "../../../../fields/Types"; -import "./HistogramBox.scss"; - -interface HistogramBoxProps { - rootDoc: Doc; - pairs: { - x: number, - y: number - }[] -} - - -export class HistogramBox extends React.Component<HistogramBoxProps> { - - private origin = {x: 0.1 * this.width, y: 0.9 * this.height}; - - @computed get width() { - return NumCast(this.props.rootDoc.width); - } - - @computed get height() { - return NumCast(this.props.rootDoc.height); - } - - @computed get x() { - return NumCast(this.props.rootDoc.x); - } - - @computed get y() { - return NumCast(this.props.rootDoc.y); - } - - @computed get generatePoints() { - // evenly distribute points along the x axis - const xVals: number[] = this.props.pairs.map(p => p.x); - const yVals: number[] = this.props.pairs.map(p => p.y); - - const xMin = Math.min(...xVals); - const xMax = Math.max(...xVals); - const yMin = Math.min(...yVals); - const yMax = Math.max(...yVals); - - const xRange = xMax - xMin; - const yRange = yMax - yMin; - - const xScale = this.width / xRange; - const yScale = this.height / yRange; - - const xOffset = (this.x + (0.1 * this.width)) - xMin * xScale; - const yOffset = (this.y + (0.25 * this.height)) - yMin * yScale; - - const points: { - x: number, - y: number - }[] = this.props.pairs.map(p => { - return { - x: (p.x * xScale + xOffset) + this.origin.x, - y: (p.y * yScale + yOffset) - } - }); - - return points; - } - - @computed get generateGraphLine() { - const points = this.generatePoints; - // loop through points and create a line from each point to the next - let lines: { - x1: number, - y1: number, - x2: number, - y2: number - }[] = []; - for (let i = 0; i < points.length - 1; i++) { - lines.push({ - x1: points[i].x, - y1: points[i].y, - x2: points[i + 1].x, - y2: points[i + 1].y - }); - } - // generate array of svg with lines - let svgLines: JSX.Element[] = []; - for (let i = 0; i < lines.length; i++) { - svgLines.push( - <line - className="svgLine" - key={i} - x1={lines[i].x1} - y1={lines[i].y1} - x2={lines[i].x2} - y2={lines[i].y2} - stroke="black" - strokeWidth={2} - /> - ); - } - - let res = []; - for (let i = 0; i < svgLines.length; i++) { - res.push(<svg className="svgContainer">{svgLines[i]}</svg>) - } - return res; - } - - @computed get generateAxes() { - - const xAxis = { - x1: 0.1 * this.width, - x2: 0.9 * this.width, - y1: 0.9 * this.height, - y2: 0.9 * this.height, - }; - - const yAxis = { - x1: 0.1 * this.width, - x2: 0.1 * this.width, - y1: 0.25 * this.height, - y2: 0.9 * this.height, - }; - - - return ( - [ - (<svg className="svgContainer"> - {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={this.width - (0.1 * this.width)} y2={xAxis} /> */} - <line className="svgLine" x1={xAxis.x1} y1={xAxis.y1} x2={xAxis.x2} y2={xAxis.y2}/> - - {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={yAxis} y2={this.y + 50} /> */} - </svg>), - ( - <svg className="svgContainer"> - <line className="svgLine" x1={yAxis.x1} y1={yAxis.y1} x2={yAxis.x2} y2={yAxis.y2} /> - {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={yAxis} y2={this.y + 50} /> */} - </svg>) - ] - ) - } - - - render() { - return ( - <div>histogram box - {/* <svg className="svgContainer"> - {this.generateSVGLine} - </svg> */} - {this.generateAxes[0]} - {this.generateAxes[1]} - {this.generateGraphLine.map(line => line)} - </div> - ) - - } - -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss new file mode 100644 index 000000000..7792a2758 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -0,0 +1,10 @@ +.tooltip { + // make the height width bigger + width: 50px; + height: 50px; +} + +.highlight { + // change the color of the circle element to be red + fill: red; +} diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx new file mode 100644 index 000000000..d893b3e12 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -0,0 +1,290 @@ +import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { DataPoint } from '../ChartBox'; +// import d3 +import * as d3 from 'd3'; +import { minMaxRange, createLineGenerator, xGrid, yGrid, drawLine, xAxisCreator, yAxisCreator, scaleCreatorNumerical, scaleCreatorCategorical } from '../utils/D3Utils'; +import { Docs } from '../../../../documents/Documents'; +import { Doc, DocListCast } from '../../../../../fields/Doc'; +import { Chart, ChartProps } from '../ChartInterface'; +import './Chart.scss'; + +type minMaxRange = { + xMin: number | undefined; + xMax: number | undefined; + yMin: number | undefined; + yMax: number | undefined; +}; + +interface SelectedDataPoint { + x: number; + y: number; + elem?: d3.Selection<d3.BaseType, unknown, SVGGElement, unknown>; +} + +@observer +export class LineChart extends React.Component<ChartProps> implements Chart { + private _dataReactionDisposer: IReactionDisposer | undefined = undefined; + private _heightReactionDisposer: IReactionDisposer | undefined = undefined; + private _widthReactionDisposer: IReactionDisposer | undefined; + @observable private _x: number = 0; + @observable private _y: number = 0; + @observable private _currSelected: SelectedDataPoint | undefined = undefined; + // create ref for the div + private _chartRef: React.RefObject<HTMLDivElement> = React.createRef(); + private _rangeVals: minMaxRange = { + xMin: undefined, + xMax: undefined, + yMin: undefined, + yMax: undefined, + }; + // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates + + private _chartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined; + + // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that + + // write the getanchor function that gets whatever I want as the link anchor + + componentDidMount = () => { + this._dataReactionDisposer = reaction( + () => this.props.chartData, + chartData => { + this._rangeVals = minMaxRange(chartData.data); + this.drawChart(); + }, + { fireImmediately: true } + ); + // DocumentDecorations.Instance.Interacting + this._heightReactionDisposer = reaction(() => this.props.height, this.drawChart.bind(this)); + this._widthReactionDisposer = reaction(() => this.props.width, this.drawChart.bind(this)); + reaction( + () => DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']), + annotations => { + // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way + // could be blue colored to make it look like anchor + console.log(annotations); + // this.drawAnnotations() + // loop through annotations and draw them + annotations.forEach(a => { + this.drawAnnotations(Number(a.x), Number(a.y)); + }); + // this.drawAnnotations(annotations.x, annotations.y); + }, + { fireImmediately: true } + ); + + this.props.setCurrChart(this); + }; + + // gets called whenever the "data-annotations" fields gets updated + drawAnnotations(dataX: number, dataY: number) { + // TODO: nda - can optimize this by having some sort of mapping of the x and y values to the individual circle elements + // loop through all html elements with class .circle-d1 and find the one that has "data-x" and "data-y" attributes that match the dataX and dataY + // if it exists, then highlight it + // if it doesn't exist, then remove the highlight + const elements = document.querySelectorAll('.datapoint'); + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + const x = element.getAttribute('data-x'); + const y = element.getAttribute('data-y'); + if (x === dataX.toString() && y === dataY.toString()) { + element.classList.add('highlight'); + } + // TODO: nda - this remove highlight code should go where we remove the links + // } else { + // element.classList.remove('highlight'); + // } + } + } + + removeAnnotations(dataX: number, dataY: number) { + // loop through and remove any annotations that no longer exist + } + + _getAnchor() { + // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element) + + // TODO: nda - look at pdfviewer get anchor for args + const doc = Docs.Create.TextanchorDocument({ + /*put in some options*/ + title: 'line doc selection' + this._currSelected?.x, + }); + // access curr selected from the charts + doc.x = this._currSelected?.x; + doc.y = this._currSelected?.y; + doc.chartType = 'line'; + return doc; + // have some other function in code that + } + + componentWillUnmount() { + if (this._dataReactionDisposer) { + this._dataReactionDisposer(); + } + if (this._heightReactionDisposer) { + this._heightReactionDisposer(); + } + if (this._widthReactionDisposer) { + this._widthReactionDisposer(); + } + } + + tooltipContent(data: DataPoint) { + return `<b>x: ${data.x} y: ${data.y}</b>`; + } + + @computed get height(): number { + return this.props.height - this.props.margin.top - this.props.margin.bottom; + } + + @computed get width(): number { + return this.props.width - this.props.margin.left - this.props.margin.right; + } + + setupTooltip() { + const tooltip = d3 + .select(this._chartRef.current) + .append('div') + .attr('class', 'tooltip') + .style('opacity', 0) + .style('background', '#fff') + .style('border', '1px solid #ccc') + .style('padding', '5px') + .style('position', 'absolute') + .style('font-size', '12px'); + return tooltip; + } + + // TODO: nda - use this everyewhere we update currSelected? + @action + setCurrSelected(x: number, y: number) { + // TODO: nda - get rid of svg element in the list? + this._currSelected = { x: x, y: y }; + } + + drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) { + if (this._chartSvg) { + const circleClass = '.circle-' + idx; + this._chartSvg + .selectAll(circleClass) + .data(data) + .join('circle') // enter append + .attr('class', `${circleClass} datapoint`) + .attr('r', '3') // radius + .attr('cx', d => xScale(d.x)) + .attr('cy', d => yScale(d.y)) + .attr('data-x', d => d.x) + .attr('data-y', d => d.y); + } + } + + // TODO: nda - can use d3.create() to create html element instead of appending + drawChart() { + const { chartData, margin } = this.props; + const data = chartData.data[0]; + // clearing tooltip and the current chart + d3.select(this._chartRef.current).select('svg').remove(); + d3.select(this._chartRef.current).select('.tooltip').remove(); + + // TODO: nda - refactor code so that it only recalculates min max and things related to data on data update + + const { xMin, xMax, yMin, yMax } = this._rangeVals; + // const svg = d3.select(this._chartRef.current).append(this.svgContainer.html()); + // adding svg + this._chartSvg = d3 + .select(this._chartRef.current) + .append('svg') + .attr('width', `${this.width + margin.right + margin.left}`) + .attr('height', `${this.height + margin.top + margin.bottom}`) + .append('g') + .attr('transform', `translate(${margin.left}, ${margin.top})`); + + const svg = this._chartSvg; + + if (xMin == undefined || xMax == undefined || yMin == undefined || yMax == undefined) { + // TODO: nda - error handle + return; + } + + // creating the x and y scales + const xScale = scaleCreatorNumerical(xMin, xMax, 0, this.width); + const yScale = scaleCreatorNumerical(0, yMax, this.height, 0); + + // create a line function that takes in the data.data.x and data.data.y + // TODO: nda - fix the types for the d here + const lineGen = createLineGenerator(xScale, yScale); + + // create x and y grids + xGrid(svg.append('g'), this.height, xScale); + yGrid(svg.append('g'), this.width, yScale); + xAxisCreator(svg.append('g'), this.height, xScale); + yAxisCreator(svg.append('g'), this.width, yScale); + + // draw the line + drawLine(svg.append('path'), data, lineGen); + + // draw the datapoint circle + this.drawDataPoints(data, 0, xScale, yScale); + + const focus = svg.append('g').attr('class', 'focus').style('display', 'none'); + focus.append('circle').attr('r', 5).attr('class', 'circle'); + const tooltip = this.setupTooltip(); + // add all the tooltipContent to the tooltip + const mousemove = action((e: any) => { + const bisect = d3.bisector((d: DataPoint) => d.x).left; + const xPos = d3.pointer(e)[0]; + const x0 = bisect(data, xScale.invert(xPos)); + const d0 = data[x0]; + this._x = d0.x; + this._y = d0.y; + focus.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`); + // TODO: nda - implement tooltips + tooltip.transition().duration(300).style('opacity', 0.9); + // TODO: nda - updating the inner html could be deadly cause injection attacks! + tooltip.html(() => this.tooltipContent(d0)).style('transform', `translate(${xScale(d0.x) - (this.width + margin.left + margin.right) + 30}px,${yScale(d0.y) + 30}px)`); + }); + + const onPointClick = action((e: any) => { + const bisect = d3.bisector((d: DataPoint) => d.x).left; + const xPos = d3.pointer(e)[0]; + const x0 = bisect(data, xScale.invert(xPos)); + const d0 = data[x0]; + this._x = d0.x; + this._y = d0.y; + // find .circle-d1 with data-x = d0.x and data-y = d0.y + const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y); + this._currSelected = { x: d0.x, y: d0.y, elem: selected }; + console.log('Getting here'); + // this.drawAnnotations(this._x, this._y); + // this.props.getAnchor(); + console.log(this._currSelected); + }); + + this._chartSvg + .append('rect') + .attr('class', 'overlay') + .attr('width', this.width) + .attr('height', this.height + margin.top + margin.bottom) + .attr('fill', 'none') + .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`) + .style('opacity', 0) + .on('mouseover', () => { + focus.style('display', null); + }) + .on('mouseout', () => { + tooltip.transition().duration(300).style('opacity', 0); + }) + .on('mousemove', mousemove) + .on('click', onPointClick); + } + + render() { + return ( + <div ref={this._chartRef} className="chart-container"> + <span>Curr Selected: {this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'}</span> + </div> + ); + } +} diff --git a/src/client/views/nodes/DataVizBox/TableBox.scss b/src/client/views/nodes/DataVizBox/components/TableBox.scss index 1264d6a46..1264d6a46 100644 --- a/src/client/views/nodes/DataVizBox/TableBox.scss +++ b/src/client/views/nodes/DataVizBox/components/TableBox.scss diff --git a/src/client/views/nodes/DataVizBox/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index dfa8262d8..dfa8262d8 100644 --- a/src/client/views/nodes/DataVizBox/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts new file mode 100644 index 000000000..2bb091999 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts @@ -0,0 +1,67 @@ +import * as d3 from 'd3'; +import { DataPoint } from '../ChartBox'; + +// TODO: nda - implement function that can handle range for strings + +export const minMaxRange = (dataPts: DataPoint[][]) => { + // find the max and min of all the data points + const yMin = d3.min(dataPts, d => d3.min(d, d => Number(d.y))); + const yMax = d3.max(dataPts, d => d3.max(d, d => Number(d.y))); + + const xMin = d3.min(dataPts, d => d3.min(d, d => Number(d.x))); + const xMax = d3.max(dataPts, d => d3.max(d, d => Number(d.x))); + + return { xMin, xMax, yMin, yMax }; +}; + +export const scaleCreatorCategorical = (labels: string[], range: number[]) => { + const scale = d3.scaleBand().domain(labels).range(range); + + return scale; +}; + +export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => { + return d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]); +}; + +export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) => { + // TODO: nda - look into the different types of curves + return d3 + .line<DataPoint>() + .x(d => xScale(d.x)) + .y(d => yScale(d.y)) + .curve(d3.curveMonotoneX); +}; + +export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, xScale: d3.ScaleLinear<number, number, never>) => { + console.log('x axis creator being called'); + g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15)); +}; + +export const yAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, marginLeft: number, yScale: d3.ScaleLinear<number, number, never>) => { + g.attr('class', 'y-axis').call(d3.axisLeft(yScale)); +}; + +export const xGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, scale: d3.ScaleLinear<number, number, never>) => { + g.attr('class', 'xGrid') + .attr('transform', `translate(0,${height})`) + .call( + d3 + .axisBottom(scale) + .tickSize(-height) + .tickFormat((a, b) => '') + ); +}; + +export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, width: number, scale: d3.ScaleLinear<number, number, never>) => { + g.attr('class', 'yGrid').call( + d3 + .axisLeft(scale) + .tickSize(-width) + .tickFormat((a, b) => '') + ); +}; + +export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>) => { + p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('class', 'line').attr('d', lineGen); +}; diff --git a/src/server/ApiManagers/DataVizManager.ts b/src/server/ApiManagers/DataVizManager.ts new file mode 100644 index 000000000..0d43130d1 --- /dev/null +++ b/src/server/ApiManagers/DataVizManager.ts @@ -0,0 +1,26 @@ +import { csvParser, csvToString } from "../DataVizUtils"; +import { Method, _success } from "../RouteManager"; +import ApiManager, { Registration } from "./ApiManager"; +import { Directory, serverPathToFile } from "./UploadManager"; +import * as path from 'path'; + +export default class DataVizManager extends ApiManager { + protected initialize(register: Registration): void { + register({ + method: Method.GET, + subscription: "/csvData", + secureHandler: async ({ req, res }) => { + const uri = req.query.uri as string; + + return new Promise<void>(resolve => { + const name = path.basename(uri); + const sPath = serverPathToFile(Directory.csv, name); + const parsedCsv = csvParser(csvToString(sPath)); + _success(res, parsedCsv); + resolve(); + }); + } + }); + } + +}
\ No newline at end of file diff --git a/src/server/DataVizUtils.ts b/src/server/DataVizUtils.ts index 4fd0ca6ff..2528fb1ab 100644 --- a/src/server/DataVizUtils.ts +++ b/src/server/DataVizUtils.ts @@ -1,3 +1,5 @@ +import { readFileSync } from "fs"; + export function csvParser(csv: string) { const lines = csv.split("\n"); const headers = lines[0].split(","); @@ -10,4 +12,8 @@ export function csvParser(csv: string) { return obj; }); return data; +} + +export function csvToString(path: string) { + return readFileSync(path, 'utf8'); }
\ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 6e6bde3cb..6562860fe 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -4,6 +4,7 @@ import * as mobileDetect from 'mobile-detect'; import * as path from 'path'; import * as qs from 'query-string'; import { log_execution } from './ActionUtilities'; +import DataVizManager from './ApiManagers/DataVizManager'; import DeleteManager from './ApiManagers/DeleteManager'; import DownloadManager from './ApiManagers/DownloadManager'; import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager'; @@ -62,7 +63,19 @@ async function preliminaryFunctions() { * with the server */ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: RouteManager) { - const managers = [new SessionManager(), new UserManager(), new UploadManager(), new DownloadManager(), new SearchManager(), new PDFManager(), new DeleteManager(), new UtilManager(), new GeneralGoogleManager(), new GooglePhotosManager()]; + const managers = [ + new SessionManager(), + new UserManager(), + new UploadManager(), + new DownloadManager(), + new SearchManager(), + new PDFManager(), + new DeleteManager(), + new UtilManager(), + new GeneralGoogleManager(), + new GooglePhotosManager(), + new DataVizManager(), + ]; // initialize API Managers console.log(yellow('\nregistering server routes...')); |