This commit is contained in:
qixinbo
2026-03-15 01:29:36 +08:00
parent 4985c1eed3
commit 76724b2313
12 changed files with 1345 additions and 82 deletions
+785
View File
@@ -21,12 +21,15 @@
"react-grid-layout": "^2.2.2",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.13.1",
"react-vega": "^8.0.0",
"recharts": "^3.8.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"shadcn": "^4.0.6",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
"vega": "^6.2.0",
"vega-lite": "^6.4.2",
"zustand": "^5.0.11"
},
"devDependencies": {
@@ -3036,6 +3039,12 @@
"@types/estree": "*"
}
},
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"license": "MIT"
},
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz",
@@ -4250,6 +4259,73 @@
"node": ">=12"
}
},
"node_modules/d3-delaunay": {
"version": "6.0.4",
"resolved": "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
"integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
"license": "ISC",
"dependencies": {
"delaunator": "5"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dsv": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz",
"integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
"license": "ISC",
"dependencies": {
"commander": "7",
"iconv-lite": "0.6",
"rw": "1"
},
"bin": {
"csv2json": "bin/dsv2json.js",
"csv2tsv": "bin/dsv2dsv.js",
"dsv2dsv": "bin/dsv2dsv.js",
"dsv2json": "bin/dsv2json.js",
"json2csv": "bin/json2dsv.js",
"json2dsv": "bin/json2dsv.js",
"json2tsv": "bin/json2dsv.js",
"tsv2csv": "bin/dsv2dsv.js",
"tsv2json": "bin/dsv2json.js"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dsv/node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"license": "MIT",
"engines": {
"node": ">= 10"
}
},
"node_modules/d3-dsv/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
@@ -4259,6 +4335,20 @@
"node": ">=12"
}
},
"node_modules/d3-force": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz",
"integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-quadtree": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.2.tgz",
@@ -4268,6 +4358,57 @@
"node": ">=12"
}
},
"node_modules/d3-geo": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz",
"integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2.5.0 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-geo-projection": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz",
"integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==",
"license": "ISC",
"dependencies": {
"commander": "7",
"d3-array": "1 - 3",
"d3-geo": "1.12.0 - 3"
},
"bin": {
"geo2svg": "bin/geo2svg.js",
"geograticule": "bin/geograticule.js",
"geoproject": "bin/geoproject.js",
"geoquantize": "bin/geoquantize.js",
"geostitch": "bin/geostitch.js"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-geo-projection/node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"license": "MIT",
"engines": {
"node": ">= 10"
}
},
"node_modules/d3-hierarchy": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
"integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
@@ -4289,6 +4430,15 @@
"node": ">=12"
}
},
"node_modules/d3-quadtree": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
"integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz",
@@ -4305,6 +4455,19 @@
"node": ">=12"
}
},
"node_modules/d3-scale-chromatic": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
"integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
"d3-interpolate": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz",
@@ -4465,6 +4628,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/delaunator": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz",
"integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
"license": "ISC",
"dependencies": {
"robust-predicates": "^3.0.2"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
@@ -5116,6 +5288,13 @@
"node": ">= 6"
}
},
"node_modules/fast-json-patch": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/fast-json-patch/-/fast-json-patch-3.1.1.tgz",
"integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==",
"license": "MIT",
"peer": true
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -6227,6 +6406,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/json-stringify-pretty-compact": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
"integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==",
"license": "MIT"
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
@@ -8621,6 +8806,19 @@
}
}
},
"node_modules/react-vega": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/react-vega/-/react-vega-8.0.0.tgz",
"integrity": "sha512-euye4Gec2ScnUK1zbSA2tzZXUwBmbba8bfbzaRVhdEJTGQfaD78bSgqrccrl9b2fKZS1TZXR0NADEHVe6nxvBg==",
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"react": "^17 || ^18 || ^19",
"react-dom": "^17 || ^18 || ^19",
"vega-embed": "^7"
}
},
"node_modules/recast": {
"version": "0.23.11",
"resolved": "https://registry.npmmirror.com/recast/-/recast-0.23.11.tgz",
@@ -8834,6 +9032,12 @@
"node": ">=0.10.0"
}
},
"node_modules/robust-predicates": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz",
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
"license": "Unlicense"
},
"node_modules/rollup": {
"version": "4.59.0",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz",
@@ -8940,6 +9144,12 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rw": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz",
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
"license": "BSD-3-Clause"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -9485,6 +9695,26 @@
"node": ">=0.6"
}
},
"node_modules/topojson-client": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/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.npmmirror.com/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/tough-cookie": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-6.0.1.tgz",
@@ -9898,6 +10128,561 @@
"node": ">= 0.8"
}
},
"node_modules/vega": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/vega/-/vega-6.2.0.tgz",
"integrity": "sha512-BIwalIcEGysJdQDjeVUmMWB3e50jPDNAMfLJscjEvpunU9bSt7X1OYnQxkg3uBwuRRI4nWfFZO9uIW910nLeGw==",
"license": "BSD-3-Clause",
"dependencies": {
"vega-crossfilter": "~5.1.0",
"vega-dataflow": "~6.1.0",
"vega-encode": "~5.1.0",
"vega-event-selector": "~4.0.0",
"vega-expression": "~6.1.0",
"vega-force": "~5.1.0",
"vega-format": "~2.1.0",
"vega-functions": "~6.1.0",
"vega-geo": "~5.1.0",
"vega-hierarchy": "~5.1.0",
"vega-label": "~2.1.0",
"vega-loader": "~5.1.0",
"vega-parser": "~7.1.0",
"vega-projection": "~2.1.0",
"vega-regression": "~2.1.0",
"vega-runtime": "~7.1.0",
"vega-scale": "~8.1.0",
"vega-scenegraph": "~5.1.0",
"vega-statistics": "~2.0.0",
"vega-time": "~3.1.0",
"vega-transforms": "~5.1.0",
"vega-typings": "~2.1.0",
"vega-util": "~2.1.0",
"vega-view": "~6.1.0",
"vega-view-transforms": "~5.1.0",
"vega-voronoi": "~5.1.0",
"vega-wordcloud": "~5.1.0"
},
"funding": {
"url": "https://app.hubspot.com/payments/GyPC972GD9Rt"
}
},
"node_modules/vega-canvas": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/vega-canvas/-/vega-canvas-2.0.0.tgz",
"integrity": "sha512-9x+4TTw/USYST5nx4yN272sy9WcqSRjAR0tkQYZJ4cQIeon7uVsnohvoPQK1JZu7K1QXGUqzj08z0u/UegBVMA==",
"license": "BSD-3-Clause"
},
"node_modules/vega-crossfilter": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-crossfilter/-/vega-crossfilter-5.1.0.tgz",
"integrity": "sha512-EmVhfP3p6AM7o/lPan/QAoqjblI19BxWUlvl2TSs0xjQd8KbaYYbS4Ixt3cmEvl0QjRdBMF6CdJJ/cy9DTS4Fw==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4",
"vega-dataflow": "^6.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-dataflow": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/vega-dataflow/-/vega-dataflow-6.1.0.tgz",
"integrity": "sha512-JxumGlODtFbzoQ4c/jQK8Tb/68ih0lrexlCozcMfTAwQ12XhTqCvlafh7MAKKTMBizjOfaQTHm4Jkyb1H5CfyQ==",
"license": "BSD-3-Clause",
"dependencies": {
"vega-format": "^2.1.0",
"vega-loader": "^5.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-embed": {
"version": "7.1.0",
"resolved": "https://registry.npmmirror.com/vega-embed/-/vega-embed-7.1.0.tgz",
"integrity": "sha512-ZmEIn5XJrQt7fSh2lwtSdXG/9uf3yIqZnvXFEwBJRppiBgrEWZcZbj6VK3xn8sNTFQ+sQDXW5sl/6kmbAW3s5A==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"fast-json-patch": "^3.1.1",
"json-stringify-pretty-compact": "^4.0.0",
"semver": "^7.7.2",
"tslib": "^2.8.1",
"vega-interpreter": "^2.0.0",
"vega-schema-url-parser": "^3.0.2",
"vega-themes": "3.0.0",
"vega-tooltip": "1.0.0"
},
"funding": {
"url": "https://app.hubspot.com/payments/GyPC972GD9Rt"
},
"peerDependencies": {
"vega": "*",
"vega-lite": "*"
}
},
"node_modules/vega-embed/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/vega-encode": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-encode/-/vega-encode-5.1.0.tgz",
"integrity": "sha512-q26oI7B+MBQYcTQcr5/c1AMsX3FvjZLQOBi7yI0vV+GEn93fElDgvhQiYrgeYSD4Exi/jBPeUXuN6p4bLz16kA==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4",
"d3-interpolate": "^3.0.1",
"vega-dataflow": "^6.1.0",
"vega-scale": "^8.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-event-selector": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/vega-event-selector/-/vega-event-selector-4.0.0.tgz",
"integrity": "sha512-CcWF4m4KL/al1Oa5qSzZ5R776q8lRxCj3IafCHs5xipoEHrkgu1BWa7F/IH5HrDNXeIDnqOpSV1pFsAWRak4gQ==",
"license": "BSD-3-Clause"
},
"node_modules/vega-expression": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/vega-expression/-/vega-expression-6.1.0.tgz",
"integrity": "sha512-hHgNx/fQ1Vn1u6vHSamH7lRMsOa/yQeHGGcWVmh8fZafLdwdhCM91kZD9p7+AleNpgwiwzfGogtpATFaMmDFYg==",
"license": "BSD-3-Clause",
"dependencies": {
"@types/estree": "^1.0.8",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-force": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-force/-/vega-force-5.1.0.tgz",
"integrity": "sha512-wdnchOSeXpF9Xx8Yp0s6Do9F7YkFeOn/E/nENtsI7NOcyHpICJ5+UkgjUo9QaQ/Yu+dIDU+sP/4NXsUtq6SMaQ==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-force": "^3.0.0",
"vega-dataflow": "^6.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-format": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/vega-format/-/vega-format-2.1.0.tgz",
"integrity": "sha512-i9Ht33IgqG36+S1gFDpAiKvXCPz+q+1vDhDGKK8YsgMxGOG4PzinKakI66xd7SdV4q97FgpR7odAXqtDN2wKqw==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4",
"d3-format": "^3.1.0",
"d3-time-format": "^4.1.0",
"vega-time": "^3.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-functions": {
"version": "6.1.1",
"resolved": "https://registry.npmmirror.com/vega-functions/-/vega-functions-6.1.1.tgz",
"integrity": "sha512-Due6jP0y0FfsGMTrHnzUGnEwXPu7VwE+9relfo+LjL/tRPYnnKqwWvzt7n9JkeBuZqjkgYjMzm/WucNn6Hkw5A==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4",
"d3-color": "^3.1.0",
"d3-geo": "^3.1.1",
"vega-dataflow": "^6.1.0",
"vega-expression": "^6.1.0",
"vega-scale": "^8.1.0",
"vega-scenegraph": "^5.1.0",
"vega-selections": "^6.1.0",
"vega-statistics": "^2.0.0",
"vega-time": "^3.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-geo": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-geo/-/vega-geo-5.1.0.tgz",
"integrity": "sha512-H8aBBHfthc3rzDbz/Th18+Nvp00J73q3uXGAPDQqizioDm/CoXCK8cX4pMePydBY9S6ikBiGJrLKFDa80wI20g==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4",
"d3-color": "^3.1.0",
"d3-geo": "^3.1.1",
"vega-canvas": "^2.0.0",
"vega-dataflow": "^6.1.0",
"vega-projection": "^2.1.0",
"vega-statistics": "^2.0.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-hierarchy": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-hierarchy/-/vega-hierarchy-5.1.0.tgz",
"integrity": "sha512-rZlU8QJNETlB6o73lGCPybZtw2fBBsRIRuFE77aCLFHdGsh6wIifhplVarqE9icBqjUHRRUOmcEYfzwVIPr65g==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-hierarchy": "^3.1.2",
"vega-dataflow": "^6.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-interpreter": {
"version": "2.2.1",
"resolved": "https://registry.npmmirror.com/vega-interpreter/-/vega-interpreter-2.2.1.tgz",
"integrity": "sha512-o+4ZEme2mdFLewlpF76dwPWW2VkZ3TAF3DMcq75/NzA5KPvnN4wnlCM8At2FVawbaHRyGdVkJSS5ROF5KwpHPQ==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"vega-util": "^2.1.0"
}
},
"node_modules/vega-label": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/vega-label/-/vega-label-2.1.0.tgz",
"integrity": "sha512-/hgf+zoA3FViDBehrQT42Lta3t8In6YwtMnwjYlh72zNn1p3c7E3YUBwqmAqTM1x+tudgzMRGLYig+bX1ewZxQ==",
"license": "BSD-3-Clause",
"dependencies": {
"vega-canvas": "^2.0.0",
"vega-dataflow": "^6.1.0",
"vega-scenegraph": "^5.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-lite": {
"version": "6.4.2",
"resolved": "https://registry.npmmirror.com/vega-lite/-/vega-lite-6.4.2.tgz",
"integrity": "sha512-Mv2PaRIpijz256LM0NdOJd9Md8cqyrXina54xW6Qp865YfY502zlXGUst+W/XznVwISGfatt0yLZuDqCUbBDuw==",
"license": "BSD-3-Clause",
"dependencies": {
"json-stringify-pretty-compact": "~4.0.0",
"tslib": "~2.8.1",
"vega-event-selector": "~4.0.0",
"vega-expression": "~6.1.0",
"vega-util": "~2.1.0",
"yargs": "~18.0.0"
},
"bin": {
"vl2pdf": "bin/vl2pdf",
"vl2png": "bin/vl2png",
"vl2svg": "bin/vl2svg",
"vl2vg": "bin/vl2vg"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://app.hubspot.com/payments/GyPC972GD9Rt"
},
"peerDependencies": {
"vega": "^6.0.0"
}
},
"node_modules/vega-lite/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/vega-lite/node_modules/cliui": {
"version": "9.0.1",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-9.0.1.tgz",
"integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
"license": "ISC",
"dependencies": {
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/vega-lite/node_modules/wrap-ansi": {
"version": "9.0.2",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/vega-lite/node_modules/yargs": {
"version": "18.0.0",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-18.0.0.tgz",
"integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
"license": "MIT",
"dependencies": {
"cliui": "^9.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"string-width": "^7.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^22.0.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=23"
}
},
"node_modules/vega-lite/node_modules/yargs-parser": {
"version": "22.0.0",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-22.0.0.tgz",
"integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
"license": "ISC",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=23"
}
},
"node_modules/vega-loader": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-loader/-/vega-loader-5.1.0.tgz",
"integrity": "sha512-GaY3BdSPbPNdtrBz8SYUBNmNd8mdPc3mtdZfdkFazQ0RD9m+Toz5oR8fKnTamNSk9fRTJX0Lp3uEqxrAlQVreg==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-dsv": "^3.0.1",
"topojson-client": "^3.1.0",
"vega-format": "^2.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-parser": {
"version": "7.1.0",
"resolved": "https://registry.npmmirror.com/vega-parser/-/vega-parser-7.1.0.tgz",
"integrity": "sha512-g0lrYxtmYVW8G6yXpIS4J3Uxt9OUSkc0bLu5afoYDo4rZmoOOdll3x3ebActp5LHPW+usZIE+p5nukRS2vEc7Q==",
"license": "BSD-3-Clause",
"dependencies": {
"vega-dataflow": "^6.1.0",
"vega-event-selector": "^4.0.0",
"vega-functions": "^6.1.0",
"vega-scale": "^8.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-projection": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/vega-projection/-/vega-projection-2.1.0.tgz",
"integrity": "sha512-EjRjVSoMR5ibrU7q8LaOQKP327NcOAM1+eZ+NO4ANvvAutwmbNVTmfA1VpPH+AD0AlBYc39ND/wnRk7SieDiXA==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-geo": "^3.1.1",
"d3-geo-projection": "^4.0.0",
"vega-scale": "^8.1.0"
}
},
"node_modules/vega-regression": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/vega-regression/-/vega-regression-2.1.0.tgz",
"integrity": "sha512-HzC7MuoEwG1rIxRaNTqgcaYF03z/ZxYkQR2D5BN0N45kLnHY1HJXiEcZkcffTsqXdspLjn47yLi44UoCwF5fxQ==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4",
"vega-dataflow": "^6.1.0",
"vega-statistics": "^2.0.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-runtime": {
"version": "7.1.0",
"resolved": "https://registry.npmmirror.com/vega-runtime/-/vega-runtime-7.1.0.tgz",
"integrity": "sha512-mItI+WHimyEcZlZrQ/zYR3LwHVeyHCWwp7MKaBjkU8EwkSxEEGVceyGUY9X2YuJLiOgkLz/6juYDbMv60pfwYA==",
"license": "BSD-3-Clause",
"dependencies": {
"vega-dataflow": "^6.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-scale": {
"version": "8.1.0",
"resolved": "https://registry.npmmirror.com/vega-scale/-/vega-scale-8.1.0.tgz",
"integrity": "sha512-VEgDuEcOec8+C8+FzLcnAmcXrv2gAJKqQifCdQhkgnsLa978vYUgVfCut/mBSMMHbH8wlUV1D0fKZTjRukA1+A==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-scale-chromatic": "^3.1.0",
"vega-time": "^3.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-scenegraph": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-scenegraph/-/vega-scenegraph-5.1.0.tgz",
"integrity": "sha512-4gA89CFIxkZX+4Nvl8SZF2MBOqnlj9J5zgdPh/HPx+JOwtzSlUqIhxFpFj7GWYfwzr/PyZnguBLPihPw1Og/cA==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-path": "^3.1.0",
"d3-shape": "^3.2.0",
"vega-canvas": "^2.0.0",
"vega-loader": "^5.1.0",
"vega-scale": "^8.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-schema-url-parser": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/vega-schema-url-parser/-/vega-schema-url-parser-3.0.2.tgz",
"integrity": "sha512-xAnR7KAvNPYewI3O0l5QGdT8Tv0+GCZQjqfP39cW/hbe/b3aYMAQ39vm8O2wfXUHzm04xTe7nolcsx8WQNVLRQ==",
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/vega-selections": {
"version": "6.1.2",
"resolved": "https://registry.npmmirror.com/vega-selections/-/vega-selections-6.1.2.tgz",
"integrity": "sha512-xJ+V4qdd46nk2RBdwIRrQm2iSTMHdlu/omhLz1pqRL3jZDrkqNBXimrisci2kIKpH2WBpA1YVagwuZEKBmF2Qw==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "3.2.4",
"vega-expression": "^6.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-statistics": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/vega-statistics/-/vega-statistics-2.0.0.tgz",
"integrity": "sha512-dGPfDXnBlgXbZF3oxtkb8JfeRXd5TYHx25Z/tIoaa9jWua4Vf/AoW2wwh8J1qmMy8J03/29aowkp1yk4DOPazQ==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4"
}
},
"node_modules/vega-themes": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/vega-themes/-/vega-themes-3.0.0.tgz",
"integrity": "sha512-1iFiI3BNmW9FrsLnDLx0ZKEddsCitRY3XmUAwp6qmp+p+IXyJYc9pfjlVj9E6KXBPfm4cQyU++s0smKNiWzO4g==",
"license": "BSD-3-Clause",
"peer": true,
"funding": {
"url": "https://app.hubspot.com/payments/GyPC972GD9Rt"
},
"peerDependencies": {
"vega": "*",
"vega-lite": "*"
}
},
"node_modules/vega-time": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/vega-time/-/vega-time-3.1.0.tgz",
"integrity": "sha512-G93mWzPwNa6UYQRkr8Ujur9uqxbBDjDT/WpXjbDY0yygdSkRT+zXF+Sb4gjhW0nPaqdiwkn0R6kZcSPMj1bMNA==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4",
"d3-time": "^3.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-tooltip": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/vega-tooltip/-/vega-tooltip-1.0.0.tgz",
"integrity": "sha512-P1R0JP29v0qnTuwzCQ0SPJlkjAzr6qeyj+H4VgUFSykHmHc1OBxda//XBaFDl/bZgIscEMvjKSjZpXd84x3aZQ==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"vega-util": "^2.0.0"
},
"funding": {
"url": "https://app.hubspot.com/payments/GyPC972GD9Rt"
}
},
"node_modules/vega-transforms": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-transforms/-/vega-transforms-5.1.0.tgz",
"integrity": "sha512-mj/sO2tSuzzpiXX8JSl4DDlhEmVwM/46MTAzTNQUQzJPMI/n4ChCjr/SdEbfEyzlD4DPm1bjohZGjLc010yuMg==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4",
"vega-dataflow": "^6.1.0",
"vega-statistics": "^2.0.0",
"vega-time": "^3.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-typings": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/vega-typings/-/vega-typings-2.1.0.tgz",
"integrity": "sha512-zdis4Fg4gv37yEvTTSZEVMNhp8hwyEl7GZ4X4HHddRVRKxWFsbyKvZx/YW5Z9Ox4sjxVA2qHzEbod4Fdx+SEJA==",
"license": "BSD-3-Clause",
"dependencies": {
"@types/geojson": "7946.0.16",
"vega-event-selector": "^4.0.0",
"vega-expression": "^6.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-util": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/vega-util/-/vega-util-2.1.0.tgz",
"integrity": "sha512-PGfp0m0QCufDmcxKJCWQy4Ov23FoF8DSXmoJwSezi3itQaa2hbxK0+xwsTMP2vy4PR16Pu25HMzgMwXVW1+33w==",
"license": "BSD-3-Clause"
},
"node_modules/vega-view": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/vega-view/-/vega-view-6.1.0.tgz",
"integrity": "sha512-hmHDm/zC65lb23mb9Tr9Gx0wkxP0TMS31LpMPYxIZpvInxvUn7TYitkOtz1elr63k2YZrgmF7ztdGyQ4iCQ5fQ==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^3.2.4",
"d3-timer": "^3.0.1",
"vega-dataflow": "^6.1.0",
"vega-format": "^2.1.0",
"vega-functions": "^6.1.0",
"vega-runtime": "^7.1.0",
"vega-scenegraph": "^5.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-view-transforms": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-view-transforms/-/vega-view-transforms-5.1.0.tgz",
"integrity": "sha512-fpigh/xn/32t+An1ShoY3MLeGzNdlbAp2+HvFKzPpmpMTZqJEWkk/J/wHU7Swyc28Ta7W1z3fO+8dZkOYO5TWQ==",
"license": "BSD-3-Clause",
"dependencies": {
"vega-dataflow": "^6.1.0",
"vega-scenegraph": "^5.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-voronoi": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-voronoi/-/vega-voronoi-5.1.0.tgz",
"integrity": "sha512-uKdsoR9x60mz7eYtVG+NhlkdQXeVdMr6jHNAHxs+W+i6kawkUp5S9jp1xf1FmW/uZvtO1eqinHQNwATcDRsiUg==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-delaunay": "^6.0.4",
"vega-dataflow": "^6.1.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vega-wordcloud": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/vega-wordcloud/-/vega-wordcloud-5.1.0.tgz",
"integrity": "sha512-sSdNmT8y2D7xXhM2h76dKyaYn3PA4eV49WUUkfYfqHz/vpcu10GSAoFxLhQQTkbZXR+q5ZB63tFUow9W2IFo6g==",
"license": "BSD-3-Clause",
"dependencies": {
"vega-canvas": "^2.0.0",
"vega-dataflow": "^6.1.0",
"vega-scale": "^8.1.0",
"vega-statistics": "^2.0.0",
"vega-util": "^2.1.0"
}
},
"node_modules/vfile": {
"version": "6.0.3",
"resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz",
+3
View File
@@ -23,12 +23,15 @@
"react-grid-layout": "^2.2.2",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.13.1",
"react-vega": "^8.0.0",
"recharts": "^3.8.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"shadcn": "^4.0.6",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
"vega": "^6.2.0",
"vega-lite": "^6.4.2",
"zustand": "^5.0.11"
},
"devDependencies": {
+9 -4
View File
@@ -4,7 +4,7 @@ import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { User, Loader2, Sparkles, Search, ArrowUp, ChevronDown, Table, Paperclip, Check, X, File as FileIcon } from "lucide-react";
import { api } from "@/lib/api";
import { useVisualizationStore } from "@/store/visualizationStore";
import { type ChartSpec, useVisualizationStore } from "@/store/visualizationStore";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { cn } from "@/lib/utils";
@@ -267,7 +267,12 @@ export function ChatInterface() {
} else {
// Fallback to existing NL2SQL or other skills (e.g. for "表格问答" or "深度问数")
const source = selectedDataSource.split('-')[0]; // postgres-main -> postgres
const response = await api.post<{sql?: string, result?: unknown, error?: string}>('/api/v1/agent/nl2sql', {
const response = await api.post<{
sql?: string,
result?: unknown,
error?: string,
chart?: { chart_spec: ChartSpec, reasoning: string, can_visualize: boolean }
}>('/api/v1/agent/nl2sql', {
query: messagePayload,
source: source,
session_id: activeSessionKey,
@@ -287,9 +292,9 @@ export function ChatInterface() {
setMessages(prev => [...prev, {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: `I've generated a SQL query and fetched ${rows.length} rows for you. Check the visualization panel.`
content: `I've generated a SQL query and fetched ${rows.length} rows for you. Check the visualization panel.${response.chart?.reasoning ? `\n\nVisualization reasoning: ${response.chart.reasoning}` : ''}`
}]);
setVisualization(rows, sql);
setVisualization(rows, sql, response.chart?.chart_spec);
}
}
} catch (error: any) {
+38
View File
@@ -0,0 +1,38 @@
import React from 'react';
import { VegaEmbed } from 'react-vega';
import type { ChartSpec } from '@/store/visualizationStore';
interface VegaChartProps {
data: any[];
spec: ChartSpec;
}
export const VegaChart: React.FC<VegaChartProps> = ({ data, spec }) => {
const vegaSpec: any = {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
description: spec.description,
title: spec.title,
width: "container",
height: "container",
mark: { type: spec.chart_type, tooltip: true },
encoding: {
x: { field: spec.x_axis, type: 'nominal', axis: { labelAngle: -45 } },
y: { field: spec.y_axis, type: 'quantitative' },
},
data: { values: data }
};
if (spec.color) {
vegaSpec.encoding.color = { field: spec.color, type: 'nominal' };
}
return (
<div className="w-full h-full">
<VegaEmbed
spec={vegaSpec}
options={{ actions: false }}
style={{width: '100%', height: '100%'}}
/>
</div>
);
};
+16 -52
View File
@@ -3,25 +3,24 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogDescription } from "@/components/ui/dialog";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, LineChart, Line } from 'recharts';
import { Code, Table as TableIcon, BarChart as ChartIcon, LineChart as LineChartIcon, Download, LayoutDashboard, Loader2 } from "lucide-react";
import { Code, Table as TableIcon, BarChart as ChartIcon, Download, LayoutDashboard, Loader2 } from "lucide-react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useDashboardStore } from "@/store/dashboardStore";
import { useVisualizationStore } from "@/store/visualizationStore";
import { VegaChart } from "./VegaChart";
export function VisualizationPanel() {
const [view, setView] = useState<'table' | 'chart'>('chart');
const [chartType, setChartType] = useState<'bar' | 'line'>('bar');
const { addChart } = useDashboardStore();
const { currentData, currentSQL, isLoading, error } = useVisualizationStore();
const { currentData, currentSQL, currentChartSpec, isLoading, error } = useVisualizationStore();
const handleAddToDashboard = () => {
if (!currentData || !currentSQL) return;
addChart({
id: Date.now().toString(),
title: 'Generated Analysis', // Could be dynamic based on query
type: chartType,
title: currentChartSpec?.title || 'Generated Analysis',
type: currentChartSpec?.chart_type as any || 'bar',
data: currentData,
sql: currentSQL,
});
@@ -67,9 +66,6 @@ export function VisualizationPanel() {
}
const columns = Object.keys(objectRows[0] as Record<string, unknown>);
const firstRow = objectRows[0] as Record<string, unknown>;
const stringColumn = columns.find(col => typeof firstRow[col] === 'string') || columns[0];
const numberColumns = columns.filter(col => typeof firstRow[col] === 'number');
return (
<div className="h-full flex flex-col bg-muted/10 overflow-hidden">
@@ -98,17 +94,6 @@ export function VisualizationPanel() {
</Button>
</div>
{view === 'chart' && (
<div className="flex gap-1 mr-2 border-r pr-2">
<Button variant={chartType === 'bar' ? 'secondary' : 'ghost'} size="icon" className="h-7 w-7" onClick={() => setChartType('bar')}>
<ChartIcon className="h-3.5 w-3.5" />
</Button>
<Button variant={chartType === 'line' ? 'secondary' : 'ghost'} size="icon" className="h-7 w-7" onClick={() => setChartType('line')}>
<LineChartIcon className="h-3.5 w-3.5" />
</Button>
</div>
)}
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={handleAddToDashboard}>
<LayoutDashboard className="h-3.5 w-3.5 mr-1.5" />
Add to Dashboard
@@ -148,42 +133,21 @@ export function VisualizationPanel() {
<div className="flex-1 p-4 overflow-hidden min-h-0">
<Card className="h-full flex flex-col shadow-sm border-muted">
<CardHeader className="pb-2 shrink-0">
<CardTitle>Analysis Result</CardTitle>
<CardDescription>Generated from your query</CardDescription>
<CardTitle>{currentChartSpec?.title || 'Analysis Result'}</CardTitle>
<CardDescription>{currentChartSpec?.description || 'Generated from your query'}</CardDescription>
</CardHeader>
<CardContent className="flex-1 min-h-0 p-4">
{view === 'chart' ? (
<div className="h-full w-full">
<ResponsiveContainer width="100%" height="100%">
{chartType === 'bar' ? (
<BarChart data={objectRows} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e5e7eb" />
<XAxis dataKey={stringColumn} tickLine={false} axisLine={false} tick={{ fontSize: 12, fill: '#6b7280' }} />
<YAxis tickLine={false} axisLine={false} tick={{ fontSize: 12, fill: '#6b7280' }} />
<Tooltip
cursor={{ fill: 'rgba(0,0,0,0.05)' }}
contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
/>
<Legend wrapperStyle={{ paddingTop: '20px' }} />
{numberColumns.map((col, idx) => (
<Bar key={col} dataKey={col} fill={`hsl(${idx * 60 + 200}, 70%, 50%)`} radius={[4, 4, 0, 0]} name={col} />
))}
</BarChart>
) : (
<LineChart data={objectRows} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e5e7eb" />
<XAxis dataKey={stringColumn} tickLine={false} axisLine={false} tick={{ fontSize: 12, fill: '#6b7280' }} />
<YAxis tickLine={false} axisLine={false} tick={{ fontSize: 12, fill: '#6b7280' }} />
<Tooltip
contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
/>
<Legend wrapperStyle={{ paddingTop: '20px' }} />
{numberColumns.map((col, idx) => (
<Line key={col} type="monotone" dataKey={col} stroke={`hsl(${idx * 60 + 200}, 70%, 50%)`} strokeWidth={2} dot={{ r: 4 }} activeDot={{ r: 6 }} name={col} />
))}
</LineChart>
)}
</ResponsiveContainer>
{currentChartSpec ? (
<VegaChart data={objectRows} spec={currentChartSpec} />
) : (
<div className="flex flex-col items-center justify-center h-full text-muted-foreground">
<ChartIcon className="h-12 w-12 mb-4 opacity-20" />
<p>No chart configuration available for this data.</p>
<Button variant="link" onClick={() => setView('table')}>View Table</Button>
</div>
)}
</div>
) : (
<ScrollArea className="h-full border rounded-md">
+14 -7
View File
@@ -1,13 +1,21 @@
import { create } from 'zustand';
export interface ChartSpec {
chart_type: string;
title: string;
x_axis: string;
y_axis: string;
color?: string;
description?: string;
}
export interface VisualizationState {
currentData: any[] | null;
currentSQL: string | null;
currentChartType: 'bar' | 'line';
currentChartSpec: ChartSpec | null;
isLoading: boolean;
error: string | null;
setVisualization: (data: any[], sql: string) => void;
setChartType: (type: 'bar' | 'line') => void;
setVisualization: (data: any[], sql: string, chartSpec?: ChartSpec | null) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
clearVisualization: () => void;
@@ -16,12 +24,11 @@ export interface VisualizationState {
export const useVisualizationStore = create<VisualizationState>((set) => ({
currentData: null,
currentSQL: null,
currentChartType: 'bar',
currentChartSpec: null,
isLoading: false,
error: null,
setVisualization: (data, sql) => set({ currentData: data, currentSQL: sql, error: null }),
setChartType: (type) => set({ currentChartType: type }),
setVisualization: (data, sql, chartSpec = null) => set({ currentData: data, currentSQL: sql, currentChartSpec: chartSpec, error: null }),
setLoading: (loading) => set({ isLoading: loading }),
setError: (error) => set({ error, isLoading: false }),
clearVisualization: () => set({ currentData: null, currentSQL: null, error: null }),
clearVisualization: () => set({ currentData: null, currentSQL: null, currentChartSpec: null, error: null }),
}));