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); + }; +}