feat: auto-bind chart fields based on dataset columns
When creating a chart, automatically assign first text column to X axis and first number column to Y axis (varies by chart type). Charts now show data immediately instead of blank. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ccbe63f9e4
commit
fe6cf014dc
|
|
@ -7,6 +7,7 @@ import { addChart, setActiveChart } from '@/adapters/state/redux/chartSlice';
|
||||||
import { addLayoutItem } from '@/adapters/state/redux/layoutSlice';
|
import { addLayoutItem } from '@/adapters/state/redux/layoutSlice';
|
||||||
import { type ChartType } from '@/domain';
|
import { type ChartType } from '@/domain';
|
||||||
import { type ChartInstance } from '@/domain/entities/ChartInstance';
|
import { type ChartInstance } from '@/domain/entities/ChartInstance';
|
||||||
|
import { type FieldBinding } from '@/domain/entities/FieldBinding';
|
||||||
|
|
||||||
function mapBackendChart(raw: any): ChartInstance {
|
function mapBackendChart(raw: any): ChartInstance {
|
||||||
return {
|
return {
|
||||||
|
|
@ -31,9 +32,58 @@ function mapBackendChart(raw: any): ChartInstance {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generate default bindings based on chart type and available columns.
|
||||||
|
*/
|
||||||
|
function autoBindings(
|
||||||
|
chartType: string,
|
||||||
|
columns: { name: string; type: string }[],
|
||||||
|
): FieldBinding[] {
|
||||||
|
const textCols = columns.filter((c) => c.type === 'text' || c.type === 'geo');
|
||||||
|
const numCols = columns.filter((c) => c.type === 'number' || c.type === 'percentage');
|
||||||
|
const dateCols = columns.filter((c) => c.type === 'date');
|
||||||
|
|
||||||
|
const bindings: FieldBinding[] = [];
|
||||||
|
|
||||||
|
switch (chartType) {
|
||||||
|
case 'pie':
|
||||||
|
case 'donut':
|
||||||
|
case 'wordcloud':
|
||||||
|
case 'radar':
|
||||||
|
if (textCols[0]) bindings.push({ axis: 'label', columnName: textCols[0].name });
|
||||||
|
if (numCols[0]) bindings.push({ axis: 'value', columnName: numCols[0].name });
|
||||||
|
break;
|
||||||
|
case 'map':
|
||||||
|
const geoCols = columns.filter((c) => c.type === 'geo');
|
||||||
|
if (geoCols[0]) bindings.push({ axis: 'geo', columnName: geoCols[0].name });
|
||||||
|
else if (textCols[0]) bindings.push({ axis: 'geo', columnName: textCols[0].name });
|
||||||
|
if (numCols[0]) bindings.push({ axis: 'value', columnName: numCols[0].name });
|
||||||
|
break;
|
||||||
|
case 'kpi':
|
||||||
|
if (numCols[0]) bindings.push({ axis: 'value', columnName: numCols[0].name });
|
||||||
|
if (textCols[0]) bindings.push({ axis: 'label', columnName: textCols[0].name });
|
||||||
|
break;
|
||||||
|
case 'scatter':
|
||||||
|
case 'boston-matrix':
|
||||||
|
if (numCols[0]) bindings.push({ axis: 'x', columnName: numCols[0].name });
|
||||||
|
if (numCols[1]) bindings.push({ axis: 'y', columnName: numCols[1].name });
|
||||||
|
break;
|
||||||
|
default: // bar, line, area, grouped-bar, stacked-bar, horizontal-bar, combo, heatmap, data-table
|
||||||
|
if (dateCols[0]) bindings.push({ axis: 'x', columnName: dateCols[0].name });
|
||||||
|
else if (textCols[0]) bindings.push({ axis: 'x', columnName: textCols[0].name });
|
||||||
|
if (numCols[0]) bindings.push({ axis: 'y', columnName: numCols[0].name });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
export function useCreateChart() {
|
export function useCreateChart() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const activeDataSetId = useAppSelector((s) => s.data.activeDataSetId);
|
const activeDataSetId = useAppSelector((s) => s.data.activeDataSetId);
|
||||||
|
const activeDataSet = useAppSelector((s) =>
|
||||||
|
s.data.dataSets.find((ds) => ds.id === s.data.activeDataSetId),
|
||||||
|
);
|
||||||
const layoutCount = useAppSelector((s) => s.layout.layouts.length);
|
const layoutCount = useAppSelector((s) => s.layout.layouts.length);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
@ -48,12 +98,27 @@ export function useCreateChart() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
|
// Auto-generate bindings from dataset columns
|
||||||
|
const columns = (activeDataSet?.columns ?? []).map((c) => ({
|
||||||
|
name: c.name,
|
||||||
|
type: c.type,
|
||||||
|
}));
|
||||||
|
const defaultBindings = autoBindings(chartType ?? 'bar', columns);
|
||||||
|
|
||||||
const rawChart = await chartServiceClient.createChart(
|
const rawChart = await chartServiceClient.createChart(
|
||||||
activeDataSetId,
|
activeDataSetId,
|
||||||
chartType ?? 'bar',
|
chartType ?? 'bar',
|
||||||
|
defaultBindings.map((b) => ({
|
||||||
|
axis: b.axis,
|
||||||
|
column_name: b.columnName,
|
||||||
|
aggregation: b.aggregation ?? null,
|
||||||
|
})),
|
||||||
);
|
);
|
||||||
const chart = mapBackendChart(rawChart);
|
const chart = mapBackendChart(rawChart);
|
||||||
|
|
||||||
|
// Override bindings with our auto-generated ones (backend may not have them)
|
||||||
|
chart.bindings = defaultBindings;
|
||||||
|
|
||||||
dispatch(addChart(chart));
|
dispatch(addChart(chart));
|
||||||
dispatch(setActiveChart(chart.id));
|
dispatch(setActiveChart(chart.id));
|
||||||
|
|
||||||
|
|
@ -76,7 +141,7 @@ export function useCreateChart() {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, activeDataSetId, layoutCount],
|
[dispatch, activeDataSetId, activeDataSet, layoutCount],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { handleCreate, activeDataSetId, loading, error };
|
return { handleCreate, activeDataSetId, loading, error };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue