Skip to content

Commit

Permalink
Merge pull request #1137 from merico-dev/1132-add-merico-heatmap
Browse files Browse the repository at this point in the history
1132 add merico heatmap
  • Loading branch information
GerilLeto authored Aug 17, 2023
2 parents 3669eeb + 169a94f commit ec98f9d
Show file tree
Hide file tree
Showing 33 changed files with 1,314 additions and 14 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": "10.20.2",
"version": "10.21.0",
"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": "10.20.2",
"version": "10.21.0",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export function getNumberOrDynamicValue(

const { value } = conf as TNumberOrDynamic_Dynamic;
try {
console.log('returning here');
return new Function(`return ${value}`)()({ variables: variableValueMap }, { lodash, interpolate });
} catch (error) {
// @ts-expect-error Object is of type 'unknown'.
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/components/plugins/plugin-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { CalendarHeatmapVizComponent } from './viz-components/calendar-heatmap';
import { HorizontalBarChartVizComponent } from './viz-components/horizontal-bar-chart';
import { MericoEstimationChartVizComponent } from './viz-components/merico-estimation-chart';
import { MericoStatsVizComponent } from './viz-components/merico-stats';
import { MericoHeatmapVizComponent } from './viz-components/merico-heatmap';

export interface IPluginContextProps {
pluginManager: IPluginManager;
Expand Down Expand Up @@ -137,6 +138,7 @@ const BuiltInPlugin: () => IDashboardPlugin = () => ({
RegressionChartVizComponent,
MericoGQMVizComponent,
MericoEstimationChartVizComponent,
MericoHeatmapVizComponent,
MericoStatsVizComponent,
ButtonVizComponent,
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import _ from 'lodash';
import { AnyObject } from '~/types';
import { parseDataKey } from '~/utils/data';
import { getLabelOverflowOptionOnAxis } from '../../../common-echarts-fields/axis-label-overflow';
import { FormatterFuncType } from '../editors/x-axis/x-axis-label-formatter/get-echarts-x-axis-tick-label';
import { IHeatmapConf } from '../type';
import { getLabelOverflowOptionOnAxis } from '../../../common-echarts-fields/axis-label-overflow';
import { parseDataKey } from '~/utils/data';

export function getXAxis(conf: IHeatmapConf, data: TPanelData, formatterFunc: FormatterFuncType) {
const x = parseDataKey(conf.x_axis.data_key);
Expand Down Expand Up @@ -35,6 +34,6 @@ export function getXAxis(conf: IHeatmapConf, data: TPanelData, formatterFunc: Fo
fontWeight: 'bold',
align: 'center',
},
z: 1,
z: 2,
};
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import _ from 'lodash';
import { AnyObject } from '~/types';
import { parseDataKey } from '~/utils/data';
import { getLabelOverflowOptionOnAxis } from '../../../common-echarts-fields/axis-label-overflow';
import { FormatterFuncType } from '../editors/x-axis/x-axis-label-formatter/get-echarts-x-axis-tick-label';
import { IHeatmapConf } from '../type';
import { getLabelOverflowOptionOnAxis } from '../../../common-echarts-fields/axis-label-overflow';
import { parseDataKey } from '~/utils/data';

export function getYAxis(conf: IHeatmapConf, data: TPanelData, formatterFunc: FormatterFuncType) {
const x = parseDataKey(conf.x_axis.data_key);
Expand Down Expand Up @@ -40,6 +39,6 @@ export function getYAxis(conf: IHeatmapConf, data: TPanelData, formatterFunc: Fo
},
nameLocation: 'end',
nameGap: 15,
z: 1,
z: 2,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Divider, Group, NumberInput, Stack, Switch, TextInput } from '@mantine/core';
import { IconTextSize } from '@tabler/icons-react';
import { Control, Controller, UseFormWatch } from 'react-hook-form';
import { DataFieldSelector } from '~/components/panel/settings/common/data-field-selector';
import { NumbroFormatSelector } from '~/components/panel/settings/common/numbro-format-selector';
import { NumberOrDynamicValue } from '~/components/plugins/common-echarts-fields/number-or-dynamic-value';
import { TMericoHeatmapConf } from '../../type';

interface IHeatBlockField {
control: Control<TMericoHeatmapConf, $TSFixMe>;
watch: UseFormWatch<TMericoHeatmapConf>;
}
export function HeatBlockField({ control, watch }: IHeatBlockField) {
watch(['heat_block']);
const showLabel = watch('heat_block.label.show');
return (
<Stack>
<Group grow noWrap>
<Controller
name="heat_block.data_key"
control={control}
render={({ field }) => <DataFieldSelector label="Data Field" required sx={{ flex: 1 }} {...field} />}
/>
<Controller
name="heat_block.name"
control={control}
render={({ field }) => <TextInput label="Name" sx={{ flex: 1 }} {...field} />}
/>
</Group>
<Group grow noWrap>
<Controller
name="heat_block.min"
control={control}
render={({ field }) => <NumberOrDynamicValue label="Min Value" {...field} />}
/>
<Controller
name="heat_block.max"
control={control}
render={({ field }) => <NumberOrDynamicValue label="Max Value" {...field} />}
/>
</Group>

<Divider mb={-15} variant="dashed" label="Value Format" labelPosition="center" />
<Controller
name={`heat_block.value_formatter`}
control={control}
render={({ field }) => <NumbroFormatSelector {...field} />}
/>

<Divider mb={-5} variant="dashed" label="Label" labelPosition="center" />
<Group grow noWrap>
<Controller
name="heat_block.label.show"
control={control}
render={({ field }) => (
<Switch
label="Show label"
checked={field.value}
onChange={(e) => field.onChange(e.currentTarget.checked)}
sx={{ flexGrow: 1 }}
/>
)}
/>
<Controller
name="heat_block.label.fontSize"
control={control}
render={({ field }) => (
// @ts-expect-error type of onChange
<NumberInput size="xs" icon={<IconTextSize size={16} />} disabled={!showLabel} {...field} />
)}
/>
</Group>
</Stack>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { ActionIcon, Group, Stack, Tabs, Text } from '@mantine/core';
import _, { defaultsDeep, isEqual } from 'lodash';
import { useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';

import { DeviceFloppy } from 'tabler-icons-react';
import { useStorageData } from '~/components/plugins/hooks';
import { VizConfigProps } from '~/types/plugin';
import { HeatBlockField } from './heat_block';
import { TooltipField } from './tooltip';
import { XAxisField } from './x-axis';
import { YAxisField } from './y-axis';
import { DEFAULT_CONFIG, TMericoHeatmapConf } from '../type';

export function EditMericoHeatmap({ context }: VizConfigProps) {
const { value: confValue, set: setConf } = useStorageData<TMericoHeatmapConf>(context.instanceData, 'config');
const { variables } = context;
const conf: TMericoHeatmapConf = useMemo(() => defaultsDeep({}, confValue, DEFAULT_CONFIG), [confValue]);
const defaultValues: TMericoHeatmapConf = useMemo(() => {
return _.cloneDeep(conf);
}, [conf]);

useEffect(() => {
const configMalformed = !isEqual(conf, defaultValues);
if (configMalformed) {
console.log('config malformed, resetting to defaults', conf, defaultValues);
void setConf(defaultValues);
}
}, [conf, defaultValues]);

const { control, handleSubmit, watch, getValues, reset } = useForm<TMericoHeatmapConf>({ defaultValues });
useEffect(() => {
reset(defaultValues);
}, [defaultValues]);

const values = getValues();
const changed = useMemo(() => {
return !isEqual(values, conf);
}, [values, conf]);

return (
<form onSubmit={handleSubmit(setConf)} style={{ flexGrow: 1 }}>
<Stack spacing="xs" sx={{ height: '100%' }}>
<Group position="left" py="md" pl="md" sx={{ borderBottom: '1px solid #eee', background: '#efefef' }}>
<Text>Chart Config</Text>
<ActionIcon type="submit" mr={5} variant="filled" color="blue" disabled={!changed}>
<DeviceFloppy size={20} />
</ActionIcon>
</Group>
<Tabs
defaultValue="X Axis"
orientation="vertical"
styles={{
root: {
// height: '100%',
flexGrow: 1,
},
tab: {
paddingLeft: '6px',
paddingRight: '6px',
},
panel: {
paddingTop: '6px',
paddingLeft: '12px',
},
}}
>
<Tabs.List>
<Tabs.Tab value="X Axis">X Axis</Tabs.Tab>
<Tabs.Tab value="Y Axis">Y Axis</Tabs.Tab>
<Tabs.Tab value="Heat Block">Heat Block</Tabs.Tab>
<Tabs.Tab value="Tooltip">Tooltip</Tabs.Tab>
</Tabs.List>

<Tabs.Panel value="X Axis">
<XAxisField control={control} watch={watch} />
</Tabs.Panel>

<Tabs.Panel value="Y Axis">
<YAxisField control={control} watch={watch} />
</Tabs.Panel>

<Tabs.Panel value="Heat Block">
<HeatBlockField control={control} watch={watch} />
</Tabs.Panel>

<Tabs.Panel value="Tooltip">
<TooltipField control={control} watch={watch} />
</Tabs.Panel>
</Tabs>
</Stack>
</form>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Control, UseFormWatch } from 'react-hook-form';
import { TMericoHeatmapConf } from '../../type';
import { TooltipMetricsField } from './metrics';

interface ITooltipField {
control: Control<TMericoHeatmapConf, $TSFixMe>;
watch: UseFormWatch<TMericoHeatmapConf>;
}
export function TooltipField({ control, watch }: ITooltipField) {
return <TooltipMetricsField control={control} watch={watch} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Button, Divider, Group, Stack, TextInput } from '@mantine/core';
import { Control, Controller } from 'react-hook-form';
import { Trash } from 'tabler-icons-react';
import { DataFieldSelector } from '~/components/panel/settings/common/data-field-selector';
import { TMericoHeatmapConf } from '../../type';

interface ITooltipMetricField {
control: Control<TMericoHeatmapConf, $TSFixMe>;
index: number;
remove: (index: number) => void;
}

export const TooltipMetricField = ({ control, index, remove }: ITooltipMetricField) => {
return (
<Stack>
<Group grow noWrap>
<Controller
name={`tooltip.metrics.${index}.name`}
control={control}
render={({ field }) => <TextInput label="Name" required sx={{ flex: 1 }} {...field} />}
/>
<Controller
name={`tooltip.metrics.${index}.data_key`}
control={control}
render={({ field }) => <DataFieldSelector label="Value Field" required sx={{ flex: 1 }} {...field} />}
/>
</Group>
<Divider mb={-10} mt={10} variant="dashed" />
<Button
leftIcon={<Trash size={16} />}
color="red"
variant="light"
onClick={() => remove(index)}
sx={{ top: 15, right: 5 }}
>
Delete this Metric
</Button>
</Stack>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Divider, Group, Tabs, Text } from '@mantine/core';
import { useEffect, useState } from 'react';
import { Control, useFieldArray, UseFormWatch } from 'react-hook-form';
import { InfoCircle, Plus } from 'tabler-icons-react';
import { TMericoHeatmapConf } from '../../type';
import { TooltipMetricField } from './metric';

interface ITooltipMetricsField {
control: Control<TMericoHeatmapConf, $TSFixMe>;
watch: UseFormWatch<TMericoHeatmapConf>;
}

export const TooltipMetricsField = ({ control, watch }: ITooltipMetricsField) => {
const { fields, append, remove } = useFieldArray({
control,
name: 'tooltip.metrics',
});

const watchFieldArray = watch('tooltip.metrics');
const controlledFields = fields.map((field, index) => {
return {
...field,
...watchFieldArray[index],
};
});

const addMetric = () =>
append({
id: Date.now().toString(),
data_key: '',
name: '',
});

const firstID = watch('tooltip.metrics.0.id');
const [tab, setTab] = useState<string | null>(() => firstID ?? null);
useEffect(() => {
if (firstID) {
setTab((t) => (t !== null ? t : firstID));
}
}, [firstID]);

return (
<>
<Group spacing={2} sx={{ cursor: 'default', userSelect: 'none' }}>
<InfoCircle size={14} color="#888" />
<Text size={14} color="#888">
Configure additional metrics to show in tooltip
</Text>
</Group>
<Divider variant="dashed" my={10} />
<Tabs
value={tab}
onTabChange={(t) => setTab(t)}
styles={{
tab: {
paddingTop: '0px',
paddingBottom: '0px',
},
panel: {
padding: '0px',
paddingTop: '6px',
},
}}
>
<Tabs.List>
{controlledFields.map((m, i) => (
<Tabs.Tab key={m.id} value={m.id}>
{m.name ? m.name : i}
</Tabs.Tab>
))}
<Tabs.Tab onClick={addMetric} value="add">
<Plus size={18} color="#228be6" />
</Tabs.Tab>
</Tabs.List>
{controlledFields.map((m, index) => (
<Tabs.Panel key={m.id} value={m.id}>
<TooltipMetricField key={m.id} control={control} index={index} remove={remove} />
</Tabs.Panel>
))}
</Tabs>
</>
);
};
Loading

0 comments on commit ec98f9d

Please sign in to comment.