mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
XYChart further improvements (#55152)
* Tooltip shows all data facets. Renamed options * Add per series line style * Remove line style option from manual panel options * Refactored tooltip view * sets selected after switch to manual * remove facet prefixes * in manual mode pull series names from config options, not y facet * unused import * Point size * x & y axes labels * Fix manual series prep * betterer Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
parent
11eb02a183
commit
3361f2c62d
@ -8940,6 +8940,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "10"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "11"]
|
||||
],
|
||||
"public/app/plugins/panel/xychart/TooltipView.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/plugins/panel/xychart/XYChartPanel2.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { FC, useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { GrafanaTheme, StandardEditorProps } from '@grafana/data';
|
||||
import { Button, Field, IconButton, useStyles } from '@grafana/ui';
|
||||
@ -9,12 +9,12 @@ import { ColorDimensionEditor, ScaleDimensionEditor } from 'app/features/dimensi
|
||||
|
||||
import { XYChartOptions, ScatterSeriesConfig, defaultScatterConfig } from './models.gen';
|
||||
|
||||
export const ManualEditor: FC<StandardEditorProps<ScatterSeriesConfig[], any, XYChartOptions>> = ({
|
||||
export const ManualEditor = ({
|
||||
value,
|
||||
onChange,
|
||||
context,
|
||||
}) => {
|
||||
const [selected, setSelected] = useState<number>(-1);
|
||||
}: StandardEditorProps<ScatterSeriesConfig[], any, XYChartOptions>) => {
|
||||
const [selected, setSelected] = useState<number>(0);
|
||||
const style = useStyles(getStyles);
|
||||
|
||||
const onFieldChange = (val: any | undefined, index: number, field: string) => {
|
||||
@ -36,22 +36,27 @@ export const ManualEditor: FC<StandardEditorProps<ScatterSeriesConfig[], any, XY
|
||||
pointSize: defaultScatterConfig.pointSize,
|
||||
},
|
||||
]);
|
||||
setSelected(value.length);
|
||||
};
|
||||
|
||||
// Component-did-mount callback to check if a new series should be created
|
||||
useEffect(() => {
|
||||
if (!value?.length) {
|
||||
createNewSeries(); // adds a new series
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const onSeriesDelete = (index: number) => {
|
||||
onChange(value.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const { options } = context;
|
||||
// const { options } = context;
|
||||
|
||||
const getRowStyle = (index: number) => {
|
||||
return index === selected ? `${style.row} ${style.sel}` : style.row;
|
||||
};
|
||||
|
||||
if (options === undefined || !options.series) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button icon="plus" size="sm" variant="secondary" onClick={createNewSeries} className={style.marginBot}>
|
||||
@ -59,7 +64,7 @@ export const ManualEditor: FC<StandardEditorProps<ScatterSeriesConfig[], any, XY
|
||||
</Button>
|
||||
|
||||
<div className={style.marginBot}>
|
||||
{options.series.map((series, index) => {
|
||||
{value.map((series, index) => {
|
||||
return (
|
||||
<div key={`series/${index}`} className={getRowStyle(index)} onMouseDown={() => setSelected(index)}>
|
||||
<LayerName
|
||||
@ -78,12 +83,12 @@ export const ManualEditor: FC<StandardEditorProps<ScatterSeriesConfig[], any, XY
|
||||
})}
|
||||
</div>
|
||||
|
||||
{selected >= 0 && options.series[selected] && (
|
||||
{selected >= 0 && value[selected] && (
|
||||
<>
|
||||
<div key={`series/${selected}`}>
|
||||
<Field label={'X Field'}>
|
||||
<FieldNamePicker
|
||||
value={options.series[selected].x ?? ''}
|
||||
value={value[selected].x ?? ''}
|
||||
context={context}
|
||||
onChange={(field) => onFieldChange(field, selected, 'x')}
|
||||
item={{} as any}
|
||||
@ -91,7 +96,7 @@ export const ManualEditor: FC<StandardEditorProps<ScatterSeriesConfig[], any, XY
|
||||
</Field>
|
||||
<Field label={'Y Field'}>
|
||||
<FieldNamePicker
|
||||
value={options.series[selected].y ?? ''}
|
||||
value={value[selected].y ?? ''}
|
||||
context={context}
|
||||
onChange={(field) => onFieldChange(field, selected, 'y')}
|
||||
item={{} as any}
|
||||
@ -99,7 +104,7 @@ export const ManualEditor: FC<StandardEditorProps<ScatterSeriesConfig[], any, XY
|
||||
</Field>
|
||||
<Field label={'Point color'}>
|
||||
<ColorDimensionEditor
|
||||
value={options.series[selected].pointColor!}
|
||||
value={value[selected].pointColor!}
|
||||
context={context}
|
||||
onChange={(field) => onFieldChange(field, selected, 'pointColor')}
|
||||
item={{} as any}
|
||||
@ -107,7 +112,7 @@ export const ManualEditor: FC<StandardEditorProps<ScatterSeriesConfig[], any, XY
|
||||
</Field>
|
||||
<Field label={'Point size'}>
|
||||
<ScaleDimensionEditor
|
||||
value={options.series[selected].pointSize!}
|
||||
value={value[selected].pointSize!}
|
||||
context={context}
|
||||
onChange={(field) => onFieldChange(field, selected, 'pointSize')}
|
||||
item={{ settings: { min: 1, max: 50 } } as any}
|
||||
|
@ -2,7 +2,6 @@ import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
arrayUtils,
|
||||
DataFrame,
|
||||
Field,
|
||||
formattedValueToString,
|
||||
@ -11,30 +10,47 @@ import {
|
||||
LinkModel,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { SortOrder } from '@grafana/schema';
|
||||
import {
|
||||
LinkButton,
|
||||
SeriesIcon,
|
||||
TooltipDisplayMode,
|
||||
usePanelContext,
|
||||
useStyles2,
|
||||
VerticalGroup,
|
||||
VizTooltipOptions,
|
||||
} from '@grafana/ui';
|
||||
import { LinkButton, usePanelContext, useStyles2, VerticalGroup, VizTooltipOptions } from '@grafana/ui';
|
||||
import { getFieldLinksForExplore } from 'app/features/explore/utils/links';
|
||||
|
||||
import { ScatterSeriesConfig, SeriesMapping } from './models.gen';
|
||||
import { ScatterSeries } from './types';
|
||||
|
||||
interface YValue {
|
||||
name: string;
|
||||
val: number;
|
||||
field: Field;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface ExtraFacets {
|
||||
colorFacetFieldName: string;
|
||||
sizeFacetFieldName: string;
|
||||
colorFacetValue: number;
|
||||
sizeFacetValue: number;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
allSeries: ScatterSeries[];
|
||||
data: DataFrame[]; // source data
|
||||
manualSeriesConfigs: ScatterSeriesConfig[] | undefined;
|
||||
rowIndex?: number; // the hover row
|
||||
seriesMapping: SeriesMapping;
|
||||
hoveredPointIndex: number; // the hovered point
|
||||
options: VizTooltipOptions;
|
||||
range: TimeRange;
|
||||
}
|
||||
|
||||
export const TooltipView = ({ allSeries, data, rowIndex, hoveredPointIndex, options, range }: Props) => {
|
||||
export const TooltipView = ({
|
||||
allSeries,
|
||||
data,
|
||||
manualSeriesConfigs,
|
||||
seriesMapping,
|
||||
rowIndex,
|
||||
hoveredPointIndex,
|
||||
options,
|
||||
range,
|
||||
}: Props) => {
|
||||
const style = useStyles2(getStyles);
|
||||
const { onSplitOpen } = usePanelContext();
|
||||
|
||||
@ -53,72 +69,57 @@ export const TooltipView = ({ allSeries, data, rowIndex, hoveredPointIndex, opti
|
||||
range,
|
||||
});
|
||||
|
||||
let yValues = [];
|
||||
if (options.mode === TooltipDisplayMode.Single) {
|
||||
yValues = [
|
||||
{
|
||||
name: getFieldDisplayName(yField, frame),
|
||||
val: yField.values.get(rowIndex),
|
||||
field: yField,
|
||||
color: series.pointColor(frame),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
yValues = allSeries
|
||||
.map((series, i) => {
|
||||
const frame = series.frame(data);
|
||||
const seriesXField = series.x(frame);
|
||||
let yValue: YValue | null = null;
|
||||
let extraFacets: ExtraFacets | null = null;
|
||||
if (seriesMapping === SeriesMapping.Manual && manualSeriesConfigs) {
|
||||
const colorFacetFieldName = manualSeriesConfigs[hoveredPointIndex].pointColor?.field ?? '';
|
||||
const sizeFacetFieldName = manualSeriesConfigs[hoveredPointIndex].pointSize?.field ?? '';
|
||||
|
||||
if (seriesXField.name !== xField.name) {
|
||||
return null;
|
||||
}
|
||||
const colorFacet = colorFacetFieldName ? frame.fields.find((f) => f.name === colorFacetFieldName) : undefined;
|
||||
const sizeFacet = sizeFacetFieldName ? frame.fields.find((f) => f.name === sizeFacetFieldName) : undefined;
|
||||
|
||||
const seriesYField = series.y(frame);
|
||||
|
||||
return {
|
||||
name: getFieldDisplayName(seriesYField, frame),
|
||||
val: seriesYField.values.get(rowIndex),
|
||||
field: seriesYField,
|
||||
color: allSeries[i].pointColor(frame),
|
||||
};
|
||||
})
|
||||
.filter((v) => v != null);
|
||||
extraFacets = {
|
||||
colorFacetFieldName,
|
||||
sizeFacetFieldName,
|
||||
colorFacetValue: colorFacet?.values.get(rowIndex),
|
||||
sizeFacetValue: sizeFacet?.values.get(rowIndex),
|
||||
};
|
||||
}
|
||||
|
||||
if (options.sort !== SortOrder.None) {
|
||||
const sortFn = arrayUtils.sortValues(options.sort);
|
||||
|
||||
yValues.sort((a, b) => {
|
||||
return sortFn(a!.val, b!.val);
|
||||
});
|
||||
}
|
||||
|
||||
let activePointIndex = -1;
|
||||
activePointIndex = yValues.findIndex((v) => v!.name === series.name);
|
||||
yValue = {
|
||||
name: getFieldDisplayName(yField, frame),
|
||||
val: yField.values.get(rowIndex),
|
||||
field: yField,
|
||||
color: series.pointColor(frame) as string,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={style.xVal} aria-label="x-val">
|
||||
{fmt(frame.fields[0], xField.values.get(rowIndex))}
|
||||
</div>
|
||||
<table className={style.infoWrap}>
|
||||
<tr>
|
||||
<th colSpan={2} style={{ backgroundColor: yValue.color }}></th>
|
||||
</tr>
|
||||
<tbody>
|
||||
{yValues.map((el, index) => {
|
||||
let color = null;
|
||||
if (typeof el!.color === 'string') {
|
||||
color = el!.color;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={`${index}/${rowIndex}`} className={index === activePointIndex ? style.highlight : ''}>
|
||||
<th>
|
||||
{color && <SeriesIcon color={color} className={style.icon} />}
|
||||
{el!.name}:
|
||||
</th>
|
||||
<td>{fmt(el!.field, el!.val)}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
<tr>
|
||||
<th>{xField.name}</th>
|
||||
<td>{fmt(frame.fields[0], xField.values.get(rowIndex))}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{yValue.name}:</th>
|
||||
<td>{fmt(yValue.field, yValue.val)}</td>
|
||||
</tr>
|
||||
{extraFacets !== null && extraFacets.colorFacetFieldName && (
|
||||
<tr>
|
||||
<th>{extraFacets.colorFacetFieldName}:</th>
|
||||
<td>{extraFacets.colorFacetValue}</td>
|
||||
</tr>
|
||||
)}
|
||||
{extraFacets !== null && extraFacets.sizeFacetFieldName && (
|
||||
<tr>
|
||||
<th>{extraFacets.sizeFacetFieldName}:</th>
|
||||
<td>{extraFacets.sizeFacetValue}</td>
|
||||
</tr>
|
||||
)}
|
||||
{links.length > 0 && (
|
||||
<tr>
|
||||
<td colSpan={2}>
|
||||
|
@ -26,7 +26,7 @@ import { FacetedData } from '@grafana/ui/src/components/uPlot/types';
|
||||
import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
|
||||
|
||||
import { TooltipView } from './TooltipView';
|
||||
import { XYChartOptions } from './models.gen';
|
||||
import { SeriesMapping, XYChartOptions } from './models.gen';
|
||||
import { prepData, prepScatter, ScatterPanelInfo } from './scatter';
|
||||
import { ScatterHoverEvent, ScatterSeries } from './types';
|
||||
|
||||
@ -73,7 +73,9 @@ export const XYChartPanel2: React.FC<Props> = (props: Props) => {
|
||||
isToolTipOpen
|
||||
);
|
||||
|
||||
if (info.series.length && props.data.series) {
|
||||
if (info.error) {
|
||||
setError(info.error);
|
||||
} else if (info.series.length && props.data.series) {
|
||||
setBuilder(info.builder);
|
||||
setSeries(info.series);
|
||||
setFacets(() => prepData(info, props.data.series));
|
||||
@ -99,10 +101,11 @@ export const XYChartPanel2: React.FC<Props> = (props: Props) => {
|
||||
const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1));
|
||||
const theme = config.theme2;
|
||||
|
||||
for (const s of series) {
|
||||
for (let si = 0; si < series.length; si++) {
|
||||
const s = series[si];
|
||||
const frame = s.frame(props.data.series);
|
||||
if (frame) {
|
||||
for (const item of s.legend(frame)) {
|
||||
for (const item of s.legend()) {
|
||||
item.getDisplayValues = () => {
|
||||
const calcs = props.options.legend.calcs;
|
||||
|
||||
@ -167,6 +170,10 @@ export const XYChartPanel2: React.FC<Props> = (props: Props) => {
|
||||
|
||||
item.disabled = !(s.show ?? true);
|
||||
|
||||
if (props.options.seriesMapping === SeriesMapping.Manual) {
|
||||
item.label = props.options.series?.[si]?.name ?? `Series ${si + 1}`;
|
||||
}
|
||||
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
@ -240,6 +247,8 @@ export const XYChartPanel2: React.FC<Props> = (props: Props) => {
|
||||
<TooltipView
|
||||
options={props.options.tooltip}
|
||||
allSeries={series}
|
||||
manualSeriesConfigs={props.options.series}
|
||||
seriesMapping={props.options.seriesMapping!}
|
||||
rowIndex={hover.xIndex}
|
||||
hoveredPointIndex={hover.scatterIndex}
|
||||
data={props.data.series}
|
||||
|
@ -5,12 +5,12 @@ import {
|
||||
identityOverrideProcessor,
|
||||
SetFieldConfigOptionsArgs,
|
||||
} from '@grafana/data';
|
||||
import { LineStyle, VisibilityMode } from '@grafana/schema';
|
||||
import { commonOptionsBuilder, graphFieldOptions } from '@grafana/ui';
|
||||
import { LineStyle } from '@grafana/schema';
|
||||
import { commonOptionsBuilder } from '@grafana/ui';
|
||||
|
||||
import { LineStyleEditor } from '../timeseries/LineStyleEditor';
|
||||
|
||||
import { ScatterFieldConfig, ScatterLineMode } from './models.gen';
|
||||
import { ScatterFieldConfig, ScatterShow } from './models.gen';
|
||||
|
||||
export function getScatterFieldConfig(cfg: ScatterFieldConfig): SetFieldConfigOptionsArgs<ScatterFieldConfig> {
|
||||
return {
|
||||
@ -30,40 +30,33 @@ export function getScatterFieldConfig(cfg: ScatterFieldConfig): SetFieldConfigOp
|
||||
useCustomConfig: (builder) => {
|
||||
builder
|
||||
.addRadio({
|
||||
path: 'point',
|
||||
name: 'Points',
|
||||
defaultValue: cfg.point,
|
||||
path: 'show',
|
||||
name: 'Show',
|
||||
defaultValue: cfg.show,
|
||||
settings: {
|
||||
options: graphFieldOptions.showPoints,
|
||||
options: [
|
||||
{ label: 'Points', value: ScatterShow.Points },
|
||||
{ label: 'Lines', value: ScatterShow.Lines },
|
||||
{ label: 'Both', value: ScatterShow.PointsAndLines },
|
||||
],
|
||||
},
|
||||
})
|
||||
.addSliderInput({
|
||||
path: 'pointSize.fixed',
|
||||
name: 'Size',
|
||||
name: 'Point size',
|
||||
defaultValue: cfg.pointSize?.fixed,
|
||||
settings: {
|
||||
min: 1,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
showIf: (c) => c.point !== VisibilityMode.Never,
|
||||
})
|
||||
.addRadio({
|
||||
path: 'line',
|
||||
name: 'Lines',
|
||||
defaultValue: cfg.line,
|
||||
settings: {
|
||||
options: [
|
||||
{ label: 'None', value: ScatterLineMode.None },
|
||||
{ label: 'Linear', value: ScatterLineMode.Linear },
|
||||
],
|
||||
},
|
||||
showIf: (c) => c.show !== ScatterShow.Lines,
|
||||
})
|
||||
.addCustomEditor<void, LineStyle>({
|
||||
id: 'lineStyle',
|
||||
path: 'lineStyle',
|
||||
name: 'Line style',
|
||||
showIf: (c) => c.line !== ScatterLineMode.None,
|
||||
showIf: (c) => c.show !== ScatterShow.Points,
|
||||
editor: LineStyleEditor,
|
||||
override: LineStyleEditor,
|
||||
process: identityOverrideProcessor,
|
||||
@ -78,7 +71,7 @@ export function getScatterFieldConfig(cfg: ScatterFieldConfig): SetFieldConfigOp
|
||||
max: 10,
|
||||
step: 1,
|
||||
},
|
||||
showIf: (c) => c.line !== ScatterLineMode.None,
|
||||
showIf: (c) => c.show !== ScatterShow.Points,
|
||||
});
|
||||
|
||||
commonOptionsBuilder.addAxisConfig(builder, cfg);
|
||||
|
@ -14,20 +14,31 @@ import {
|
||||
TextDimensionConfig,
|
||||
} from 'app/features/dimensions';
|
||||
|
||||
export enum ScatterLineMode {
|
||||
None = 'none',
|
||||
Linear = 'linear',
|
||||
// Smooth
|
||||
// r2, etc
|
||||
// export enum ScatterLineMode {
|
||||
// None = 'none',
|
||||
// Linear = 'linear',
|
||||
// Smooth
|
||||
// r2, etc
|
||||
// }
|
||||
|
||||
export enum ScatterShow {
|
||||
Points = 'points',
|
||||
Lines = 'lines',
|
||||
PointsAndLines = 'points+lines',
|
||||
}
|
||||
|
||||
export enum SeriesMapping {
|
||||
Auto = 'auto',
|
||||
Manual = 'manual',
|
||||
}
|
||||
|
||||
export interface ScatterFieldConfig extends HideableFieldConfig, AxisConfig {
|
||||
line?: ScatterLineMode;
|
||||
show?: ScatterShow;
|
||||
|
||||
lineWidth?: number;
|
||||
lineStyle?: LineStyle;
|
||||
lineColor?: ColorDimensionConfig;
|
||||
|
||||
point?: VisibilityMode;
|
||||
pointSize?: ScaleDimensionConfig; // only 'fixed' is exposed in the UI
|
||||
pointColor?: ColorDimensionConfig;
|
||||
pointSymbol?: DimensionSupplier<string>;
|
||||
@ -44,12 +55,11 @@ export interface ScatterSeriesConfig extends ScatterFieldConfig {
|
||||
}
|
||||
|
||||
export const defaultScatterConfig: ScatterFieldConfig = {
|
||||
line: ScatterLineMode.None, // no line
|
||||
show: ScatterShow.Points,
|
||||
lineWidth: 1,
|
||||
lineStyle: {
|
||||
fill: 'solid',
|
||||
},
|
||||
point: VisibilityMode.Auto,
|
||||
pointSize: {
|
||||
fixed: 5,
|
||||
min: 1,
|
||||
@ -66,7 +76,7 @@ export interface XYDimensionConfig {
|
||||
}
|
||||
|
||||
export interface XYChartOptions extends OptionsWithLegend, OptionsWithTooltip {
|
||||
mode?: 'auto' | 'manual';
|
||||
seriesMapping?: SeriesMapping;
|
||||
dims: XYDimensionConfig;
|
||||
|
||||
series?: ScatterSeriesConfig[];
|
||||
|
@ -12,8 +12,8 @@ export const plugin = new PanelPlugin<XYChartOptions, ScatterFieldConfig>(XYChar
|
||||
.setPanelOptions((builder) => {
|
||||
builder
|
||||
.addRadio({
|
||||
path: 'mode',
|
||||
name: 'Mode',
|
||||
path: 'seriesMapping',
|
||||
name: 'Series mapping',
|
||||
defaultValue: 'auto',
|
||||
settings: {
|
||||
options: [
|
||||
@ -27,7 +27,7 @@ export const plugin = new PanelPlugin<XYChartOptions, ScatterFieldConfig>(XYChar
|
||||
path: 'dims',
|
||||
name: '',
|
||||
editor: AutoEditor,
|
||||
showIf: (cfg) => cfg.mode === 'auto',
|
||||
showIf: (cfg) => cfg.seriesMapping === 'auto',
|
||||
})
|
||||
.addCustomEditor({
|
||||
id: 'series',
|
||||
@ -35,7 +35,7 @@ export const plugin = new PanelPlugin<XYChartOptions, ScatterFieldConfig>(XYChar
|
||||
name: '',
|
||||
defaultValue: [],
|
||||
editor: ManualEditor,
|
||||
showIf: (cfg) => cfg.mode === 'manual',
|
||||
showIf: (cfg) => cfg.seriesMapping === 'manual',
|
||||
});
|
||||
|
||||
commonOptionsBuilder.addTooltipOptions(builder);
|
||||
|
@ -26,7 +26,7 @@ import {
|
||||
import { pointWithin, Quadtree, Rect } from '../barchart/quadtree';
|
||||
|
||||
import { isGraphable } from './dims';
|
||||
import { defaultScatterConfig, ScatterFieldConfig, ScatterLineMode, XYChartOptions } from './models.gen';
|
||||
import { defaultScatterConfig, ScatterFieldConfig, ScatterShow, XYChartOptions } from './models.gen';
|
||||
import { DimensionValues, ScatterHoverCallback, ScatterSeries } from './types';
|
||||
|
||||
export interface ScatterPanelInfo {
|
||||
@ -161,7 +161,7 @@ function getScatterSeries(
|
||||
|
||||
x: (frame) => frame.fields[xIndex],
|
||||
y: (frame) => frame.fields[yIndex],
|
||||
legend: (frame) => {
|
||||
legend: () => {
|
||||
return [
|
||||
{
|
||||
label: name,
|
||||
@ -172,12 +172,12 @@ function getScatterSeries(
|
||||
];
|
||||
},
|
||||
|
||||
line: fieldConfig.line ?? ScatterLineMode.None,
|
||||
showLine: fieldConfig.show !== ScatterShow.Points,
|
||||
lineWidth: fieldConfig.lineWidth ?? 2,
|
||||
lineStyle: fieldConfig.lineStyle!,
|
||||
lineColor: () => seriesColor,
|
||||
|
||||
point: fieldConfig.point!,
|
||||
showPoints: fieldConfig.show !== ScatterShow.Lines ? VisibilityMode.Always : VisibilityMode.Never,
|
||||
pointSize,
|
||||
pointColor,
|
||||
pointSymbol: (frame: DataFrame, from?: number) => 'circle', // single field, multiple symbols.... kinda equals multiple series 🤔
|
||||
@ -201,44 +201,46 @@ function prepSeries(options: XYChartOptions, frames: DataFrame[]): ScatterSeries
|
||||
throw 'Missing data';
|
||||
}
|
||||
|
||||
if (options.mode === 'manual') {
|
||||
if (options.series?.length) {
|
||||
const scatterSeries: ScatterSeries[] = [];
|
||||
if (options.seriesMapping === 'manual') {
|
||||
if (!options.series?.length) {
|
||||
throw 'Missing series config';
|
||||
}
|
||||
|
||||
for (const series of options.series) {
|
||||
if (!series?.x) {
|
||||
throw 'Select X dimension';
|
||||
}
|
||||
const scatterSeries: ScatterSeries[] = [];
|
||||
|
||||
if (!series?.y) {
|
||||
throw 'Select Y dimension';
|
||||
}
|
||||
|
||||
for (let frameIndex = 0; frameIndex < frames.length; frameIndex++) {
|
||||
const frame = frames[frameIndex];
|
||||
const xIndex = findFieldIndex(frame, series.x);
|
||||
|
||||
if (xIndex != null) {
|
||||
// TODO: this should find multiple y fields
|
||||
const yIndex = findFieldIndex(frame, series.y);
|
||||
|
||||
if (yIndex == null) {
|
||||
throw 'Y must be in the same frame as X';
|
||||
}
|
||||
|
||||
const dims: Dims = {
|
||||
pointColorFixed: series.pointColor?.fixed,
|
||||
pointColorIndex: findFieldIndex(frame, series.pointColor?.field),
|
||||
pointSizeConfig: series.pointSize,
|
||||
pointSizeIndex: findFieldIndex(frame, series.pointSize?.field),
|
||||
};
|
||||
scatterSeries.push(getScatterSeries(seriesIndex++, frames, frameIndex, xIndex, yIndex, dims));
|
||||
}
|
||||
}
|
||||
for (const series of options.series) {
|
||||
if (!series?.x) {
|
||||
throw 'Select X dimension';
|
||||
}
|
||||
|
||||
return scatterSeries;
|
||||
if (!series?.y) {
|
||||
throw 'Select Y dimension';
|
||||
}
|
||||
|
||||
for (let frameIndex = 0; frameIndex < frames.length; frameIndex++) {
|
||||
const frame = frames[frameIndex];
|
||||
const xIndex = findFieldIndex(frame, series.x);
|
||||
|
||||
if (xIndex != null) {
|
||||
// TODO: this should find multiple y fields
|
||||
const yIndex = findFieldIndex(frame, series.y);
|
||||
|
||||
if (yIndex == null) {
|
||||
throw 'Y must be in the same frame as X';
|
||||
}
|
||||
|
||||
const dims: Dims = {
|
||||
pointColorFixed: series.pointColor?.fixed,
|
||||
pointColorIndex: findFieldIndex(frame, series.pointColor?.field),
|
||||
pointSizeConfig: series.pointSize,
|
||||
pointSizeIndex: findFieldIndex(frame, series.pointSize?.field),
|
||||
};
|
||||
scatterSeries.push(getScatterSeries(seriesIndex++, frames, frameIndex, xIndex, yIndex, dims));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scatterSeries;
|
||||
}
|
||||
|
||||
// Default behavior
|
||||
@ -323,9 +325,9 @@ const prepConfig = (
|
||||
const scatterInfo = scatterSeries[seriesIdx - 1];
|
||||
let d = u.data[seriesIdx] as unknown as FacetSeries;
|
||||
|
||||
let showLine = scatterInfo.line !== ScatterLineMode.None;
|
||||
let showPoints = scatterInfo.point === VisibilityMode.Always;
|
||||
if (!showPoints && scatterInfo.point === VisibilityMode.Auto) {
|
||||
let showLine = scatterInfo.showLine;
|
||||
let showPoints = scatterInfo.showPoints === VisibilityMode.Always;
|
||||
if (!showPoints && scatterInfo.showPoints === VisibilityMode.Auto) {
|
||||
showPoints = d[0].length < 1000;
|
||||
}
|
||||
|
||||
@ -589,16 +591,22 @@ const prepConfig = (
|
||||
range: (u, min, max) => [min, max],
|
||||
});
|
||||
|
||||
// why does this fall back to '' instead of null or undef?
|
||||
let xAxisLabel = xField.config.custom.axisLabel;
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
placement:
|
||||
xField.config.custom?.axisPlacement !== AxisPlacement.Hidden ? AxisPlacement.Bottom : AxisPlacement.Hidden,
|
||||
show: xField.config.custom?.axisPlacement !== AxisPlacement.Hidden,
|
||||
theme,
|
||||
label: xField.config.custom.axisLabel,
|
||||
label:
|
||||
xAxisLabel == null || xAxisLabel === ''
|
||||
? getFieldDisplayName(xField, scatterSeries[0].frame(frames), frames)
|
||||
: xAxisLabel,
|
||||
});
|
||||
|
||||
scatterSeries.forEach((s) => {
|
||||
scatterSeries.forEach((s, si) => {
|
||||
let frame = s.frame(frames);
|
||||
let field = s.y(frame);
|
||||
|
||||
@ -617,11 +625,17 @@ const prepConfig = (
|
||||
});
|
||||
|
||||
if (field.config.custom?.axisPlacement !== AxisPlacement.Hidden) {
|
||||
// why does this fall back to '' instead of null or undef?
|
||||
let yAxisLabel = field.config.custom?.axisLabel;
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey,
|
||||
theme,
|
||||
placement: field.config.custom?.axisPlacement,
|
||||
label: field.config.custom.axisLabel,
|
||||
label:
|
||||
yAxisLabel == null || yAxisLabel === ''
|
||||
? getFieldDisplayName(field, scatterSeries[si].frame(frames), frames)
|
||||
: yAxisLabel,
|
||||
values: (u, splits) => splits.map((s) => field.display!(s).text),
|
||||
});
|
||||
}
|
||||
|
@ -3,8 +3,6 @@ import { LineStyle, VisibilityMode } from '@grafana/schema';
|
||||
import { VizLegendItem } from '@grafana/ui';
|
||||
import { ScaleDimensionConfig } from 'app/features/dimensions';
|
||||
|
||||
import { ScatterLineMode } from './models.gen';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -37,14 +35,14 @@ export interface ScatterSeries {
|
||||
x: (frame: DataFrame) => Field;
|
||||
y: (frame: DataFrame) => Field;
|
||||
|
||||
legend: (frame: DataFrame) => VizLegendItem[]; // could be single if symbol is constant
|
||||
legend: () => VizLegendItem[]; // could be single if symbol is constant
|
||||
|
||||
line: ScatterLineMode;
|
||||
showLine: boolean;
|
||||
lineWidth: number;
|
||||
lineStyle: LineStyle;
|
||||
lineColor: (frame: DataFrame) => CanvasRenderingContext2D['strokeStyle'];
|
||||
|
||||
point: VisibilityMode;
|
||||
showPoints: VisibilityMode;
|
||||
pointSize: DimensionValues<number>;
|
||||
pointColor: DimensionValues<CanvasRenderingContext2D['strokeStyle']>;
|
||||
pointSymbol: DimensionValues<string>; // single field, multiple symbols.... kinda equals multiple series
|
||||
|
Loading…
Reference in New Issue
Block a user