[Dashboard]- Add GraphWidgetLineChart (#14386)
closes https://github.com/twentyhq/core-team-issues/issues/1371
This commit is contained in:
+7
@@ -5,8 +5,10 @@ import {
|
||||
WidgetType,
|
||||
} from '@/settings/page-layout/mocks/mockWidgets';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import {
|
||||
IconChartBar,
|
||||
IconChartLine,
|
||||
IconChartPie,
|
||||
IconGauge,
|
||||
IconNumber,
|
||||
@@ -49,6 +51,11 @@ const graphTypeOptions = [
|
||||
icon: IconNumber,
|
||||
title: 'Number',
|
||||
},
|
||||
{
|
||||
type: GraphType.LINE,
|
||||
icon: IconChartLine,
|
||||
title: 'Line Chart',
|
||||
},
|
||||
];
|
||||
|
||||
export const CommandMenuPageLayoutGraphTypeSelect = () => {
|
||||
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
ResponsiveLine,
|
||||
type LineSeries,
|
||||
type Point,
|
||||
type SliceTooltipProps,
|
||||
} from '@nivo/line';
|
||||
import { type ScaleLinearSpec, type ScaleSpec } from '@nivo/scales';
|
||||
import { useId, useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { type GraphColor } from '../types/GraphColor';
|
||||
import { createGradientDef } from '../utils/createGradientDef';
|
||||
import { createGraphColorRegistry } from '../utils/createGraphColorRegistry';
|
||||
import { getColorScheme } from '../utils/getColorScheme';
|
||||
import {
|
||||
formatGraphValue,
|
||||
type GraphValueFormatOptions,
|
||||
} from '../utils/graphFormatters';
|
||||
import { GraphWidgetLegend } from './GraphWidgetLegend';
|
||||
import { GraphWidgetTooltip } from './GraphWidgetTooltip';
|
||||
|
||||
type LineChartDataPoint = {
|
||||
x: number | string | Date;
|
||||
y: number | null;
|
||||
to?: string;
|
||||
};
|
||||
|
||||
type LineChartSeries = {
|
||||
id: string;
|
||||
label?: string;
|
||||
color?: GraphColor;
|
||||
data: LineChartDataPoint[];
|
||||
enableArea?: boolean;
|
||||
};
|
||||
|
||||
type GraphWidgetLineChartProps = {
|
||||
data: LineChartSeries[];
|
||||
showLegend?: boolean;
|
||||
showGrid?: boolean;
|
||||
enablePoints?: boolean;
|
||||
xAxisLabel?: string;
|
||||
yAxisLabel?: string;
|
||||
id: string;
|
||||
enableArea?: boolean;
|
||||
stackedArea?: boolean;
|
||||
curve?:
|
||||
| 'linear'
|
||||
| 'monotoneX'
|
||||
| 'step'
|
||||
| 'stepBefore'
|
||||
| 'stepAfter'
|
||||
| 'natural';
|
||||
lineWidth?: number;
|
||||
enableSlices?: 'x' | 'y' | false;
|
||||
xScale?: ScaleSpec;
|
||||
yScale?: ScaleSpec;
|
||||
} & GraphValueFormatOptions;
|
||||
|
||||
const getYScaleWithStacking = (
|
||||
yScale: ScaleSpec | undefined,
|
||||
stackedArea: boolean | undefined,
|
||||
): ScaleSpec => {
|
||||
if (!yScale || yScale.type === 'linear') {
|
||||
const linearScale: ScaleLinearSpec = {
|
||||
min: 0,
|
||||
max: 'auto',
|
||||
...yScale,
|
||||
type: 'linear',
|
||||
stacked: stackedArea,
|
||||
};
|
||||
return linearScale;
|
||||
}
|
||||
|
||||
return yScale;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledChartContainer = styled.div<{ $isClickable?: boolean }>`
|
||||
flex: 1;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
${({ $isClickable }) =>
|
||||
$isClickable &&
|
||||
`
|
||||
svg g circle {
|
||||
cursor: pointer;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const GraphWidgetLineChart = ({
|
||||
data,
|
||||
showLegend = true,
|
||||
showGrid = true,
|
||||
enablePoints = false,
|
||||
xAxisLabel,
|
||||
yAxisLabel,
|
||||
id,
|
||||
enableArea = false,
|
||||
stackedArea = false,
|
||||
curve = 'monotoneX',
|
||||
lineWidth = 2,
|
||||
enableSlices = 'x',
|
||||
xScale = { type: 'linear' },
|
||||
yScale = { type: 'linear', min: 0, max: 'auto' },
|
||||
displayType,
|
||||
decimals,
|
||||
prefix,
|
||||
suffix,
|
||||
customFormatter,
|
||||
}: GraphWidgetLineChartProps) => {
|
||||
const theme = useTheme();
|
||||
const instanceId = useId();
|
||||
|
||||
const colorRegistry = createGraphColorRegistry(theme);
|
||||
|
||||
const formatOptions: GraphValueFormatOptions = {
|
||||
displayType,
|
||||
decimals,
|
||||
prefix,
|
||||
suffix,
|
||||
customFormatter,
|
||||
};
|
||||
|
||||
const dataMap = useMemo(() => {
|
||||
const map: Record<string, LineChartSeries> = {};
|
||||
for (const series of data) {
|
||||
map[series.id] = series;
|
||||
}
|
||||
return map;
|
||||
}, [data]);
|
||||
|
||||
const enrichedSeries = useMemo(() => {
|
||||
return data.map((series, index) => {
|
||||
const colorScheme = getColorScheme(colorRegistry, series.color, index);
|
||||
const shouldEnableArea = series.enableArea ?? enableArea;
|
||||
const gradientId = `lineGradient-${id}-${instanceId}-${series.id}-${index}`;
|
||||
|
||||
return {
|
||||
...series,
|
||||
colorScheme,
|
||||
gradientId,
|
||||
shouldEnableArea,
|
||||
label: series.label || series.id,
|
||||
};
|
||||
});
|
||||
}, [data, colorRegistry, id, instanceId, enableArea]);
|
||||
|
||||
const nivoData = data.map((series) => ({
|
||||
id: series.id,
|
||||
data: series.data.map((point) => ({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
})),
|
||||
}));
|
||||
|
||||
const defs = enrichedSeries
|
||||
.filter((series) => series.shouldEnableArea)
|
||||
.map((series) =>
|
||||
createGradientDef(
|
||||
series.colorScheme,
|
||||
series.gradientId,
|
||||
false,
|
||||
90,
|
||||
theme.name === 'light',
|
||||
),
|
||||
);
|
||||
|
||||
const fill = enrichedSeries
|
||||
.filter((series) => series.shouldEnableArea)
|
||||
.map((series) => ({
|
||||
match: { id: series.id },
|
||||
id: series.gradientId,
|
||||
}));
|
||||
|
||||
const colors = enrichedSeries.map((series) => series.colorScheme.solid);
|
||||
|
||||
const handlePointClick = (point: Point<LineSeries>) => {
|
||||
const series = dataMap[point.seriesId];
|
||||
if (isDefined(series)) {
|
||||
const dataPoint = series.data[point.indexInSeries];
|
||||
if (isDefined(dataPoint?.to) === true) {
|
||||
window.location.href = dataPoint.to;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const hasClickableItems = data.some((series) =>
|
||||
series.data.some((point) => isDefined(point.to)),
|
||||
);
|
||||
|
||||
const renderSliceTooltip = ({ slice }: SliceTooltipProps<LineSeries>) => {
|
||||
const tooltipItems = slice.points
|
||||
.map((point) => {
|
||||
const enrichedSeriesItem = enrichedSeries.find(
|
||||
(s) => s.id === point.seriesId,
|
||||
);
|
||||
if (!enrichedSeriesItem) return null;
|
||||
|
||||
return {
|
||||
label: enrichedSeriesItem.label,
|
||||
formattedValue: formatGraphValue(
|
||||
Number(point.data.y || 0),
|
||||
formatOptions,
|
||||
),
|
||||
dotColor: enrichedSeriesItem.colorScheme.solid,
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
const hasClickablePoint = slice.points.some((point) => {
|
||||
const series = dataMap[point.seriesId];
|
||||
if (isDefined(series)) {
|
||||
const dataPoint = series.data[point.indexInSeries];
|
||||
return isDefined(dataPoint?.to);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return (
|
||||
<GraphWidgetTooltip
|
||||
items={tooltipItems}
|
||||
showClickHint={hasClickablePoint}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderPointTooltip = (point: Point<LineSeries>) => {
|
||||
const enrichedSeriesItem = enrichedSeries.find(
|
||||
(s) => s.id === point.seriesId,
|
||||
);
|
||||
if (!enrichedSeriesItem) return null;
|
||||
|
||||
const series = dataMap[point.seriesId];
|
||||
const dataPoint = series?.data[point.indexInSeries];
|
||||
|
||||
return (
|
||||
<GraphWidgetTooltip
|
||||
items={[
|
||||
{
|
||||
label: enrichedSeriesItem.label,
|
||||
formattedValue: formatGraphValue(
|
||||
Number(point.data.y || 0),
|
||||
formatOptions,
|
||||
),
|
||||
dotColor: enrichedSeriesItem.colorScheme.solid,
|
||||
},
|
||||
]}
|
||||
showClickHint={isDefined(dataPoint?.to)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getAxisBottomConfig = () => ({
|
||||
tickSize: 0,
|
||||
tickPadding: 5,
|
||||
tickRotation: 0,
|
||||
legend: xAxisLabel,
|
||||
legendPosition: 'middle' as const,
|
||||
legendOffset: 40,
|
||||
});
|
||||
|
||||
const getAxisLeftConfig = () => ({
|
||||
tickSize: 0,
|
||||
tickPadding: 5,
|
||||
tickRotation: 0,
|
||||
legend: yAxisLabel,
|
||||
legendPosition: 'middle' as const,
|
||||
legendOffset: -50,
|
||||
format: (value: number) => formatGraphValue(value, formatOptions),
|
||||
});
|
||||
|
||||
const legendItems = enrichedSeries.map((series) => {
|
||||
const total = series.data.reduce((sum, point) => sum + (point.y || 0), 0);
|
||||
return {
|
||||
id: series.id,
|
||||
label: series.label,
|
||||
formattedValue: formatGraphValue(total, formatOptions),
|
||||
color: series.colorScheme.solid,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledContainer id={id}>
|
||||
<StyledChartContainer $isClickable={hasClickableItems}>
|
||||
<ResponsiveLine
|
||||
data={nivoData}
|
||||
margin={{ top: 20, right: 20, bottom: 60, left: 70 }}
|
||||
xScale={xScale}
|
||||
yScale={getYScaleWithStacking(yScale, stackedArea)}
|
||||
curve={curve}
|
||||
lineWidth={lineWidth}
|
||||
enableArea={enableArea}
|
||||
areaBaselineValue={0}
|
||||
enablePoints={enablePoints}
|
||||
pointSize={6}
|
||||
pointBorderWidth={0}
|
||||
areaOpacity={theme.name === 'dark' ? 0.8 : 1}
|
||||
colors={colors}
|
||||
areaBlendMode={theme.name === 'dark' ? 'screen' : 'multiply'}
|
||||
defs={defs}
|
||||
fill={fill}
|
||||
axisTop={null}
|
||||
axisRight={null}
|
||||
axisBottom={getAxisBottomConfig()}
|
||||
axisLeft={getAxisLeftConfig()}
|
||||
enableGridX={showGrid}
|
||||
enableGridY={showGrid}
|
||||
enableSlices={enableSlices}
|
||||
sliceTooltip={enableSlices === 'x' ? renderSliceTooltip : undefined}
|
||||
tooltip={
|
||||
enableSlices === false
|
||||
? ({ point }) => renderPointTooltip(point)
|
||||
: undefined
|
||||
}
|
||||
onClick={(datum) => {
|
||||
if ('seriesId' in datum) {
|
||||
handlePointClick(datum as Point<LineSeries>);
|
||||
}
|
||||
}}
|
||||
useMesh={true}
|
||||
crosshairType="cross"
|
||||
theme={{
|
||||
axis: {
|
||||
domain: {
|
||||
line: {
|
||||
stroke: theme.border.color.light,
|
||||
strokeWidth: 1,
|
||||
},
|
||||
},
|
||||
ticks: {
|
||||
line: {
|
||||
stroke: theme.border.color.light,
|
||||
strokeWidth: 1,
|
||||
},
|
||||
text: {
|
||||
fill: theme.font.color.secondary,
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
text: {
|
||||
fill: theme.font.color.secondary,
|
||||
fontSize: 12,
|
||||
fontWeight: theme.font.weight.regular,
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
line: {
|
||||
stroke: theme.border.color.light,
|
||||
strokeWidth: 1,
|
||||
strokeDasharray: '4 4',
|
||||
},
|
||||
},
|
||||
crosshair: {
|
||||
line: {
|
||||
stroke: theme.font.color.tertiary,
|
||||
strokeWidth: 1,
|
||||
strokeDasharray: '2 2',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</StyledChartContainer>
|
||||
<GraphWidgetLegend show={showLegend} items={legendItems} />
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
+862
@@ -0,0 +1,862 @@
|
||||
import { type Meta, type StoryObj } from '@storybook/react';
|
||||
|
||||
import { CatalogDecorator, ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { GraphWidgetLineChart } from '../GraphWidgetLineChart';
|
||||
import { type ComponentProps } from 'react';
|
||||
|
||||
const meta: Meta<typeof GraphWidgetLineChart> = {
|
||||
title: 'Modules/Dashboards/Graphs/GraphWidgetLineChart',
|
||||
component: GraphWidgetLineChart,
|
||||
decorators: [ComponentDecorator],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
argTypes: {
|
||||
data: {
|
||||
control: 'object',
|
||||
},
|
||||
displayType: {
|
||||
control: 'select',
|
||||
options: ['percentage', 'number', 'shortNumber', 'currency', 'custom'],
|
||||
},
|
||||
prefix: {
|
||||
control: 'text',
|
||||
},
|
||||
suffix: {
|
||||
control: 'text',
|
||||
},
|
||||
decimals: {
|
||||
control: 'number',
|
||||
},
|
||||
showLegend: {
|
||||
control: 'boolean',
|
||||
},
|
||||
showGrid: {
|
||||
control: 'boolean',
|
||||
},
|
||||
enablePoints: {
|
||||
control: 'boolean',
|
||||
},
|
||||
xAxisLabel: {
|
||||
control: 'text',
|
||||
},
|
||||
yAxisLabel: {
|
||||
control: 'text',
|
||||
},
|
||||
enableArea: {
|
||||
control: 'boolean',
|
||||
},
|
||||
stackedArea: {
|
||||
control: 'boolean',
|
||||
},
|
||||
curve: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'linear',
|
||||
'monotoneX',
|
||||
'step',
|
||||
'stepBefore',
|
||||
'stepAfter',
|
||||
'natural',
|
||||
],
|
||||
},
|
||||
lineWidth: {
|
||||
control: 'number',
|
||||
},
|
||||
enableSlices: {
|
||||
control: 'select',
|
||||
options: ['x', 'y', false],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof GraphWidgetLineChart>;
|
||||
type ChartArgs = ComponentProps<typeof GraphWidgetLineChart>;
|
||||
|
||||
const Container = ({ children }: { children: React.ReactNode }) => (
|
||||
<div style={{ width: '700px', height: '500px' }}>{children}</div>
|
||||
);
|
||||
|
||||
const renderChart = (args: ChartArgs) => (
|
||||
<Container>
|
||||
<GraphWidgetLineChart
|
||||
id={args.id}
|
||||
data={args.data}
|
||||
showLegend={args.showLegend}
|
||||
showGrid={args.showGrid}
|
||||
enablePoints={args.enablePoints}
|
||||
xAxisLabel={args.xAxisLabel}
|
||||
yAxisLabel={args.yAxisLabel}
|
||||
displayType={args.displayType}
|
||||
prefix={args.prefix}
|
||||
suffix={args.suffix}
|
||||
decimals={args.decimals}
|
||||
enableArea={args.enableArea}
|
||||
stackedArea={args.stackedArea}
|
||||
curve={args.curve}
|
||||
lineWidth={args.lineWidth}
|
||||
enableSlices={args.enableSlices}
|
||||
xScale={args.xScale}
|
||||
yScale={args.yScale}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
|
||||
const generateLinearData = (points: number = 10) => {
|
||||
return Array.from({ length: points }, (_, i) => ({
|
||||
x: i,
|
||||
y: Math.floor(Math.random() * 100) + 20,
|
||||
}));
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
id: 'line-chart-default',
|
||||
data: [
|
||||
{
|
||||
id: 'series1',
|
||||
label: 'Revenue',
|
||||
color: 'blue',
|
||||
data: generateLinearData(12),
|
||||
},
|
||||
{
|
||||
id: 'series2',
|
||||
label: 'Profit',
|
||||
color: 'turquoise',
|
||||
data: generateLinearData(12),
|
||||
},
|
||||
],
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: false,
|
||||
xAxisLabel: 'Month',
|
||||
yAxisLabel: 'Value',
|
||||
displayType: 'shortNumber',
|
||||
prefix: '$',
|
||||
},
|
||||
render: renderChart,
|
||||
};
|
||||
|
||||
export const WithArea: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-area',
|
||||
data: [
|
||||
{
|
||||
id: 'sales',
|
||||
label: 'Sales',
|
||||
color: 'purple',
|
||||
data: generateLinearData(12),
|
||||
},
|
||||
{
|
||||
id: 'costs',
|
||||
label: 'Costs',
|
||||
color: 'orange',
|
||||
data: generateLinearData(12),
|
||||
},
|
||||
],
|
||||
enableArea: true,
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: false,
|
||||
xAxisLabel: 'Period',
|
||||
yAxisLabel: 'Amount',
|
||||
displayType: 'currency',
|
||||
},
|
||||
};
|
||||
|
||||
export const StackedArea: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-stacked',
|
||||
data: [
|
||||
{
|
||||
id: 'product-a',
|
||||
label: 'Product A',
|
||||
color: 'blue',
|
||||
data: generateLinearData(8),
|
||||
},
|
||||
{
|
||||
id: 'product-b',
|
||||
label: 'Product B',
|
||||
color: 'turquoise',
|
||||
data: generateLinearData(8),
|
||||
},
|
||||
{
|
||||
id: 'product-c',
|
||||
label: 'Product C',
|
||||
color: 'purple',
|
||||
data: generateLinearData(8),
|
||||
},
|
||||
],
|
||||
enableArea: true,
|
||||
stackedArea: true,
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: false,
|
||||
curve: 'monotoneX',
|
||||
xAxisLabel: 'Quarter',
|
||||
yAxisLabel: 'Revenue',
|
||||
yScale: {
|
||||
type: 'linear',
|
||||
min: 0,
|
||||
max: 'auto',
|
||||
},
|
||||
displayType: 'shortNumber',
|
||||
prefix: '$',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithPoints: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-points',
|
||||
data: [
|
||||
{
|
||||
id: 'performance',
|
||||
label: 'Performance',
|
||||
color: 'pink',
|
||||
data: generateLinearData(8),
|
||||
},
|
||||
],
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: true,
|
||||
lineWidth: 3,
|
||||
xAxisLabel: 'Week',
|
||||
yAxisLabel: 'Score',
|
||||
displayType: 'percentage',
|
||||
},
|
||||
};
|
||||
|
||||
export const StepChart: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-step',
|
||||
data: [
|
||||
{
|
||||
id: 'inventory',
|
||||
label: 'Inventory Level',
|
||||
color: 'orange',
|
||||
data: generateLinearData(10),
|
||||
},
|
||||
],
|
||||
curve: 'step',
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: false,
|
||||
lineWidth: 2,
|
||||
xAxisLabel: 'Day',
|
||||
yAxisLabel: 'Units',
|
||||
displayType: 'number',
|
||||
},
|
||||
};
|
||||
|
||||
export const LogScaleDemo: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-log-scale',
|
||||
data: [
|
||||
{
|
||||
id: 'exponential',
|
||||
label: 'Exponential Growth',
|
||||
color: 'purple',
|
||||
data: [
|
||||
{ x: 0, y: 10 },
|
||||
{ x: 1, y: 100 },
|
||||
{ x: 2, y: 1000 },
|
||||
{ x: 3, y: 10000 },
|
||||
{ x: 4, y: 100000 },
|
||||
{ x: 5, y: 1000000 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'linear',
|
||||
label: 'Linear Growth',
|
||||
color: 'turquoise',
|
||||
data: [
|
||||
{ x: 0, y: 50 },
|
||||
{ x: 1, y: 100 },
|
||||
{ x: 2, y: 150 },
|
||||
{ x: 3, y: 200 },
|
||||
{ x: 4, y: 250 },
|
||||
{ x: 5, y: 300 },
|
||||
],
|
||||
},
|
||||
],
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: true,
|
||||
xAxisLabel: 'Time',
|
||||
yAxisLabel: 'Value (log scale)',
|
||||
yScale: {
|
||||
type: 'log',
|
||||
base: 10,
|
||||
min: 'auto',
|
||||
max: 'auto',
|
||||
},
|
||||
displayType: 'shortNumber',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithNullValues: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-nulls',
|
||||
data: [
|
||||
{
|
||||
id: 'incomplete',
|
||||
label: 'With Gaps',
|
||||
color: 'blue',
|
||||
data: [
|
||||
{ x: 0, y: 30 },
|
||||
{ x: 1, y: 40 },
|
||||
{ x: 2, y: null },
|
||||
{ x: 3, y: null },
|
||||
{ x: 4, y: 60 },
|
||||
{ x: 5, y: 70 },
|
||||
{ x: 6, y: 65 },
|
||||
],
|
||||
},
|
||||
],
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: true,
|
||||
xAxisLabel: 'Time',
|
||||
yAxisLabel: 'Measurement',
|
||||
displayType: 'number',
|
||||
},
|
||||
};
|
||||
|
||||
export const InteractiveWithLinks: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-interactive',
|
||||
data: [
|
||||
{
|
||||
id: 'clickable',
|
||||
label: 'Click Points',
|
||||
color: 'turquoise',
|
||||
data: [
|
||||
{ x: 0, y: 30, to: '#point-0' },
|
||||
{ x: 1, y: 45, to: '#point-1' },
|
||||
{ x: 2, y: 38, to: '#point-2' },
|
||||
{ x: 3, y: 52, to: '#point-3' },
|
||||
{ x: 4, y: 48, to: '#point-4' },
|
||||
{ x: 5, y: 60, to: '#point-5' },
|
||||
],
|
||||
},
|
||||
],
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: true,
|
||||
enableSlices: false,
|
||||
xAxisLabel: 'Step',
|
||||
yAxisLabel: 'Progress',
|
||||
displayType: 'percentage',
|
||||
},
|
||||
};
|
||||
|
||||
export const MultiSeriesMixed: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-mixed',
|
||||
data: [
|
||||
{
|
||||
id: 'actual',
|
||||
label: 'Actual',
|
||||
color: 'blue',
|
||||
data: generateLinearData(12),
|
||||
enableArea: true,
|
||||
},
|
||||
{
|
||||
id: 'forecast',
|
||||
label: 'Forecast',
|
||||
color: 'purple',
|
||||
data: generateLinearData(12),
|
||||
enableArea: false,
|
||||
},
|
||||
{
|
||||
id: 'target',
|
||||
label: 'Target',
|
||||
color: 'orange',
|
||||
data: generateLinearData(12).map((d) => ({ ...d, y: 75 })),
|
||||
enableArea: false,
|
||||
},
|
||||
],
|
||||
enableArea: false,
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: false,
|
||||
curve: 'monotoneX',
|
||||
xAxisLabel: 'Month',
|
||||
yAxisLabel: 'Value',
|
||||
displayType: 'shortNumber',
|
||||
enableSlices: 'x',
|
||||
},
|
||||
};
|
||||
|
||||
export const OverlappingGradientBlend: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-blend',
|
||||
data: [
|
||||
{
|
||||
id: 'red-series',
|
||||
label: 'Red Wave',
|
||||
color: 'red',
|
||||
data: [
|
||||
{ x: 0, y: 50 },
|
||||
{ x: 1, y: 70 },
|
||||
{ x: 2, y: 65 },
|
||||
{ x: 3, y: 80 },
|
||||
{ x: 4, y: 75 },
|
||||
{ x: 5, y: 90 },
|
||||
{ x: 6, y: 85 },
|
||||
{ x: 7, y: 95 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'blue-series',
|
||||
label: 'Blue Wave',
|
||||
color: 'blue',
|
||||
data: [
|
||||
{ x: 0, y: 40 },
|
||||
{ x: 1, y: 60 },
|
||||
{ x: 2, y: 70 },
|
||||
{ x: 3, y: 75 },
|
||||
{ x: 4, y: 80 },
|
||||
{ x: 5, y: 70 },
|
||||
{ x: 6, y: 65 },
|
||||
{ x: 7, y: 60 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'green-series',
|
||||
label: 'Green Wave',
|
||||
color: 'turquoise',
|
||||
data: [
|
||||
{ x: 0, y: 30 },
|
||||
{ x: 1, y: 45 },
|
||||
{ x: 2, y: 55 },
|
||||
{ x: 3, y: 60 },
|
||||
{ x: 4, y: 65 },
|
||||
{ x: 5, y: 60 },
|
||||
{ x: 6, y: 55 },
|
||||
{ x: 7, y: 50 },
|
||||
],
|
||||
},
|
||||
],
|
||||
enableArea: true,
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: false,
|
||||
curve: 'monotoneX',
|
||||
xAxisLabel: 'Time',
|
||||
yAxisLabel: 'Value',
|
||||
displayType: 'number',
|
||||
},
|
||||
};
|
||||
|
||||
export const HighContrastOverlap: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-contrast',
|
||||
data: [
|
||||
{
|
||||
id: 'yellow-series',
|
||||
label: 'Yellow',
|
||||
color: 'yellow',
|
||||
data: [
|
||||
{ x: 0, y: 60 },
|
||||
{ x: 1, y: 80 },
|
||||
{ x: 2, y: 85 },
|
||||
{ x: 3, y: 90 },
|
||||
{ x: 4, y: 85 },
|
||||
{ x: 5, y: 80 },
|
||||
{ x: 6, y: 75 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'purple-series',
|
||||
label: 'Purple',
|
||||
color: 'purple',
|
||||
data: [
|
||||
{ x: 0, y: 50 },
|
||||
{ x: 1, y: 70 },
|
||||
{ x: 2, y: 80 },
|
||||
{ x: 3, y: 85 },
|
||||
{ x: 4, y: 90 },
|
||||
{ x: 5, y: 85 },
|
||||
{ x: 6, y: 70 },
|
||||
],
|
||||
},
|
||||
],
|
||||
enableArea: true,
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: true,
|
||||
curve: 'natural',
|
||||
xAxisLabel: 'Day',
|
||||
yAxisLabel: 'Score',
|
||||
displayType: 'number',
|
||||
},
|
||||
};
|
||||
|
||||
export const CurveComparison: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-curves',
|
||||
data: [
|
||||
{
|
||||
id: 'dataset',
|
||||
label: 'Same Data',
|
||||
color: 'orange',
|
||||
data: [
|
||||
{ x: 0, y: 20 },
|
||||
{ x: 1, y: 60 },
|
||||
{ x: 2, y: 40 },
|
||||
{ x: 3, y: 80 },
|
||||
{ x: 4, y: 30 },
|
||||
{ x: 5, y: 70 },
|
||||
{ x: 6, y: 50 },
|
||||
],
|
||||
},
|
||||
],
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: true,
|
||||
curve: 'linear',
|
||||
xAxisLabel: 'X Axis',
|
||||
yAxisLabel: 'Y Axis',
|
||||
displayType: 'number',
|
||||
},
|
||||
};
|
||||
|
||||
export const StepInterpolations: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-steps',
|
||||
data: [
|
||||
{
|
||||
id: 'step-normal',
|
||||
label: 'Step',
|
||||
color: 'blue',
|
||||
data: [
|
||||
{ x: 0, y: 30 },
|
||||
{ x: 1, y: 50 },
|
||||
{ x: 2, y: 45 },
|
||||
{ x: 3, y: 60 },
|
||||
{ x: 4, y: 55 },
|
||||
{ x: 5, y: 70 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'step-before',
|
||||
label: 'Step Before',
|
||||
color: 'purple',
|
||||
data: [
|
||||
{ x: 0, y: 25 },
|
||||
{ x: 1, y: 45 },
|
||||
{ x: 2, y: 40 },
|
||||
{ x: 3, y: 55 },
|
||||
{ x: 4, y: 50 },
|
||||
{ x: 5, y: 65 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'step-after',
|
||||
label: 'Step After',
|
||||
color: 'turquoise',
|
||||
data: [
|
||||
{ x: 0, y: 20 },
|
||||
{ x: 1, y: 40 },
|
||||
{ x: 2, y: 35 },
|
||||
{ x: 3, y: 50 },
|
||||
{ x: 4, y: 45 },
|
||||
{ x: 5, y: 60 },
|
||||
],
|
||||
},
|
||||
],
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: true,
|
||||
curve: 'step',
|
||||
xAxisLabel: 'Time',
|
||||
yAxisLabel: 'Value',
|
||||
displayType: 'number',
|
||||
},
|
||||
};
|
||||
|
||||
export const NaturalVsMonotone: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-smooth',
|
||||
data: [
|
||||
{
|
||||
id: 'natural',
|
||||
label: 'Natural Curve',
|
||||
color: 'pink',
|
||||
data: [
|
||||
{ x: 0, y: 40 },
|
||||
{ x: 1, y: 70 },
|
||||
{ x: 2, y: 50 },
|
||||
{ x: 3, y: 80 },
|
||||
{ x: 4, y: 45 },
|
||||
{ x: 5, y: 75 },
|
||||
{ x: 6, y: 60 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'monotone',
|
||||
label: 'Monotone X',
|
||||
color: 'orange',
|
||||
data: [
|
||||
{ x: 0, y: 35 },
|
||||
{ x: 1, y: 65 },
|
||||
{ x: 2, y: 45 },
|
||||
{ x: 3, y: 75 },
|
||||
{ x: 4, y: 40 },
|
||||
{ x: 5, y: 70 },
|
||||
{ x: 6, y: 55 },
|
||||
],
|
||||
},
|
||||
],
|
||||
enableArea: true,
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: true,
|
||||
curve: 'natural',
|
||||
xAxisLabel: 'Sample',
|
||||
yAxisLabel: 'Measurement',
|
||||
displayType: 'number',
|
||||
},
|
||||
};
|
||||
|
||||
export const SliceTooltipDemo: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-slice-tooltip',
|
||||
data: [
|
||||
{
|
||||
id: 'revenue',
|
||||
label: 'Revenue',
|
||||
color: 'blue',
|
||||
data: [
|
||||
{ x: 'Jan', y: 4500 },
|
||||
{ x: 'Feb', y: 5200 },
|
||||
{ x: 'Mar', y: 4800 },
|
||||
{ x: 'Apr', y: 6100 },
|
||||
{ x: 'May', y: 5500 },
|
||||
{ x: 'Jun', y: 7200 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'costs',
|
||||
label: 'Costs',
|
||||
color: 'red',
|
||||
data: [
|
||||
{ x: 'Jan', y: 3200 },
|
||||
{ x: 'Feb', y: 3500 },
|
||||
{ x: 'Mar', y: 3100 },
|
||||
{ x: 'Apr', y: 3800 },
|
||||
{ x: 'May', y: 3600 },
|
||||
{ x: 'Jun', y: 4200 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'profit',
|
||||
label: 'Profit',
|
||||
color: 'turquoise',
|
||||
data: [
|
||||
{ x: 'Jan', y: 1300 },
|
||||
{ x: 'Feb', y: 1700 },
|
||||
{ x: 'Mar', y: 1700 },
|
||||
{ x: 'Apr', y: 2300 },
|
||||
{ x: 'May', y: 1900 },
|
||||
{ x: 'Jun', y: 3000 },
|
||||
],
|
||||
},
|
||||
],
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: false,
|
||||
enableSlices: 'x',
|
||||
xScale: { type: 'point' },
|
||||
xAxisLabel: 'Month',
|
||||
yAxisLabel: 'Amount ($)',
|
||||
displayType: 'shortNumber',
|
||||
prefix: '$',
|
||||
},
|
||||
};
|
||||
|
||||
export const PointTooltipDemo: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-point-tooltip',
|
||||
data: [
|
||||
{
|
||||
id: 'revenue',
|
||||
label: 'Revenue',
|
||||
color: 'blue',
|
||||
data: [
|
||||
{ x: 'Jan', y: 4500 },
|
||||
{ x: 'Feb', y: 5200 },
|
||||
{ x: 'Mar', y: 4800 },
|
||||
{ x: 'Apr', y: 6100 },
|
||||
{ x: 'May', y: 5500 },
|
||||
{ x: 'Jun', y: 7200 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'costs',
|
||||
label: 'Costs',
|
||||
color: 'red',
|
||||
data: [
|
||||
{ x: 'Jan', y: 3200 },
|
||||
{ x: 'Feb', y: 3500 },
|
||||
{ x: 'Mar', y: 3100 },
|
||||
{ x: 'Apr', y: 3800 },
|
||||
{ x: 'May', y: 3600 },
|
||||
{ x: 'Jun', y: 4200 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'profit',
|
||||
label: 'Profit',
|
||||
color: 'turquoise',
|
||||
data: [
|
||||
{ x: 'Jan', y: 1300 },
|
||||
{ x: 'Feb', y: 1700 },
|
||||
{ x: 'Mar', y: 1700 },
|
||||
{ x: 'Apr', y: 2300 },
|
||||
{ x: 'May', y: 1900 },
|
||||
{ x: 'Jun', y: 3000 },
|
||||
],
|
||||
},
|
||||
],
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: true,
|
||||
enableSlices: false,
|
||||
xScale: { type: 'point' },
|
||||
xAxisLabel: 'Month',
|
||||
yAxisLabel: 'Amount ($)',
|
||||
displayType: 'shortNumber',
|
||||
prefix: '$',
|
||||
},
|
||||
};
|
||||
|
||||
export const IntenseOverlapRGB: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-rgb',
|
||||
data: [
|
||||
{
|
||||
id: 'red',
|
||||
label: 'Red Channel',
|
||||
color: 'red',
|
||||
data: [
|
||||
{ x: 0, y: 70 },
|
||||
{ x: 1, y: 85 },
|
||||
{ x: 2, y: 75 },
|
||||
{ x: 3, y: 90 },
|
||||
{ x: 4, y: 80 },
|
||||
{ x: 5, y: 85 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'green',
|
||||
label: 'Green Channel',
|
||||
color: 'turquoise',
|
||||
data: [
|
||||
{ x: 0, y: 65 },
|
||||
{ x: 1, y: 75 },
|
||||
{ x: 2, y: 85 },
|
||||
{ x: 3, y: 80 },
|
||||
{ x: 4, y: 75 },
|
||||
{ x: 5, y: 70 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'blue',
|
||||
label: 'Blue Channel',
|
||||
color: 'blue',
|
||||
data: [
|
||||
{ x: 0, y: 60 },
|
||||
{ x: 1, y: 70 },
|
||||
{ x: 2, y: 80 },
|
||||
{ x: 3, y: 75 },
|
||||
{ x: 4, y: 85 },
|
||||
{ x: 5, y: 80 },
|
||||
],
|
||||
},
|
||||
],
|
||||
enableArea: true,
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: false,
|
||||
curve: 'monotoneX',
|
||||
xAxisLabel: 'Position',
|
||||
yAxisLabel: 'Intensity',
|
||||
displayType: 'percentage',
|
||||
},
|
||||
};
|
||||
|
||||
export const Catalog: Story = {
|
||||
render: renderChart,
|
||||
args: {
|
||||
id: 'line-chart-catalog',
|
||||
data: [
|
||||
{
|
||||
id: 'series1',
|
||||
label: 'Series 1',
|
||||
color: 'blue',
|
||||
data: generateLinearData(8),
|
||||
},
|
||||
{
|
||||
id: 'series2',
|
||||
label: 'Series 2',
|
||||
color: 'purple',
|
||||
data: generateLinearData(8),
|
||||
},
|
||||
],
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: false,
|
||||
enableArea: true,
|
||||
},
|
||||
decorators: [CatalogDecorator],
|
||||
parameters: {
|
||||
pseudo: { hover: ['.content'] },
|
||||
catalog: {
|
||||
dimensions: [
|
||||
{
|
||||
name: 'colors',
|
||||
values: [
|
||||
'blue',
|
||||
'purple',
|
||||
'turquoise',
|
||||
'orange',
|
||||
'pink',
|
||||
'red',
|
||||
'yellow',
|
||||
'green',
|
||||
'sky',
|
||||
],
|
||||
props: (color: string) => ({
|
||||
data: [
|
||||
{
|
||||
id: 'series',
|
||||
label: `${color} Series`,
|
||||
color,
|
||||
data: generateLinearData(8),
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1 +1,10 @@
|
||||
export type GraphColor = 'blue' | 'purple' | 'turquoise' | 'orange' | 'pink';
|
||||
export type GraphColor =
|
||||
| 'blue'
|
||||
| 'purple'
|
||||
| 'turquoise'
|
||||
| 'orange'
|
||||
| 'pink'
|
||||
| 'yellow'
|
||||
| 'red'
|
||||
| 'green'
|
||||
| 'sky';
|
||||
|
||||
+32
@@ -48,4 +48,36 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
solid: theme.color.pink,
|
||||
},
|
||||
yellow: {
|
||||
name: 'yellow',
|
||||
gradient: {
|
||||
normal: [theme.adaptiveColors.yellow1, theme.adaptiveColors.yellow2],
|
||||
hover: [theme.adaptiveColors.yellow3, theme.adaptiveColors.yellow4],
|
||||
},
|
||||
solid: theme.color.yellow,
|
||||
},
|
||||
red: {
|
||||
name: 'red',
|
||||
gradient: {
|
||||
normal: [theme.adaptiveColors.red1, theme.adaptiveColors.red2],
|
||||
hover: [theme.adaptiveColors.red3, theme.adaptiveColors.red4],
|
||||
},
|
||||
solid: theme.color.red,
|
||||
},
|
||||
green: {
|
||||
name: 'green',
|
||||
gradient: {
|
||||
normal: [theme.adaptiveColors.green1, theme.adaptiveColors.green2],
|
||||
hover: [theme.adaptiveColors.green3, theme.adaptiveColors.green4],
|
||||
},
|
||||
solid: theme.color.green,
|
||||
},
|
||||
sky: {
|
||||
name: 'sky',
|
||||
gradient: {
|
||||
normal: [theme.adaptiveColors.sky1, theme.adaptiveColors.sky2],
|
||||
hover: [theme.adaptiveColors.sky3, theme.adaptiveColors.sky4],
|
||||
},
|
||||
solid: theme.color.sky,
|
||||
},
|
||||
});
|
||||
|
||||
+23
@@ -1,5 +1,6 @@
|
||||
import { GraphWidgetBarChart } from '@/dashboards/widgets/graph/components/GraphWidgetBarChart';
|
||||
import { GraphWidgetGaugeChart } from '@/dashboards/widgets/graph/components/GraphWidgetGaugeChart';
|
||||
import { GraphWidgetLineChart } from '@/dashboards/widgets/graph/components/GraphWidgetLineChart';
|
||||
import { GraphWidgetNumberChart } from '@/dashboards/widgets/graph/components/GraphWidgetNumberChart';
|
||||
import { GraphWidgetPieChart } from '@/dashboards/widgets/graph/components/GraphWidgetPieChart';
|
||||
import { GraphType } from '../mocks/mockWidgets';
|
||||
@@ -69,6 +70,28 @@ export const GraphWidgetRenderer = ({ widget }: GraphWidgetRendererProps) => {
|
||||
/>
|
||||
);
|
||||
|
||||
case GraphType.LINE:
|
||||
return (
|
||||
<GraphWidgetLineChart
|
||||
id={`line-chart-${widget.id}`}
|
||||
data={widget.data.series}
|
||||
enableArea={widget.data.enableArea}
|
||||
showLegend={widget.data.showLegend}
|
||||
showGrid={widget.data.showGrid}
|
||||
enablePoints={widget.data.enablePoints}
|
||||
xAxisLabel={widget.data.xAxisLabel}
|
||||
yAxisLabel={widget.data.yAxisLabel}
|
||||
displayType={widget.data.displayType}
|
||||
prefix={widget.data.prefix}
|
||||
suffix={widget.data.suffix}
|
||||
xScale={widget.data.xScale}
|
||||
yScale={widget.data.yScale}
|
||||
curve={widget.data.curve}
|
||||
stackedArea={widget.data.stackedArea}
|
||||
enableSlices={widget.data.enableSlices}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export enum GraphType {
|
||||
GAUGE = 'GAUGE',
|
||||
PIE = 'PIE',
|
||||
BAR = 'BAR',
|
||||
LINE = 'LINE',
|
||||
}
|
||||
|
||||
export const mockPageLayoutWidgets: PageLayoutWidget[] = [
|
||||
|
||||
@@ -41,6 +41,67 @@ export const getDefaultWidgetData = (graphType: GraphType) => {
|
||||
layout: 'vertical' as const,
|
||||
};
|
||||
|
||||
case GraphType.LINE:
|
||||
return {
|
||||
series: [
|
||||
{
|
||||
id: 'revenue',
|
||||
label: 'Revenue',
|
||||
color: 'blue',
|
||||
data: [
|
||||
{ x: 0, y: 30 },
|
||||
{ x: 1, y: 50 },
|
||||
{ x: 2, y: 45 },
|
||||
{ x: 3, y: 70 },
|
||||
{ x: 4, y: 65 },
|
||||
{ x: 5, y: 80 },
|
||||
{ x: 6, y: 75 },
|
||||
{ x: 7, y: 85 },
|
||||
],
|
||||
enableArea: true,
|
||||
},
|
||||
{
|
||||
id: 'costs',
|
||||
label: 'Costs',
|
||||
color: 'red',
|
||||
data: [
|
||||
{ x: 0, y: 60 },
|
||||
{ x: 1, y: 45 },
|
||||
{ x: 2, y: 55 },
|
||||
{ x: 3, y: 40 },
|
||||
{ x: 4, y: 60 },
|
||||
{ x: 5, y: 50 },
|
||||
{ x: 6, y: 70 },
|
||||
{ x: 7, y: 65 },
|
||||
],
|
||||
enableArea: true,
|
||||
},
|
||||
{
|
||||
id: 'profit',
|
||||
label: 'Profit',
|
||||
color: 'turquoise',
|
||||
data: [
|
||||
{ x: 0, y: 45 },
|
||||
{ x: 1, y: 60 },
|
||||
{ x: 2, y: 35 },
|
||||
{ x: 3, y: 55 },
|
||||
{ x: 4, y: 50 },
|
||||
{ x: 5, y: 65 },
|
||||
{ x: 6, y: 40 },
|
||||
{ x: 7, y: 75 },
|
||||
],
|
||||
enableArea: true,
|
||||
},
|
||||
],
|
||||
enableArea: true,
|
||||
showLegend: true,
|
||||
showGrid: true,
|
||||
enablePoints: false,
|
||||
curve: 'monotoneX',
|
||||
displayType: 'shortNumber',
|
||||
prefix: '$',
|
||||
};
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
@@ -52,6 +113,7 @@ export const getWidgetTitle = (graphType: GraphType, index: number): string => {
|
||||
[GraphType.GAUGE]: 'Gauge',
|
||||
[GraphType.PIE]: 'Pie Chart',
|
||||
[GraphType.BAR]: 'Bar Chart',
|
||||
[GraphType.LINE]: 'Line Chart',
|
||||
};
|
||||
|
||||
return `${baseNames[graphType] || 'Widget'} ${index + 1}`;
|
||||
@@ -67,6 +129,8 @@ export const getWidgetSize = (graphType: GraphType) => {
|
||||
return { w: 4, h: 4 };
|
||||
case GraphType.BAR:
|
||||
return { w: 6, h: 4 };
|
||||
case GraphType.LINE:
|
||||
return { w: 6, h: 10 };
|
||||
default:
|
||||
return { w: 4, h: 4 };
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ import { Button } from 'twenty-ui/input';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const StyledGridContainer = styled.div`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 100%;
|
||||
|
||||
@@ -47,6 +47,7 @@ export {
|
||||
IconChartBar,
|
||||
IconChartCandle,
|
||||
IconChartDots3,
|
||||
IconChartLine,
|
||||
IconChartPie,
|
||||
IconCheck,
|
||||
IconCheckbox,
|
||||
|
||||
@@ -109,6 +109,7 @@ export {
|
||||
IconChartBar,
|
||||
IconChartCandle,
|
||||
IconChartDots3,
|
||||
IconChartLine,
|
||||
IconChartPie,
|
||||
IconCheck,
|
||||
IconCheckbox,
|
||||
|
||||
Reference in New Issue
Block a user