Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions timeserieschart/schemas/time-series.cue
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ spec: close({
lineStyle?: #lineStyle
areaOpacity?: #areaOpacity
format?: common.#format
stack?: bool
}]

#lineStyle: "solid" | "dashed" | "dotted"
Expand Down
1 change: 1 addition & 0 deletions timeserieschart/sdk/go/time-series.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ type QuerySettingsItem struct {
LineStyle string `json:"lineStyle,omitempty" yaml:"lineStyle,omitempty"`
AreaOpacity float64 `json:"areaOpacity,omitempty" yaml:"areaOpacity,omitempty"`
Format *common.Format `json:"format,omitempty" yaml:"format,omitempty"`
Stack *bool `json:"stack,omitempty" yaml:"stack,omitempty"`
}

type Option func(plugin *Builder) error
Expand Down
45 changes: 43 additions & 2 deletions timeserieschart/src/QuerySettingsEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
MenuItem,
Slider,
Stack,
Switch,
TextField,
ToggleButton,
ToggleButtonGroup,
Expand Down Expand Up @@ -217,6 +218,24 @@ export function QuerySettingsEditor(props: TimeSeriesChartOptionsEditorProps): R
});
};

const addStack = (i: number): void => {
updateQuerySettings(i, (qs) => {
qs.stack = false;
});
};

const removeStack = (i: number): void => {
updateQuerySettings(i, (qs) => {
qs.stack = undefined;
});
};

const handleStackChange = (i: number, checked: boolean): void => {
updateQuerySettings(i, (qs) => {
qs.stack = checked;
});
};

const handleFormatChange = (i: number, format?: FormatOptions): void => {
updateQuerySettings(i, (qs) => {
qs.format = format;
Expand Down Expand Up @@ -287,6 +306,9 @@ export function QuerySettingsEditor(props: TimeSeriesChartOptionsEditorProps): R
onAddFormat={() => addFormat(i)}
onRemoveFormat={() => removeFormat(i)}
onFormatChange={(format) => handleFormatChange(i, format)}
onAddStack={() => addStack(i)}
onRemoveStack={() => removeStack(i)}
onStackChange={(checked) => handleStackChange(i, checked)}
/>
))
)}
Expand Down Expand Up @@ -319,10 +341,13 @@ interface QuerySettingsInputProps {
onAddFormat: () => void;
onRemoveFormat: () => void;
onFormatChange: (format?: FormatOptions) => void;
onAddStack: () => void;
onRemoveStack: () => void;
onStackChange: (checked: boolean) => void;
}

