From 489c9a905c0dd0519476fda944f54a5771c8563d Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 19 Mar 2021 20:12:13 +0100 Subject: [PATCH] chart initial import --- packages/web/package.json | 3 + packages/web/src/charts/ChartCore.svelte | 34 ++++ packages/web/src/charts/ChartEditor.svelte | 169 ++++++++++++++++++ packages/web/src/charts/DataChart.svelte | 137 ++++++++++++++ packages/web/src/charts/chartDataLoader.ts | 122 +++++++++++++ packages/web/src/designer/Designer.svelte | 2 + .../FormFieldTemplateLarge.svelte | 0 .../FormFieldTemplateRow.svelte | 0 .../src/forms/FormFieldTemplateTiny.svelte | 40 +++++ packages/web/src/forms/FormProvider.svelte | 2 +- .../web/src/forms/FormProviderCore.svelte | 2 +- .../web/src/freetable/MacroParameters.svelte | 2 +- .../web/src/modals/ConnectionModal.svelte | 2 +- packages/web/src/modals/SetFilterModal.svelte | 2 +- packages/web/src/tabs/ChartTab.svelte | 56 ++++++ packages/web/src/tabs/index.js | 4 +- yarn.lock | 49 ++++- 17 files changed, 617 insertions(+), 9 deletions(-) create mode 100644 packages/web/src/charts/ChartCore.svelte create mode 100644 packages/web/src/charts/ChartEditor.svelte create mode 100644 packages/web/src/charts/DataChart.svelte create mode 100644 packages/web/src/charts/chartDataLoader.ts rename packages/web/src/{modals => forms}/FormFieldTemplateLarge.svelte (100%) rename packages/web/src/{modals => forms}/FormFieldTemplateRow.svelte (100%) create mode 100644 packages/web/src/forms/FormFieldTemplateTiny.svelte create mode 100644 packages/web/src/tabs/ChartTab.svelte diff --git a/packages/web/package.json b/packages/web/package.json index 32b835c7d..9d4a2d801 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -8,11 +8,13 @@ "validate": "svelte-check" }, "devDependencies": { + "@ant-design/colors": "^5.0.0", "@rollup/plugin-commonjs": "^17.0.0", "@rollup/plugin-node-resolve": "^11.0.0", "@rollup/plugin-typescript": "^6.0.0", "@tsconfig/svelte": "^1.0.0", "ace-builds": "^1.4.8", + "chart.js": "^2.9.4", "dbgate-datalib": "^3.9.5", "dbgate-sqltree": "^3.9.5", "dbgate-tools": "^3.9.5", @@ -20,6 +22,7 @@ "json-stable-stringify": "^1.0.1", "localforage": "^1.9.0", "lodash": "^4.17.15", + "randomcolor": "^0.6.2", "rollup": "^2.3.4", "rollup-plugin-copy": "^3.3.0", "rollup-plugin-css-only": "^3.1.0", diff --git a/packages/web/src/charts/ChartCore.svelte b/packages/web/src/charts/ChartCore.svelte new file mode 100644 index 000000000..49247f7dd --- /dev/null +++ b/packages/web/src/charts/ChartCore.svelte @@ -0,0 +1,34 @@ + + + diff --git a/packages/web/src/charts/ChartEditor.svelte b/packages/web/src/charts/ChartEditor.svelte new file mode 100644 index 000000000..77b53f191 --- /dev/null +++ b/packages/web/src/charts/ChartEditor.svelte @@ -0,0 +1,169 @@ + + + + +
+ + + + + + + + + + + + + {#if availableColumnNames.length > 0} + ({ value: col, label: col }))]} + /> + {/if} + + {#each availableColumnNames as col (col)} + + {#if config[`dataColumn_${col}`]} + ({ value: color, label: _.startCase(color) })), + ]} + /> + {/if} + {/each} + + + +
+ + + + +
+
+ + diff --git a/packages/web/src/charts/DataChart.svelte b/packages/web/src/charts/DataChart.svelte new file mode 100644 index 000000000..17c695b10 --- /dev/null +++ b/packages/web/src/charts/DataChart.svelte @@ -0,0 +1,137 @@ + + + + +
+ +
+ + diff --git a/packages/web/src/charts/chartDataLoader.ts b/packages/web/src/charts/chartDataLoader.ts new file mode 100644 index 000000000..7f7bcbc54 --- /dev/null +++ b/packages/web/src/charts/chartDataLoader.ts @@ -0,0 +1,122 @@ +import { dumpSqlSelect, Select } from 'dbgate-sqltree'; +import { EngineDriver } from 'dbgate-types'; +import axiosInstance from '../utility/axiosInstance'; +import _ from 'lodash'; + +export async function loadChartStructure(driver: EngineDriver, conid, database, sql) { + const select: Select = { + commandType: 'select', + selectAll: true, + topRecords: 1, + from: { + subQueryString: sql, + alias: 'subq', + }, + }; + + const dmp = driver.createDumper(); + dumpSqlSelect(dmp, select); + const resp = await axiosInstance.post('database-connections/query-data', { conid, database, sql: dmp.s }); + if (resp.data.errorMessage) throw new Error(resp.data.errorMessage); + return resp.data.columns.map(x => x.columnName); +} + +export async function loadChartData(driver: EngineDriver, conid, database, sql, config) { + const dataColumns = extractDataColumns(config); + const { labelColumn, truncateFrom, truncateLimit, showRelativeValues } = config; + if (!labelColumn || !dataColumns || dataColumns.length == 0) return null; + + const select: Select = { + commandType: 'select', + + columns: [ + { + exprType: 'column', + source: { alias: 'subq' }, + columnName: labelColumn, + alias: labelColumn, + }, + // @ts-ignore + ...dataColumns.map(columnName => ({ + exprType: 'call', + func: 'SUM', + args: [ + { + exprType: 'column', + columnName, + source: { alias: 'subq' }, + }, + ], + alias: columnName, + })), + ], + topRecords: truncateLimit || 100, + from: { + subQueryString: sql, + alias: 'subq', + }, + groupBy: [ + { + exprType: 'column', + source: { alias: 'subq' }, + columnName: labelColumn, + }, + ], + orderBy: [ + { + exprType: 'column', + source: { alias: 'subq' }, + columnName: labelColumn, + direction: truncateFrom == 'end' ? 'DESC' : 'ASC', + }, + ], + }; + + const dmp = driver.createDumper(); + dumpSqlSelect(dmp, select); + const resp = await axiosInstance.post('database-connections/query-data', { conid, database, sql: dmp.s }); + let { rows, columns } = resp.data; + if (truncateFrom == 'end' && rows) { + rows = _.reverse([...rows]); + } + if (showRelativeValues) { + const maxValues = dataColumns.map(col => _.max(rows.map(row => row[col]))); + for (const [col, max] of _.zip(dataColumns, maxValues)) { + if (!max) continue; + if (!_.isNumber(max)) continue; + if (!(max > 0)) continue; + rows = rows.map(row => ({ + ...row, + [col]: (row[col] / max) * 100, + })); + // columns = columns.map((x) => { + // if (x.columnName == col) { + // return { columnName: `${col} %` }; + // } + // return x; + // }); + } + } + return { + columns, + rows, + }; +} + +export function extractDataColumns(values) { + const dataColumns = []; + for (const key in values) { + if (key.startsWith('dataColumn_') && values[key]) { + dataColumns.push(key.substring('dataColumn_'.length)); + } + } + return dataColumns; +} +export function extractDataColumnColors(values, dataColumns) { + const res = {}; + for (const column of dataColumns) { + const color = values[`dataColumnColor_${column}`]; + if (color) res[column] = color; + } + return res; +} diff --git a/packages/web/src/designer/Designer.svelte b/packages/web/src/designer/Designer.svelte index 23975368c..243ca5d78 100644 --- a/packages/web/src/designer/Designer.svelte +++ b/packages/web/src/designer/Designer.svelte @@ -12,11 +12,13 @@ import DesignerReference from './DesignerReference.svelte'; import { writable } from 'svelte/store'; import { tick } from 'svelte'; + import contextMenu from '../utility/contextMenu'; export let value; export let onChange; export let conid; export let database; + // export let menu; let domCanvas; diff --git a/packages/web/src/modals/FormFieldTemplateLarge.svelte b/packages/web/src/forms/FormFieldTemplateLarge.svelte similarity index 100% rename from packages/web/src/modals/FormFieldTemplateLarge.svelte rename to packages/web/src/forms/FormFieldTemplateLarge.svelte diff --git a/packages/web/src/modals/FormFieldTemplateRow.svelte b/packages/web/src/forms/FormFieldTemplateRow.svelte similarity index 100% rename from packages/web/src/modals/FormFieldTemplateRow.svelte rename to packages/web/src/forms/FormFieldTemplateRow.svelte diff --git a/packages/web/src/forms/FormFieldTemplateTiny.svelte b/packages/web/src/forms/FormFieldTemplateTiny.svelte new file mode 100644 index 000000000..dff6b6486 --- /dev/null +++ b/packages/web/src/forms/FormFieldTemplateTiny.svelte @@ -0,0 +1,40 @@ + + +
+ {#if type == 'checkbox'} + + {label} + {:else} +
+
+ {label} +
+
+
+ +
+ {/if} +
+ + diff --git a/packages/web/src/forms/FormProvider.svelte b/packages/web/src/forms/FormProvider.svelte index 11387d99b..073f26da7 100644 --- a/packages/web/src/forms/FormProvider.svelte +++ b/packages/web/src/forms/FormProvider.svelte @@ -1,6 +1,6 @@ + +{#if $editorState.isLoading} + +{:else if $editorState.errorMessage} + +{:else} + +{/if} diff --git a/packages/web/src/tabs/index.js b/packages/web/src/tabs/index.js index d272c5a62..7f2973d77 100644 --- a/packages/web/src/tabs/index.js +++ b/packages/web/src/tabs/index.js @@ -7,7 +7,7 @@ import * as ShellTab from './ShellTab.svelte'; import * as ArchiveFileTab from './ArchiveFileTab.svelte'; import * as FreeTableTab from './FreeTableTab.svelte'; // import PluginTab from './PluginTab'; -// import ChartTab from './ChartTab'; +import * as ChartTab from './ChartTab.svelte'; import * as MarkdownEditorTab from './MarkdownEditorTab.svelte'; // import MarkdownViewTab from './MarkdownViewTab'; // import MarkdownPreviewTab from './MarkdownPreviewTab'; @@ -24,7 +24,7 @@ export default { ArchiveFileTab, FreeTableTab, // PluginTab, - // ChartTab, + ChartTab, MarkdownEditorTab, // MarkdownViewTab, // MarkdownPreviewTab, diff --git a/yarn.lock b/yarn.lock index 6cd3e984f..3454249ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@ant-design/colors@^5.0.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-5.1.1.tgz#800b2186b1e27e66432e67d03ed96af3e21d8940" + integrity sha512-Txy4KpHrp3q4XZdfgOBqLl+lkQIc3tEvHXOimRN1giX1AEC7mGtyrO9p8iRGJ3FLuVMGa2gNEzQyghVymLttKQ== + dependencies: + "@ctrl/tinycolor" "^3.3.1" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -160,6 +167,11 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@ctrl/tinycolor@^3.3.1": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz#c3c5ae543c897caa9c2a68630bed355be5f9990f" + integrity sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ== + "@jest/console@^24.7.1", "@jest/console@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" @@ -1523,6 +1535,29 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +chart.js@^2.9.4: + version "2.9.4" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684" + integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A== + dependencies: + chartjs-color "^2.1.0" + moment "^2.10.2" + +chartjs-color-string@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71" + integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A== + dependencies: + color-name "^1.0.0" + +chartjs-color@^2.1.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0" + integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w== + dependencies: + chartjs-color-string "^0.6.0" + color-convert "^1.9.3" + chokidar@^2.0.2: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -1661,7 +1696,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.9.0: +color-convert@^1.9.0, color-convert@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -1680,7 +1715,7 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@~1.1.4: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -4881,6 +4916,11 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +moment@^2.10.2: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" @@ -5781,6 +5821,11 @@ randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +randomcolor@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/randomcolor/-/randomcolor-0.6.2.tgz#7a57362ae1a1278439aeed2c15e5deb8ea33f56d" + integrity sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A== + randomfill@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458"