TimeSeries: Support multiple timezones in x axis (#52424)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
Ryan McKinley
2022-07-22 20:18:27 -07:00
committed by GitHub
parent 17ea5f4f3e
commit 2fa10dc903
25 changed files with 211 additions and 45 deletions

View File

@@ -44,7 +44,7 @@ export interface GraphNGProps extends Themeable2 {
width: number;
height: number;
timeRange: TimeRange;
timeZone: TimeZone;
timeZones: TimeZone[] | TimeZone;
legend: VizLegendOptions;
fields?: XYFieldMatchers; // default will assume timeseries data
renderers?: Renderers;
@@ -216,17 +216,17 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
}
componentDidUpdate(prevProps: GraphNGProps) {
const { frames, structureRev, timeZone, propsToDiff } = this.props;
const { frames, structureRev, timeZones, propsToDiff } = this.props;
const propsChanged = !sameProps(prevProps, this.props, propsToDiff);
if (frames !== prevProps.frames || propsChanged || timeZone !== prevProps.timeZone) {
if (frames !== prevProps.frames || propsChanged || timeZones !== prevProps.timeZones) {
let newState = this.prepState(this.props, false);
if (newState) {
const shouldReconfig =
this.state.config === undefined ||
timeZone !== prevProps.timeZone ||
timeZones !== prevProps.timeZones ||
structureRev !== prevProps.structureRev ||
!structureRev ||
propsChanged;

View File

@@ -214,7 +214,7 @@ describe('GraphNG utils', () => {
const result = preparePlotConfigBuilder({
frame: frame!,
theme: createTheme(),
timeZone: DefaultTimeZone,
timeZones: [DefaultTimeZone],
getTimeRange: getDefaultTimeRange,
eventBus: new EventBusSrv(),
sync: () => DashboardCursorSync.Tooltip,

View File

@@ -22,12 +22,12 @@ export class UnthemedTimeSeries extends React.Component<TimeSeriesProps> {
prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => {
const { eventBus, sync } = this.context as PanelContext;
const { theme, timeZone, renderers, tweakAxis, tweakScale } = this.props;
const { theme, timeZones, renderers, tweakAxis, tweakScale } = this.props;
return preparePlotConfigBuilder({
frame: alignedFrame,
theme,
timeZone,
timeZones: Array.isArray(timeZones) ? timeZones : [timeZones],
getTimeRange,
eventBus,
sync,

View File

@@ -48,7 +48,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
}> = ({
frame,
theme,
timeZone,
timeZones,
getTimeRange,
eventBus,
sync,
@@ -57,7 +57,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
tweakScale = (opts) => opts,
tweakAxis = (opts) => opts,
}) => {
const builder = new UPlotConfigBuilder(timeZone);
const builder = new UPlotConfigBuilder(timeZones[0]);
let alignedFrame: DataFrame;
@@ -97,16 +97,51 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
},
});
builder.addAxis({
scaleKey: xScaleKey,
isTime: true,
placement: xFieldAxisPlacement,
show: xFieldAxisShow,
label: xField.config.custom?.axisLabel,
timeZone,
theme,
grid: { show: xField.config.custom?.axisGridShow },
});
// filters first 2 ticks to make space for timezone labels
const filterTicks: uPlot.Axis.Filter | undefined =
timeZones.length > 1
? (u, splits) => {
return splits.map((v, i) => (i < 2 ? null : v));
}
: undefined;
for (let i = 0; i < timeZones.length; i++) {
const timeZone = timeZones[i];
builder.addAxis({
scaleKey: xScaleKey,
isTime: true,
placement: xFieldAxisPlacement,
show: xFieldAxisShow,
label: xField.config.custom?.axisLabel,
timeZone,
theme,
grid: { show: i === 0 && xField.config.custom?.axisGridShow },
filter: filterTicks,
});
}
// render timezone labels
if (timeZones.length > 1) {
builder.addHook('drawAxes', (u: uPlot) => {
u.ctx.save();
u.ctx.fillStyle = theme.colors.text.primary;
u.ctx.textAlign = 'left';
u.ctx.textBaseline = 'bottom';
let i = 0;
u.axes.forEach((a) => {
if (a.side === 2) {
//@ts-ignore
let cssBaseline: number = a._pos + a._size;
u.ctx.fillText(timeZones[i], u.bbox.left, cssBaseline * uPlot.pxRatio);
i++;
}
});
u.ctx.restore();
});
}
} else {
// Not time!
if (xField.config.unit) {

View File

@@ -151,7 +151,7 @@ export class UPlotAxisBuilder extends PlotConfigBuilder<AxisProps, Axis> {
ticks
),
splits,
values: values,
values,
space:
space ??
((self, axisIdx, scaleMin, scaleMax, plotDim) => {
@@ -227,7 +227,7 @@ export function formatTime(
format = systemDateFormats.interval.month;
}
return splits.map((v) => dateTimeFormat(v, { format, timeZone }));
return splits.map((v) => (v == null ? '' : dateTimeFormat(v, { format, timeZone })));
}
export function getUPlotSideFromAxis(axis: AxisPlacement) {

View File

@@ -87,8 +87,14 @@ export class UPlotConfigBuilder {
addAxis(props: AxisProps) {
props.placement = props.placement ?? AxisPlacement.Auto;
props.grid = props.grid ?? {};
if (this.axes[props.scaleKey]) {
this.axes[props.scaleKey].merge(props);
let scaleKey = props.scaleKey;
if (scaleKey === 'x') {
scaleKey += props.timeZone ?? '';
}
if (this.axes[scaleKey]) {
this.axes[scaleKey].merge(props);
return;
}
@@ -106,7 +112,7 @@ export class UPlotConfigBuilder {
props.size = 0;
}
this.axes[props.scaleKey] = new UPlotAxisBuilder(props);
this.axes[scaleKey] = new UPlotAxisBuilder(props);
}
getAxisPlacement(scaleKey: string): AxisPlacement {
@@ -285,7 +291,7 @@ export type Renderers = Array<{
type UPlotConfigPrepOpts<T extends Record<string, any> = {}> = {
frame: DataFrame;
theme: GrafanaTheme2;
timeZone: TimeZone;
timeZones: TimeZone[];
getTimeRange: () => TimeRange;
eventBus: EventBus;
allFrames: DataFrame[];