function QuerySettingsInput({
querySettings: { queryIndex, colorMode, colorValue, lineStyle, areaOpacity, format },
querySettings: { queryIndex, colorMode, colorValue, lineStyle, areaOpacity, format, stack },
availableQueryIndexes,
onQueryIndexChange,
onColorModeChange,
Expand All @@ -340,6 +365,9 @@ function QuerySettingsInput({
onAddFormat,
onRemoveFormat,
onFormatChange,
onAddStack,
onRemoveStack,
onStackChange,
}: QuerySettingsInputProps): ReactElement {
// current query index should also be selectable
const selectableQueryIndexes = availableQueryIndexes.concat(queryIndex).sort((a, b) => a - b);
Expand All @@ -354,8 +382,9 @@ function QuerySettingsInput({
if (!lineStyle) options.push({ key: 'lineStyle', label: 'Line Style', action: onAddLineStyle });
if (areaOpacity === undefined) options.push({ key: 'opacity', label: 'Opacity', action: onAddAreaOpacity });
if (format === undefined) options.push({ key: 'format', label: 'Format', action: onAddFormat });
if (stack === undefined) options.push({ key: 'stack', label: 'Stack', action: onAddStack });
return options;
}, [colorMode, lineStyle, areaOpacity, format, onAddColor, onAddLineStyle, onAddAreaOpacity, onAddFormat]);
}, [colorMode, lineStyle, areaOpacity, format, stack, onAddColor, onAddLineStyle, onAddAreaOpacity, onAddFormat, onAddStack]);

const handleAddMenuClick = (event: React.MouseEvent<HTMLElement>): void => {
if (availableOptions.length === 1 && availableOptions[0]) {
Expand Down Expand Up @@ -472,6 +501,18 @@ function QuerySettingsInput({
</SettingsSection>
)}

{/* Stack section */}
{stack !== undefined && (
<SettingsSection label="Stack" onRemove={onRemoveStack}>
<Switch
checked={stack}
onChange={(e) => onStackChange(e.target.checked)}
slotProps={{ input: { 'aria-label': 'stack override' } }}
/>
<Box sx={{ flexGrow: 1 }} />
</SettingsSection>
)}

{/* Add Options Button - only show if there are available options */}
{availableOptions.length > 0 && (
<>
Expand Down
2 changes: 1 addition & 1 deletion timeserieschart/src/TimeSeriesChartBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
TooltipComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { getCommonTimeScale } from '@perses-dev/components';
import {
ChartInstance,
Comment thread
SoWieMarkus marked this conversation as resolved.
ChartInstanceFocusOpts,
Expand All @@ -48,7 +49,6 @@ import {
enableDataZoom,
FormatOptions,
getClosestTimestamp,
getCommonTimeScale,
getFormattedAxis,
getPointInGrid,
OnEventsType,
Expand Down
7 changes: 5 additions & 2 deletions timeserieschart/src/TimeSeriesChartPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,11 @@ export function TimeSeriesChartPanel(props: TimeSeriesChartProps): ReactElement
setTimeRange({ start: new Date(event.start), end: new Date(event.end) });
};

// Used to opt in to ECharts trigger item which show subgroup data accurately
const isStackedBar = visual.display === 'bar' && visual.stack === 'all';
// Used to opt in to ECharts trigger item which show subgroup data accurately.
// Derived from the actual series mapping rather than `visual.stack` alone so that
// bar charts stacked only via per-query overrides also use the right tooltip mode.
const isStackedBar =
visual.display === 'bar' && timeSeriesMapping.some((s) => s?.type === 'bar' && s.stack === 'all');

// Turn on tooltip pinning by default but opt out for stacked bar or if explicitly set in tooltip panel spec
let enablePinning = true;
Expand Down
2 changes: 1 addition & 1 deletion timeserieschart/src/VisualOptionsEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function VisualOptionsEditor({ value, onChange }: VisualOptionsEditorProp
onChange={(__, newValue) => {
const updatedValue: TimeSeriesChartVisualOptions = {
...value,
stack: newValue.id === 'none' ? undefined : newValue.id, // stack is optional so remove property when 'None' is selected
stack: newValue.id === 'none' ? undefined : newValue.id,
};
// stacked area chart preset to automatically set area under a curve shading
if (newValue.id === 'all' && !value.areaOpacity) {
Expand Down
1 change: 1 addition & 0 deletions timeserieschart/src/time-series-chart-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface QuerySettingsOptions {
lineStyle?: LineStyleType;
areaOpacity?: number;
format?: FormatOptions;
stack?: boolean;
}

export type TimeSeriesChartOptionsEditorProps = OptionsEditorProps<TimeSeriesChartOptions>;
Expand Down
43 changes: 41 additions & 2 deletions timeserieschart/src/utils/data-transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
// limitations under the License.

import { LegacyTimeSeries } from '@perses-dev/components';
import { TimeSeriesChartYAxisOptions } from '../time-series-chart-model';
import { convertPercentThreshold, convertPanelYAxis, roundDown } from './data-transform';
import { TimeScale } from '@perses-dev/spec';
import { TimeSeriesChartVisualOptions, TimeSeriesChartYAxisOptions } from '../time-series-chart-model';
import { convertPercentThreshold, convertPanelYAxis, getTimeSeries, roundDown } from './data-transform';

const MAX_VALUE = 120;
const MOCK_ECHART_TIME_SERIES_DATA: LegacyTimeSeries[] = [
Expand Down Expand Up @@ -144,3 +145,41 @@ describe('roundDown', () => {
expect(roundDown(value)).toEqual(expected);
});
});

describe('getTimeSeries stack behavior', () => {
const TIME_SCALE: TimeScale = { startMs: 0, endMs: 60_000, stepMs: 1000, rangeMs: 60_000 };

// 6 combinations of (global visual.stack) x (per-query stack override) -> whether
// the resulting series should have stack === 'all'. Run for both line and bar display.
const STACK_CASES: Array<{
label: string;
globalStack: 'all' | undefined;
querySettingsStack: boolean | undefined;
expectStacked: boolean;
}> = [
{ label: 'global=none + query=unset → not stacked', globalStack: undefined, querySettingsStack: undefined, expectStacked: false },
{ label: 'global=none + query=true → stacked', globalStack: undefined, querySettingsStack: true, expectStacked: true },
{ label: 'global=none + query=false → not stacked', globalStack: undefined, querySettingsStack: false, expectStacked: false },
{ label: 'global=all + query=unset → stacked', globalStack: 'all', querySettingsStack: undefined, expectStacked: true },
{ label: 'global=all + query=true → stacked', globalStack: 'all', querySettingsStack: true, expectStacked: true },
{ label: 'global=all + query=false → not stacked', globalStack: 'all', querySettingsStack: false, expectStacked: false },
];

describe.each(['line', 'bar'] as const)('display: %s', (display) => {
it.each(STACK_CASES)('$label', ({ globalStack, querySettingsStack, expectStacked }) => {
const visual: TimeSeriesChartVisualOptions = { display, stack: globalStack };
const series = getTimeSeries(
'series-id',
0,
'series',
visual,
TIME_SCALE,
'#000000',
{ stack: querySettingsStack }
);

expect(series.type).toEqual(display);
expect(series.stack).toEqual(expectStacked ? 'all' : undefined);
});
});
});
10 changes: 6 additions & 4 deletions timeserieschart/src/utils/data-transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
EChartsValues,
TimeSeriesOption,
StepOptions,
getCommonTimeScale,
getCommonTimeScale
} from '@perses-dev/components';
import { useTimeSeriesQueries, PanelData } from '@perses-dev/plugin-system';
import { TimeScale, TimeSeries, TimeSeriesData, TimeSeriesValueTuple } from '@perses-dev/spec';
Expand Down Expand Up @@ -69,11 +69,13 @@ export function getTimeSeries(
visual: TimeSeriesChartVisualOptions,
timeScale: TimeScale,
paletteColor: string,
querySettings?: { lineStyle?: LineStyleType; areaOpacity?: number },
querySettings?: { lineStyle?: LineStyleType; areaOpacity?: number; stack?: boolean },
yAxisIndex?: number
): TimeSeriesOption {
const lineWidth = visual.lineWidth ?? DEFAULT_LINE_WIDTH;
const pointRadius = visual.pointRadius ?? DEFAULT_POINT_RADIUS;
const shouldStack =
querySettings?.stack !== undefined ? querySettings.stack : visual.stack === 'all';

Comment thread
SoWieMarkus marked this conversation as resolved.
// Shows datapoint symbols when selected time range is roughly 15 minutes or less
const minuteMs = 60000;
Expand All @@ -90,7 +92,7 @@ export function getTimeSeries(
datasetIndex,
name: formattedName,
color: paletteColor,
stack: visual.stack === 'all' ? visual.stack : undefined,
stack: shouldStack ? 'all' : undefined,
yAxisIndex: yAxisIndex,
Comment on lines 75 to 96

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SoWieMarkus, I can also confirm that the tooltip is not working anymore when it is stacked after your changes. I tested manually.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what exactly is not working anymore? I know there are issues with the tooltip when stacking is enabled, but that an issue that was there before my change.

label: {
show: false,
Expand All @@ -106,7 +108,7 @@ export function getTimeSeries(
name: formattedName,
connectNulls: visual.connectNulls ?? DEFAULT_CONNECT_NULLS,
color: paletteColor,
stack: visual.stack === 'all' ? visual.stack : undefined,
stack: shouldStack ? 'all' : undefined,
yAxisIndex: yAxisIndex,
sampling: 'lttb',
progressiveThreshold: OPTIMIZED_MODE_SERIES_LIMIT, // https://echarts.apache.org/en/option.html#series-lines.progressiveThreshold
Expand Down