diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 052af3de6e..4d095a575f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,8 @@ "@popperjs/core": "^2.11.6", "@redocly/json-to-json-schema": "^0.0.1", "@tanstack/svelte-table": "^8.9.9", + "ag-charts-community": "^9.0.1", + "ag-charts-enterprise": "^9.0.1", "ag-grid-community": "^31.0.0", "ag-grid-enterprise": "^31.0.0", "ansi_up": "^5.2.1", @@ -1687,6 +1689,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/ag-charts-community": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ag-charts-community/-/ag-charts-community-9.0.1.tgz", + "integrity": "sha512-FZy1tAWO10TzRheoM622/vJWRqldAYH3VGreQ8qirt7FB/ctIR4kjY9GPZywDJnVcb/wET3nrgwq2vIiFKhKBw==" + }, + "node_modules/ag-charts-enterprise": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ag-charts-enterprise/-/ag-charts-enterprise-9.0.1.tgz", + "integrity": "sha512-b8Lz6wgs/CSL1lz0PUnNod016Z5n7vus1mTM9GXhxJ3uyEYP0klwFyvgtK0Cjgc0YeFyhoVDmb38dMKIvHnUyg==", + "dependencies": { + "ag-charts-community": "9.0.1" + } + }, "node_modules/ag-grid-community": { "version": "31.0.0", "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 59fe79dffc..901dc06555 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -97,6 +97,8 @@ "@popperjs/core": "^2.11.6", "@redocly/json-to-json-schema": "^0.0.1", "@tanstack/svelte-table": "^8.9.9", + "ag-charts-community": "^9.0.1", + "ag-charts-enterprise": "^9.0.1", "ag-grid-community": "^31.0.0", "ag-grid-enterprise": "^31.0.0", "ansi_up": "^5.2.1", diff --git a/frontend/src/lib/components/apps/components/display/AppChartJs.svelte b/frontend/src/lib/components/apps/components/display/AppChartJs.svelte index a03bfaa80c..0bc3e47773 100644 --- a/frontend/src/lib/components/apps/components/display/AppChartJs.svelte +++ b/frontend/src/lib/components/apps/components/display/AppChartJs.svelte @@ -16,7 +16,7 @@ export let componentInput: AppInput | undefined export let configuration: RichConfigurations export let initializing: boolean | undefined = undefined - export let customCss: ComponentCustomCSS<'piechartcomponent'> | undefined = undefined + export let customCss: ComponentCustomCSS<'chartjscomponent'> | undefined = undefined export let render: boolean const { app, worldStore } = getContext('AppViewerContext') diff --git a/frontend/src/lib/components/apps/components/display/AppChartJsV2.svelte b/frontend/src/lib/components/apps/components/display/AppChartJsV2.svelte index 22586da35e..7442496572 100644 --- a/frontend/src/lib/components/apps/components/display/AppChartJsV2.svelte +++ b/frontend/src/lib/components/apps/components/display/AppChartJsV2.svelte @@ -22,7 +22,7 @@ export let componentInput: AppInput | undefined export let configuration: RichConfigurations export let initializing: boolean | undefined = undefined - export let customCss: ComponentCustomCSS<'piechartcomponent'> | undefined = undefined + export let customCss: ComponentCustomCSS<'chartjscomponent'> | undefined = undefined export let render: boolean export let datasets: RichConfiguration | undefined export let xData: RichConfiguration | undefined diff --git a/frontend/src/lib/components/apps/components/display/charts/AppAgCharts.svelte b/frontend/src/lib/components/apps/components/display/charts/AppAgCharts.svelte new file mode 100644 index 0000000000..b9872352dd --- /dev/null +++ b/frontend/src/lib/components/apps/components/display/charts/AppAgCharts.svelte @@ -0,0 +1,300 @@ + + +{#if datasets} + +{/if} + +{#if xData} + +{/if} + +{#if resolvedDatasets} + {#each resolvedDatasets as resolvedDataset, index (resolvedDataset.name + index)} + + {/each} +{/if} + +{#each Object.keys(components['agchartscomponent'].initialData.configuration) as key (key)} + +{/each} + +{#each Object.keys(css ?? {}) as key (key)} + +{/each} + + +
+
+
+ diff --git a/frontend/src/lib/components/apps/editor/component/Component.svelte b/frontend/src/lib/components/apps/editor/component/Component.svelte index 3942899f79..7dc695bd34 100644 --- a/frontend/src/lib/components/apps/editor/component/Component.svelte +++ b/frontend/src/lib/components/apps/editor/component/Component.svelte @@ -66,6 +66,7 @@ import AppStatCard from '../../components/display/AppStatCard.svelte' import AppMenu from '../../components/display/AppMenu.svelte' import AppDecisionTree from '../../components/layout/AppDecisionTree.svelte' + import AppAgCharts from '../../components/display/charts/AppAgCharts.svelte' import AppDbExplorer from '../../components/display/dbtable/AppDbExplorer.svelte' export let component: AppComponent @@ -296,6 +297,30 @@ componentInput={component.componentInput} {render} /> + {:else if component.type === 'agchartscomponent'} + + {:else if component.type === 'agchartscomponentee'} + {:else if component.type === 'tablecomponent'} & { datasets: RichConfiguration | undefined } +export type AgChartsComponent = BaseComponent<'agchartscomponent'> & { + xData: RichConfiguration | undefined + datasets: RichConfiguration | undefined +} + +export type AgChartsComponentEe = BaseComponent<'agchartscomponentee'> & { + license: string + xData: RichConfiguration | undefined + datasets: RichConfiguration | undefined +} + export type ScatterChartComponent = BaseComponent<'scatterchartcomponent'> export type TableComponent = BaseComponent<'tablecomponent'> & { @@ -253,6 +264,8 @@ export type TypedComponent = | StatisticCardComponent | MenuComponent | DecisionTreeComponent + | AgChartsComponent + | AgChartsComponentEe export type AppComponent = BaseAppComponent & TypedComponent @@ -630,6 +643,20 @@ const aggridcomponentconst = { } } as const +const agchartscomponentconst = { + name: 'AgCharts', + icon: BarChart4, + documentationLink: `${documentationBaseUrl}/agcharts`, + dims: '2:8-6:8' as AppComponentDimensions, + customCss: { + container: { class: '', style: '' } + }, + initialData: { + configuration: {}, + componentInput: undefined + } +} as const + export const components = { displaycomponent: { name: 'Rich Result', @@ -1232,6 +1259,9 @@ export const components = { } } }, + + agchartscomponent: agchartscomponentconst, + agchartscomponentee: { ...agchartscomponentconst, name: 'AgCharts EE' }, htmlcomponent: { name: 'HTML', icon: Code2, diff --git a/frontend/src/lib/components/apps/editor/component/sets.ts b/frontend/src/lib/components/apps/editor/component/sets.ts index 1c095f04ea..3ae09cef74 100644 --- a/frontend/src/lib/components/apps/editor/component/sets.ts +++ b/frontend/src/lib/components/apps/editor/component/sets.ts @@ -77,7 +77,13 @@ const tables: ComponentSet = { const charts: ComponentSet = { title: 'Charts', - components: ['plotlycomponentv2', 'chartjscomponentv2', 'vegalitecomponent'] + components: [ + 'plotlycomponentv2', + 'chartjscomponentv2', + 'vegalitecomponent', + 'agchartscomponent', + 'agchartscomponentee' + ] } as const export const COMPONENT_SETS = [layout, tabs, buttons, inputs, tables, display, charts] as const diff --git a/frontend/src/lib/components/apps/editor/componentsPanel/cssUtils.ts b/frontend/src/lib/components/apps/editor/componentsPanel/cssUtils.ts index bb8b6e2c77..bd9affd8ee 100644 --- a/frontend/src/lib/components/apps/editor/componentsPanel/cssUtils.ts +++ b/frontend/src/lib/components/apps/editor/componentsPanel/cssUtils.ts @@ -548,6 +548,11 @@ export const customisationByComponent: Customisation[] = [ selectors: [{ selector: '.wm-chartjs', comment: 'ChartJS', customCssKey: 'container' }], variables: [] }, + { + components: ['agchartscomponent'], + selectors: [{ selector: '.wm-agchart', comment: 'AgCharts', customCssKey: 'container' }], + variables: [] + }, { components: ['timeseriescomponent'], selectors: [{ selector: '.wm-timeseries', comment: 'Time series', customCssKey: 'container' }], diff --git a/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts b/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts index 32ecfb70d5..caa3bd9255 100644 --- a/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts +++ b/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts @@ -668,6 +668,12 @@ export const quickStyleProperties: Record< piechartcomponent: { container: containerDefaultProps }, + agchartscomponent: { + container: containerDefaultProps + }, + agchartscomponentee: { + container: containerDefaultProps + }, chartjscomponent: { container: containerDefaultProps }, diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/AGChartRichEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/AGChartRichEditor.svelte new file mode 100644 index 0000000000..bdc24b261d --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/AGChartRichEditor.svelte @@ -0,0 +1,58 @@ + + + +
+ {#if xData} + + {/if} + + {#if datasets} + + {/if} +
+
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/ArrayStaticInputEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/ArrayStaticInputEditor.svelte index fa4eef3ea5..20d9784260 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/ArrayStaticInputEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/ArrayStaticInputEditor.svelte @@ -26,7 +26,8 @@ if (subFieldType === 'boolean') { value.push(false) } else if (subFieldType === 'number') { - value.push(0) + value.push(1) + value = value } else if (subFieldType === 'object') { value.push({}) } else if (subFieldType === 'labeledresource' || subFieldType === 'labeledselect') { @@ -74,6 +75,69 @@ }, name: 'New dataset' }) + } else if (subFieldType === 'ag-chart') { + value.push({ + value: { + type: 'oneOf', + selected: 'bar', + labels: { + bar: 'Bar', + scatter: 'Scatter', + line: 'Line', + area: 'Area', + 'range-bar': 'Range Bar' + }, + configuration: { + bar: { + value: { + type: 'static', + fieldType: 'array', + subFieldType: 'number', + value: [25, 25, 50] + } + }, + scatter: { + value: { + type: 'static', + fieldType: 'array', + subFieldType: 'number', + value: [25, 25, 50] + } + }, + line: { + value: { + type: 'static', + fieldType: 'array', + subFieldType: 'number', + value: [25, 25, 50] + } + }, + area: { + value: { + type: 'static', + fieldType: 'array', + subFieldType: 'number', + value: [25, 25, 50] + } + }, + 'range-bar': { + value: { + type: 'static', + fieldType: 'array', + subFieldType: 'number-tuple', + value: [ + [10, 15], + [20, 25], + [18, 27] + ] + } + } + } + }, + name: 'New dataset' + }) + } else if (subFieldType === 'number-tuple') { + value.push([0, 5]) } } else { value.push('') diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte index e01804f15f..2024e81511 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte @@ -38,6 +38,7 @@ import ComponentPanelDataSource from './ComponentPanelDataSource.svelte' import MenuItems from './MenuItems.svelte' import DecisionTreeGraphEditor from './DecisionTreeGraphEditor.svelte' + import GridAgChartsLicenseKe from './GridAgChartsLicenseKe.svelte' export let componentSettings: { item: GridItem; parent: string | undefined } | undefined = undefined @@ -326,6 +327,8 @@ /> {:else if componentSettings.item.data.type === 'aggridcomponentee'} + {:else if componentSettings.item.data.type === 'agchartscomponentee'} + {:else if componentSettings.item.data.type === 'steppercomponent'} { if ( - (component.type === 'plotlycomponentv2' || component.type === 'chartjscomponentv2') && + (component.type === 'plotlycomponentv2' || + component.type === 'chartjscomponentv2' || + component.type === 'agchartscomponent' || + component.type === 'agchartscomponentee') && component.componentInput === undefined && component.datasets === undefined ) { @@ -29,6 +33,7 @@ } }) + const { worldStore } = getContext('AppViewerContext') interface Dataset { value: any // Define more specific type if possible name: string @@ -82,9 +87,47 @@ } else if (selected === 'json') { convertChartJSToJson() } + } else if (isAgChartsComponent()) { + if (selected === 'ui-editor') { + convertToUIEditorCallback = () => { + component.componentInput = undefined + setUpUIEditor() + } + + setTimeout(() => { + const activeElement = document.activeElement as HTMLElement + activeElement?.blur() + document.body.focus() + }) + } else if (selected === 'json') { + convertAgChartToJson() + } } } + function convertAgChartToJson() { + if (component.type !== 'agchartscomponent' && component.type !== 'agchartscomponentee') { + return + } + + const connections: InputConnectionEval[] = [] + + if (component.datasets === undefined || component.datasets.type !== 'static') return + + const datasetsAsString = datasetToAgChartJson() + + component.componentInput = { + type: 'evalv2', + fieldType: 'object', + noStatic: true, + expr: datasetsAsString, + connections: connections.filter(Boolean) + } + + component.datasets = undefined + component.xData = undefined + } + function setUpUIEditor() { if (component.type === 'plotlycomponentv2') { component.datasets = createPlotlyComponentDataset() @@ -92,6 +135,81 @@ } else if (component.type === 'chartjscomponentv2') { component.datasets = createChartjsComponentDataset() component.xData = createXData() + } else if (component.type === 'agchartscomponent' || component.type === 'agchartscomponentee') { + component.datasets = createAgChartsComponentDataset() + component.xData = createXData() + } + } + + function createAgChartsComponentDataset(): RichConfiguration { + return { + type: 'static', + fieldType: 'array', + subFieldType: 'ag-chart', + + value: [ + { + value: { + type: 'oneOf', + selected: 'bar', + labels: { + bar: 'Bar', + scatter: 'Scatter', + line: 'Line', + area: 'Area', + ['range-bar']: 'Range Bar' + }, + configuration: { + bar: { + value: { + type: 'static', + fieldType: 'array', + subFieldType: 'number', + value: [25, 25, 50] + } + }, + scatter: { + value: { + type: 'static', + fieldType: 'array', + subFieldType: 'number', + value: [25, 25, 50] + } + }, + line: { + value: { + type: 'static', + fieldType: 'array', + subFieldType: 'number', + value: [25, 25, 50] + } + }, + area: { + value: { + type: 'static', + fieldType: 'array', + subFieldType: 'number', + value: [25, 25, 50] + } + }, + ['range-bar']: { + value: { + type: 'static', + fieldType: 'array', + subFieldType: 'number-tuple', + value: [ + [10, 15], + [20, 25], + [18, 27] + ] + } + } + } + } as const, + name: 'Dataset 1', + type: 'bar' + } + ] } } @@ -146,8 +264,16 @@ } } + function isAgChartsComponent(): boolean { + return component.type === 'agchartscomponent' || component.type === 'agchartscomponentee' + } + function convertToJson() { - if (component.type !== 'plotlycomponentv2') { + if ( + component.type !== 'plotlycomponentv2' && + component.type !== 'agchartscomponent' && + component.type !== 'agchartscomponentee' + ) { return } @@ -227,9 +353,31 @@ \t\t} \t}` } + + function datasetToAgChartJson(): string { + const outputs = $worldStore.outputsById[component.id] + const result = outputs?.result.peak() + + if (!result.data && !result.series) { + throw new Error('Invalid result') + } + + return ( + '(' + + JSON.stringify( + { + data: result.data, + series: result.series + }, + null, + '\t' + ) + + ')' + ) + } -{#if component.type === 'plotlycomponentv2' || component.type === 'chartjscomponentv2'} +{#if component.type === 'plotlycomponentv2' || component.type === 'chartjscomponentv2' || component.type === 'agchartscomponent' || component.type === 'agchartscomponentee'}
+ {:else if isAgChartsComponent()} + {:else if component.type === 'chartjscomponentv2'} + import Badge from '$lib/components/common/badge/Badge.svelte' + + export let license: string + + let valid = false + $: license && checkLicenseKey(license) + + async function checkLicenseKey(key: string) { + try { + const { AgCharts } = await import('ag-charts-enterprise') + + // @ts-ignore + valid = AgCharts.licenseKey + } catch (e) { + console.error(e) + } + } + + +
+ AgCharts EE License Key + + + {#if valid} + Valid + {:else} + Invalid + {/if} +
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte index 10821eb904..062281ef8e 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte @@ -13,6 +13,7 @@ export let id: string export let resourceOnly: boolean export let tooltip: string | undefined + export let disabledOptions: string[] = [] $: { if (oneOf == undefined) { @@ -63,7 +64,7 @@ }} > {#each Object.keys(inputSpecsConfiguration ?? {}) as choice} - {#if !getValueOfDeprecated(inputSpecsConfiguration[choice]) || oneOf.selected === choice} + {#if (!disabledOptions.includes(choice) && !getValueOfDeprecated(inputSpecsConfiguration[choice])) || oneOf.selected === choice} {/if} {/each} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/StaticInputEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/StaticInputEditor.svelte index 8fd6f70d34..87774eac59 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/StaticInputEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/StaticInputEditor.svelte @@ -17,7 +17,9 @@ import TableColumnWizard from '$lib/components/wizards/TableColumnWizard.svelte' import PlotlyWizard from '$lib/components/wizards/PlotlyWizard.svelte' import ChartJSWizard from '$lib/components/wizards/ChartJSWizard.svelte' + import AgChartWizard from '$lib/components/wizards/AgChartWizard.svelte' import DBExplorerWizard from '$lib/components/wizards/DBExplorerWizard.svelte' + import Label from '$lib/components/Label.svelte' export let componentInput: StaticInput | undefined export let fieldType: InputType | undefined = undefined @@ -31,80 +33,35 @@ $: componentInput && onchange?.() -{#if componentInput?.type === 'static'} - {#if fieldType === 'number' || fieldType === 'integer'} - - {:else if fieldType === 'textarea'} -