diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b2cce21e49..de391e4416 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,11 +11,10 @@ "@creditkarma/thrift-server-core": "^1.0.4", "@creditkarma/thrift-typescript": "^3.7.6", "@layerstack/utils": "^0.0.7", - "@types/echarts": "^4.9.22", "clsx": "^2.1.1", "d3": "^7.9.0", "dotenv": "^16.4.7", - "echarts": "^5.6.0", + "layerchart": "^0.93.9", "lodash": "^4.17.21", "tailwind-merge": "^2.6.0", "tailwind-variants": "^0.3.0" @@ -74,7 +73,6 @@ }, "node_modules/@ampproject/remapping": { "version": "2.3.0", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -144,6 +142,24 @@ "node": ">=4.2.0" } }, + "node_modules/@dagrejs/dagre": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-1.1.4.tgz", + "integrity": "sha512-QUTc54Cg/wvmlEUxB+uvoPVKFazM1H18kVHBQNmK2NbrDR5ihOCR6CXLnDSZzMcSQKJtabPUWridBOlJM3WkDg==", + "license": "MIT", + "dependencies": { + "@dagrejs/graphlib": "2.2.4" + } + }, + "node_modules/@dagrejs/graphlib": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-2.2.4.tgz", + "integrity": "sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==", + "license": "MIT", + "engines": { + "node": ">17.0.0" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.24.0", "cpu": [ @@ -281,7 +297,6 @@ }, "node_modules/@floating-ui/core": { "version": "1.6.8", - "dev": true, "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.8" @@ -289,7 +304,6 @@ }, "node_modules/@floating-ui/dom": { "version": "1.6.12", - "dev": true, "license": "MIT", "dependencies": { "@floating-ui/core": "^1.6.0", @@ -298,7 +312,6 @@ }, "node_modules/@floating-ui/utils": { "version": "0.2.8", - "dev": true, "license": "MIT" }, "node_modules/@humanfs/core": { @@ -453,6 +466,50 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@layerstack/svelte-actions": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@layerstack/svelte-actions/-/svelte-actions-0.0.11.tgz", + "integrity": "sha512-sfplFX8rOBW74xjDCAY7QzHilvUGjFQTkfQXxUO6e68cKCHmxsSuTCtFbHRCfIUJCQ31gAbeY1xpWKnlVfgZ9g==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.12", + "@layerstack/utils": "0.0.7", + "d3-array": "^3.2.4", + "d3-scale": "^4.0.2", + "date-fns": "^4.1.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/@layerstack/svelte-stores": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@layerstack/svelte-stores/-/svelte-stores-0.0.9.tgz", + "integrity": "sha512-5yyi7eV/2hOraw7wQgEObVR4s2/4T3G/GjGaZLR9BQH8mnsLuXw1n3k6rFT8NxiRvpEjWJ9l9ILOGc/XzFJb3g==", + "license": "MIT", + "dependencies": { + "@layerstack/utils": "0.0.7", + "d3-array": "^3.2.4", + "date-fns": "^4.1.0", + "immer": "^10.1.1", + "lodash-es": "^4.17.21", + "zod": "^3.23.8" + } + }, + "node_modules/@layerstack/tailwind": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@layerstack/tailwind/-/tailwind-0.0.11.tgz", + "integrity": "sha512-dHvJM5xpVisPrM6NClSCNPqB/u+pSwemWzkKeb911/8pLUuyr+7HceTpv5AZMyE/I5A2RxoTa5mTZokmvEqPnQ==", + "license": "MIT", + "dependencies": { + "@layerstack/utils": "^0.0.7", + "clsx": "^2.1.1", + "culori": "^4.0.1", + "d3-array": "^3.2.4", + "date-fns": "^4.1.0", + "lodash-es": "^4.17.21", + "tailwind-merge": "^2.5.4", + "tailwindcss": "^3.4.15" + } + }, "node_modules/@layerstack/utils": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/@layerstack/utils/-/utils-0.0.7.tgz", @@ -1212,13 +1269,6 @@ "@types/d3-selection": "*" } }, - "node_modules/@types/echarts": { - "version": "4.9.22", - "license": "MIT", - "dependencies": { - "@types/zrender": "*" - } - }, "node_modules/@types/eslint": { "version": "9.6.1", "dev": true, @@ -1230,7 +1280,6 @@ }, "node_modules/@types/estree": { "version": "1.0.6", - "dev": true, "license": "MIT" }, "node_modules/@types/fs-extra": { @@ -1266,10 +1315,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/zrender": { - "version": "4.0.6", - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.18.1", "dev": true, @@ -1565,7 +1610,6 @@ }, "node_modules/acorn": { "version": "8.14.0", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1584,7 +1628,6 @@ }, "node_modules/acorn-typescript": { "version": "1.4.13", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": ">=8.9.0" @@ -1655,12 +1698,17 @@ }, "node_modules/aria-query": { "version": "5.3.2", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" } }, + "node_modules/array-source": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/array-source/-/array-source-0.0.4.tgz", + "integrity": "sha512-frNdc+zBn80vipY+GdcJkLEbMWj3xmzArYApmUGxoiV8uAu/ygcs9icPdsGdA26h0MkHUMW6EN2piIvVx+M5Mw==", + "license": "BSD-3-Clause" + }, "node_modules/assertion-error": { "version": "2.0.1", "dev": true, @@ -1707,7 +1755,6 @@ }, "node_modules/axobject-query": { "version": "4.1.0", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -1990,6 +2037,15 @@ "node": ">=4" } }, + "node_modules/culori": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/culori/-/culori-4.0.1.tgz", + "integrity": "sha512-LSnjA6HuIUOlkfKVbzi2OlToZE8OjFi667JWN9qNymXVXzGDmvuP60SSgC+e92sd7B7158f7Fy3Mb6rXS5EDPw==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/d3": { "version": "7.9.0", "license": "ISC", @@ -2031,6 +2087,8 @@ }, "node_modules/d3-array": { "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", "dependencies": { "internmap": "1 - 2" @@ -2072,6 +2130,8 @@ }, "node_modules/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==", "license": "ISC", "engines": { "node": ">=12" @@ -2184,6 +2244,21 @@ "node": ">=12" } }, + "node_modules/d3-geo-voronoi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/d3-geo-voronoi/-/d3-geo-voronoi-2.1.0.tgz", + "integrity": "sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-delaunay": "6", + "d3-geo": "3", + "d3-tricontour": "1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-hierarchy": { "version": "3.1.2", "license": "ISC", @@ -2201,6 +2276,12 @@ "node": ">=12" } }, + "node_modules/d3-interpolate-path": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-interpolate-path/-/d3-interpolate-path-2.3.0.tgz", + "integrity": "sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ==", + "license": "BSD-3-Clause" + }, "node_modules/d3-path": { "version": "3.1.0", "license": "ISC", @@ -2229,8 +2310,50 @@ "node": ">=12" } }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, "node_modules/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==", "license": "ISC", "dependencies": { "d3-array": "2.10.0 - 3", @@ -2271,6 +2394,12 @@ "node": ">=12" } }, + "node_modules/d3-tile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d3-tile/-/d3-tile-1.0.0.tgz", + "integrity": "sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q==", + "license": "BSD-3-Clause" + }, "node_modules/d3-time": { "version": "3.1.0", "license": "ISC", @@ -2315,6 +2444,19 @@ "d3-selection": "2 - 3" } }, + "node_modules/d3-tricontour": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-tricontour/-/d3-tricontour-1.0.2.tgz", + "integrity": "sha512-HIRxHzHagPtUPNabjOlfcyismJYIsc+Xlq4mlsts4e8eAcwyq9Tgk/sYdyhlBpQ0MHwVquc/8j+e29YjXnmxeA==", + "license": "ISC", + "dependencies": { + "d3-delaunay": "6", + "d3-scale": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-zoom": { "version": "3.0.0", "license": "ISC", @@ -2414,20 +2556,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/echarts": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", - "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "2.3.0", - "zrender": "5.6.1" - } - }, - "node_modules/echarts/node_modules/tslib": { - "version": "2.3.0", - "license": "0BSD" - }, "node_modules/electron-to-chromium": { "version": "1.5.75", "dev": true, @@ -2651,7 +2779,6 @@ }, "node_modules/esm-env": { "version": "1.2.1", - "dev": true, "license": "MIT" }, "node_modules/espree": { @@ -2683,7 +2810,6 @@ }, "node_modules/esrap": { "version": "1.3.2", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -2799,6 +2925,15 @@ "node": ">=16.0.0" } }, + "node_modules/file-source": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-source/-/file-source-0.6.1.tgz", + "integrity": "sha512-1R1KneL7eTXmXfKxC10V/9NeGOdbsAXJ+lQ//fvvcHUgtaZcZDWNJNblxAoVOyV1cj45pOtUrR3vZTBwqcW8XA==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-source": "0.3" + } + }, "node_modules/fill-range": { "version": "7.1.1", "license": "MIT", @@ -3007,6 +3142,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "dev": true, @@ -3177,6 +3322,62 @@ "dev": true, "license": "MIT" }, + "node_modules/layercake": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/layercake/-/layercake-8.4.2.tgz", + "integrity": "sha512-ZVjdl6ryjz7F4jXX8Dh871nN/Nv6o3m7gLI7KrMPNGiy3Mg5BuA9j0UeucrmK/uLe20XpDyG6X4v/iYJYSPLrg==", + "license": "MIT", + "dependencies": { + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0" + }, + "peerDependencies": { + "svelte": "3 - 5 || >=5.0.0-next.120", + "typescript": "^5.0.2" + } + }, + "node_modules/layerchart": { + "version": "0.93.9", + "resolved": "https://registry.npmjs.org/layerchart/-/layerchart-0.93.9.tgz", + "integrity": "sha512-rI5n6RrX6UvWSCJEB+Hw2uR415lTAdePwGuKSgMieqUGvTAK2hwF1xp/YpbvHaTkTNS6ljGoXCeszwQxklnVdQ==", + "license": "MIT", + "dependencies": { + "@dagrejs/dagre": "^1.1.4", + "@layerstack/svelte-actions": "^0.0.11", + "@layerstack/svelte-stores": "^0.0.9", + "@layerstack/tailwind": "^0.0.11", + "@layerstack/utils": "^0.0.7", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-delaunay": "^6.0.4", + "d3-dsv": "^3.0.1", + "d3-force": "^3.0.0", + "d3-geo": "^3.1.1", + "d3-geo-voronoi": "^2.1.0", + "d3-hierarchy": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-interpolate-path": "^2.3.0", + "d3-path": "^3.1.0", + "d3-quadtree": "^3.0.1", + "d3-random": "^3.0.1", + "d3-sankey": "^0.12.3", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "d3-shape": "^3.2.0", + "d3-tile": "^1.0.0", + "d3-time": "^3.1.0", + "date-fns": "^4.1.0", + "layercake": "^8.4.2", + "lodash-es": "^4.17.21", + "shapefile": "^0.6.6", + "topojson-client": "^3.1.0" + }, + "peerDependencies": { + "svelte": "^3.56.0 || ^4.0.0 || ^5.0.0" + } + }, "node_modules/levn": { "version": "0.4.1", "dev": true, @@ -3218,7 +3419,6 @@ }, "node_modules/locate-character": { "version": "3.0.0", - "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -3271,7 +3471,6 @@ }, "node_modules/magic-string": { "version": "0.30.17", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -3520,6 +3719,16 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/path-source": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/path-source/-/path-source-0.1.3.tgz", + "integrity": "sha512-dWRHm5mIw5kw0cs3QZLNmpUWty48f5+5v9nWD2dw3Y0Hf+s01Ag8iJEWV0Sm0kocE8kK27DrIowha03e1YR+Qw==", + "license": "BSD-3-Clause", + "dependencies": { + "array-source": "0.0", + "file-source": "0.6" + } + }, "node_modules/pathe": { "version": "1.1.2", "dev": true, @@ -4005,6 +4214,30 @@ "dev": true, "license": "MIT" }, + "node_modules/shapefile": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/shapefile/-/shapefile-0.6.6.tgz", + "integrity": "sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw==", + "license": "BSD-3-Clause", + "dependencies": { + "array-source": "0.0", + "commander": "2", + "path-source": "0.1", + "slice-source": "0.4", + "stream-source": "0.3", + "text-encoding": "^0.6.4" + }, + "bin": { + "dbf2json": "bin/dbf2json", + "shp2json": "bin/shp2json" + } + }, + "node_modules/shapefile/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "dev": true, @@ -4042,6 +4275,12 @@ "node": ">=18" } }, + "node_modules/slice-source": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/slice-source/-/slice-source-0.4.1.tgz", + "integrity": "sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==", + "license": "BSD-3-Clause" + }, "node_modules/source-map-js": { "version": "1.2.1", "license": "BSD-3-Clause", @@ -4059,6 +4298,12 @@ "dev": true, "license": "MIT" }, + "node_modules/stream-source": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz", + "integrity": "sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g==", + "license": "BSD-3-Clause" + }, "node_modules/strip-json-comments": { "version": "3.1.1", "dev": true, @@ -4120,7 +4365,6 @@ }, "node_modules/svelte": { "version": "5.15.0", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -4294,7 +4538,6 @@ }, "node_modules/svelte/node_modules/is-reference": { "version": "3.0.3", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.6" @@ -4481,6 +4724,13 @@ "node": ">=8.10.0" } }, + "node_modules/text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", + "deprecated": "no longer maintained", + "license": "Unlicense" + }, "node_modules/thenify": { "version": "3.3.1", "license": "MIT", @@ -4551,6 +4801,26 @@ "node": ">=8.0" } }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-client/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/totalist": { "version": "3.0.1", "dev": true, @@ -4592,7 +4862,6 @@ }, "node_modules/typescript": { "version": "5.7.2", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -4986,23 +5255,16 @@ }, "node_modules/zimmerframe": { "version": "1.1.2", - "dev": true, "license": "MIT" }, - "node_modules/zrender": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", - "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", - "license": "BSD-3-Clause", - "dependencies": { - "tslib": "2.3.0" + "node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/zrender/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" } } } diff --git a/frontend/package.json b/frontend/package.json index f8f874c82e..825d33b9cc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -63,11 +63,10 @@ "@creditkarma/thrift-server-core": "^1.0.4", "@creditkarma/thrift-typescript": "^3.7.6", "@layerstack/utils": "^0.0.7", - "@types/echarts": "^4.9.22", "clsx": "^2.1.1", "d3": "^7.9.0", "dotenv": "^16.4.7", - "echarts": "^5.6.0", + "layerchart": "^0.93.9", "lodash": "^4.17.21", "tailwind-merge": "^2.6.0", "tailwind-variants": "^0.3.0" diff --git a/frontend/src/lib/components/CustomEChartLegend.svelte b/frontend/src/lib/components/CustomEChartLegend.svelte deleted file mode 100644 index ccd18e3d12..0000000000 --- a/frontend/src/lib/components/CustomEChartLegend.svelte +++ /dev/null @@ -1,135 +0,0 @@ - - -
-
-
- {#each items as { feature } (feature)} - {@const isHidden = hiddenSeries[groupName]?.has(feature)} - {@const color = getSeriesColor(chart, feature)} - - - {/each} -
- - {#if hasOverflow || isExpanded || containerHeight > containerHeightLine} - - {/if} -
-
- - diff --git a/frontend/src/lib/components/EChart.svelte b/frontend/src/lib/components/EChart.svelte deleted file mode 100644 index c04d08c017..0000000000 --- a/frontend/src/lib/components/EChart.svelte +++ /dev/null @@ -1,453 +0,0 @@ - - - - -
-
- {#if enableCustomTooltip} - - {/if} -
-{#if showCustomLegend && legendGroup && chartInstance} - -{/if} diff --git a/frontend/src/lib/components/EChartTooltip.svelte b/frontend/src/lib/components/EChartTooltip.svelte deleted file mode 100644 index b6efc5940f..0000000000 --- a/frontend/src/lib/components/EChartTooltip.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - -
-
- {#if xValue !== null && visible} -
-
- {getTooltipTitle(xValue, xAxisCategories)} -
- -
- {#each series as item} - - {/each} -
-
- -
- {isMacOS() ? '⌘' : 'Ctrl'} to lock tooltip -
-
- {/if} -
diff --git a/frontend/src/lib/components/PercentileChart.svelte b/frontend/src/lib/components/PercentileChart.svelte deleted file mode 100644 index 61e3fc83b8..0000000000 --- a/frontend/src/lib/components/PercentileChart.svelte +++ /dev/null @@ -1,59 +0,0 @@ - - - diff --git a/frontend/src/lib/components/charts/FeaturesLineChart.svelte b/frontend/src/lib/components/charts/FeaturesLineChart.svelte new file mode 100644 index 0000000000..5f83d0142b --- /dev/null +++ b/frontend/src/lib/components/charts/FeaturesLineChart.svelte @@ -0,0 +1,102 @@ + + + { + return { + key: d.feature, + data: d.points.map((p) => { + return { + date: new Date(p.ts), + value: p.value === NULL_VALUE ? null : p.value + }; + }), + color: colorScale(d.feature) + }; + })} + padding={{ left: 24, bottom: 48 }} + legend={{ placement: 'bottom-left', classes: { root: 'right-0 overflow-auto scrollbar-none' } }} + renderContext="canvas" + {...lineChartProps} + {...restProps} + brush={{ onbrushend }} +> + + {#if markPoint} + {@const x = xScale(markPoint.date)} + {@const y = yScale(markPoint.value)} + + + {/if} + + + + + + {formatDate(x(data))} + + + + {#each visibleSeries as s} + {@const seriesTooltipData = s.data ? findRelatedData(s.data, data, x) : data} + {@const valueAccessor = accessor(s.value ?? (s.data ? (y as unknown) : s.key))} + {@const value = seriesTooltipData ? valueAccessor(seriesTooltipData) : null} + + + {/each} + + +
+ {isMacOS() ? '⌘' : 'Ctrl'} to lock tooltip +
+
+
+
diff --git a/frontend/src/lib/components/charts/PercentileLineChart.svelte b/frontend/src/lib/components/charts/PercentileLineChart.svelte new file mode 100644 index 0000000000..532871f7d5 --- /dev/null +++ b/frontend/src/lib/components/charts/PercentileLineChart.svelte @@ -0,0 +1,54 @@ + + + { + return { + key: c.label, + data: + data + ?.filter((d) => d.label === c.label) + .map((d) => { + return { + date: new Date(d.ts), + value: d.value + }; + }) ?? [], + color: c.color + }; + })} + padding={{ left: 36, bottom: 20 }} + brush={{ onbrushend }} + {...merge( + {}, + lineChartProps, + { + props: { + yAxis: { format: 'metric' } + } + }, + restProps + )} +/> diff --git a/frontend/src/lib/components/charts/common.ts b/frontend/src/lib/components/charts/common.ts new file mode 100644 index 0000000000..7941a25174 --- /dev/null +++ b/frontend/src/lib/components/charts/common.ts @@ -0,0 +1,71 @@ +import type { BarChart, LineChart, PieChart } from 'layerchart'; +import type { ComponentProps } from 'svelte'; + +export type DateValue = { date: Date; value: number }; + +export const colors = [ + '#E5174B', + '#E54D4A', + '#E17545', + '#E3994C', + '#DFAF4F', + '#87BE52', + '#53B167', + '#4DA67D', + '#4EA797', + '#4491CE', + '#4592CC', + '#4172D2', + '#5B5AD1', + '#785AD4', + '#9055D5', + '#BF50D3', + '#CB5587' +]; + +export const tooltipProps = { + root: { + variant: 'none', + class: 'text-small bg-neutral-200 border border-neutral-400 rounded-md shadow-lg', + motion: false + }, + header: { + class: 'text-neutral-700 bg-neutral-300 px-3 py-1' + }, + list: { + class: 'gap-4 px-3 py-2' + }, + item: { + valueAlign: 'right', + classes: { + label: 'text-neutral-800' + } + } +} satisfies NonNullable['props']>['tooltip']; + +export const highlightProps = { + motion: false +} satisfies NonNullable['props']>['highlight']; + +export const barChartProps = { + props: { + tooltip: tooltipProps + } +} satisfies NonNullable>; + +export const lineChartProps = { + grid: { + x: { class: 'stroke-neutral-500/30' }, + y: { class: 'stroke-neutral-500/30' } + }, + props: { + tooltip: tooltipProps, + highlight: highlightProps + } +} satisfies NonNullable>; + +export const pieChartProps = { + props: { + tooltip: tooltipProps + } +} satisfies NonNullable>>; diff --git a/frontend/src/lib/constants/common.ts b/frontend/src/lib/constants/common.ts new file mode 100644 index 0000000000..edd3d4ad05 --- /dev/null +++ b/frontend/src/lib/constants/common.ts @@ -0,0 +1,2 @@ +// Maps to `Constants.magicNullDouble` on server - https://github.com/zipline-ai/chronon/blob/main/api/src/main/scala/ai/chronon/api/Constants.scala#L87 +export const NULL_VALUE = -1234567890; diff --git a/frontend/src/lib/util/chart-options.svelte.ts b/frontend/src/lib/util/chart-options.svelte.ts deleted file mode 100644 index 7da101d2e1..0000000000 --- a/frontend/src/lib/util/chart-options.svelte.ts +++ /dev/null @@ -1,119 +0,0 @@ -import type { EChartOption } from 'echarts'; -import merge from 'lodash/merge'; -import { getCssColorAsHex } from '$lib/util/colors'; - -let neutral300 = $state(''); -let neutral700 = $state(''); - -const colorInterval = setInterval(() => { - const color300 = getCssColorAsHex('--neutral-300'); - const color700 = getCssColorAsHex('--neutral-700'); - - if (color300 && color700) { - neutral300 = color300; - neutral700 = color700; - clearInterval(colorInterval); - } -}, 100); - -export function createChartOption( - customOption: Partial = {}, - customColors = false -): EChartOption { - const defaultOption: EChartOption = { - color: customColors - ? [ - '#E5174B', - '#E54D4A', - '#E17545', - '#E3994C', - '#DFAF4F', - '#87BE52', - '#53B167', - '#4DA67D', - '#4EA797', - '#4491CE', - '#4592CC', - '#4172D2', - '#5B5AD1', - '#785AD4', - '#9055D5', - '#BF50D3', - '#CB5587' - ] - : undefined, - tooltip: { - trigger: 'axis', - axisPointer: { - type: 'line', - lineStyle: { - color: neutral700, - type: 'solid' - } - }, - position: 'top', - confine: true - }, - xAxis: { - type: 'time', - axisLabel: { - formatter: { - month: '{MMM} {d}', - day: '{MMM} {d}' - } as unknown as string, - color: neutral700 - }, - splitLine: { - show: true, - lineStyle: { - color: neutral300 - } - }, - axisLine: { - lineStyle: { - color: neutral300 - } - } - }, - yAxis: { - type: 'value', - axisLabel: { - formatter: (value: number) => (value % 1 === 0 ? value.toFixed(0) : value.toFixed(1)), - color: neutral700 - }, - splitLine: { - show: true, - lineStyle: { - color: neutral300 - } - }, - axisLine: { - lineStyle: { - color: neutral300 - } - } - }, - grid: { - top: 5, - right: 1, - bottom: 0, - left: 0, - containLabel: true - } - }; - - const baseSeriesStyle = { - showSymbol: false, - lineStyle: { - width: 1 - }, - symbolSize: 7 - }; - - if (customOption.series) { - const series = Array.isArray(customOption.series) ? customOption.series : [customOption.series]; - customOption.series = series.map((s) => merge({}, baseSeriesStyle, s)); - } - - return merge({}, defaultOption, customOption); -} diff --git a/frontend/src/lib/util/chart.ts b/frontend/src/lib/util/chart.ts deleted file mode 100644 index fc0e5e208a..0000000000 --- a/frontend/src/lib/util/chart.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { EChartsType } from 'echarts'; - -export function handleChartHighlight( - chart: EChartsType | null, - seriesName: string, - type: 'highlight' | 'downplay' -) { - if (!chart || !seriesName) return; - - // Get the series selected state from legend - const options = chart.getOption(); - const legendOpt = Array.isArray(options.legend) ? options.legend[0] : options.legend; - const isSelected = legendOpt?.selected?.[seriesName]; - - // Only highlight if the series is selected (visible) - if (isSelected !== false) { - chart.dispatchAction({ - type, - seriesName - }); - } -} - -export function getSeriesColor(chart: EChartsType | null, seriesName: string): string { - if (!chart) return '#000000'; - - const options = chart.getOption(); - if (!options?.series || !options?.color) return '#000000'; - - // Find the series index by name - const seriesIndex = options.series.findIndex((s) => s.name === seriesName); - if (seriesIndex === -1) return '#000000'; - - // Get color using the correct series index - const colors = options.color as string[]; - return colors[seriesIndex] || '#000000'; -} diff --git a/frontend/src/routes/joins/[slug]/observability/distributions/+page.svelte b/frontend/src/routes/joins/[slug]/observability/distributions/+page.svelte index 09f3129b35..d9c7cb5532 100644 --- a/frontend/src/routes/joins/[slug]/observability/distributions/+page.svelte +++ b/frontend/src/routes/joins/[slug]/observability/distributions/+page.svelte @@ -1,23 +1,20 @@ {#if data.model} @@ -119,11 +74,14 @@ {#each sortedDistributions as feature} {#snippet collapsibleContent()} - +
+ (xDomain = e.xDomain)} + renderContext="canvas" + /> +
{/snippet}
{/each} diff --git a/frontend/src/routes/joins/[slug]/observability/drift/+page.svelte b/frontend/src/routes/joins/[slug]/observability/drift/+page.svelte index 433166a882..d0142d7568 100644 --- a/frontend/src/routes/joins/[slug]/observability/drift/+page.svelte +++ b/frontend/src/routes/joins/[slug]/observability/drift/+page.svelte @@ -1,194 +1,75 @@ {#if data.model} @@ -452,30 +136,33 @@
- {#each joinTimeseries.items as group (group.name)} + {#each data.joinTimeseries.items as group, i (group.name)} {#snippet collapsibleContent()} - - handleChartClick( - event.detail.detail, - groupByCharts[group.name], - event.detail.fromTooltip - )} - on:datazoom={handleZoom} - enableMousemove={true} - enableCustomZoom={true} - enableCustomTooltip={true} - enableTooltipClick={true} - showCustomLegend={true} - legendGroup={group} - /> +
+ { + selectSeriesPoint({ series: series, data: data as unknown as DateValue }); + }} + onitemclick={({ series, data }) => { + selectSeriesPoint({ series: series, data }); + }} + {xDomain} + onbrushend={(e) => (xDomain = e.xDomain)} + tooltip={{ locked: lockedTooltip }} + /> +
{/snippet}
{/each} @@ -483,23 +170,25 @@ {/snippet} - + (selectedSeriesPoint = null)}> highlightSeries(selectedSeries ?? '', dialogGroupChart, 'highlight')} - onmouseleave={() => highlightSeries(selectedSeries ?? '', dialogGroupChart, 'downplay')} + onmouseenter={() => { + /*highlightSeries(selectedSeries ?? '', dialogGroupChart, 'highlight')*/ + }} + onmouseleave={() => { + /*highlightSeries(selectedSeries ?? '', dialogGroupChart, 'downplay')*/ + }} > - {#if selectedSeries && dialogGroupChart} -
- {/if} +
- {selectedSeries ? `${selectedSeries} at ` : ''}{formatEventDate()} + {selectedSeriesPoint?.series.key} at {formatDate(selectedSeriesPoint?.data.date)}
@@ -519,115 +208,139 @@
- {#if selectedEvents[0]?.seriesName} - {@const selectedGroup = joinTimeseries.items.find((group) => - group.items.some((item) => item.feature === selectedEvents[0].seriesName) + {#if selectedSeriesPoint} + {@const selectedGroup = data.joinTimeseries.items.find((group) => + group.items.some((item) => item.feature === selectedSeriesPoint?.series.key) )} {#if selectedGroup} {#snippet collapsibleContent()} - - handleChartClick( - event.detail.detail, - groupByCharts[selectedGroup.name], - event.detail.fromTooltip - )} - enableMousemove={true} - enableCustomZoom={true} - enableCustomTooltip={true} - enableTooltipClick={true} - showCustomLegend={true} - legendGroup={selectedGroup} - /> +
+ { + selectSeriesPoint({ series: series, data: data as unknown as DateValue }); + }} + onitemclick={({ series, data }) => { + selectSeriesPoint({ series: series, data }); + }} + {xDomain} + onbrushend={(e) => (xDomain = e.xDomain)} + tooltip={{ locked: lockedTooltip }} + /> +
{/snippet}
{/if} {/if} - {#if selectedSeries} - {#if percentileData} - - {#snippet collapsibleContent()} - + {#snippet collapsibleContent()} +
+ (xDomain = e.xDomain)} + tooltip={{ locked: lockedTooltip }} /> - {/snippet} - - {/if} +
+ {/snippet} +
+ {/if} - {#if comparedFeatureData} - - {#snippet headerContentRight()} - {#if isComparedFeatureZoomed} -
- -
- {/if} - {/snippet} - {#snippet collapsibleContent()} - + {#snippet collapsibleContent()} +
+ d.ts === timestamp) ?? [], + color: '#4B92FF' // TODO: copied from ECharts defaults + }, + { + key: 'current', + data: comparedFeatureData?.current?.filter((d) => d.ts === timestamp) ?? [], + color: '#7DFFB3' // TODO: copied from ECharts defaults + } + ]} + seriesLayout="group" + groupPadding={0.1} + bandPadding={0.1} + {...barChartProps} + props={{ + yAxis: { tweened: { duration: 200 } }, + tooltip: { ...tooltipProps, hideTotal: true } + }} /> - {/snippet} - - {/if} +
+ {/snippet} +
+ {#snippet collapsibleContent()} - + {#if nullData} + {@const currentData = nullData.current?.find((point) => point.ts === timestamp)} + {@const baselineData = nullData.baseline?.find((point) => point.ts === timestamp)} + +
+ {#each [{ label: 'Baseline', data: baselineData }, { label: 'Current', data: currentData }] as c} + {#if c.data} + + {@const mockNullValue = Math.random() * 100} +
+
+ + +
+
+ {c.label} +
+
+ {/if} + {/each} +
+ {/if} {/snippet}
- {:else if selectedEvents.length > 1} -
-
- Select a data point - -
- {#each selectedEvents as event} - - {/each} -
{/if}
+ + { + if (isMacOS() ? e.metaKey : e.ctrlKey) { + lockedTooltip = true; + } + }} + onkeyup={(e) => { + if (isMacOS() ? !e.metaKey : !e.ctrlKey) { + lockedTooltip = false; + } + }} +/> diff --git a/frontend/src/routes/observability/+page.svelte b/frontend/src/routes/observability/+page.svelte deleted file mode 100644 index 16a674af8c..0000000000 --- a/frontend/src/routes/observability/+page.svelte +++ /dev/null @@ -1,126 +0,0 @@ - - -
- - -
- -
- ECharts heatmaps are slow when there are lots of cells. I'm figuring it out/testing optimizaiton - stuff -
- - - {#snippet main()} -
- -
- {/snippet} - - {#snippet sidebar()} -
-
Cell Details
-
Information about the clicked cell
- {#if clickedCellData} -
-

X: {Array.isArray(clickedCellData) ? clickedCellData[0] : ''}

-

Y: {Array.isArray(clickedCellData) ? clickedCellData[1] : ''}

-

Value: {Array.isArray(clickedCellData) ? clickedCellData[2] : ''}

-
- {/if} -
- {/snippet} -
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index eab2254344..ff981be27d 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -5,7 +5,7 @@ import { createColorScale } from './src/lib/util/colors'; const config = { plugins: [typography], darkMode: ['class'], - content: ['./src/**/*.{html,js,svelte,ts}'], + content: ['./src/**/*.{html,js,svelte,ts}', './node_modules/layerchart/**/*.{svelte,js}'], safelist: [ 'dark', { @@ -93,6 +93,14 @@ const config = { border: 'hsl(var(--job-invalid-border) / )', 'active-border': 'hsl(var(--job-invalid-active-border) / )' } + }, + // Additional LayerChart colors + surface: { + content: 'hsl(var(--card-foreground) / )', + 100: 'hsl(var(--background) / )', + 200: 'hsl(var(--muted) / )', + // not sure what color maps here (should be darker than 200). Could add a new color to `app.css` + 300: 'hsl(var(--background) / )' } }, borderRadius: {