Skip to content

Commit

Permalink
Merge pull request #1586 from merico-dev/1581-able-to-use-a-name-to-c…
Browse files Browse the repository at this point in the history
…olor-map-to-get-color-for-each-series-in-pie-chart

1581 able to use a name to color map to get color for each series in pie chart
  • Loading branch information
GerilLeto authored Dec 13, 2024
2 parents 2d7d7bf + 561e9bf commit 43d850e
Show file tree
Hide file tree
Showing 17 changed files with 439 additions and 59 deletions.
2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devtable/api",
"version": "14.2.0",
"version": "14.3.1",
"description": "",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devtable/dashboard",
"version": "14.2.0",
"version": "14.3.1",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Button, Flex, Group } from '@mantine/core';
import { IconPlus } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { NameColorMapRow } from '../../type';

type AddARowProps = {
append: (v: NameColorMapRow) => void;
};

export const AddARow = ({ append }: AddARowProps) => {
const { t } = useTranslation();

const add = () => {
append({ name: '', color: '' });
};
return (
<Flex gap="sm" justify="flex-start" align="center" direction="row" wrap="nowrap">
<div style={{ minWidth: '30px', maxWidth: '30px', flex: 0 }} />
<Group wrap="nowrap" style={{ flex: 1 }}>
<Button size="xs" variant="subtle" onClick={add} leftSection={<IconPlus size={14} />}>
{t('viz.pie_chart.color.map.add_a_row')}
</Button>
</Group>
<div style={{ minWidth: '40px', maxWidth: '40px', flex: 0 }} />
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './pie-color-map-editor';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import _ from 'lodash';
import { ChartTheme } from '~/styles/register-themes';

export type PieChartPaletteOption = {
name: string;
colors: string[];
};
export const PieChartPalettes = Object.entries(
_.pick(ChartTheme.graphics, ['compared', 'level', 'depth', 'multiple']),
).map(([name, record]) => {
return {
name,
colors: Object.values(record),
} as PieChartPaletteOption;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { move } from '@dnd-kit/helpers';
import { DragDropProvider } from '@dnd-kit/react';
import { Group, Stack, Text } from '@mantine/core';
import _ from 'lodash';
import { forwardRef, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { v4 as uuidv4 } from 'uuid';
import { NameColorMapRow } from '../../type';
import { RowEditor } from './row-editor';
import { AddARow } from './add-a-row';
import { SelectPalette } from './select-palette';

type Props = {
value: NameColorMapRow[];
onChange: (v: NameColorMapRow[]) => void;
zIndex?: number;
names: string[];
};

export const PieColorMapEditor = forwardRef<HTMLDivElement, Props>(({ value, onChange, zIndex = 340, names }, ref) => {
const { t } = useTranslation();
const rows = useMemo(() => {
return value.map((r) => ({
id: uuidv4(),
...r,
}));
}, [value]);

const append = (v: NameColorMapRow) => {
onChange([...value, v]);
};
const remove = (index: number) => {
const newValue = [...value];
newValue.splice(index, 1);
onChange(newValue);
};
const replace = (values: NameColorMapRow[]) => {
onChange([...values]);
};
const getChangeHandler = (index: number) => (v: NameColorMapRow) => {
const newValue = [...value];
newValue[index] = v;
onChange(newValue);
};

const onDragEnd = (event: any) => {
const { source, target } = event.operation;
const newRows = move(rows, source, target);
onChange(newRows.map((v) => _.omit(v, 'id')));
};

return (
<Stack ref={ref} pt={4}>
<Group justify="space-between">
<Text size="sm" fw="500" mb={-4}>
{t('viz.pie_chart.color.map.label')}
</Text>
<SelectPalette value={value} onChange={onChange} />
</Group>
<DragDropProvider onDragEnd={onDragEnd}>
{rows.map((r, index) => (
<RowEditor
key={r.id}
row={r}
handleChange={getChangeHandler(index)}
handleRemove={() => remove(index)}
index={index}
names={names}
/>
))}
</DragDropProvider>
<AddARow append={append} />
</Stack>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useSortable } from '@dnd-kit/react/sortable';
import { ActionIcon, Autocomplete, Badge, Center, CloseButton, ColorInput, Flex, Group } from '@mantine/core';
import { IconGripVertical } from '@tabler/icons-react';
import { useBoolean } from 'ahooks';
import { useTranslation } from 'react-i18next';
import { NameColorMapRow } from '../../type';

type RowFieldItem = {
id: string;
} & NameColorMapRow;

type NameColorMapRowProps = {
row: RowFieldItem;
handleChange: (v: RowFieldItem) => void;
handleRemove: () => void;
index: number;
names: string[];
};
export const RowEditor = ({ row, index, handleChange, handleRemove, names }: NameColorMapRowProps) => {
const { t } = useTranslation();
const [touched, { setTrue: setTouched }] = useBoolean(false);
const [hovering, { setTrue, setFalse }] = useBoolean(false);
const { ref, handleRef } = useSortable({
id: row.id,
index,
});

const changeName = (name: string) => {
handleChange({
...row,
name,
});
};

const changeColor = (color: string) => {
handleChange({
...row,
color,
});
};

return (
<Flex
ref={ref}
gap="sm"
justify="flex-start"
align="center"
direction="row"
wrap="nowrap"
onMouseEnter={setTrue}
onMouseLeave={setFalse}
>
<Center style={{ minWidth: '30px', maxWidth: '30px', flex: 0 }}>
{hovering ? (
<ActionIcon size="xs" ref={handleRef} variant="subtle">
<IconGripVertical />
</ActionIcon>
) : (
<Badge size="sm" variant="light">
{index + 1}
</Badge>
)}
</Center>
<Group grow wrap="nowrap" style={{ flex: 1 }}>
<Autocomplete
size="xs"
value={row.name}
placeholder={t('viz.pie_chart.color.map.name')}
onChange={changeName}
onClick={setTouched}
error={touched && !row.name}
data={names}
maxDropdownHeight={500}
/>
<ColorInput
styles={{
root: {
flexGrow: 1,
},
}}
popoverProps={{
withinPortal: true,
zIndex: 340,
}}
size="xs"
value={row.color}
onChange={changeColor}
onClick={setTouched}
error={touched && !row.color}
/>
</Group>
<div style={{ minWidth: '40px', maxWidth: '40px', flex: 0 }}>
<CloseButton onClick={handleRemove} size="sm" />
</div>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Box, Button, Combobox, Menu } from '@mantine/core';
import _ from 'lodash';
import numbro from 'numbro';
import { useTranslation } from 'react-i18next';
import { NameColorMapRow } from '../../type';
import { PieChartPalettes } from './palette';

function getBackgroundImage(colors: string[]) {
const len = colors.length;
if (len === 0) {
return '';
}
const format: numbro.Format = { output: 'percent', mantissa: 4, trimMantissa: true };
const step = _.divide(1, len);
const stops: string[] = [];
colors.forEach((c, i) => {
stops.push(`${c} ${numbro(i * step).format(format)}`);
stops.push(`${c} ${numbro((i + 1) * step).format(format)}`);
});
const ret = `linear-gradient(90deg, ${stops.join(',')})`;
console.log(ret);
return ret;
}

type Props = {
value: NameColorMapRow[];
onChange: (v: NameColorMapRow[]) => void;
};

export const SelectPalette = ({ value, onChange }: Props) => {
const { t, i18n } = useTranslation();

const applyPalette = (colors: string[]) => {
const newValue = value.map((v, i) => ({
name: v.name,
color: colors[i],
}));
for (let j = newValue.length - 1; j < colors.length; j++) {
newValue.push({
name: '',
color: colors[j],
});
}
onChange(newValue);
};

return (
<Menu
withArrow
shadow="md"
width={400}
styles={{
item: {
overflowX: 'hidden',
},
}}
>
<Menu.Target>
<Button size="compact-xs" variant="subtle" rightSection={<Combobox.Chevron size="xs" />}>
{t('viz.pie_chart.color.map.use_a_palette')}
</Button>
</Menu.Target>

<Menu.Dropdown>
<Menu.Label>{t('viz.pie_chart.color.map.click_to_apply_palette')}</Menu.Label>
<Menu.Divider />
{PieChartPalettes.map((p) => (
<Menu.Item key={p.name} onClick={() => applyPalette(p.colors)}>
<Box h={28} style={{ backgroundImage: getBackgroundImage(p.colors) }} />
</Menu.Item>
))}
</Menu.Dropdown>
</Menu>
);
};
Loading

0 comments on commit 43d850e

Please sign in to comment.