update trendline

This commit is contained in:
Nate Kelley 2025-05-13 10:38:42 -06:00
parent 38ea7378d3
commit 5cf34201b4
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
7 changed files with 69 additions and 11 deletions

View File

@ -548,6 +548,27 @@ pub struct Trendline {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "trend_line_color")] #[serde(alias = "trend_line_color")]
pub trend_line_color: Option<String>, pub trend_line_color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "trendline_label_position_offset")]
pub trendline_label_position_offset: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "projection")]
pub projection: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "line_style")]
pub line_style: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "offset")]
pub offset: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "polynomial_order")]
pub polynomial_order: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "aggregate_all_categories")]
pub aggregate_all_categories: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "id")]
pub id: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -705,7 +726,7 @@ pub enum MetricValueAggregate {
Count, Count,
Max, Max,
Min, Min,
First First,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]

View File

@ -22,10 +22,10 @@ export interface Trendline {
| 'median'; //default is linear trend | 'median'; //default is linear trend
trendLineColor?: string | null | 'inherit'; //OPTIONAL: default is #000000, inherit will inherit the color from the line/bar trendLineColor?: string | null | 'inherit'; //OPTIONAL: default is #000000, inherit will inherit the color from the line/bar
columnId: string; columnId: string;
trendlineLabelPositionOffset?: number; //OPTIONAL: default is 0.85. Goes from 0 to 1. trendlineLabelPositionOffset?: number; //OPTIONAL: default is 0.85. Goes from 0 to 1. This is where the label will be placed on the trendline.
projection?: boolean; //OPTIONAL: default is false. if true, the trendline will be projected to the end of the chart. projection?: boolean; //OPTIONAL: default is false. if true, the trendline will be projected to the end of the chart.
lineStyle?: 'solid' | 'dotted' | 'dashed' | 'dashdot'; lineStyle?: 'solid' | 'dotted' | 'dashed' | 'dashdot';
offset?: number; //OPTIONAL: default is -15. if true, the trendline will be projected to the end of the chart. offset?: number; //OPTIONAL: default is 0. if true, the label will be offset vertically from the trendline.
polynomialOrder?: number; polynomialOrder?: number;
aggregateAllCategories?: boolean; //OPTIONAL: default is true. if true, the trendline will be calculated for all categories. if false, the trendline will be calculated for the category specified in the columnId. aggregateAllCategories?: boolean; //OPTIONAL: default is true. if true, the trendline will be calculated for all categories. if false, the trendline will be calculated for the category specified in the columnId.
id?: string; id?: string;

View File

@ -20,6 +20,7 @@ interface ColorPickerProps {
children?: React.ReactNode; children?: React.ReactNode;
showInput?: boolean; showInput?: boolean;
showPicker?: boolean; showPicker?: boolean;
pickerBackgroundImage?: string;
} }
const colorPickerWrapperVariants = cva('border p-0.5 rounded cursor-pointer shadow', { const colorPickerWrapperVariants = cva('border p-0.5 rounded cursor-pointer shadow', {
@ -47,6 +48,7 @@ const ColorPicker = ({
children, children,
showInput = true, showInput = true,
showPicker = true, showPicker = true,
pickerBackgroundImage,
...props ...props
}: ColorPickerProps) => { }: ColorPickerProps) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -89,7 +91,12 @@ const ColorPicker = ({
<PopoverRoot onOpenChange={setOpen} open={open}> <PopoverRoot onOpenChange={setOpen} open={open}>
<PopoverTrigger asChild disabled={disabled}> <PopoverTrigger asChild disabled={disabled}>
<div> <div>
<ColorPickerInputBox parsedValue={parsedValue} size={size} disabled={disabled} /> <ColorPickerInputBox
parsedValue={parsedValue}
size={size}
disabled={disabled}
pickerBackgroundImage={pickerBackgroundImage}
/>
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-full" align="end" side="bottom"> <PopoverContent className="w-full" align="end" side="bottom">
@ -116,16 +123,19 @@ ColorPicker.displayName = 'ColorPicker';
const ColorPickerInputBox = ({ const ColorPickerInputBox = ({
parsedValue, parsedValue,
size, size,
disabled disabled,
pickerBackgroundImage
}: { }: {
parsedValue: string; parsedValue: string;
size: 'default' | 'small' | 'tall'; size: 'default' | 'small' | 'tall';
disabled: boolean | undefined; disabled: boolean | undefined;
pickerBackgroundImage: string | undefined;
}) => { }) => {
const backgroundStyle = const backgroundStyle =
parsedValue === 'inherit' parsedValue === 'inherit' || pickerBackgroundImage
? { ? {
backgroundImage: backgroundImage:
pickerBackgroundImage ||
'linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet)' 'linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet)'
} }
: { backgroundColor: parsedValue }; : { backgroundColor: parsedValue };

View File

@ -177,6 +177,7 @@ export const MetricStylingApp: React.FC<{
barShowTotalAtTop={barShowTotalAtTop} barShowTotalAtTop={barShowTotalAtTop}
rowCount={rowCount} rowCount={rowCount}
pieSortBy={pieSortBy} pieSortBy={pieSortBy}
colors={colors}
/> />
)} )}

View File

@ -20,11 +20,11 @@ import { TypeToLabel } from './config';
import { JOIN_CHARACTER } from '@/components/ui/charts/commonHelpers'; import { JOIN_CHARACTER } from '@/components/ui/charts/commonHelpers';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import { TrendlineLabelPositionOffset } from './TrendlineLabelPositionOffset'; import { TrendlineLabelPositionOffset } from './TrendlineLabelPositionOffset';
import { TrendlineProjection } from './TrendlineProjection';
import { TrendlineLineStyle } from './TrendlineLineStyle'; import { TrendlineLineStyle } from './TrendlineLineStyle';
import { TrendlineOffset } from './TrendlineOffset'; import { TrendlineOffset } from './TrendlineOffset';
import { TrendlinePolynomialOrder } from './TrendlinePolynomialOrder';
import { TrendlineAggregateAllCategories } from './TrendlineAggregateAllCategories'; import { TrendlineAggregateAllCategories } from './TrendlineAggregateAllCategories';
// import { TrendlineProjection } from './TrendlineProjection';
// import { TrendlinePolynomialOrder } from './TrendlinePolynomialOrder';
export interface LoopTrendline extends Trendline { export interface LoopTrendline extends Trendline {
id: string; id: string;
@ -32,6 +32,7 @@ export interface LoopTrendline extends Trendline {
export const EditTrendline: React.FC<{ export const EditTrendline: React.FC<{
trendlines: IBusterMetricChartConfig['trendlines']; trendlines: IBusterMetricChartConfig['trendlines'];
colors: string[];
onUpdateChartConfig: (chartConfig: Partial<IBusterMetricChartConfig>) => void; onUpdateChartConfig: (chartConfig: Partial<IBusterMetricChartConfig>) => void;
selectedAxis: ChartEncodes; selectedAxis: ChartEncodes;
columnMetadata: ColumnMetaData[]; columnMetadata: ColumnMetaData[];
@ -40,6 +41,7 @@ export const EditTrendline: React.FC<{
}> = React.memo( }> = React.memo(
({ ({
trendlines, trendlines,
colors,
onUpdateChartConfig, onUpdateChartConfig,
selectedAxis, selectedAxis,
columnMetadata, columnMetadata,
@ -175,6 +177,7 @@ export const EditTrendline: React.FC<{
onDeleteTrendline={onDeleteTrendline} onDeleteTrendline={onDeleteTrendline}
onUpdateExisitingTrendline={onUpdateExisitingTrendline} onUpdateExisitingTrendline={onUpdateExisitingTrendline}
isNewTrend={newTrendIds.has(trend.id)} isNewTrend={newTrendIds.has(trend.id)}
colors={colors}
/> />
</motion.div> </motion.div>
))} ))}
@ -193,6 +196,7 @@ const EditTrendlineItem: React.FC<{
yAxisEncodes: string[]; yAxisEncodes: string[];
xAxisEncodes: string[]; xAxisEncodes: string[];
categoryEncodes: string[] | undefined; categoryEncodes: string[] | undefined;
colors: string[];
selectedChartType: IBusterMetricChartConfig['selectedChartType']; selectedChartType: IBusterMetricChartConfig['selectedChartType'];
onUpdateExisitingTrendline: (trend: LoopTrendline) => void; onUpdateExisitingTrendline: (trend: LoopTrendline) => void;
onDeleteTrendline: (id: string) => void; onDeleteTrendline: (id: string) => void;
@ -205,6 +209,7 @@ const EditTrendlineItem: React.FC<{
yAxisEncodes, yAxisEncodes,
xAxisEncodes, xAxisEncodes,
categoryEncodes, categoryEncodes,
colors,
selectedChartType, selectedChartType,
onUpdateExisitingTrendline, onUpdateExisitingTrendline,
onDeleteTrendline onDeleteTrendline
@ -234,6 +239,7 @@ const EditTrendlineItem: React.FC<{
columnLabelFormats={columnLabelFormats} columnLabelFormats={columnLabelFormats}
yAxisEncodes={yAxisEncodes} yAxisEncodes={yAxisEncodes}
xAxisEncodes={xAxisEncodes} xAxisEncodes={xAxisEncodes}
colors={colors}
categoryEncodes={categoryEncodes} categoryEncodes={categoryEncodes}
selectedChartType={selectedChartType} selectedChartType={selectedChartType}
onUpdateExisitingTrendline={onUpdateExisitingTrendline} onUpdateExisitingTrendline={onUpdateExisitingTrendline}
@ -249,6 +255,7 @@ const TrendlineItemContent: React.FC<{
columnMetadata: ColumnMetaData[]; columnMetadata: ColumnMetaData[];
yAxisEncodes: string[]; yAxisEncodes: string[];
xAxisEncodes: string[]; xAxisEncodes: string[];
colors: string[];
categoryEncodes: string[] | undefined; categoryEncodes: string[] | undefined;
columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats']; columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats'];
selectedChartType: IBusterMetricChartConfig['selectedChartType']; selectedChartType: IBusterMetricChartConfig['selectedChartType'];
@ -256,6 +263,7 @@ const TrendlineItemContent: React.FC<{
}> = React.memo( }> = React.memo(
({ ({
trend, trend,
colors,
categoryEncodes, categoryEncodes,
yAxisEncodes, yAxisEncodes,
xAxisEncodes, xAxisEncodes,
@ -316,6 +324,7 @@ const TrendlineItemContent: React.FC<{
<TrendlineColorPicker <TrendlineColorPicker
trend={trend} trend={trend}
colors={colors}
onUpdateExisitingTrendline={onUpdateExisitingTrendline} onUpdateExisitingTrendline={onUpdateExisitingTrendline}
/> />
</> </>

View File

@ -1,5 +1,5 @@
import { ColorPicker } from '@/components/ui/color-picker'; import { ColorPicker } from '@/components/ui/color-picker';
import React from 'react'; import React, { useMemo } from 'react';
import { LabelAndInput } from '../../Common'; import { LabelAndInput } from '../../Common';
import { LoopTrendline } from './EditTrendline'; import { LoopTrendline } from './EditTrendline';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
@ -8,10 +8,12 @@ import { Switch } from '@/components/ui/switch';
export const TrendlineColorPicker = React.memo( export const TrendlineColorPicker = React.memo(
({ ({
trend, trend,
colors,
onUpdateExisitingTrendline onUpdateExisitingTrendline
}: { }: {
trend: LoopTrendline; trend: LoopTrendline;
onUpdateExisitingTrendline: (trend: LoopTrendline) => void; onUpdateExisitingTrendline: (trend: LoopTrendline) => void;
colors: string[];
}) => { }) => {
const onChangeComplete = useMemoizedFn((color: string) => { const onChangeComplete = useMemoizedFn((color: string) => {
const hexColor = color; const hexColor = color;
@ -24,6 +26,16 @@ export const TrendlineColorPicker = React.memo(
const isInheritColor = trend.trendLineColor === 'inherit'; const isInheritColor = trend.trendLineColor === 'inherit';
const pickerBackgroundImage = useMemo(() => {
if (isInheritColor) {
const colorsToUse = colors.slice(0, 4);
return `repeating-linear-gradient(90deg, ${colorsToUse
.map((color, index) => `${color} ${index * 25}%, ${color} ${(index + 1) * 25}%`)
.join(', ')})`;
}
return undefined;
}, [colors, isInheritColor]);
return ( return (
<LabelAndInput label="Color"> <LabelAndInput label="Color">
<div className="flex w-full items-center justify-end"> <div className="flex w-full items-center justify-end">
@ -31,6 +43,7 @@ export const TrendlineColorPicker = React.memo(
size="small" size="small"
showInput={!isInheritColor} showInput={!isInheritColor}
showPicker={!isInheritColor} showPicker={!isInheritColor}
pickerBackgroundImage={pickerBackgroundImage}
value={trend.trendLineColor || '#000000'} value={trend.trendLineColor || '#000000'}
onChangeComplete={onChangeComplete}> onChangeComplete={onChangeComplete}>
<LabelAndInput label="Inherit color"> <LabelAndInput label="Inherit color">

View File

@ -72,7 +72,8 @@ export const StylingAppStyling: React.FC<
barShowTotalAtTop, barShowTotalAtTop,
yAxisShowAxisTitle, yAxisShowAxisTitle,
rowCount, rowCount,
pieSortBy pieSortBy,
colors
}) => { }) => {
const { onUpdateMetricChartConfig } = useUpdateMetricChart(); const { onUpdateMetricChartConfig } = useUpdateMetricChart();
@ -175,6 +176,7 @@ export const StylingAppStyling: React.FC<
lineGroupType={lineGroupType} lineGroupType={lineGroupType}
barGroupType={barGroupType} barGroupType={barGroupType}
onUpdateChartConfig={onUpdateChartConfig} onUpdateChartConfig={onUpdateChartConfig}
colors={colors}
/> />
</div> </div>
); );
@ -477,7 +479,8 @@ const EtcSettings: React.FC<
columnMetadata, columnMetadata,
columnLabelFormats, columnLabelFormats,
lineGroupType, lineGroupType,
barGroupType barGroupType,
colors
}) => { }) => {
const isScatterChart = selectedChartType === 'scatter'; const isScatterChart = selectedChartType === 'scatter';
const isPieChart = selectedChartType === 'pie'; const isPieChart = selectedChartType === 'pie';
@ -546,6 +549,7 @@ const EtcSettings: React.FC<
columnMetadata={columnMetadata} columnMetadata={columnMetadata}
selectedChartType={selectedChartType} selectedChartType={selectedChartType}
onUpdateChartConfig={onUpdateChartConfig} onUpdateChartConfig={onUpdateChartConfig}
colors={colors}
/> />
) )
} }