diff --git a/scatterchart/src/Scatterplot.tsx b/scatterchart/src/Scatterplot.tsx
index b49d187ab..efba503f8 100644
--- a/scatterchart/src/Scatterplot.tsx
+++ b/scatterchart/src/Scatterplot.tsx
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { ReactElement, useMemo } from 'react';
-import { EChart, formatValue, OnEventsType, useChartsTheme } from '@perses-dev/components';
+import { ReactElement, useCallback, useMemo } from 'react';
+import { EChart, formatValue, OnEventsType, useChartsTheme, useTimeZone } from '@perses-dev/components';
import { use, EChartsCoreOption } from 'echarts/core';
import { ScatterChart as EChartsScatterChart } from 'echarts/charts';
import {
@@ -32,6 +32,7 @@ import {
useTimeRange,
} from '@perses-dev/plugin-system';
import { EChartTraceValue } from './ScatterChartPanel';
+import { createTimezoneAwareAxisFormatter } from './utils/timezone-formatter';
use([
DatasetComponent,
@@ -44,6 +45,16 @@ use([
CanvasRenderer,
]);
+const DATE_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric',
+ fractionalSecondDigits: 3,
+};
+
export interface ScatterplotProps {
width: number;
height: number;
@@ -51,18 +62,22 @@ export interface ScatterplotProps {
link?: string;
}
-const DATE_FORMATTER = new Intl.DateTimeFormat(undefined, {
- dateStyle: 'long',
- timeStyle: 'medium',
-}).format;
-
export function Scatterplot(props: ScatterplotProps): ReactElement {
const { width, height, options, link: linkTemplate } = props;
const chartsTheme = useChartsTheme();
const { absoluteTimeRange } = useTimeRange();
+ const { timeZone, dateFormatOptionsWithUserTimeZone } = useTimeZone();
+
+ const dateFormatter = useMemo(() => {
+ const dateFormatOptions = dateFormatOptionsWithUserTimeZone(DATE_FORMAT_OPTIONS);
+ return new Intl.DateTimeFormat(undefined, dateFormatOptions).format;
+ }, [dateFormatOptionsWithUserTimeZone]);
const variableValues = useAllVariableValues();
const { navigate } = useRouterContext();
+ const rangeMs = absoluteTimeRange.end.valueOf() - absoluteTimeRange.start.valueOf();
+ const getAxisFormatter = useCallback(() => createTimezoneAwareAxisFormatter(rangeMs, timeZone), [rangeMs, timeZone]);
+
// Apache EChart Options Docs: https://echarts.apache.org/en/option.html
const eChartOptions: EChartsCoreOption = {
dataset: options.dataset,
@@ -78,6 +93,10 @@ export function Scatterplot(props: ScatterplotProps): ReactElement {
type: 'time',
min: absoluteTimeRange.start,
max: absoluteTimeRange.end,
+ axisLabel: {
+ hideOverlap: true,
+ formatter: getAxisFormatter(),
+ },
},
yAxis: {
scale: true,
@@ -103,7 +122,7 @@ export function Scatterplot(props: ScatterplotProps): ReactElement {
return [
`Service name: ${data.rootServiceName}
`,
`Span name: ${data.rootTraceName}
`,
- `Time: ${DATE_FORMATTER(data.startTime)}
`,
+ `Time: ${dateFormatter(data.startTime)}
`,
`Duration: ${formatValue(data.durationMs, { unit: 'milliseconds' })}
`,
`Span count: ${data.spanCount} (${data.errorCount} errors)
`,
].join('');
diff --git a/scatterchart/src/utils/timezone-formatter.ts b/scatterchart/src/utils/timezone-formatter.ts
new file mode 100644
index 000000000..019a31c6e
--- /dev/null
+++ b/scatterchart/src/utils/timezone-formatter.ts
@@ -0,0 +1,49 @@
+// Copyright The Perses Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { formatWithTimeZone } from '@perses-dev/components';
+
+const DAY_MS = 86400000;
+const YEAR_MS = 31536000000;
+
+/**
+ * Creates a timezone-aware axis formatter function for different time ranges
+ */
+export function createTimezoneAwareAxisFormatter(rangeMs: number, timeZone: string) {
+ return function (value: number): string {
+ const timeStamp = new Date(Number(value));
+
+ // more than 5 years
+ if (rangeMs > YEAR_MS * 5) {
+ return formatWithTimeZone(timeStamp, 'yyyy', timeZone);
+ }
+
+ // more than 6 months
+ if (rangeMs > DAY_MS * 180) {
+ return formatWithTimeZone(timeStamp, 'MMM yyyy', timeZone);
+ }
+
+ // more than 10 days
+ if (rangeMs > DAY_MS * 10) {
+ return formatWithTimeZone(timeStamp, 'dd.MM', timeZone);
+ }
+
+ // more than 2 days
+ if (rangeMs > DAY_MS * 2) {
+ return formatWithTimeZone(timeStamp, 'dd.MM HH:mm', timeZone);
+ }
+
+ // less or equal 2 days
+ return formatWithTimeZone(timeStamp, 'HH:mm', timeZone);
+ };
+}