Merge pull request #220 from buster-so/evals

Evals
This commit is contained in:
Nate Kelley 2025-04-22 16:53:01 -06:00 committed by GitHub
commit e3c1bb9d8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 9291 additions and 111 deletions

View File

@ -1,3 +1,4 @@
import { AppPageLayout } from '@/components/ui/layouts';
import { checkIfUserIsAdmin_server } from '@/context/Users/checkIfUserIsAdmin';
import { BusterRoutes, createBusterRoute } from '@/routes/busterRoutes';
import { redirect } from 'next/navigation';
@ -14,5 +15,5 @@ export default async function Layout({ children }: { children: React.ReactNode }
);
}
return <>{children}</>;
return <AppPageLayout scrollable>{children}</AppPageLayout>;
}

View File

@ -33,7 +33,7 @@ export default function Page() {
<div className="flex flex-col space-y-4">
<div className="px-[30px] pt-[46px]">
<SettingsPageHeader
title="User Management"
title="User management"
description="Manage your organization's users and their permissions"
type="alternate"
/>

View File

@ -88,8 +88,8 @@ export const LoginForm: React.FC<{}> = ({}) => {
if (res?.error) throw res.error;
} catch (error: any) {
errorFallback(error);
setLoading(null);
}
setLoading('azure');
});
const onSignUp = useMemoizedFn(async (d: { email: string; password: string }) => {
@ -211,18 +211,11 @@ const LoginOptions: React.FC<{
<WelcomeText signUpFlow={signUpFlow} />
</div>
<form
className="my-6 space-y-3"
onSubmit={(v) => {
v.preventDefault();
onSubmitClickPreflight({
email,
password
});
}}>
<div className="mt-6 mb-4 flex flex-col space-y-3">
<Button
prefix={<Google />}
size={'tall'}
type="button"
onClick={() => {
clearAllCookies();
onSignInWithGoogle();
@ -235,6 +228,7 @@ const LoginOptions: React.FC<{
<Button
prefix={<Github />}
size={'tall'}
type="button"
onClick={() => {
clearAllCookies();
onSignInWithGithub();
@ -247,6 +241,7 @@ const LoginOptions: React.FC<{
<Button
prefix={<Microsoft />}
size={'tall'}
type="button"
onClick={() => {
clearAllCookies();
onSignInWithAzure();
@ -256,8 +251,18 @@ const LoginOptions: React.FC<{
tabIndex={3}>
{!signUpFlow ? `Continue with Azure` : `Sign up with Azure`}
</Button>
</div>
<div className="bg-border h-[0.5px] w-full" />
<form
className="space-y-3"
onSubmit={(v) => {
v.preventDefault();
onSubmitClickPreflight({
email,
password
});
}}>
<div className="bg-border mb-4 h-[0.5px] w-full" />
<Input
type="email"
@ -333,7 +338,7 @@ const LoginOptions: React.FC<{
</Button>
</form>
<div className="flex flex-col gap-y-2 pt-0">
<div className="mt-2 flex flex-col gap-y-2">
<AlreadyHaveAccount
setErrorMessages={setErrorMessages}
setPassword2={setPassword2}

View File

@ -16,9 +16,11 @@ export const AvatarUserButton = React.forwardRef<
ref={ref}
className="hover:bg-item-hover active:bg-item-active flex w-full cursor-pointer items-center gap-x-2 rounded-md p-2">
<Avatar size={32} fallbackClassName="text-base" image={avatarUrl} name={username} />
<div className="flex flex-col gap-y-0.5">
<Text className="flex-grow">{username}</Text>
<Text size={'sm'} variant={'secondary'}>
<div className="flex w-full flex-col gap-y-0.5 overflow-hidden">
<Text truncate className="flex-grow">
{username}
</Text>
<Text truncate size={'sm'} variant={'secondary'}>
{email}
</Text>
</div>

View File

@ -22,13 +22,15 @@ export const BusterChartComponent: React.FC<BusterChartRenderComponentProps> = (
selectedChartType,
selectedAxis
} = props;
const {
datasetOptions,
dataTrendlineOptions,
y2AxisKeys,
yAxisKeys,
tooltipKeys,
hasMismatchedTooltipsAndMeasures
hasMismatchedTooltipsAndMeasures,
isDownsampled
} = useDatasetOptions({
data: dataProp,
selectedAxis,
@ -52,7 +54,8 @@ export const BusterChartComponent: React.FC<BusterChartRenderComponentProps> = (
y2AxisKeys,
yAxisKeys,
tooltipKeys,
hasMismatchedTooltipsAndMeasures
hasMismatchedTooltipsAndMeasures,
isDownsampled
}),
[
props,
@ -62,7 +65,8 @@ export const BusterChartComponent: React.FC<BusterChartRenderComponentProps> = (
y2AxisKeys,
yAxisKeys,
hasMismatchedTooltipsAndMeasures,
tooltipKeys
tooltipKeys,
isDownsampled
]
);

View File

@ -6,7 +6,7 @@ import React, { useCallback, useRef, useState } from 'react';
import { DEFAULT_CHART_CONFIG, DEFAULT_COLUMN_METADATA } from '@/api/asset_interfaces/metric';
import { BusterChartJSLegendWrapper } from './BusterChartJSLegendWrapper';
import { ChartJSOrUndefined } from './core/types';
import { useMemoizedFn, useMount } from '@/hooks';
import { useMemoizedFn } from '@/hooks';
import { BusterChartJSComponent } from './BusterChartJSComponent';
import type { BusterChartComponentProps } from '../interfaces';
@ -59,6 +59,7 @@ export const BusterChartJS: React.FC<BusterChartComponentProps> = ({
colors={colors}
chartRef={chartRef}
datasetOptions={datasetOptions}
isDownsampled={props.isDownsampled}
pieMinimumSlicePercentage={pieMinimumSlicePercentage}>
<BusterChartJSComponent
ref={chartRef}

View File

@ -17,7 +17,6 @@ import { useChartSpecificOptions } from './hooks/useChartSpecificOptions';
import { BusterChartTypeComponentProps } from '../interfaces/chartComponentInterfaces';
import { useTrendlines } from './hooks/useTrendlines';
import { ScatterAxis } from '@/api/asset_interfaces/metric/charts';
import { useMount } from '@/hooks';
export const BusterChartJSComponent = React.memo(
React.forwardRef<ChartJSOrUndefined, BusterChartTypeComponentProps>(

View File

@ -24,6 +24,7 @@ interface BusterChartJSLegendWrapperProps {
chartRef: React.RefObject<ChartJSOrUndefined | null>;
datasetOptions: DatasetOption[];
pieMinimumSlicePercentage: NonNullable<BusterChartProps['pieMinimumSlicePercentage']>;
isDownsampled: boolean;
}
export const BusterChartJSLegendWrapper = React.memo<BusterChartJSLegendWrapperProps>(
@ -45,7 +46,8 @@ export const BusterChartJSLegendWrapper = React.memo<BusterChartJSLegendWrapperP
barGroupType,
colors,
datasetOptions,
pieMinimumSlicePercentage
pieMinimumSlicePercentage,
isDownsampled
}) => {
const {
renderLegend,
@ -81,6 +83,7 @@ export const BusterChartJSLegendWrapper = React.memo<BusterChartJSLegendWrapperP
renderLegend={renderLegend}
legendItems={legendItems}
showLegend={showLegend}
isDownsampled={isDownsampled}
showLegendHeadline={showLegendHeadline}
inactiveDatasets={inactiveDatasets}
onHoverItem={onHoverItem}

View File

@ -113,7 +113,7 @@ ChartJS.defaults.font = {
scale.ticks.autoSkipPadding = 4;
scale.ticks.align = 'center';
scale.ticks.callback = function (value, index, values) {
return truncateText(this.getLabelForValue(index), 18);
return truncateText(this.getLabelForValue(value as number), 18);
};
});

View File

@ -8,7 +8,7 @@ import {
ChartType,
ComboChartAxis
} from '@/api/asset_interfaces/metric/charts';
import { useDebounceEffect, useMemoizedFn } from '@/hooks';
import { useDebounceEffect, useDebounceFn, useMemoizedFn } from '@/hooks';
import type { IBusterMetricChartConfig } from '@/api/asset_interfaces/metric';
import {
addLegendHeadlines,
@ -18,7 +18,8 @@ import {
} from '../../../BusterChartLegend';
import { getLegendItems } from './helper';
import { DatasetOption } from '../../../chartHooks';
import { ANIMATION_THRESHOLD } from '../../../config';
import { ANIMATION_THRESHOLD, LEGEND_ANIMATION_THRESHOLD } from '../../../config';
import { timeout } from '@/lib';
interface UseBusterChartJSLegendProps {
chartRef: React.RefObject<ChartJSOrUndefined | null>;
@ -57,6 +58,9 @@ export const useBusterChartJSLegend = ({
}: UseBusterChartJSLegendProps): UseChartLengendReturnValues => {
const [isPending, startTransition] = useTransition();
const [isUpdatingChart, setIsUpdatingChart] = useState(false);
const [numberOfDataPoints, setNumberOfDataPoints] = useState(0);
const isLargeDataset = numberOfDataPoints > LEGEND_ANIMATION_THRESHOLD;
const legendTimeoutDuration = isLargeDataset ? 95 : 0;
const {
inactiveDatasets,
@ -105,6 +109,13 @@ export const useBusterChartJSLegend = ({
);
}
const numberOfPoints =
chartRef.current?.data.datasets.reduce<number>((acc, dataset) => {
if (dataset.hidden) return acc;
return acc + dataset.data.length;
}, 0) || 0;
setNumberOfDataPoints(numberOfPoints);
startTransition(() => {
setLegendItems(items);
});
@ -144,19 +155,33 @@ export const useBusterChartJSLegend = ({
chartjs.update();
});
const onLegendItemClick = useMemoizedFn((item: BusterChartLegendItem) => {
const { run: debouncedChartUpdate } = useDebounceFn(
useMemoizedFn((timeoutDuration: number) => {
const chartjs = chartRef.current;
if (!chartjs) return;
// Schedule the heavy update operation with minimal delay to allow UI to remain responsive
setTimeout(() => {
startTransition(() => {
chartjs.update();
// Set a timeout to turn off loading state after the update is complete
requestAnimationFrame(() => {
setIsUpdatingChart(false);
});
});
}, timeoutDuration);
}),
{ wait: isLargeDataset ? 250 : 0 }
);
const onLegendItemClick = useMemoizedFn(async (item: BusterChartLegendItem) => {
const chartjs = chartRef.current;
if (!chartjs) return;
const data = chartjs.data;
const hasAnimation = chartjs.options.animation !== false;
const numberOfPoints = data.datasets.reduce((acc, dataset) => acc + dataset.data.length, 0);
const isLargeChart = numberOfPoints > ANIMATION_THRESHOLD;
const timeoutDuration = isLargeChart && hasAnimation ? 125 : 0;
// Set updating state
if (timeoutDuration) setIsUpdatingChart(true);
if (legendTimeoutDuration) setIsUpdatingChart(true);
// Update dataset visibility state
setInactiveDatasets((prev) => ({
@ -164,6 +189,8 @@ export const useBusterChartJSLegend = ({
[item.id]: prev[item.id] ? !prev[item.id] : true
}));
await timeout(legendTimeoutDuration);
// Defer visual updates to prevent UI blocking
requestAnimationFrame(() => {
// This is a synchronous, lightweight operation that toggles visibility flags
@ -177,53 +204,48 @@ export const useBusterChartJSLegend = ({
}
}
// Schedule the heavy update operation with minimal delay to allow UI to remain responsive
setTimeout(() => {
startTransition(() => {
chartjs.update();
// Set a timeout to turn off loading state after the update is complete
requestAnimationFrame(() => {
setIsUpdatingChart(false);
});
});
}, timeoutDuration);
debouncedChartUpdate(legendTimeoutDuration);
});
});
const onLegendItemFocus = useMemoizedFn((item: BusterChartLegendItem) => {
const onLegendItemFocus = useMemoizedFn(async (item: BusterChartLegendItem) => {
const chartjs = chartRef.current;
if (!chartjs) return;
const datasets = chartjs.data.datasets.filter((dataset) => !dataset.hidden);
const hasMultipleDatasets = datasets?.length > 1;
const assosciatedDatasetIndex = datasets?.findIndex((dataset) => dataset.label === item.id);
if (legendTimeoutDuration) setIsUpdatingChart(true);
if (hasMultipleDatasets) {
const hasOtherDatasetsVisible = datasets?.some(
(dataset, index) =>
dataset.label !== item.id && chartjs.isDatasetVisible(index) && !dataset.hidden
);
const inactiveDatasetsRecord: Record<string, boolean> = {};
if (hasOtherDatasetsVisible) {
datasets?.forEach((dataset, index) => {
const value = index === assosciatedDatasetIndex;
chartjs.setDatasetVisibility(index, value);
inactiveDatasetsRecord[dataset.label!] = !value;
});
} else {
datasets?.forEach((dataset, index) => {
chartjs.setDatasetVisibility(index, true);
inactiveDatasetsRecord[dataset.label!] = false;
});
// Defer visual updates to prevent UI blocking
requestAnimationFrame(() => {
const datasets = chartjs.data.datasets.filter((dataset) => !dataset.hidden);
const hasMultipleDatasets = datasets?.length > 1;
const assosciatedDatasetIndex = datasets?.findIndex((dataset) => dataset.label === item.id);
if (hasMultipleDatasets) {
const hasOtherDatasetsVisible = datasets?.some(
(dataset, index) =>
dataset.label !== item.id && chartjs.isDatasetVisible(index) && !dataset.hidden
);
const inactiveDatasetsRecord: Record<string, boolean> = {};
if (hasOtherDatasetsVisible) {
datasets?.forEach((dataset, index) => {
const value = index === assosciatedDatasetIndex;
chartjs.setDatasetVisibility(index, value);
inactiveDatasetsRecord[dataset.label!] = !value;
});
} else {
datasets?.forEach((dataset, index) => {
chartjs.setDatasetVisibility(index, true);
inactiveDatasetsRecord[dataset.label!] = false;
});
}
setInactiveDatasets((prev) => ({
...prev,
...inactiveDatasetsRecord
}));
}
setInactiveDatasets((prev) => ({
...prev,
...inactiveDatasetsRecord
}));
chartjs.update();
}
debouncedChartUpdate(legendTimeoutDuration);
});
});
useDebounceEffect(
@ -231,7 +253,7 @@ export const useBusterChartJSLegend = ({
calculateLegendItems();
},
[selectedChartType],
{ wait: 3 }
{ wait: 4 }
);
//immediate items

View File

@ -167,8 +167,8 @@ export const useOptions = ({
const interaction = useInteractions({ selectedChartType, barLayout });
const numberOfSources = useMemo(() => {
return datasetOptions.reduce((acc, curr) => {
return acc + curr.source.length;
return datasetOptions.reduce((acc, dataset) => {
return acc + dataset.source.length;
}, 0);
}, [datasetOptions]);

View File

@ -34,6 +34,8 @@ interface UseTooltipOptionsProps {
colors: string[];
}
const MAX_TOOLTIP_CACHE_SIZE = 30;
export const useTooltipOptions = ({
columnLabelFormats,
selectedChartType,
@ -129,7 +131,6 @@ export const useTooltipOptions = ({
selectedChartType,
matchedCacheItem,
keyToUsePercentage,
columnSettings,
hasCategoryAxis,
hasMultipleMeasures,
barGroupType,
@ -137,6 +138,9 @@ export const useTooltipOptions = ({
);
if (result) {
if (Object.keys(tooltipCache.current).length > MAX_TOOLTIP_CACHE_SIZE) {
tooltipCache.current = {};
}
tooltipCache.current[key] = result;
}
});
@ -225,7 +229,6 @@ const externalTooltip = (
selectedChartType: NonNullable<BusterChartProps['selectedChartType']>,
matchedCacheItem: string | undefined,
keyToUsePercentage: string[],
columnSettings: NonNullable<BusterChartProps['columnSettings']>,
hasCategoryAxis: boolean,
hasMultipleMeasures: boolean,
barGroupType: BusterChartProps['barGroupType'],

View File

@ -7,6 +7,7 @@ import {
} from '../chartHooks/useChartWrapperProvider';
import { cn } from '@/lib/classMerge';
import { CircleSpinnerLoader } from '../../loaders';
import { DownsampleAlert } from './DownsampleAlert';
export type BusterChartLegendWrapper = {
children: React.ReactNode;
@ -18,6 +19,7 @@ export type BusterChartLegendWrapper = {
className: string | undefined;
animateLegend: boolean;
isUpdatingChart?: boolean;
isDownsampled: boolean;
onHoverItem: (item: BusterChartLegendItem, isHover: boolean) => void;
onLegendItemClick: (item: BusterChartLegendItem) => void;
onLegendItemFocus: ((item: BusterChartLegendItem) => void) | undefined;
@ -34,6 +36,7 @@ export const BusterChartLegendWrapper: React.FC<BusterChartLegendWrapper> = Reac
animateLegend,
className,
isUpdatingChart,
isDownsampled,
onHoverItem,
onLegendItemClick,
onLegendItemFocus
@ -42,7 +45,8 @@ export const BusterChartLegendWrapper: React.FC<BusterChartLegendWrapper> = Reac
return (
<ChartLegendWrapperProvider inactiveDatasets={inactiveDatasets}>
<div className={cn(className, 'flex h-full w-full flex-col overflow-hidden')}>
<div
className={cn('legend-wrapper flex h-full w-full flex-col overflow-hidden', className)}>
{renderLegend && (
<BusterChartLegend
show={showLegend}
@ -56,9 +60,10 @@ export const BusterChartLegendWrapper: React.FC<BusterChartLegendWrapper> = Reac
/>
)}
<div className="relative flex h-full w-full items-center justify-center overflow-hidden">
<div className="relative flex h-full w-full flex-col items-center justify-center overflow-hidden">
{isUpdatingChart && <LoadingOverlay />}
{children}
{isDownsampled && <DownsampleAlert isDownsampled={isDownsampled} />}
</div>
</div>
</ChartLegendWrapperProvider>

View File

@ -0,0 +1,65 @@
import React, { useEffect, useState } from 'react';
import { Text } from '../../typography/Text';
import { TriangleWarning } from '../../icons/NucleoIconFilled';
import { Popover } from '../../popover';
import { DOWNSIZE_SAMPLE_THRESHOLD } from '../config';
import { cn } from '@/lib/classMerge';
import { Xmark } from '../../icons';
export const DownsampleAlert = React.memo(({ isDownsampled }: { isDownsampled: boolean }) => {
const [close, setClose] = useState(false);
const [onHover, setOnHover] = useState(false);
const [open, setOpen] = useState(false);
useEffect(() => {
setClose(false);
}, [isDownsampled]);
if (close) {
return null;
}
return (
<div
className="absolute right-0 bottom-0 left-0 w-full px-1 pb-0"
onMouseEnter={() => setOnHover(true)}
onMouseLeave={() => setOnHover(false)}>
<Popover
align="center"
side="top"
open={open}
sideOffset={8}
onOpenChange={setOpen}
content={
<div className="max-w-68">
<Text>{`This chart has been downsampled to ${DOWNSIZE_SAMPLE_THRESHOLD} data points to improve performance. Click the results tab or download the data to see all points.`}</Text>
</div>
}>
<div
onClick={() => open && setClose(true)}
className={cn(
'group relative z-10 flex h-6 w-full cursor-pointer items-center justify-center overflow-hidden rounded-sm border border-yellow-300 bg-yellow-200 px-1.5 text-sm text-yellow-700 shadow transition-all duration-300 hover:bg-yellow-300'
)}>
<div
className={cn(
'absolute flex items-center space-x-1 transition-all duration-300',
open ? 'scale-50 opacity-0' : 'scale-100 opacity-100'
)}>
<TriangleWarning />
<span>Downsampled</span>
</div>
<div
className={cn(
'absolute flex items-center space-x-1 transition-all duration-300',
!open ? 'scale-50 opacity-0' : 'scale-100 opacity-100'
)}>
<Xmark />
<span>Close</span>
</div>
</div>
</Popover>
</div>
);
});
DownsampleAlert.displayName = 'DownsampleAlert';

View File

@ -1,8 +1,4 @@
import {
type BusterChartProps,
type ColumnLabelFormat,
type ScatterAxis
} from '@/api/asset_interfaces/metric/charts';
import { type BusterChartProps, type ScatterAxis } from '@/api/asset_interfaces/metric/charts';
import { createDimension } from './datasetHelpers_BarLinePie';
import { appendToKeyValueChain } from './groupingHelpers';
import { DatasetOption } from './interfaces';
@ -68,10 +64,7 @@ export const processScatterData = (
measureFields.forEach((measure) => {
categories.forEach((category) => {
const columnLabelFormat = columnLabelFormats[measure];
const replaceMissingDataWith =
columnLabelFormat?.replaceMissingDataWith !== undefined
? columnLabelFormat?.replaceMissingDataWith
: defaultReplaceMissingDataWith;
const replaceMissingDataWith = defaultReplaceMissingDataWith;
if (categoryKey === category) {
const value = item[measure] || replaceMissingDataWith;
row.push(value as string | number);

View File

@ -34,6 +34,7 @@ import {
import { type TrendlineDataset, useDataTrendlineOptions } from './useDataTrendlineOptions';
import type { DatasetOption } from './interfaces';
import { DEFAULT_COLUMN_LABEL_FORMAT } from '@/api/asset_interfaces/metric';
import { DOWNSIZE_SAMPLE_THRESHOLD } from '../../config';
type DatasetHookResult = {
datasetOptions: DatasetOption[];
@ -42,6 +43,7 @@ type DatasetHookResult = {
y2AxisKeys: string[];
tooltipKeys: string[];
hasMismatchedTooltipsAndMeasures: boolean;
isDownsampled: boolean;
};
type DatasetHookParams = {
@ -125,6 +127,10 @@ export const useDatasetOptions = (params: DatasetHookParams): DatasetHookResult
return uniq([...yAxisFields, ...y2AxisFields, ...tooltipFields]);
}, [yAxisFieldsString, y2AxisFieldsString, tooltipFieldsString]);
const isDownsampled = useMemo(() => {
return data.length > DOWNSIZE_SAMPLE_THRESHOLD;
}, [data]);
const sortedAndLimitedData = useMemo(() => {
if (isScatter) return downsampleScatterData(data);
return sortLineBarData(data, columnMetadata, xFieldSorts, xFields);
@ -139,18 +145,25 @@ export const useDatasetOptions = (params: DatasetHookParams): DatasetHookResult
return measureFields
.map((field) => {
const value = columnLabelFormats[field]?.replaceMissingDataWith;
if (value === undefined) return 0;
if (value === null) return null;
if (value === '') return '';
if (value === undefined) return 'undefined';
if (value === null) return 'null';
if (value === '') return 'empty';
return value;
})
.join(',');
}, [measureFields.join(''), columnLabelFormats]);
const dimensions: string[] = useMemo(() => {
if (isScatter) {
return getScatterDimensions(categoriesSet, xAxisField, measureFields, sizeField);
}
return getLineBarPieDimensions(categoriesSet, measureFields, xFields);
}, [categoriesSet, measureFields, xFieldsString, sizeFieldString, isScatter]);
const processedData = useMemo(() => {
if (isScatter) {
return processScatterData(
data,
sortedAndLimitedData,
xAxisField,
measureFields,
categoryFields,
@ -168,7 +181,7 @@ export const useDatasetOptions = (params: DatasetHookParams): DatasetHookResult
columnLabelFormats
);
}, [
data,
sortedAndLimitedData,
xFieldSortsString,
xFieldsString,
isScatter,
@ -177,16 +190,10 @@ export const useDatasetOptions = (params: DatasetHookParams): DatasetHookResult
dataMap,
measureFields,
sizeFieldString,
dimensions,
measureFieldsReplaceDataWithKey //use this instead of columnLabelFormats
]);
const dimensions: string[] = useMemo(() => {
if (isScatter) {
return getScatterDimensions(categoriesSet, xAxisField, measureFields, sizeField);
}
return getLineBarPieDimensions(categoriesSet, measureFields, xFields);
}, [categoriesSet, measureFields, xFieldsString, sizeFieldString, isScatter]);
const yAxisKeys = useMemo(() => {
if (isScatter) return getLineBarPieYAxisKeys(categoriesSet, yAxisFields); //not a typo. I want to use the same function for both scatter and bar/line/pie
return getLineBarPieYAxisKeys(categoriesSet, yAxisFields);
@ -258,11 +265,12 @@ export const useDatasetOptions = (params: DatasetHookParams): DatasetHookResult
});
return {
datasetOptions,
datasetOptions, //this is a matrix of dimensions x measures
dataTrendlineOptions,
yAxisKeys,
y2AxisKeys,
tooltipKeys,
hasMismatchedTooltipsAndMeasures
hasMismatchedTooltipsAndMeasures,
isDownsampled
};
};

View File

@ -1,6 +1,7 @@
export const DOWNSIZE_SAMPLE_THRESHOLD = 500;
export const DOWNSIZE_SAMPLE_THRESHOLD = 1000;
export const LINE_DECIMATION_THRESHOLD = 300;
export const LINE_DECIMATION_SAMPLES = 450;
export const ANIMATION_THRESHOLD = 150;
export const LEGEND_ANIMATION_THRESHOLD = 200; //we use this when there is a large dataset and need to show a loading animation
export const ANIMATION_THRESHOLD = 125; //testing 175 with scatter chart
export const ANIMATION_DURATION = 1000;
export const TOOLTIP_THRESHOLD = 500;
export const TOOLTIP_THRESHOLD = 2500;

View File

@ -23,6 +23,7 @@ export interface BusterChartComponentProps
>,
ReturnType<typeof useDatasetOptions> {
selectedAxis: ChartEncodes;
isDownsampled: boolean;
}
export interface BusterChartRenderComponentProps

View File

@ -8,6 +8,7 @@ import React from 'react';
import { Slider } from '@/components/ui/slider';
import { useDebounceFn } from '@/hooks';
import dayjs from 'dayjs';
import { scatterConfig_problematic1, scatterDataProblematic1 } from './scatterData_problematic1';
type ScatterChartData = ReturnType<typeof generateScatterChartData>;
@ -25,7 +26,7 @@ export const Default: Story = {
data: generateScatterChartData(50),
scatterAxis: {
x: ['x'],
y: ['y'],
y: ['y', 'y2'],
size: [],
category: ['category']
},
@ -57,7 +58,7 @@ export const Default: Story = {
style: 'string'
} satisfies IColumnLabelFormat
} satisfies Record<keyof ScatterChartData, IColumnLabelFormat>,
className: 'w-[400px] h-[400px]'
className: 'w-[400px] h-[400px] max-w-[400px] max-h-[400px]'
}
};
@ -437,3 +438,18 @@ export const ScatterWithTrendline_DateXAxisLogarithmicRegression: Story = {
}
}
};
export const ProblematicDataset: Story = {
args: {
...Default.args,
data: scatterDataProblematic1.data,
columnMetadata: scatterDataProblematic1.data_metadata.column_metadata,
...(scatterConfig_problematic1 as any),
barAndLineAxis: {
x: ['eligible_orders'],
y: ['attach_rate'],
category: ['merchant']
},
selectedChartType: ChartType.Scatter
}
};

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ export const Sidebar: React.FC<SidebarProps> = React.memo(
))}
</div>
</div>
{footer && <div className="mt-auto mb-2 pt-5">{footer}</div>}
{footer && <div className="mt-auto mb-2 overflow-hidden pt-5">{footer}</div>}
</div>
);
}

View File

@ -12,7 +12,7 @@ export interface TooltipProps
extends Pick<React.ComponentProps<typeof TooltipContentBase>, 'align' | 'side' | 'sideOffset'>,
Pick<React.ComponentProps<typeof TooltipProvider>, 'delayDuration' | 'skipDelayDuration'> {
children: React.ReactNode;
title: string | undefined;
title: string | React.ReactNode | undefined;
shortcuts?: string[];
open?: boolean;
}
@ -55,7 +55,7 @@ export const Tooltip = React.memo(
);
const TooltipContent: React.FC<{
title: string;
title: string | React.ReactNode;
shortcut?: string[];
}> = ({ title, shortcut }) => {
return (

View File

@ -25,7 +25,7 @@ export const HomePageController: React.FC<{}> = () => {
return (
<div className="flex flex-col items-center justify-center p-4.5">
<div className="mt-[150px] flex w-full max-w-[650px] flex-col space-y-6">
<div className="flex flex-col justify-center gap-y-2.5 text-center">
<div className="flex flex-col justify-center gap-y-2 text-center">
<Title as="h1" className="mb-0!">
{greeting}
</Title>

View File

@ -62,7 +62,8 @@ export const generateScatterChartData = (pointCount = 30): IDataResult => {
const categories = ['Electronics', 'Clothing', 'Home Goods'];
return Array.from({ length: pointCount }, (_, index) => ({
x: (index % 10) * 10, // Values from 0-90 in steps of 10
y: Math.floor(index / 10) * 10, // Creates a grid pattern
y: Math.floor(index / 10) * 10, // Creates a grid pattern,
y2: Math.floor(index / 10) * 40, // Creates a grid pattern,
size: 10 + (index % 5) * 10, // Sizes cycle between 10-50 in steps of 10
category: categories[index % categories.length]
}));