mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
VizTooltips: Heatmap fixes and improvements (#83876)
Co-authored-by: Adela Almasan <adela.almasan@grafana.com>
This commit is contained in:
@@ -85,7 +85,7 @@ export const getContentItems = (
|
||||
): VizTooltipItem[] => {
|
||||
let rows: VizTooltipItem[] = [];
|
||||
|
||||
let allNumeric = false;
|
||||
let allNumeric = true;
|
||||
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const field = fields[i];
|
||||
|
||||
@@ -39,7 +39,7 @@ export const ExemplarHoverView = ({ displayValues, links, header = 'Exemplar' }:
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{links && (
|
||||
{links && links.length > 0 && (
|
||||
<div className={styles.exemplarFooter}>
|
||||
{links.map((link, i) => (
|
||||
<LinkButton key={i} href={link.href} className={styles.linkButton}>
|
||||
|
||||
@@ -9,9 +9,7 @@ import {
|
||||
getFieldDisplayName,
|
||||
LinkModel,
|
||||
TimeRange,
|
||||
getLinksSupplier,
|
||||
InterpolateFunction,
|
||||
ScopedVars,
|
||||
} from '@grafana/data';
|
||||
import { HeatmapCellLayout } from '@grafana/schema';
|
||||
import { LinkButton, VerticalGroup } from '@grafana/ui';
|
||||
@@ -19,6 +17,8 @@ import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
|
||||
import { DataHoverView } from 'app/features/visualization/data-hover/DataHoverView';
|
||||
|
||||
import { getDataLinks } from '../status-history/utils';
|
||||
|
||||
import { HeatmapData } from './fields';
|
||||
import { renderHistogram } from './renderHistogram';
|
||||
import { HeatmapHoverEvent } from './utils';
|
||||
@@ -29,7 +29,6 @@ type Props = {
|
||||
showHistogram?: boolean;
|
||||
timeRange: TimeRange;
|
||||
replaceVars: InterpolateFunction;
|
||||
scopedVars: ScopedVars[];
|
||||
};
|
||||
|
||||
export const HeatmapHoverView = (props: Props) => {
|
||||
@@ -39,7 +38,7 @@ export const HeatmapHoverView = (props: Props) => {
|
||||
return <HeatmapHoverCell {...props} />;
|
||||
};
|
||||
|
||||
const HeatmapHoverCell = ({ data, hover, showHistogram = false, scopedVars, replaceVars }: Props) => {
|
||||
const HeatmapHoverCell = ({ data, hover, showHistogram = false }: Props) => {
|
||||
const index = hover.dataIdx;
|
||||
|
||||
const [isSparse] = useState(
|
||||
@@ -70,7 +69,8 @@ const HeatmapHoverCell = ({ data, hover, showHistogram = false, scopedVars, repl
|
||||
const meta = readHeatmapRowsCustomMeta(data.heatmap);
|
||||
const yDisp = yField?.display ? (v: string) => formattedValueToString(yField.display!(v)) : (v: string) => `${v}`;
|
||||
|
||||
const yValueIdx = index % data.yBucketCount! ?? 0;
|
||||
const yValueIdx = index % (data.yBucketCount ?? 1);
|
||||
const xValueIdx = Math.floor(index / (data.yBucketCount ?? 1));
|
||||
|
||||
let yBucketMin: string;
|
||||
let yBucketMax: string;
|
||||
@@ -126,33 +126,16 @@ const HeatmapHoverCell = ({ data, hover, showHistogram = false, scopedVars, repl
|
||||
|
||||
const count = countVals?.[index];
|
||||
|
||||
const visibleFields = data.heatmap?.fields.filter((f) => !Boolean(f.config.custom?.hideFrom?.tooltip));
|
||||
const links: Array<LinkModel<Field>> = [];
|
||||
const linkLookup = new Set<string>();
|
||||
let links: Array<LinkModel<Field>> = [];
|
||||
|
||||
for (const field of visibleFields ?? []) {
|
||||
const hasLinks = field.config.links && field.config.links.length > 0;
|
||||
const linksField = data.series?.fields[yValueIdx + 1];
|
||||
|
||||
if (hasLinks && data.heatmap) {
|
||||
const appropriateScopedVars = scopedVars.find(
|
||||
(scopedVar) =>
|
||||
scopedVar && scopedVar.__dataContext && scopedVar.__dataContext.value.field.name === nonNumericOrdinalDisplay
|
||||
);
|
||||
if (linksField != null) {
|
||||
const visible = !Boolean(linksField.config.custom?.hideFrom?.tooltip);
|
||||
const hasLinks = (linksField.config.links?.length ?? 0) > 0;
|
||||
|
||||
field.getLinks = getLinksSupplier(data.heatmap, field, appropriateScopedVars || {}, replaceVars);
|
||||
}
|
||||
|
||||
if (field.getLinks) {
|
||||
const value = field.values[index];
|
||||
const display = field.display ? field.display(value) : { text: `${value}`, numeric: +value };
|
||||
|
||||
field.getLinks({ calculatedValue: display, valueRowIndex: index }).forEach((link) => {
|
||||
const key = `${link.title}/${link.href}`;
|
||||
if (!linkLookup.has(key)) {
|
||||
links.push(link);
|
||||
linkLookup.add(key);
|
||||
}
|
||||
});
|
||||
if (visible && hasLinks) {
|
||||
links = getDataLinks(linksField, xValueIdx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import {
|
||||
DashboardCursorSync,
|
||||
DataFrame,
|
||||
DataFrameType,
|
||||
Field,
|
||||
getLinksSupplier,
|
||||
GrafanaTheme2,
|
||||
PanelProps,
|
||||
ScopedVars,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { DashboardCursorSync, DataFrameType, GrafanaTheme2, PanelProps, TimeRange } from '@grafana/data';
|
||||
import { config, PanelDataErrorView } from '@grafana/runtime';
|
||||
import { ScaleDistributionConfig } from '@grafana/schema';
|
||||
import {
|
||||
@@ -34,8 +24,8 @@ import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/tra
|
||||
import { AnnotationsPlugin2 } from '../timeseries/plugins/AnnotationsPlugin2';
|
||||
|
||||
import { ExemplarModalHeader } from './ExemplarModalHeader';
|
||||
import { HeatmapHoverView } from './HeatmapHoverView';
|
||||
import { HeatmapHoverView as HeatmapHoverViewOld } from './HeatmapHoverViewOld';
|
||||
import { HeatmapHoverView } from './HeatmapHoverViewOld';
|
||||
import { HeatmapTooltip } from './HeatmapTooltip';
|
||||
import { prepareHeatmapData } from './fields';
|
||||
import { quantizeScheme } from './palettes';
|
||||
import { Options } from './types';
|
||||
@@ -70,50 +60,26 @@ export const HeatmapPanel = ({
|
||||
// temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2
|
||||
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);
|
||||
|
||||
// necessary for enabling datalinks in hover view
|
||||
let scopedVarsFromRawData: ScopedVars[] = [];
|
||||
for (const series of data.series) {
|
||||
for (const field of series.fields) {
|
||||
if (field.state?.scopedVars) {
|
||||
scopedVarsFromRawData.push(field.state.scopedVars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ugh
|
||||
let timeRangeRef = useRef<TimeRange>(timeRange);
|
||||
timeRangeRef.current = timeRange;
|
||||
|
||||
const getFieldLinksSupplier = useCallback(
|
||||
(exemplars: DataFrame, field: Field) => {
|
||||
return getLinksSupplier(exemplars, field, field.state?.scopedVars ?? {}, replaceVariables);
|
||||
},
|
||||
[replaceVariables]
|
||||
);
|
||||
|
||||
const palette = useMemo(() => quantizeScheme(options.color, theme), [options.color, theme]);
|
||||
|
||||
const info = useMemo(() => {
|
||||
try {
|
||||
return prepareHeatmapData(
|
||||
data.series,
|
||||
data.annotations,
|
||||
options,
|
||||
palette,
|
||||
theme,
|
||||
getFieldLinksSupplier,
|
||||
replaceVariables
|
||||
);
|
||||
return prepareHeatmapData(data.series, data.annotations, options, palette, theme, replaceVariables);
|
||||
} catch (ex) {
|
||||
return { warning: `${ex}` };
|
||||
}
|
||||
}, [data.series, data.annotations, options, palette, theme, getFieldLinksSupplier, replaceVariables]);
|
||||
}, [data.series, data.annotations, options, palette, theme, replaceVariables]);
|
||||
|
||||
const facets = useMemo(() => {
|
||||
let exemplarsXFacet: number[] | undefined = []; // "Time" field
|
||||
let exemplarsYFacet: Array<number | undefined> = [];
|
||||
|
||||
const meta = readHeatmapRowsCustomMeta(info.heatmap);
|
||||
|
||||
if (info.exemplars?.length) {
|
||||
exemplarsXFacet = info.exemplars?.fields[0].values;
|
||||
|
||||
@@ -265,7 +231,7 @@ export const HeatmapPanel = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<HeatmapHoverView
|
||||
<HeatmapTooltip
|
||||
mode={viaSync ? TooltipDisplayMode.Multi : options.tooltip.mode}
|
||||
dataIdxs={dataIdxs}
|
||||
seriesIdx={seriesIdx}
|
||||
@@ -275,8 +241,6 @@ export const HeatmapPanel = ({
|
||||
showHistogram={options.tooltip.yHistogram}
|
||||
showColorScale={options.tooltip.showColorScale}
|
||||
panelData={data}
|
||||
replaceVars={replaceVariables}
|
||||
scopedVars={scopedVarsFromRawData}
|
||||
annotate={enableAnnotationCreation ? annotate : undefined}
|
||||
/>
|
||||
);
|
||||
@@ -308,13 +272,12 @@ export const HeatmapPanel = ({
|
||||
allowPointerEvents={isToolTipOpen.current}
|
||||
>
|
||||
{shouldDisplayCloseButton && <ExemplarModalHeader onClick={onCloseToolTip} />}
|
||||
<HeatmapHoverViewOld
|
||||
<HeatmapHoverView
|
||||
timeRange={timeRange}
|
||||
data={info}
|
||||
hover={hover}
|
||||
showHistogram={options.tooltip.yHistogram}
|
||||
replaceVars={replaceVariables}
|
||||
scopedVars={scopedVarsFromRawData}
|
||||
/>
|
||||
</VizTooltipContainer>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ReactElement, useEffect, useRef, useState } from 'react';
|
||||
import React, { ReactElement, useEffect, useRef, useState, ReactNode } from 'react';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import {
|
||||
@@ -7,11 +7,8 @@ import {
|
||||
FieldType,
|
||||
formattedValueToString,
|
||||
getFieldDisplayName,
|
||||
getLinksSupplier,
|
||||
InterpolateFunction,
|
||||
LinkModel,
|
||||
PanelData,
|
||||
ScopedVars,
|
||||
} from '@grafana/data';
|
||||
import { HeatmapCellLayout } from '@grafana/schema';
|
||||
import { TooltipDisplayMode, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
@@ -24,13 +21,14 @@ import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
|
||||
import { DataHoverView } from 'app/features/visualization/data-hover/DataHoverView';
|
||||
|
||||
import { getDataLinks } from '../status-history/utils';
|
||||
import { getStyles } from '../timeseries/TimeSeriesTooltip';
|
||||
|
||||
import { HeatmapData } from './fields';
|
||||
import { renderHistogram } from './renderHistogram';
|
||||
import { formatMilliseconds, getFieldFromData, getHoverCellColor, getSparseCellMinMax } from './tooltip/utils';
|
||||
|
||||
interface Props {
|
||||
interface HeatmapTooltipProps {
|
||||
mode: TooltipDisplayMode;
|
||||
dataIdxs: Array<number | null>;
|
||||
seriesIdx: number | null | undefined;
|
||||
@@ -40,12 +38,10 @@ interface Props {
|
||||
isPinned: boolean;
|
||||
dismiss: () => void;
|
||||
panelData: PanelData;
|
||||
replaceVars: InterpolateFunction;
|
||||
scopedVars: ScopedVars[];
|
||||
annotate?: () => void;
|
||||
}
|
||||
|
||||
export const HeatmapHoverView = (props: Props) => {
|
||||
export const HeatmapTooltip = (props: HeatmapTooltipProps) => {
|
||||
if (props.seriesIdx === 2) {
|
||||
return (
|
||||
<DataHoverView
|
||||
@@ -66,11 +62,9 @@ const HeatmapHoverCell = ({
|
||||
showHistogram,
|
||||
isPinned,
|
||||
showColorScale = false,
|
||||
scopedVars,
|
||||
replaceVars,
|
||||
mode,
|
||||
annotate,
|
||||
}: Props) => {
|
||||
}: HeatmapTooltipProps) => {
|
||||
const index = dataIdxs[1]!;
|
||||
const data = dataRef.current;
|
||||
|
||||
@@ -114,11 +108,8 @@ const HeatmapHoverCell = ({
|
||||
|
||||
let contentItems: VizTooltipItem[] = [];
|
||||
|
||||
const getYValueIndex = (idx: number) => {
|
||||
return idx % data.yBucketCount! ?? 0;
|
||||
};
|
||||
|
||||
let yValueIdx = getYValueIndex(index);
|
||||
const yValueIdx = index % (data.yBucketCount ?? 1);
|
||||
const xValueIdx = Math.floor(index / (data.yBucketCount ?? 1));
|
||||
|
||||
const getData = (idx: number = index) => {
|
||||
if (meta.yOrdinalDisplay) {
|
||||
@@ -187,7 +178,6 @@ const HeatmapHoverCell = ({
|
||||
if (isSparse) {
|
||||
({ xBucketMin, xBucketMax, yBucketMin, yBucketMax } = getSparseCellMinMax(data!, idx));
|
||||
} else {
|
||||
yValueIdx = getYValueIndex(idx);
|
||||
getData(idx);
|
||||
}
|
||||
|
||||
@@ -283,34 +273,23 @@ const HeatmapHoverCell = ({
|
||||
});
|
||||
}
|
||||
|
||||
const visibleFields = data.heatmap?.fields.filter((f) => !Boolean(f.config.custom?.hideFrom?.tooltip));
|
||||
const links: Array<LinkModel<Field>> = [];
|
||||
const linkLookup = new Set<string>();
|
||||
let footer: ReactNode;
|
||||
|
||||
for (const field of visibleFields ?? []) {
|
||||
const hasLinks = field.config.links && field.config.links.length > 0;
|
||||
if (isPinned) {
|
||||
let links: Array<LinkModel<Field>> = [];
|
||||
|
||||
if (hasLinks && data.heatmap) {
|
||||
const appropriateScopedVars = scopedVars.find(
|
||||
(scopedVar) =>
|
||||
scopedVar && scopedVar.__dataContext && scopedVar.__dataContext.value.field.name === nonNumericOrdinalDisplay
|
||||
);
|
||||
const linksField = data.series?.fields[yValueIdx + 1];
|
||||
|
||||
field.getLinks = getLinksSupplier(data.heatmap, field, appropriateScopedVars || {}, replaceVars);
|
||||
if (linksField != null) {
|
||||
const visible = !Boolean(linksField.config.custom?.hideFrom?.tooltip);
|
||||
const hasLinks = (linksField.config.links?.length ?? 0) > 0;
|
||||
|
||||
if (visible && hasLinks) {
|
||||
links = getDataLinks(linksField, xValueIdx);
|
||||
}
|
||||
}
|
||||
|
||||
if (field.getLinks) {
|
||||
const value = field.values[index];
|
||||
const display = field.display ? field.display(value) : { text: `${value}`, numeric: +value };
|
||||
|
||||
field.getLinks({ calculatedValue: display, valueRowIndex: index }).forEach((link) => {
|
||||
const key = `${link.title}/${link.href}`;
|
||||
if (!linkLookup.has(key)) {
|
||||
links.push(link);
|
||||
linkLookup.add(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
footer = <VizTooltipFooter dataLinks={links} annotate={annotate} />;
|
||||
}
|
||||
|
||||
let can = useRef<HTMLCanvasElement>(null);
|
||||
@@ -377,9 +356,7 @@ const HeatmapHoverCell = ({
|
||||
</div>
|
||||
))}
|
||||
</VizTooltipContent>
|
||||
{(links.length > 0 || isPinned) && (
|
||||
<VizTooltipFooter dataLinks={links} annotate={isPinned ? annotate : undefined} />
|
||||
)}
|
||||
{footer}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,12 +6,11 @@ import {
|
||||
FieldType,
|
||||
formattedValueToString,
|
||||
getDisplayProcessor,
|
||||
getLinksSupplier,
|
||||
GrafanaTheme2,
|
||||
InterpolateFunction,
|
||||
LinkModel,
|
||||
outerJoinDataFrames,
|
||||
ValueFormatter,
|
||||
ValueLinkConfig,
|
||||
} from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { HeatmapCellLayout } from '@grafana/schema';
|
||||
@@ -39,6 +38,8 @@ export interface HeatmapData {
|
||||
maxValue: number;
|
||||
};
|
||||
|
||||
series?: DataFrame; // the joined single frame for nonNumericOrdinalY data links
|
||||
|
||||
exemplars?: DataFrame; // optionally linked exemplars
|
||||
exemplarColor?: string;
|
||||
|
||||
@@ -70,8 +71,7 @@ export function prepareHeatmapData(
|
||||
options: Options,
|
||||
palette: string[],
|
||||
theme: GrafanaTheme2,
|
||||
getFieldLinks?: (exemplars: DataFrame, field: Field) => (config: ValueLinkConfig) => Array<LinkModel<Field>>,
|
||||
replaceVariables?: InterpolateFunction
|
||||
replaceVariables: InterpolateFunction = (v) => v
|
||||
): HeatmapData {
|
||||
if (!frames?.length) {
|
||||
return {};
|
||||
@@ -81,11 +81,9 @@ export function prepareHeatmapData(
|
||||
|
||||
const exemplars = annotations?.find((f) => f.name === 'exemplar');
|
||||
|
||||
if (getFieldLinks) {
|
||||
exemplars?.fields.forEach((field, index) => {
|
||||
exemplars.fields[index].getLinks = getFieldLinks(exemplars, field);
|
||||
});
|
||||
}
|
||||
exemplars?.fields.forEach((field) => {
|
||||
field.getLinks = getLinksSupplier(exemplars, field, field.state?.scopedVars ?? {}, replaceVariables);
|
||||
});
|
||||
|
||||
if (options.calculate) {
|
||||
if (config.featureToggles.transformationsVariableSupport) {
|
||||
@@ -138,7 +136,7 @@ export function prepareHeatmapData(
|
||||
}
|
||||
|
||||
// Everything past here assumes a field for each row in the heatmap (buckets)
|
||||
if (!rowsHeatmap) {
|
||||
if (rowsHeatmap == null) {
|
||||
if (frames.length > 1) {
|
||||
let allNamesNumeric = frames.every(
|
||||
(frame) => !Number.isNaN(parseSampleValue(frame.fields[1].state?.displayName!))
|
||||
@@ -148,11 +146,10 @@ export function prepareHeatmapData(
|
||||
frames.sort(sortSeriesByLabel);
|
||||
}
|
||||
|
||||
rowsHeatmap = [
|
||||
outerJoinDataFrames({
|
||||
frames,
|
||||
})!,
|
||||
][0];
|
||||
rowsHeatmap = outerJoinDataFrames({
|
||||
frames,
|
||||
keepDisplayNames: true,
|
||||
})!;
|
||||
} else {
|
||||
let frame = frames[0];
|
||||
let numberFields = frame.fields.filter((field) => field.type === FieldType.number);
|
||||
@@ -171,18 +168,31 @@ export function prepareHeatmapData(
|
||||
}
|
||||
}
|
||||
|
||||
return getDenseHeatmapData(
|
||||
rowsToCellsHeatmap({
|
||||
unit: options.yAxis?.unit, // used to format the ordinal lookup values
|
||||
decimals: options.yAxis?.decimals,
|
||||
...options.rowsFrame,
|
||||
frame: rowsHeatmap,
|
||||
}),
|
||||
exemplars,
|
||||
options,
|
||||
palette,
|
||||
theme
|
||||
);
|
||||
// config data links
|
||||
rowsHeatmap.fields.forEach((field) => {
|
||||
if ((field.config.links?.length ?? 0) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this expects that the tooltip is able to identify the field and rowIndex from a dense hovered index
|
||||
field.getLinks = getLinksSupplier(rowsHeatmap!, field, field.state?.scopedVars ?? {}, replaceVariables);
|
||||
});
|
||||
|
||||
return {
|
||||
...getDenseHeatmapData(
|
||||
rowsToCellsHeatmap({
|
||||
unit: options.yAxis?.unit, // used to format the ordinal lookup values
|
||||
decimals: options.yAxis?.decimals,
|
||||
...options.rowsFrame,
|
||||
frame: rowsHeatmap,
|
||||
}),
|
||||
exemplars,
|
||||
options,
|
||||
palette,
|
||||
theme
|
||||
),
|
||||
series: rowsHeatmap,
|
||||
};
|
||||
}
|
||||
|
||||
const getSparseHeatmapData = (
|
||||
|
||||
@@ -53,15 +53,7 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(HeatmapPanel)
|
||||
// NOTE: this feels like overkill/expensive just to assert if we have an ordinal y
|
||||
// can probably simplify without doing full dataprep
|
||||
const palette = quantizeScheme(opts.color, config.theme2);
|
||||
const v = prepareHeatmapData(
|
||||
context.data,
|
||||
undefined,
|
||||
opts,
|
||||
palette,
|
||||
config.theme2,
|
||||
undefined,
|
||||
context.replaceVariables
|
||||
);
|
||||
const v = prepareHeatmapData(context.data, undefined, opts, palette, config.theme2);
|
||||
isOrdinalY = readHeatmapRowsCustomMeta(v.heatmap).yOrdinalDisplay != null;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -557,7 +557,8 @@ export function prepConfig(opts: PrepConfigOpts) {
|
||||
});
|
||||
},
|
||||
},
|
||||
exemplarFillColor
|
||||
exemplarFillColor,
|
||||
dataRef.current.yLayout
|
||||
),
|
||||
theme,
|
||||
scaleKey: '', // facets' scales used (above)
|
||||
@@ -585,6 +586,10 @@ export function prepConfig(opts: PrepConfigOpts) {
|
||||
|
||||
return hRect && seriesIdx === hRect.sidx ? hRect.didx : null;
|
||||
},
|
||||
focus: {
|
||||
prox: 1e3,
|
||||
dist: (u, seriesIdx) => (hRect?.sidx === seriesIdx ? 0 : Infinity),
|
||||
},
|
||||
points: {
|
||||
fill: 'rgba(255,255,255, 0.3)',
|
||||
bbox: (u, seriesIdx) => {
|
||||
@@ -744,7 +749,7 @@ export function heatmapPathsDense(opts: PathbuilderOpts) {
|
||||
};
|
||||
}
|
||||
|
||||
export function heatmapPathsPoints(opts: PointsBuilderOpts, exemplarColor: string) {
|
||||
export function heatmapPathsPoints(opts: PointsBuilderOpts, exemplarColor: string, yLayout?: HeatmapCellLayout) {
|
||||
return (u: uPlot, seriesIdx: number) => {
|
||||
uPlot.orient(
|
||||
u,
|
||||
@@ -772,6 +777,8 @@ export function heatmapPathsPoints(opts: PointsBuilderOpts, exemplarColor: strin
|
||||
let fillPaths = [points];
|
||||
let fillPalette = [exemplarColor ?? 'rgba(255,0,255,0.7)'];
|
||||
|
||||
let yShift = yLayout === HeatmapCellLayout.le ? -0.5 : yLayout === HeatmapCellLayout.ge ? 0.5 : 0;
|
||||
|
||||
for (let i = 0; i < dataX.length; i++) {
|
||||
let yVal = dataY[i]!;
|
||||
|
||||
@@ -782,10 +789,7 @@ export function heatmapPathsPoints(opts: PointsBuilderOpts, exemplarColor: strin
|
||||
let isSparseHeatmap = scaleY.distr === 3 && scaleY.log === 2;
|
||||
|
||||
if (!isSparseHeatmap) {
|
||||
yVal -= 0.5; // center vertically in bucket (when tiles are le)
|
||||
// y-randomize vertically to distribute exemplars in same bucket at same time
|
||||
let randSign = Math.round(Math.random()) * 2 - 1;
|
||||
yVal += randSign * 0.5 * Math.random();
|
||||
yVal += yShift;
|
||||
}
|
||||
|
||||
let x = valToPosX(dataX[i], scaleX, xDim, xOff);
|
||||
|
||||
@@ -56,7 +56,7 @@ export const TimeSeriesTooltip = ({
|
||||
seriesIdx,
|
||||
mode,
|
||||
sortOrder,
|
||||
(field) => field.type === FieldType.number
|
||||
(field) => field.type === FieldType.number || field.type === FieldType.enum
|
||||
);
|
||||
|
||||
let footer: ReactNode;
|
||||
|
||||
Reference in New Issue
Block a user