XYChart: Add support for x=time (#106459)
* XYChart: Add support for x=time * prettier * Add time axis demo to provisioned dashboard for XY * Add details about time fields to the documentation --------- Co-authored-by: Kristina Durivage <kristina.durivage@grafana.com>
This commit is contained in:
co-authored by
Kristina Durivage
parent
17952d45e4
commit
cf8e3bf7d4
File diff suppressed because one or more lines are too long
@@ -65,7 +65,7 @@ XY charts provide a way to visualize arbitrary x and y values in a graph so that
|
||||
|
||||
## Supported data formats
|
||||
|
||||
You can use any type of tabular data with at least two numeric fields in an xy chart. This type of visualization doesn't require time data.
|
||||
You can use any type of tabular data with at least two numeric fields in an xy chart. The x field can be a time field. This type of visualization doesn't require time data, but it can be used.
|
||||
|
||||
## Configuration options
|
||||
|
||||
@@ -117,7 +117,7 @@ When you select **Auto** as your series mapping mode, the following options are
|
||||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| Frame | By default, an xy chart displays all data frames. You can filter to select only one frame. |
|
||||
| [X field](#x-field) | Select which field or fields x represents. By default, this is the first number field in each data frame. For an example of this in **Auto** mode, refer to the [X field section](#x-field). |
|
||||
| [X field](#x-field) | Select which field or fields x represents. By default, this is the first number or time field in each data frame. For an example of this in **Auto** mode, refer to the [X field section](#x-field). |
|
||||
| [Y field](#y-field) | After the x-field is set, by default, all the remaining number fields in the data frame are designated as the y-fields. You can use this option to explicitly choose which fields to use for y. For more information on how to use this in **Auto** mode, refer to the [Y field section](#y-field). |
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
@@ -145,7 +145,7 @@ In **Manual** mode, these fields are required:
|
||||
|
||||
#### X field
|
||||
|
||||
In **Auto** series mapping mode, select which field or fields x represents. By default, this is the first number field in each data frame. For example, you enter the following CSV content:
|
||||
In **Auto** series mapping mode, select which field or fields x represents. By default, this is the first number or time field in each data frame. For example, you enter the following CSV content:
|
||||
|
||||
| a | b | c |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -8,6 +8,7 @@ import { PlotConfigBuilder } from '../types';
|
||||
export interface ScaleProps {
|
||||
scaleKey: string;
|
||||
isTime?: boolean;
|
||||
auto?: boolean;
|
||||
min?: number | null;
|
||||
max?: number | null;
|
||||
softMin?: number | null;
|
||||
@@ -32,6 +33,7 @@ export class UPlotScaleBuilder extends PlotConfigBuilder<ScaleProps, Scale> {
|
||||
getConfig(): Scale {
|
||||
let {
|
||||
isTime,
|
||||
auto,
|
||||
scaleKey,
|
||||
min: hardMin,
|
||||
max: hardMax,
|
||||
@@ -256,7 +258,7 @@ export class UPlotScaleBuilder extends PlotConfigBuilder<ScaleProps, Scale> {
|
||||
return minMax;
|
||||
};
|
||||
|
||||
let auto = !isTime && !hasFixedRange;
|
||||
auto ??= !isTime && !hasFixedRange;
|
||||
|
||||
if (isBooleanUnit(scaleKey)) {
|
||||
auto = false;
|
||||
|
||||
@@ -182,10 +182,11 @@ export const SeriesEditor = ({
|
||||
filter: (field) =>
|
||||
(mapping === SeriesMapping.Auto ||
|
||||
field.state?.origin?.frameIndex === series.frame?.matcher.options) &&
|
||||
field.type === FieldType.number &&
|
||||
(field.type === FieldType.number || field.type === FieldType.time) &&
|
||||
!field.config.custom?.hideFrom?.viz,
|
||||
baseNameMode,
|
||||
placeholderText: mapping === SeriesMapping.Auto ? 'First number field in each frame' : undefined,
|
||||
placeholderText:
|
||||
mapping === SeriesMapping.Auto ? 'First number or time field in each frame' : undefined,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -298,6 +298,7 @@ export const prepConfig = (xySeries: XYSeries[], theme: GrafanaTheme2) => {
|
||||
builder.setMode(2);
|
||||
|
||||
let xField = xySeries[0].x.field;
|
||||
let xIsTime = xField.type === FieldType.time;
|
||||
|
||||
let fieldConfig = xField.config;
|
||||
let customConfig = fieldConfig.custom;
|
||||
@@ -305,7 +306,8 @@ export const prepConfig = (xySeries: XYSeries[], theme: GrafanaTheme2) => {
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
isTime: false,
|
||||
isTime: xIsTime,
|
||||
auto: true,
|
||||
orientation: ScaleOrientation.Horizontal,
|
||||
direction: ScaleDirection.Right,
|
||||
distribution: scaleDistr?.type,
|
||||
@@ -317,6 +319,7 @@ export const prepConfig = (xySeries: XYSeries[], theme: GrafanaTheme2) => {
|
||||
softMax: customConfig?.axisSoftMax,
|
||||
centeredZero: customConfig?.axisCenteredZero,
|
||||
decimals: fieldConfig.decimals,
|
||||
range: xIsTime ? (u, min, max) => [min, max] : undefined,
|
||||
});
|
||||
|
||||
// why does this fall back to '' instead of null or undef?
|
||||
@@ -339,13 +342,14 @@ export const prepConfig = (xySeries: XYSeries[], theme: GrafanaTheme2) => {
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
isTime: xIsTime,
|
||||
placement: customConfig?.axisPlacement !== AxisPlacement.Hidden ? AxisPlacement.Bottom : AxisPlacement.Hidden,
|
||||
show: customConfig?.axisPlacement !== AxisPlacement.Hidden,
|
||||
grid: { show: customConfig?.axisGridShow },
|
||||
border: { show: customConfig?.axisBorderShow },
|
||||
theme,
|
||||
label: xAxisLabel,
|
||||
formatValue: (v, decimals) => formattedValueToString(xField.display!(v, decimals)),
|
||||
formatValue: xIsTime ? undefined : (v, decimals) => formattedValueToString(xField.display!(v, decimals)),
|
||||
});
|
||||
|
||||
xySeries.forEach((s, si) => {
|
||||
|
||||
@@ -62,8 +62,8 @@ export function prepSeries(
|
||||
|
||||
let xMatcher = getFieldMatcher(
|
||||
seriesCfg.x?.matcher ?? {
|
||||
id: FieldMatcherID.byType,
|
||||
options: 'number',
|
||||
id: FieldMatcherID.byTypes,
|
||||
options: new Set(['number', 'time']),
|
||||
}
|
||||
);
|
||||
let yMatcher = getFieldMatcher(
|
||||
@@ -89,11 +89,16 @@ export function prepSeries(
|
||||
|
||||
let frameSeries: XYSeries[] = [];
|
||||
|
||||
// only grabbing number fields (exclude time, string, enum, other)
|
||||
let onlyNumFields = frame.fields.filter((field) => field.type === FieldType.number);
|
||||
let onlyNumTimeFields = frame.fields.filter(
|
||||
(field) => field.type === FieldType.number || field.type === FieldType.time
|
||||
);
|
||||
|
||||
// only one of these per frame
|
||||
let x = onlyNumFields.find((field) => xMatcher(field, frame, frames));
|
||||
let x = onlyNumTimeFields.find((field) => xMatcher(field, frame, frames));
|
||||
|
||||
// only grabbing number fields (exclude time, string, enum, other)
|
||||
let onlyNumFields = onlyNumTimeFields.filter((field) => field.type === FieldType.number);
|
||||
|
||||
let color =
|
||||
colorMatcher != null
|
||||
? onlyNumFields.find((field) => field !== x && colorMatcher!(field, frame, frames))
|
||||
|
||||
Reference in New Issue
Block a user