From 1644587072afcae228841c9e3e5103c14ba013eb Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 5 Dec 2020 15:13:08 +0100 Subject: [PATCH] charts version 0 --- packages/web/package.json | 2 + packages/web/src/charts/ChartEditor.js | 56 +++++++++++ packages/web/src/charts/DataChart.js | 99 +++++++++++++++++++ .../web/src/datagrid/DataGridContextMenu.js | 2 + packages/web/src/datagrid/DataGridCore.js | 30 +++++- packages/web/src/icons.js | 1 + packages/web/src/tabs/ChartTab.js | 53 ++++++++++ packages/web/src/tabs/index.js | 2 + packages/web/src/utility/useDimensions.js | 4 +- yarn.lock | 43 +++++++- 10 files changed, 284 insertions(+), 8 deletions(-) create mode 100644 packages/web/src/charts/ChartEditor.js create mode 100644 packages/web/src/charts/DataChart.js create mode 100644 packages/web/src/tabs/ChartTab.js diff --git a/packages/web/package.json b/packages/web/package.json index c506298b..79d93dbf 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^7.1.2", "ace-builds": "^1.4.8", "axios": "^0.19.0", + "chart.js": "^2.9.4", "cross-env": "^6.0.3", "dbgate-datalib": "^1.0.0", "dbgate-sqltree": "^1.0.0", @@ -21,6 +22,7 @@ "localforage": "^1.9.0", "react": "^16.12.0", "react-ace": "^8.0.0", + "react-chartjs-2": "^2.11.1", "react-dom": "^16.12.0", "react-dropzone": "^11.2.3", "react-helmet": "^6.1.0", diff --git a/packages/web/src/charts/ChartEditor.js b/packages/web/src/charts/ChartEditor.js new file mode 100644 index 00000000..866cb11a --- /dev/null +++ b/packages/web/src/charts/ChartEditor.js @@ -0,0 +1,56 @@ +import React from 'react'; +import Chart from 'react-chartjs-2'; +import styled from 'styled-components'; +import useTheme from '../theme/useTheme'; +import useDimensions from '../utility/useDimensions'; +import { HorizontalSplitter } from '../widgets/Splitter'; +import WidgetColumnBar, { WidgetColumnBarItem } from '../widgets/WidgetColumnBar'; +import { FormCheckboxField, FormSelectField } from '../utility/forms'; +import { Formik, Form } from 'formik'; +import DataChart from './DataChart'; + +const LeftContainer = styled.div` + background-color: ${(props) => props.theme.manager_background}; + display: flex; + flex: 1; +`; + +export default function ChartEditor({ data }) { + const [managerSize, setManagerSize] = React.useState(0); + const theme = useTheme(); + const availableColumnNames = data ? data.structure.columns.map((x) => x.columnName) : []; + + return ( + {}}> +
+ + + + + + + + + + + + + {availableColumnNames.map((col) => ( + + ))} + + {availableColumnNames.map((col) => ( + + ))} + + + + + + +
+
+ ); +} diff --git a/packages/web/src/charts/DataChart.js b/packages/web/src/charts/DataChart.js new file mode 100644 index 00000000..9e697315 --- /dev/null +++ b/packages/web/src/charts/DataChart.js @@ -0,0 +1,99 @@ +import React from 'react'; +import _ from 'lodash' +import Chart from 'react-chartjs-2'; +import styled from 'styled-components'; +import useTheme from '../theme/useTheme'; +import useDimensions from '../utility/useDimensions'; +import { HorizontalSplitter } from '../widgets/Splitter'; +import WidgetColumnBar, { WidgetColumnBarItem } from '../widgets/WidgetColumnBar'; +import { FormSelectField } from '../utility/forms'; +import { Formik, Form, useFormikContext } from 'formik'; + +const ChartWrapper = styled.div` + flex: 1; + overflow: hidden; +`; + +const chartData = { + labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], + datasets: [ + { + label: '# of Votes', + data: [12, 19, 3, 5, 2, 3], + backgroundColor: [ + 'rgba(255, 99, 132, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(255, 206, 86, 0.2)', + 'rgba(75, 192, 192, 0.2)', + 'rgba(153, 102, 255, 0.2)', + 'rgba(255, 159, 64, 0.2)', + ], + borderColor: [ + 'rgba(255, 99, 132, 1)', + 'rgba(54, 162, 235, 1)', + 'rgba(255, 206, 86, 1)', + 'rgba(75, 192, 192, 1)', + 'rgba(153, 102, 255, 1)', + 'rgba(255, 159, 64, 1)', + ], + borderWidth: 1, + }, + ], +}; + +function createChartData(freeData, labelColumn, dataColumns) { + if (!freeData || !labelColumn || !dataColumns || dataColumns.length == 0) return {}; + const labels = _.uniq(freeData.rows.map((x) => x[labelColumn])); + const res = { + labels: freeData.rows.map((x) => x[labelColumn]), + datasets: dataColumns.map((dataColumn) => ({ + label: dataColumn, + // data: [12, 19, 3, 5, 2, 3], + data: freeData.rows.map((x) => x[dataColumn]), + backgroundColor: [ + 'rgba(255, 99, 132, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(255, 206, 86, 0.2)', + 'rgba(75, 192, 192, 0.2)', + 'rgba(153, 102, 255, 0.2)', + 'rgba(255, 159, 64, 0.2)', + ], + borderColor: [ + 'rgba(255, 99, 132, 1)', + 'rgba(54, 162, 235, 1)', + 'rgba(255, 206, 86, 1)', + 'rgba(75, 192, 192, 1)', + 'rgba(153, 102, 255, 1)', + 'rgba(255, 159, 64, 1)', + ], + borderWidth: 1, + })), + }; + + return res; +} + +export default function DataChart({ data }) { + const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions(); + const { values } = useFormikContext(); + + const { labelColumn } = values; + const dataColumns = []; + for (const key in values) { + if (key.startsWith('dataColumn_') && values[key]) { + dataColumns.push(key.substring('dataColumn_'.length)); + } + } + + return ( + + + + ); +} diff --git a/packages/web/src/datagrid/DataGridContextMenu.js b/packages/web/src/datagrid/DataGridContextMenu.js index 76b76ec6..f9b0178d 100644 --- a/packages/web/src/datagrid/DataGridContextMenu.js +++ b/packages/web/src/datagrid/DataGridContextMenu.js @@ -12,6 +12,7 @@ export default function DataGridContextMenu({ filterSelectedValue, openQuery, openFreeTable, + openChart, }) { return ( <> @@ -53,6 +54,7 @@ export default function DataGridContextMenu({ )} {openQuery && Open query} Open selection in free table editor + Open chart from selection ); } diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js index 2d3cf614..15aadf5d 100644 --- a/packages/web/src/datagrid/DataGridCore.js +++ b/packages/web/src/datagrid/DataGridCore.js @@ -321,9 +321,18 @@ export default function DataGridCore(props) { setFirstVisibleColumnScrollIndex(value); }; - const handleOpenFreeTable = () => { + const getSelectedFreeData = () => { const columns = getSelectedColumns(); const rows = getSelectedRowData().map((row) => _.pickBy(row, (v, col) => columns.find((x) => x.columnName == col))); + return { + structure: { + columns, + }, + rows, + }; + }; + + const handleOpenFreeTable = () => { openNewTab( setOpenedTabs, { @@ -332,11 +341,21 @@ export default function DataGridCore(props) { tabComponent: 'FreeTableTab', props: {}, }, + getSelectedFreeData() + ); + }; + + const handleOpenChart = () => { + openNewTab( + setOpenedTabs, { - structure: { - columns, - }, - rows, + title: 'Chart', + icon: 'img chart', + tabComponent: 'ChartTab', + props: {}, + }, + { + data: getSelectedFreeData(), } ); }; @@ -357,6 +376,7 @@ export default function DataGridCore(props) { filterSelectedValue={display.filterable ? filterSelectedValue : null} openQuery={openQuery} openFreeTable={handleOpenFreeTable} + openChart={handleOpenChart} /> ); }; diff --git a/packages/web/src/icons.js b/packages/web/src/icons.js index fe2f6b61..5c647702 100644 --- a/packages/web/src/icons.js +++ b/packages/web/src/icons.js @@ -59,6 +59,7 @@ const iconNames = { 'img foreign-key': 'mdi mdi-key-link', 'img sql-file': 'mdi mdi-file', 'img shell': 'mdi mdi-flash color-blue-7', + 'img chart': 'mdi mdi-chart-bar color-magenta-7', 'img free-table': 'mdi mdi-table color-green-7', 'img macro': 'mdi mdi-hammer-wrench', diff --git a/packages/web/src/tabs/ChartTab.js b/packages/web/src/tabs/ChartTab.js new file mode 100644 index 00000000..6348c252 --- /dev/null +++ b/packages/web/src/tabs/ChartTab.js @@ -0,0 +1,53 @@ +import React from 'react'; +import { createFreeTableModel } from 'dbgate-datalib'; +import useUndoReducer from '../utility/useUndoReducer'; +import { useSetOpenedTabs } from '../utility/globalState'; +import useGridConfig from '../utility/useGridConfig'; +import FreeTableGrid from '../freetable/FreeTableGrid'; +import SaveArchiveModal from '../modals/SaveArchiveModal'; +import useModalState from '../modals/useModalState'; +import axios from '../utility/axios'; +import LoadingInfo from '../widgets/LoadingInfo'; +import { changeTab } from '../utility/common'; +import ErrorInfo from '../widgets/ErrorInfo'; +import useEditorData from '../utility/useEditorData'; +import SaveTabModal from '../modals/SaveTabModal'; +import ChartEditor from '../charts/ChartEditor'; + +export default function ChartTab({ tabVisible, toolbarPortalRef, tabid }) { + const [config, setConfig] = useGridConfig(tabid); + const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel()); + const saveFileModalState = useModalState(); + const { initialData, setEditorData, errorMessage, isLoading } = useEditorData({ + tabid, + }); + + React.useEffect(() => { + // @ts-ignore + if (initialData) dispatchModel({ type: 'reset', value: initialData }); + }, [initialData]); + + React.useEffect(() => { + setEditorData(modelState.value); + }, [modelState]); + + if (isLoading) { + return ; + } + if (errorMessage) { + return ; + } + + return ( + <> + + + + ); +} diff --git a/packages/web/src/tabs/index.js b/packages/web/src/tabs/index.js index e77d0bc3..37feccef 100644 --- a/packages/web/src/tabs/index.js +++ b/packages/web/src/tabs/index.js @@ -7,6 +7,7 @@ import InfoPageTab from './InfoPageTab'; import ArchiveFileTab from './ArchiveFileTab'; import FreeTableTab from './FreeTableTab'; import PluginTab from './PluginTab'; +import ChartTab from './ChartTab'; export default { TableDataTab, @@ -18,4 +19,5 @@ export default { ArchiveFileTab, FreeTableTab, PluginTab, + ChartTab, }; diff --git a/packages/web/src/utility/useDimensions.js b/packages/web/src/utility/useDimensions.js index 62f83e2e..867b90c5 100644 --- a/packages/web/src/utility/useDimensions.js +++ b/packages/web/src/utility/useDimensions.js @@ -51,7 +51,7 @@ import ResizeObserver from 'resize-observer-polyfill'; // Export hook export default function useDimensions(dependencies = []) { const [node, setNode] = useState(null); - const ref = useCallback(newNode => { + const ref = useCallback((newNode) => { setNode(newNode); }, []); @@ -68,7 +68,7 @@ export default function useDimensions(dependencies = []) { }); // Define measure function - const measure = useCallback(innerNode => { + const measure = useCallback((innerNode) => { const rect = innerNode.getBoundingClientRect(); setDimensions({ x: rect.left, diff --git a/yarn.lock b/yarn.lock index 6a64ad2f..638bcf4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2971,6 +2971,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, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -3144,7 +3167,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.1: +color-convert@^1.9.0, color-convert@^1.9.1, 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== @@ -7364,6 +7387,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.17.19: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + loglevel@^1.6.4: version "1.6.6" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.6.tgz#0ee6300cc058db6b3551fa1c4bf73b83bb771312" @@ -7755,6 +7783,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" @@ -9675,6 +9708,14 @@ react-base16-styling@^0.6.0: lodash.flow "^3.3.0" pure-color "^1.2.0" +react-chartjs-2@^2.11.1: + version "2.11.1" + resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-2.11.1.tgz#a78d0df05fc8bc8ffcd4c4ab5b89a25dd2ca3278" + integrity sha512-G7cNq/n2Bkh/v4vcI+GKx7Q1xwZexKYhOSj2HmrFXlvNeaURWXun6KlOUpEQwi1cv9Tgs4H3kGywDWMrX2kxfA== + dependencies: + lodash "^4.17.19" + prop-types "^15.7.2" + react-dev-utils@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.1.0.tgz#ccf82135f6dc2fc91969bc729ce57a69d8e86025"