mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-22 23:56:00 +00:00
charts version 0
This commit is contained in:
56
packages/web/src/charts/ChartEditor.js
Normal file
56
packages/web/src/charts/ChartEditor.js
Normal file
@@ -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 (
|
||||
<Formik initialValues={{ chartType: 'bar' }} onSubmit={() => {}}>
|
||||
<Form>
|
||||
<HorizontalSplitter initialValue="300px" size={managerSize} setSize={setManagerSize}>
|
||||
<LeftContainer theme={theme}>
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Style" name="style" height="40%">
|
||||
<FormSelectField label="Chart type" name="chartType">
|
||||
<option value="bar">Bar</option>
|
||||
<option value="line">Line</option>
|
||||
</FormSelectField>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Data" name="data">
|
||||
<FormSelectField label="Label column" name="labelColumn">
|
||||
<option value=""></option>
|
||||
{availableColumnNames.map((col) => (
|
||||
<option value={col} key={col}>
|
||||
{col}
|
||||
</option>
|
||||
))}
|
||||
</FormSelectField>
|
||||
{availableColumnNames.map((col) => (
|
||||
<FormCheckboxField label={col} name={`dataColumn_${col}`} key={col} />
|
||||
))}
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
</LeftContainer>
|
||||
|
||||
<DataChart data={data} />
|
||||
</HorizontalSplitter>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
99
packages/web/src/charts/DataChart.js
Normal file
99
packages/web/src/charts/DataChart.js
Normal file
@@ -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 (
|
||||
<ChartWrapper ref={containerRef}>
|
||||
<Chart
|
||||
key={`${values.chartType}|${containerWidth}|${containerHeight}`}
|
||||
width={containerWidth}
|
||||
height={containerHeight}
|
||||
data={createChartData(data, labelColumn, dataColumns)}
|
||||
type={values.chartType}
|
||||
/>
|
||||
</ChartWrapper>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export default function DataGridContextMenu({
|
||||
filterSelectedValue,
|
||||
openQuery,
|
||||
openFreeTable,
|
||||
openChart,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
@@ -53,6 +54,7 @@ export default function DataGridContextMenu({
|
||||
)}
|
||||
{openQuery && <DropDownMenuItem onClick={openQuery}>Open query</DropDownMenuItem>}
|
||||
<DropDownMenuItem onClick={openFreeTable}>Open selection in free table editor</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={openChart}>Open chart from selection</DropDownMenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
53
packages/web/src/tabs/ChartTab.js
Normal file
53
packages/web/src/tabs/ChartTab.js
Normal file
@@ -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 <LoadingInfo wrapper message="Loading data" />;
|
||||
}
|
||||
if (errorMessage) {
|
||||
return <ErrorInfo message={errorMessage} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChartEditor data={modelState.value && modelState.value.data} />
|
||||
<SaveTabModal
|
||||
modalState={saveFileModalState}
|
||||
data={modelState.value}
|
||||
format="json"
|
||||
folder="charts"
|
||||
tabid={tabid}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user