diff --git a/api/src/utils/agents/modify_visualization_agent.rs b/api/src/utils/agents/modify_visualization_agent.rs index e84112e93..f411bd463 100644 --- a/api/src/utils/agents/modify_visualization_agent.rs +++ b/api/src/utils/agents/modify_visualization_agent.rs @@ -367,7 +367,7 @@ pub async fn modify_visualization_agent( let ( format_labels_result, configure_charts_result, - global_styling_result, + mut global_styling_result, stylize_columns_result, ) = tokio::join!( async { @@ -400,6 +400,29 @@ pub async fn modify_visualization_agent( } ); + let time_unit = match configure_charts_result.clone() { + Some(mut result) => { + // Try to get and remove from bar_line_chart first + if let Some(Value::String(time_unit)) = result.bar_line_chart.as_object_mut().and_then(|obj| obj.remove("x_axis_time_unit")) { + time_unit + } else { + // If not found in bar_line_chart, try combo_chart + if let Some(Value::String(time_unit)) = result.combo_chart.as_object_mut().and_then(|obj| obj.remove("x_axis_time_unit")) { + time_unit + } else { + String::new() + } + } + }, + None => String::new(), + }; + + if !time_unit.is_empty() { + global_styling_result = Some(json!({ + "xAxisTimeInterval": time_unit + })); + } + // Transform format_labels_result into columnLabelFormats let column_label_formats = match format_labels_result { Some(result) => result, diff --git a/api/src/utils/prompts/modify_visualization_prompts/build_charts_prompts/bar_line_chart_prompt.rs b/api/src/utils/prompts/modify_visualization_prompts/build_charts_prompts/bar_line_chart_prompt.rs index 29e2ab381..a789f9da8 100644 --- a/api/src/utils/prompts/modify_visualization_prompts/build_charts_prompts/bar_line_chart_prompt.rs +++ b/api/src/utils/prompts/modify_visualization_prompts/build_charts_prompts/bar_line_chart_prompt.rs @@ -1,4 +1,6 @@ pub fn bar_line_chart_system_prompt() -> String { + // time_unit: 'day' | 'week' | 'month' | 'quarter' | 'year' | null; //OPTIONAL: default is null. This will only apply if the x axis is a date column. This will convert the date to the specified time unit. + String::from( r#" ## TYPESCRIPT CONFIGS @@ -8,6 +10,7 @@ pub fn bar_line_chart_system_prompt() -> String { type BarAndLineAxis = { x: string[]; //the column names to use for the x axis. If multiple columns are provided, they will be grouped together and summed. The LLM should NEVER set multiple x axis columns. Only the user can set this. y: string[]; //the column names to use for the y axis. These columns MUST be numerical column type and should NEVER be ID columns. If there is not matching, then this can be empty. If multiple columns are provided, they will be grouped together and summed. The LLM should NEVER set multiple y axis columns. Only the user can set this. + x_axis_time_unit?: 'day' | 'week' | 'month' | 'quarter' | 'year' | null; // OPTIONAL: default is null if not selected. If the sql query is grouping by a specific interval, you MUST match the interval to the x_axis_time_unit. category?: string[]; // OPTIONAL: the column names to use for the category axis. This is optional and should only be used if the user needs to group or stack their data. This should likely not be the same as the x axis column. The category is ALMOST ALWAYS a string type column. tooltip?: string[] | null; //OPTIONAL: if null the y axis will automatically be used }; diff --git a/api/src/utils/prompts/modify_visualization_prompts/build_charts_prompts/combo_chart_prompt.rs b/api/src/utils/prompts/modify_visualization_prompts/build_charts_prompts/combo_chart_prompt.rs index dab8b15c0..96e59df74 100644 --- a/api/src/utils/prompts/modify_visualization_prompts/build_charts_prompts/combo_chart_prompt.rs +++ b/api/src/utils/prompts/modify_visualization_prompts/build_charts_prompts/combo_chart_prompt.rs @@ -9,6 +9,7 @@ type ComboChartAxis = { x: string[]; //the column names to use for the x axis. If multiple columns are provided, they will be grouped together and summed. The LLM should NEVER set multiple x axis columns. Only the user can set this. y: string[]; //the column names to use for the y axis. These columns MUST be numerical column type and should NEVER be ID columns. If there is not matching, then this can be empty. If multiple columns are provided, they will be grouped together and summed. The LLM should NEVER set multiple y axis columns. Only the user can set this. y2?: string[]; //the column names to use for the second y axis. These columns MUST be numerical column type and should NEVER be ID columns. The y2 axis is often called the "right axes". The y2 is also what makes this a dual axes chart. + x_axis_time_unit?: 'day' | 'week' | 'month' | 'quarter' | 'year' | null; // OPTIONAL: default is null if not selected. If the sql query is grouping by a specific interval, you MUST match the interval to the x_axis_time_unit. category?: string[] | null; //the column names to use for the category axis. This is optional and should only be used if the user needs to group or stack their data. This should likely not be the same as the x axis column. The category is ALMOST ALWAYS a string type column. tooltip?: string[] | null; //if null the y axis will automatically be used }; diff --git a/api/src/utils/prompts/modify_visualization_prompts/format_label_prompt.rs b/api/src/utils/prompts/modify_visualization_prompts/format_label_prompt.rs index d8144c41a..e1f2964a3 100644 --- a/api/src/utils/prompts/modify_visualization_prompts/format_label_prompt.rs +++ b/api/src/utils/prompts/modify_visualization_prompts/format_label_prompt.rs @@ -22,7 +22,7 @@ type BusterChartLabelFormatCurrency = { } & ColumnLabelFormatBase; type BusterChartLabelFormatDate = { - dateFormat?: 'auto' | string; //OPTIONAL: default is 'LL'. This will only apply if the format is set to 'date'. This will convert the date to the specified format. This MUST BE IN dayjs format. If you determine that a column type is a date column, you should specify it's date format here. + dateFormat?: 'auto' | string; //OPTIONAL: The default to 'auto'. Only specify the day.js string format if the user asks for it. useRelativeTime?: boolean; isUTC?: boolean; // This is useful if a date column is actually returned as a number. For example, if the column is the day of the week (1-7) or month of the year (1-12), then you should set this to 'day_of_week' or 'month_of_year'. diff --git a/web/src/api/buster_rest/threads/defaults.ts b/web/src/api/buster_rest/threads/defaults.ts index a773e221d..c0e450547 100644 --- a/web/src/api/buster_rest/threads/defaults.ts +++ b/web/src/api/buster_rest/threads/defaults.ts @@ -19,6 +19,7 @@ export const DEFAULT_CHART_CONFIG: IBusterThreadMessageChartConfig = { y2AxisShowAxisTitle: true, y2AxisStartAxisAtZero: true, y2AxisScaleType: 'linear', + xAxisTimeInterval: null, xAxisShowAxisLabel: true, xAxisShowAxisTitle: true, xAxisAxisTitle: null, diff --git a/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/SelectAxis/SelectAxis.tsx b/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/SelectAxis/SelectAxis.tsx index 12f4d57ea..fafef29a3 100644 --- a/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/SelectAxis/SelectAxis.tsx +++ b/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/SelectAxis/SelectAxis.tsx @@ -1,4 +1,3 @@ -import { IBusterThreadMessage } from '@/context/Threads/interfaces'; import React, { useMemo } from 'react'; import { DropZone, SelectAxisDropzones, SelectAxisItem } from './SelectAxisDragContainer'; import { IBusterThreadMessageChartConfig } from '@/api/buster_rest'; @@ -19,7 +18,7 @@ import { SelectAxisEmptyState } from './SelectAxisEmptyState'; export const SelectAxis: React.FC< Required & - Required & + Required> & Required & Required & ISelectAxisContext diff --git a/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/SelectAxis/useSelectAxisContext.tsx b/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/SelectAxis/useSelectAxisContext.tsx index a4563e02e..38825b804 100644 --- a/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/SelectAxis/useSelectAxisContext.tsx +++ b/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/SelectAxis/useSelectAxisContext.tsx @@ -17,7 +17,7 @@ import React from 'react'; export interface ISelectAxisContext extends Required, Required, - Required, + Required>, Required { selectedAxis: ChartEncodes | null; columnLabelFormats: IBusterThreadMessageChartConfig['columnLabelFormats']; diff --git a/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/StylingAppVisualize.tsx b/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/StylingAppVisualize.tsx index 7e497feb4..64ee35d6d 100644 --- a/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/StylingAppVisualize.tsx +++ b/web/src/app/app/_controllers/ThreadController/ThreadControllerEditContent/SidebarStylingApp/StylingAppVisualize/StylingAppVisualize.tsx @@ -24,7 +24,6 @@ export const StylingAppVisualize: React.FC< colors: string[]; disableTooltip: IBusterThreadMessageChartConfig['disableTooltip']; } & Required & - Required & Required & Required & Omit & diff --git a/web/src/app/test/chartjs/chartjsfixedline/page.tsx b/web/src/app/test/chartjs/chartjsfixedline/page.tsx new file mode 100644 index 000000000..71a326ea3 --- /dev/null +++ b/web/src/app/test/chartjs/chartjsfixedline/page.tsx @@ -0,0 +1,160 @@ +'use client'; + +import { ColumnMetaData, DEFAULT_COLUMN_SETTINGS } from '@/api/buster_rest'; +import { + BusterChart, + BusterChartProps, + ChartType, + IColumnLabelFormat, + ViewType +} from '@/components/charts'; +import { faker } from '@faker-js/faker'; +import { Button, Checkbox, Select, Slider } from 'antd'; +import { useMemo, useState } from 'react'; + +const chartData = [ + { date: '2024-01-01', sales: 100 }, + { date: '2024-02-01', sales: 200 }, + { date: '2024-03-01', sales: 300 }, + { date: '2024-04-01', sales: 250 }, + { date: '2024-05-01', sales: 400 }, + { date: '2024-06-01', sales: 350 }, + { date: '2024-07-01', sales: 450 }, + { date: '2024-08-01', sales: 500 }, + { date: '2024-09-01', sales: 550 }, + { date: '2024-10-01', sales: 600 }, + { date: '2024-11-01', sales: 650 }, + { date: '2024-12-01', sales: 700 } +]; + +const barAndLineAxis = { + x: ['date'], + y: ['sales'], + category: [] +}; + +const pieConfig = { + x: ['date'], + y: ['sales'] +}; + +const scatterAxis = { + x: ['date'], + y: ['sales'] +}; + +const comboConfig = { + x: ['date'], + y: ['sales'], + y2: [] +}; + +const columnLabelFormats: Record = { + sales: { + style: 'currency', + currency: 'USD', + columnType: 'number' + // displayName: 'This is a display that is really long just for testing to make sure it works' + }, + date: { style: 'date', dateFormat: undefined, columnType: 'date' } +}; + +const columnSettings: BusterChartProps['columnSettings'] = { + sales: { + ...DEFAULT_COLUMN_SETTINGS, + // columnVisualization: 'bar', + // showDataLabels: false, + // barRoundness: 7, + // showDataLabelsAsPercentage: false, + lineSymbolSize: 3 + // lineStyle: 'line', + // lineType: 'normal', + // lineWidth: 2 + } +}; + +const columnMetadata: ColumnMetaData[] = Object.entries({ + ...chartData[0] +}).map(([key, value]) => ({ + name: key, + min_value: 100, + max_value: 190, + unique_values: 1000, + simple_type: typeof value === 'number' ? 'number' : 'text', + type: typeof value as 'text' +})); + +export default function ChartjsFixedLine() { + const [numberOfPoints, setNumberOfPoints] = useState(10); + const [useGeneratedData, setUseGeneratedData] = useState<'static' | 'generated' | 'multi-year'>( + 'static' + ); + const [rerenderNumber, setRerenderNumber] = useState(1); + + const data = useMemo(() => { + if (useGeneratedData === 'generated') { + return Array.from({ length: numberOfPoints }, (_, index) => ({ + date: faker.date.past({ years: 1 }).toISOString(), + sales: faker.number.int({ min: 590 + index, max: index + 10 + 590 }) + })); + } + + if (useGeneratedData === 'multi-year') { + return Array.from({ length: numberOfPoints }, (_, index) => ({ + date: faker.date.past({ years: 5 }).toISOString(), + sales: faker.number.int({ min: 590 + index, max: index + 10 + 590 }) + })); + } + + return chartData; + }, [numberOfPoints, useGeneratedData]); + + return ( +
+
+ +
+ +
+