diff --git a/apps/desktop/src/components/chart/QueryChart.vue b/apps/desktop/src/components/chart/QueryChart.vue index b7a41e1d4..c10d4c942 100644 --- a/apps/desktop/src/components/chart/QueryChart.vue +++ b/apps/desktop/src/components/chart/QueryChart.vue @@ -6,11 +6,13 @@ import { CanvasRenderer } from "echarts/renderers"; import { LineChart, BarChart, PieChart } from "echarts/charts"; import { GridComponent, TooltipComponent, LegendComponent } from "echarts/components"; import VChart from "vue-echarts"; -import { BarChart3 } from "@lucide/vue"; +import { BarChart3, ChevronDown } from "@lucide/vue"; import { Button } from "@/components/ui/button"; +import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import type { QueryResult } from "@/types/database"; import { useTheme } from "@/composables/useTheme"; +import { axisColumnLabel, chartableColumnIndexes, toChartNumber } from "@/lib/chartData"; use([CanvasRenderer, LineChart, BarChart, PieChart, GridComponent, TooltipComponent, LegendComponent]); @@ -23,41 +25,58 @@ const { isDark } = useTheme(); type ChartType = "line" | "bar" | "pie"; const chartType = ref("bar"); -const xColumn = ref(""); -const yColumns = ref([]); +const xColumnIndex = ref(0); +const yColumnIndexes = ref([]); -const numericColumns = computed(() => props.result.columns.filter((_, idx) => props.result.rows.some((row) => typeof row[idx] === "number"))); +const numericColumnIndexes = computed(() => chartableColumnIndexes(props.result)); -const allColumns = computed(() => props.result.columns); +const allColumnOptions = computed(() => props.result.columns.map((_, index) => ({ index, label: axisColumnLabel(props.result.columns, index) }))); +const numericColumnOptions = computed(() => numericColumnIndexes.value.map((index) => ({ index, label: axisColumnLabel(props.result.columns, index) }))); +const xColumnValue = computed({ + get: () => String(xColumnIndex.value), + set: (value: string) => { + const index = Number(value); + if (Number.isInteger(index) && index >= 0 && index < props.result.columns.length) { + xColumnIndex.value = index; + } + }, +}); +const yColumnLabel = computed(() => { + if (yColumnIndexes.value.length === 0) return "0"; + const [first, ...rest] = yColumnIndexes.value; + const label = axisColumnLabel(props.result.columns, first); + return rest.length > 0 ? `${label} +${rest.length}` : label; +}); watch( () => props.result, () => { const cols = props.result.columns; - const numCols = numericColumns.value; - xColumn.value = cols.find((c) => !numCols.includes(c)) || cols[0] || ""; - yColumns.value = numCols.length > 0 ? [numCols[0]] : []; + const numCols = numericColumnIndexes.value; + xColumnIndex.value = cols.findIndex((_, index) => !numCols.includes(index)); + if (xColumnIndex.value < 0) xColumnIndex.value = cols.length > 0 ? 0 : -1; + yColumnIndexes.value = numCols.length > 0 ? [numCols[0]] : []; }, { immediate: true }, ); -function toggleYColumn(col: string) { - const idx = yColumns.value.indexOf(col); +function toggleYColumn(index: number) { + const idx = yColumnIndexes.value.indexOf(index); if (idx >= 0) { - yColumns.value = yColumns.value.filter((c) => c !== col); + yColumnIndexes.value = yColumnIndexes.value.filter((selected) => selected !== index); } else { - yColumns.value = [...yColumns.value, col]; + yColumnIndexes.value = [...yColumnIndexes.value, index]; } } const chartOption = computed(() => { - const xIdx = props.result.columns.indexOf(xColumn.value); - if (xIdx < 0 || yColumns.value.length === 0) return null; + const xIdx = xColumnIndex.value; + if (xIdx < 0 || yColumnIndexes.value.length === 0) return null; const xData = props.result.rows.map((row) => String(row[xIdx] ?? "")); if (chartType.value === "pie") { - const yIdx = props.result.columns.indexOf(yColumns.value[0]); + const yIdx = yColumnIndexes.value[0]; if (yIdx < 0) return null; return { tooltip: { trigger: "item" }, @@ -68,14 +87,14 @@ const chartOption = computed(() => { radius: ["30%", "60%"], data: xData.map((name, i) => ({ name, - value: Number(props.result.rows[i][yIdx]) || 0, + value: toChartNumber(props.result.rows[i][yIdx]) ?? 0, })), }, ], }; } - const yIndices = yColumns.value.map((c) => props.result.columns.indexOf(c)).filter((i) => i >= 0); + const yIndices = yColumnIndexes.value.filter((index) => index >= 0 && index < props.result.columns.length); return { tooltip: { trigger: "axis" }, @@ -94,15 +113,15 @@ const chartOption = computed(() => { axisLabel: { color: isDark.value ? "#aaa" : "#666" }, }, series: yIndices.map((yIdx) => ({ - name: props.result.columns[yIdx], + name: axisColumnLabel(props.result.columns, yIdx), type: chartType.value, - data: props.result.rows.map((row) => Number(row[yIdx]) || 0), + data: props.result.rows.map((row) => toChartNumber(row[yIdx]) ?? 0), smooth: chartType.value === "line", })), }; }); -const hasData = computed(() => props.result.rows.length > 0 && numericColumns.value.length > 0); +const hasData = computed(() => props.result.rows.length > 0 && numericColumnIndexes.value.length > 0);