mirror of https://github.com/buster-so/buster.git
add default for trendline
This commit is contained in:
parent
756f57c5f6
commit
38ea7378d3
|
@ -10,7 +10,7 @@ export interface GoalLine {
|
|||
export interface Trendline {
|
||||
show: boolean; //OPTIONAL: default is true. this should only be used if the user explicitly requests a trendline
|
||||
showTrendlineLabel: boolean; //OPTIONAL: default is true
|
||||
trendlineLabel: string | null; //OPTIONAL: if showTrendlineLabel is true, this will be the label. default is "Slope".
|
||||
trendlineLabel: string | null; //OPTIONAL: if showTrendlineLabel is true, this will be the label
|
||||
type:
|
||||
| 'average'
|
||||
| 'linear_regression'
|
||||
|
@ -20,7 +20,7 @@ export interface Trendline {
|
|||
| 'min'
|
||||
| 'max'
|
||||
| 'median'; //default is linear trend
|
||||
trendLineColor?: string | null | 'inherit'; //OPTIONAL: default is #000000
|
||||
trendLineColor?: string | null | 'inherit'; //OPTIONAL: default is #000000, inherit will inherit the color from the line/bar
|
||||
columnId: string;
|
||||
trendlineLabelPositionOffset?: number; //OPTIONAL: default is 0.85. Goes from 0 to 1.
|
||||
projection?: boolean; //OPTIONAL: default is false. if true, the trendline will be projected to the end of the chart.
|
||||
|
|
|
@ -121,11 +121,8 @@ export const DEFAULT_COLUMN_LABEL_FORMAT: Required<ColumnLabelFormat> = {
|
|||
};
|
||||
|
||||
export const ENABLED_DOTS_ON_LINE = 3.5;
|
||||
|
||||
export const DEFAULT_CHART_CONFIG_ENTRIES = Object.entries(DEFAULT_CHART_CONFIG);
|
||||
|
||||
export const DEFAULT_BAR_ROUNDNESS = DEFAULT_COLUMN_SETTINGS.barRoundness;
|
||||
|
||||
export const MIN_DONUT_WIDTH = 15;
|
||||
|
||||
export const DEFAULT_DAY_OF_WEEK_FORMAT = 'ddd';
|
||||
|
@ -134,7 +131,6 @@ export const DEFAULT_DATE_FORMAT_MONTH_OF_YEAR = 'MMMM';
|
|||
export const DEFAULT_DATE_FORMAT_QUARTER = 'YYYY [Q]Q';
|
||||
|
||||
export const ENABLED_DOTS_ON_LINE_SIZE = 4;
|
||||
|
||||
export const DEFAULT_COLUMN_METADATA: ColumnMetaData[] = [];
|
||||
|
||||
export const DEFAULT_IBUSTER_METRIC: Required<IBusterMetric> = {
|
||||
|
@ -171,3 +167,19 @@ export const DEFAULT_IBUSTER_METRIC: Required<IBusterMetric> = {
|
|||
public_password: null,
|
||||
versions: []
|
||||
};
|
||||
|
||||
export const DEFAULT_TRENDLINE_CONFIG: Required<IBusterMetricChartConfig['trendlines'][number]> = {
|
||||
id: 'DEFAULT_ID',
|
||||
columnId: '',
|
||||
show: true,
|
||||
showTrendlineLabel: false,
|
||||
trendlineLabel: null,
|
||||
type: 'linear_regression',
|
||||
trendLineColor: '#FF0000',
|
||||
trendlineLabelPositionOffset: 0,
|
||||
projection: false,
|
||||
lineStyle: 'solid',
|
||||
polynomialOrder: 2,
|
||||
aggregateAllCategories: true,
|
||||
offset: 0
|
||||
};
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import { forwardRef, useCallback, useMemo, useState } from 'react';
|
||||
import React, { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { HexColorPicker } from 'react-colorful';
|
||||
import { PopoverRoot, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Input } from '@/components/ui/inputs';
|
||||
import { useDebounceFn } from '@/hooks';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { cn } from '@/lib/classMerge';
|
||||
|
||||
interface ColorPickerProps {
|
||||
value: string | null | undefined;
|
||||
|
@ -16,6 +17,9 @@ interface ColorPickerProps {
|
|||
size?: 'default' | 'small' | 'tall';
|
||||
name?: string;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
showInput?: boolean;
|
||||
showPicker?: boolean;
|
||||
}
|
||||
|
||||
const colorPickerWrapperVariants = cva('border p-0.5 rounded cursor-pointer shadow', {
|
||||
|
@ -32,71 +36,105 @@ const colorPickerWrapperVariants = cva('border p-0.5 rounded cursor-pointer shad
|
|||
}
|
||||
});
|
||||
|
||||
const ColorPicker = forwardRef<HTMLInputElement, ColorPickerProps>(
|
||||
(
|
||||
{
|
||||
disabled,
|
||||
onChangeComplete,
|
||||
size = 'default',
|
||||
value: valueProp = '#000000',
|
||||
onChange,
|
||||
name,
|
||||
className = '',
|
||||
...props
|
||||
const ColorPicker = ({
|
||||
disabled,
|
||||
onChangeComplete,
|
||||
size = 'default',
|
||||
value: valueProp = '#000000',
|
||||
onChange,
|
||||
name,
|
||||
className = '',
|
||||
children,
|
||||
showInput = true,
|
||||
showPicker = true,
|
||||
...props
|
||||
}: ColorPickerProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [value, setValue] = useState(valueProp);
|
||||
|
||||
const parsedValue = useMemo(() => {
|
||||
return value || '#000000';
|
||||
}, [value]);
|
||||
|
||||
const { run: debouncedOnChangeComplete } = useDebounceFn(
|
||||
(value: string) => {
|
||||
onChangeComplete?.(value);
|
||||
},
|
||||
forwardedRef
|
||||
) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [value, setValue] = useState(valueProp);
|
||||
{ wait: 150 }
|
||||
);
|
||||
|
||||
const parsedValue = useMemo(() => {
|
||||
return value || '#000000';
|
||||
}, [value]);
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e?.currentTarget?.value);
|
||||
onChange?.(e?.currentTarget?.value);
|
||||
debouncedOnChangeComplete?.(e?.currentTarget?.value);
|
||||
},
|
||||
[onChange, debouncedOnChangeComplete]
|
||||
);
|
||||
|
||||
const { run: debouncedOnChangeComplete } = useDebounceFn(
|
||||
(value: string) => {
|
||||
onChangeComplete?.(value);
|
||||
},
|
||||
{ wait: 150 }
|
||||
);
|
||||
const handleHexColorPickerChange = useCallback(
|
||||
(color: string) => {
|
||||
setValue(color);
|
||||
onChange?.(color);
|
||||
debouncedOnChangeComplete?.(color);
|
||||
},
|
||||
[onChange, debouncedOnChangeComplete]
|
||||
);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e?.currentTarget?.value);
|
||||
onChange?.(e?.currentTarget?.value);
|
||||
debouncedOnChangeComplete?.(e?.currentTarget?.value);
|
||||
},
|
||||
[onChange, debouncedOnChangeComplete]
|
||||
);
|
||||
useEffect(() => {
|
||||
setValue(valueProp);
|
||||
}, [valueProp]);
|
||||
|
||||
const handleHexColorPickerChange = useCallback(
|
||||
(color: string) => {
|
||||
setValue(color);
|
||||
onChange?.(color);
|
||||
debouncedOnChangeComplete?.(color);
|
||||
},
|
||||
[onChange, debouncedOnChangeComplete]
|
||||
);
|
||||
return (
|
||||
<PopoverRoot onOpenChange={setOpen} open={open}>
|
||||
<PopoverTrigger asChild disabled={disabled}>
|
||||
<div className={colorPickerWrapperVariants({ size, disabled })}>
|
||||
<div className="h-full w-full rounded-sm" style={{ backgroundColor: parsedValue }} />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full" align="end" side="bottom">
|
||||
<HexColorPicker color={parsedValue} onChange={handleHexColorPickerChange} />
|
||||
<Input
|
||||
className="mt-2.5"
|
||||
maxLength={7}
|
||||
onChange={handleInputChange}
|
||||
value={parsedValue}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</PopoverRoot>
|
||||
);
|
||||
}
|
||||
);
|
||||
return (
|
||||
<PopoverRoot onOpenChange={setOpen} open={open}>
|
||||
<PopoverTrigger asChild disabled={disabled}>
|
||||
<div>
|
||||
<ColorPickerInputBox parsedValue={parsedValue} size={size} disabled={disabled} />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full" align="end" side="bottom">
|
||||
<div>
|
||||
{showPicker && (
|
||||
<HexColorPicker color={parsedValue} onChange={handleHexColorPickerChange} />
|
||||
)}
|
||||
{showInput && (
|
||||
<Input
|
||||
className="mt-2.5"
|
||||
maxLength={7}
|
||||
onChange={handleInputChange}
|
||||
value={parsedValue}
|
||||
/>
|
||||
)}
|
||||
{children && <div className={cn((showInput || showPicker) && 'mt-2.5')}>{children}</div>}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</PopoverRoot>
|
||||
);
|
||||
};
|
||||
ColorPicker.displayName = 'ColorPicker';
|
||||
|
||||
const ColorPickerInputBox = ({
|
||||
parsedValue,
|
||||
size,
|
||||
disabled
|
||||
}: {
|
||||
parsedValue: string;
|
||||
size: 'default' | 'small' | 'tall';
|
||||
disabled: boolean | undefined;
|
||||
}) => {
|
||||
const backgroundStyle =
|
||||
parsedValue === 'inherit'
|
||||
? {
|
||||
backgroundImage:
|
||||
'linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet)'
|
||||
}
|
||||
: { backgroundColor: parsedValue };
|
||||
|
||||
return (
|
||||
<div className={colorPickerWrapperVariants({ size, disabled })}>
|
||||
<div className="h-full w-full rounded-sm" style={backgroundStyle} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ColorPicker };
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IBusterMetricChartConfig } from '@/api/asset_interfaces';
|
||||
import { DEFAULT_TRENDLINE_CONFIG, IBusterMetricChartConfig } from '@/api/asset_interfaces';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import type { ChartEncodes, ScatterAxis, Trendline } from '@/api/asset_interfaces/metric/charts';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
@ -85,19 +85,10 @@ export const EditTrendline: React.FC<{
|
|||
const type = hasLinearRegression ? getNewType() : ('linear_regression' as const);
|
||||
|
||||
const newTrendline: Required<LoopTrendline> = {
|
||||
...DEFAULT_TRENDLINE_CONFIG,
|
||||
id: uuidv4(),
|
||||
show: true,
|
||||
showTrendlineLabel: false,
|
||||
trendlineLabel: null,
|
||||
type,
|
||||
trendLineColor: '#FF0000',
|
||||
columnId: selectedAxis.y[0] || '',
|
||||
trendlineLabelPositionOffset: 0,
|
||||
projection: false,
|
||||
lineStyle: 'solid',
|
||||
polynomialOrder: 2,
|
||||
aggregateAllCategories: true,
|
||||
offset: 0
|
||||
columnId: selectedAxis.y[0] || ''
|
||||
};
|
||||
|
||||
addNewTrendId(newTrendline.id);
|
||||
|
@ -283,48 +274,52 @@ const TrendlineItemContent: React.FC<{
|
|||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
|
||||
<TrendlineColumnId
|
||||
trend={trend}
|
||||
columnMetadata={columnMetadata}
|
||||
columnLabelFormats={columnLabelFormats}
|
||||
yAxisEncodes={yAxisEncodes}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
{show && (
|
||||
<>
|
||||
<TrendlineColumnId
|
||||
trend={trend}
|
||||
columnMetadata={columnMetadata}
|
||||
columnLabelFormats={columnLabelFormats}
|
||||
yAxisEncodes={yAxisEncodes}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
|
||||
<EditTrendlineOption
|
||||
trend={trend}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
yAxisEncodes={yAxisEncodes}
|
||||
xAxisEncodes={xAxisEncodes}
|
||||
columnLabelFormats={columnLabelFormats}
|
||||
selectedChartType={selectedChartType}
|
||||
/>
|
||||
<EditTrendlineOption
|
||||
trend={trend}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
yAxisEncodes={yAxisEncodes}
|
||||
xAxisEncodes={xAxisEncodes}
|
||||
columnLabelFormats={columnLabelFormats}
|
||||
selectedChartType={selectedChartType}
|
||||
/>
|
||||
|
||||
<TrendlineLineStyle
|
||||
trend={trend}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
<TrendlineLineStyle
|
||||
trend={trend}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
|
||||
<TrendlinePolynomialOrder
|
||||
{/* <TrendlinePolynomialOrder
|
||||
trend={trend}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<TrendlineProjection
|
||||
{/* <TrendlineProjection
|
||||
trend={trend}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<TrendlineAggregateAllCategories
|
||||
trend={trend}
|
||||
categoryEncodes={categoryEncodes}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
<TrendlineAggregateAllCategories
|
||||
trend={trend}
|
||||
categoryEncodes={categoryEncodes}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
|
||||
<TrendlineColorPicker
|
||||
trend={trend}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
<TrendlineColorPicker
|
||||
trend={trend}
|
||||
onUpdateExisitingTrendline={onUpdateExisitingTrendline}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{show && (
|
||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||
import { LabelAndInput } from '../../Common';
|
||||
import { LoopTrendline } from './EditTrendline';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
|
||||
export const TrendlineColorPicker = React.memo(
|
||||
({
|
||||
|
@ -17,14 +18,27 @@ export const TrendlineColorPicker = React.memo(
|
|||
onUpdateExisitingTrendline({ ...trend, trendLineColor: hexColor });
|
||||
});
|
||||
|
||||
const handleInheritColorChange = useMemoizedFn((checked: boolean) => {
|
||||
onUpdateExisitingTrendline({ ...trend, trendLineColor: checked ? 'inherit' : '#000000' });
|
||||
});
|
||||
|
||||
const isInheritColor = trend.trendLineColor === 'inherit';
|
||||
|
||||
return (
|
||||
<LabelAndInput label="Color">
|
||||
<div className="flex w-full items-center justify-end">
|
||||
<ColorPicker
|
||||
size="small"
|
||||
value={trend.trendLineColor || 'black'}
|
||||
onChangeComplete={onChangeComplete}
|
||||
/>
|
||||
showInput={!isInheritColor}
|
||||
showPicker={!isInheritColor}
|
||||
value={trend.trendLineColor || '#000000'}
|
||||
onChangeComplete={onChangeComplete}>
|
||||
<LabelAndInput label="Inherit color">
|
||||
<div className="flex w-full items-center justify-end">
|
||||
<Switch checked={isInheritColor} onCheckedChange={handleInheritColorChange} />
|
||||
</div>
|
||||
</LabelAndInput>
|
||||
</ColorPicker>
|
||||
</div>
|
||||
</LabelAndInput>
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { LabelAndInput } from '../../Common';
|
||||
import { LoopTrendline } from './EditTrendline';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
|
||||
interface TrendlineOffsetProps {
|
||||
trend: LoopTrendline;
|
||||
|
@ -24,11 +25,12 @@ export const TrendlineOffset: React.FC<TrendlineOffsetProps> = React.memo(
|
|||
return (
|
||||
<LabelAndInput label="Label offset">
|
||||
<div className="flex w-full justify-end">
|
||||
<input
|
||||
type="number"
|
||||
className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
value={trend.offset ?? 0}
|
||||
onChange={handleChange}
|
||||
<Slider
|
||||
value={[trend.offset ?? 0]}
|
||||
min={-75}
|
||||
max={75}
|
||||
step={1}
|
||||
onValueChange={(value) => onUpdateExisitingTrendline({ ...trend, offset: value[0] })}
|
||||
/>
|
||||
</div>
|
||||
</LabelAndInput>
|
||||
|
|
Loading…
Reference in New Issue