mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Timeline: Adds opacity & line width option (#34118)
* Timeline: Adds opacity & line width option * Updated devenv dashboards, added back original timeline modes
This commit is contained in:
parent
c61a610f72
commit
953aadd6e4
414
devenv/dev-dashboards/panel-timeline/timeline-demo.json
Normal file
414
devenv/dev-dashboards/panel-timeline/timeline-demo.json
Normal file
@ -0,0 +1,414 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 80,
|
||||
"lineWidth": 1
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
"CRITICAL": {
|
||||
"color": "red",
|
||||
"index": 3
|
||||
},
|
||||
"HIGH": {
|
||||
"color": "orange",
|
||||
"index": 2
|
||||
},
|
||||
"LOW": {
|
||||
"color": "blue",
|
||||
"index": 0
|
||||
},
|
||||
"NORMAL": {
|
||||
"color": "green",
|
||||
"index": 1
|
||||
}
|
||||
},
|
||||
"type": "value"
|
||||
}
|
||||
],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 9,
|
||||
"options": {
|
||||
"alignValue": "center",
|
||||
"colWidth": 0.9,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"mode": "changes",
|
||||
"rowHeight": 0.98,
|
||||
"showValue": "always"
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "SensorA",
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "LOW,HIGH,NORMAL,NORMAL,NORMAL,LOW,LOW,NORMAL,HIGH,CRITICAL"
|
||||
},
|
||||
{
|
||||
"alias": "SensorB",
|
||||
"hide": false,
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "NORMAL,LOW,LOW,CRITICAL,CRITICAL,LOW,LOW,NORMAL,HIGH,CRITICAL"
|
||||
},
|
||||
{
|
||||
"alias": "SensorA",
|
||||
"hide": false,
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "NORMAL,NORMAL,NORMAL,NORMAL,CRITICAL,LOW,NORMAL,NORMAL,NORMAL,LOW"
|
||||
}
|
||||
],
|
||||
"title": "State changes strings",
|
||||
"type": "timeline"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 70,
|
||||
"lineWidth": 1
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
"match": "true",
|
||||
"result": {
|
||||
"color": "semi-dark-green",
|
||||
"index": 0,
|
||||
"text": "ON"
|
||||
}
|
||||
},
|
||||
"type": "special"
|
||||
},
|
||||
{
|
||||
"options": {
|
||||
"match": "false",
|
||||
"result": {
|
||||
"color": "red",
|
||||
"index": 1,
|
||||
"text": "OFF"
|
||||
}
|
||||
},
|
||||
"type": "special"
|
||||
}
|
||||
],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
"w": 13,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
},
|
||||
"id": 13,
|
||||
"options": {
|
||||
"alignValue": "center",
|
||||
"colWidth": 1,
|
||||
"mode": "changes",
|
||||
"rowHeight": 0.98,
|
||||
"showValue": "always"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "true,false,true,true,true,true,false,false"
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "false,true,false,true,true,false,false,false,true,true"
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "true,false,true,true"
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"refId": "D",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "false,true,false,true,true"
|
||||
}
|
||||
],
|
||||
"title": "State changes with boolean values",
|
||||
"type": "timeline"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"description": "Should show gaps",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 80,
|
||||
"lineWidth": 1
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
"match": "true",
|
||||
"result": {
|
||||
"color": "semi-dark-green",
|
||||
"index": 0,
|
||||
"text": "ON"
|
||||
}
|
||||
},
|
||||
"type": "special"
|
||||
},
|
||||
{
|
||||
"options": {
|
||||
"match": "false",
|
||||
"result": {
|
||||
"color": "red",
|
||||
"index": 1,
|
||||
"text": "OFF"
|
||||
}
|
||||
},
|
||||
"type": "special"
|
||||
}
|
||||
],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
"w": 11,
|
||||
"x": 13,
|
||||
"y": 8
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"alignValue": "center",
|
||||
"colWidth": 1,
|
||||
"mode": "changes",
|
||||
"rowHeight": 0.98,
|
||||
"showValue": "always"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "true,false,true,true,true,true,false,false"
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "false,true,false,true,true,false,false,false,true,true"
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "true,false,null,true,true"
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"refId": "D",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "false,null,null,false,true,true"
|
||||
}
|
||||
],
|
||||
"title": "State changes with nulls",
|
||||
"type": "timeline"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-GrYlRd"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 96,
|
||||
"lineWidth": 0
|
||||
},
|
||||
"decimals": 0,
|
||||
"mappings": [],
|
||||
"max": 30,
|
||||
"min": -10,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 19
|
||||
},
|
||||
"id": 4,
|
||||
"interval": null,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"alignValue": "center",
|
||||
"colWidth": 0.96,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"mode": "samples",
|
||||
"rowHeight": 0.98,
|
||||
"showValue": "always"
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"csvWave": {
|
||||
"timeStep": 60,
|
||||
"valuesCSV": "0,0,2,2,1,1"
|
||||
},
|
||||
"lines": 10,
|
||||
"max": 30,
|
||||
"min": -10,
|
||||
"noise": 2,
|
||||
"points": [],
|
||||
"pulseWave": {
|
||||
"offCount": 3,
|
||||
"offValue": 1,
|
||||
"onCount": 3,
|
||||
"onValue": 2,
|
||||
"timeStep": 60
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 4,
|
||||
"spread": 15,
|
||||
"startValue": 5,
|
||||
"stream": {
|
||||
"bands": 1,
|
||||
"noise": 2.2,
|
||||
"speed": 250,
|
||||
"spread": 3.5,
|
||||
"type": "signal"
|
||||
},
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"title": "\"Periodic samples\" Mode",
|
||||
"type": "timeline"
|
||||
}
|
||||
],
|
||||
"refresh": false,
|
||||
"schemaVersion": 30,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"gdev",
|
||||
"demo"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "utc",
|
||||
"title": "Timeline Demo",
|
||||
"uid": "mIJjFy8Kz",
|
||||
"version": 13
|
||||
}
|
@ -338,7 +338,7 @@
|
||||
"refresh": false,
|
||||
"schemaVersion": 29,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"tags": ["gdev", "panel-tests"],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
|
@ -257,7 +257,7 @@ export function createColors(colors: ThemeColorsInput): ThemeColors {
|
||||
|
||||
function getContrastText(background: string, threshold: number = contrastThreshold) {
|
||||
const contrastText =
|
||||
getContrastRatio(background, dark.text.maxContrast) >= threshold ? dark.text.maxContrast : light.text.maxContrast;
|
||||
getContrastRatio(dark.text.maxContrast, background) >= threshold ? dark.text.maxContrast : light.text.maxContrast;
|
||||
// todo, need color framework
|
||||
return contrastText;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { FieldConfigEditorProps, FieldConfigPropertyItem, FieldConfigEditorConfig } from '../types/fieldOverrides';
|
||||
import { OptionsUIRegistryBuilder } from '../types/OptionsUIRegistryBuilder';
|
||||
import { FieldType } from '../types/dataFrame';
|
||||
import { PanelOptionsEditorConfig, PanelOptionsEditorItem } from '../types/panel';
|
||||
import {
|
||||
numberOverrideProcessor,
|
||||
@ -33,7 +32,7 @@ export class FieldConfigEditorBuilder<TOptions> extends OptionsUIRegistryBuilder
|
||||
override: standardEditorsRegistry.get('number').editor as any,
|
||||
editor: standardEditorsRegistry.get('number').editor as any,
|
||||
process: numberOverrideProcessor,
|
||||
shouldApply: config.shouldApply ? config.shouldApply : (field) => field.type === FieldType.number,
|
||||
shouldApply: config.shouldApply ?? (() => true),
|
||||
settings: config.settings || {},
|
||||
});
|
||||
}
|
||||
@ -45,7 +44,7 @@ export class FieldConfigEditorBuilder<TOptions> extends OptionsUIRegistryBuilder
|
||||
override: standardEditorsRegistry.get('slider').editor as any,
|
||||
editor: standardEditorsRegistry.get('slider').editor as any,
|
||||
process: numberOverrideProcessor,
|
||||
shouldApply: config.shouldApply ? config.shouldApply : (field) => field.type === FieldType.number,
|
||||
shouldApply: config.shouldApply ?? (() => true),
|
||||
settings: config.settings || {},
|
||||
});
|
||||
}
|
||||
@ -57,7 +56,7 @@ export class FieldConfigEditorBuilder<TOptions> extends OptionsUIRegistryBuilder
|
||||
override: standardEditorsRegistry.get('text').editor as any,
|
||||
editor: standardEditorsRegistry.get('text').editor as any,
|
||||
process: stringOverrideProcessor,
|
||||
shouldApply: config.shouldApply ? config.shouldApply : (field) => field.type === FieldType.string,
|
||||
shouldApply: config.shouldApply ?? (() => true),
|
||||
settings: config.settings || {},
|
||||
});
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ export class UPlotConfigBuilder {
|
||||
setStacking(enabled = true) {
|
||||
this.isStacking = enabled;
|
||||
}
|
||||
|
||||
addSeries(props: SeriesProps) {
|
||||
this.series.push(new UPlotSeriesBuilder(props));
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||
import { TimelinePanel } from './TimelinePanel';
|
||||
import { TimelineOptions, TimelineFieldConfig, TimelineMode } from './types';
|
||||
import { TimelineOptions, TimelineFieldConfig, TimelineMode, defaultTimelineFieldConfig } from './types';
|
||||
import { BarValueVisibility } from '@grafana/ui';
|
||||
|
||||
export const plugin = new PanelPlugin<TimelineOptions, TimelineFieldConfig>(TimelinePanel)
|
||||
@ -15,15 +15,12 @@ export const plugin = new PanelPlugin<TimelineOptions, TimelineFieldConfig>(Time
|
||||
},
|
||||
},
|
||||
},
|
||||
/*
|
||||
useCustomConfig: (builder) => {
|
||||
const cfg = defaultBarChartFieldConfig;
|
||||
|
||||
builder
|
||||
.addSliderInput({
|
||||
path: 'lineWidth',
|
||||
name: 'Line width',
|
||||
defaultValue: cfg.lineWidth,
|
||||
defaultValue: defaultTimelineFieldConfig.lineWidth,
|
||||
settings: {
|
||||
min: 0,
|
||||
max: 10,
|
||||
@ -33,26 +30,14 @@ export const plugin = new PanelPlugin<TimelineOptions, TimelineFieldConfig>(Time
|
||||
.addSliderInput({
|
||||
path: 'fillOpacity',
|
||||
name: 'Fill opacity',
|
||||
defaultValue: cfg.fillOpacity,
|
||||
defaultValue: defaultTimelineFieldConfig.fillOpacity,
|
||||
settings: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
})
|
||||
.addRadio({
|
||||
path: 'gradientMode',
|
||||
name: 'Gradient mode',
|
||||
defaultValue: graphFieldOptions.fillGradient[0].value,
|
||||
settings: {
|
||||
options: graphFieldOptions.fillGradient,
|
||||
},
|
||||
});
|
||||
|
||||
// addAxisConfig(builder, cfg, true);
|
||||
addHideFrom(builder);
|
||||
},
|
||||
*/
|
||||
})
|
||||
.setPanelOptions((builder) => {
|
||||
builder
|
||||
|
@ -2,9 +2,10 @@ import uPlot, { Series, Cursor } from 'uplot';
|
||||
import { FIXED_UNIT } from '@grafana/ui/src/components/GraphNG/GraphNG';
|
||||
import { Quadtree, Rect, pointWithin } from 'app/plugins/panel/barchart/quadtree';
|
||||
import { distribute, SPACE_BETWEEN } from 'app/plugins/panel/barchart/distribute';
|
||||
import { TimelineMode, TimelineValueAlignment } from './types';
|
||||
import { TimelineFieldConfig, TimelineMode, TimelineValueAlignment } from './types';
|
||||
import { GrafanaTheme2, TimeRange } from '@grafana/data';
|
||||
import { BarValueVisibility } from '@grafana/ui';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
const { round, min, ceil } = Math;
|
||||
|
||||
@ -26,6 +27,7 @@ function walk(rowHeight: number, yIdx: number | null, count: number, dim: number
|
||||
interface TimelineBoxRect extends Rect {
|
||||
left: number;
|
||||
strokeWidth: number;
|
||||
fillColor: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,12 +42,11 @@ export interface TimelineCoreOptions {
|
||||
showValue: BarValueVisibility;
|
||||
alignValue: TimelineValueAlignment;
|
||||
isDiscrete: (seriesIdx: number) => boolean;
|
||||
colorLookup: (seriesIdx: number, value: any) => string;
|
||||
getValueColor: (seriesIdx: number, value: any) => string;
|
||||
label: (seriesIdx: number) => string;
|
||||
fill: (seriesIdx: number, value: any) => CanvasRenderingContext2D['fillStyle'];
|
||||
stroke: (seriesIdx: number, value: any) => CanvasRenderingContext2D['strokeStyle'];
|
||||
getTimeRange: () => TimeRange;
|
||||
formatValue?: (seriesIdx: number, value: any) => string;
|
||||
getFieldConfig: (seriesIdx: number) => TimelineFieldConfig;
|
||||
onHover?: (seriesIdx: number, valueIdx: number) => void;
|
||||
onLeave?: (seriesIdx: number, valueIdx: number) => void;
|
||||
}
|
||||
@ -64,11 +65,10 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
alignValue,
|
||||
theme,
|
||||
label,
|
||||
fill,
|
||||
stroke,
|
||||
formatValue,
|
||||
getTimeRange,
|
||||
colorLookup,
|
||||
getValueColor,
|
||||
getFieldConfig,
|
||||
// onHover,
|
||||
// onLeave,
|
||||
} = opts;
|
||||
@ -85,7 +85,7 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
return mark;
|
||||
});
|
||||
|
||||
// alignement-aware text position cache filled by drawPaths->putBox for use in drawPoints
|
||||
// Needed for to calculate text positions
|
||||
let boxRectsBySeries: TimelineBoxRect[][];
|
||||
|
||||
const resetBoxRectsBySeries = (count: number) => {
|
||||
@ -134,8 +134,32 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
value: any,
|
||||
discrete: boolean
|
||||
) {
|
||||
// do not render super small boxes
|
||||
if (boxWidth < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const valueColor = getValueColor(seriesIdx + 1, value);
|
||||
const fieldConfig = getFieldConfig(seriesIdx);
|
||||
const fillColor = getFillColor(fieldConfig, valueColor);
|
||||
|
||||
const boxRect = (boxRectsBySeries[seriesIdx][valueIdx] = {
|
||||
x: round(left - xOff),
|
||||
y: round(top - yOff),
|
||||
w: boxWidth,
|
||||
h: boxHeight,
|
||||
sidx: seriesIdx + 1,
|
||||
didx: valueIdx,
|
||||
// These two are needed for later text positioning
|
||||
left: left,
|
||||
strokeWidth,
|
||||
fillColor,
|
||||
});
|
||||
|
||||
qt.add(boxRect);
|
||||
|
||||
if (discrete) {
|
||||
let fillStyle = fill(seriesIdx + 1, value);
|
||||
let fillStyle = fillColor;
|
||||
let fillPath = fillPaths.get(fillStyle);
|
||||
|
||||
if (fillPath == null) {
|
||||
@ -145,7 +169,7 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
rect(fillPath, left, top, boxWidth, boxHeight);
|
||||
|
||||
if (strokeWidth) {
|
||||
let strokeStyle = stroke(seriesIdx + 1, value);
|
||||
let strokeStyle = valueColor;
|
||||
let strokePath = strokePaths.get(strokeStyle);
|
||||
|
||||
if (strokePath == null) {
|
||||
@ -163,30 +187,17 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
rect(ctx, left, top, boxWidth, boxHeight);
|
||||
ctx.fillStyle = fill(seriesIdx, value);
|
||||
ctx.fillStyle = fillColor;
|
||||
ctx.fill();
|
||||
|
||||
if (strokeWidth) {
|
||||
ctx.beginPath();
|
||||
rect(ctx, left + strokeWidth / 2, top + strokeWidth / 2, boxWidth - strokeWidth, boxHeight - strokeWidth);
|
||||
ctx.strokeStyle = stroke(seriesIdx, value);
|
||||
ctx.strokeStyle = valueColor;
|
||||
ctx.lineWidth = strokeWidth;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
const boxRect = (boxRectsBySeries[seriesIdx][valueIdx] = {
|
||||
x: round(left - xOff),
|
||||
y: round(top - yOff),
|
||||
w: boxWidth,
|
||||
h: boxHeight,
|
||||
sidx: seriesIdx + 1,
|
||||
didx: valueIdx,
|
||||
// These two are needed for later text positioning
|
||||
left: left,
|
||||
strokeWidth,
|
||||
});
|
||||
|
||||
qt.add(boxRect);
|
||||
}
|
||||
|
||||
const drawPaths: Series.PathBuilder = (u, sidx, idx0, idx1) => {
|
||||
@ -202,17 +213,17 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
rect(u.ctx, u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
|
||||
u.ctx.clip();
|
||||
|
||||
walk(rowHeight, sidx - 1, numSeries, yDim, (iy, y0, hgt) => {
|
||||
walk(rowHeight, sidx - 1, numSeries, yDim, (iy, y0, height) => {
|
||||
if (mode === TimelineMode.Changes) {
|
||||
for (let ix = 0; ix < dataY.length; ix++) {
|
||||
if (dataY[ix] != null) {
|
||||
let lft = Math.round(valToPosX(dataX[ix], scaleX, xDim, xOff));
|
||||
let left = Math.round(valToPosX(dataX[ix], scaleX, xDim, xOff));
|
||||
|
||||
let nextIx = ix;
|
||||
while (dataY[++nextIx] === undefined && nextIx < dataY.length) {}
|
||||
|
||||
// to now (not to end of chart)
|
||||
let rgt =
|
||||
let right =
|
||||
nextIx === dataY.length
|
||||
? xOff + xDim + strokeWidth
|
||||
: Math.round(valToPosX(dataX[nextIx], scaleX, xDim, xOff));
|
||||
@ -222,10 +233,10 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
rect,
|
||||
xOff,
|
||||
yOff,
|
||||
lft,
|
||||
left,
|
||||
round(yOff + y0),
|
||||
rgt - lft - 2,
|
||||
round(hgt),
|
||||
right - left - 2,
|
||||
round(height),
|
||||
strokeWidth,
|
||||
iy,
|
||||
ix,
|
||||
@ -246,17 +257,17 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
for (let ix = idx0; ix <= idx1; ix++) {
|
||||
if (dataY[ix] != null) {
|
||||
// TODO: all xPos can be pre-computed once for all series in aligned set
|
||||
let lft = valToPosX(dataX[ix], scaleX, xDim, xOff);
|
||||
let left = valToPosX(dataX[ix], scaleX, xDim, xOff);
|
||||
|
||||
putBox(
|
||||
u.ctx,
|
||||
rect,
|
||||
xOff,
|
||||
yOff,
|
||||
round(lft - xShift),
|
||||
round(left - xShift),
|
||||
round(yOff + y0),
|
||||
barWid,
|
||||
round(hgt),
|
||||
round(height),
|
||||
strokeWidth,
|
||||
iy,
|
||||
ix,
|
||||
@ -315,14 +326,13 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
const boxRect = boxRectsBySeries[sidx - 1][ix];
|
||||
|
||||
// Todo refine this to better know when to not render text (when values do not fit)
|
||||
if (boxRect.w < 20) {
|
||||
if (!boxRect || boxRect.w < 20) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const x = getTextPositionOffet(boxRect, alignValue);
|
||||
const valueColor = colorLookup(sidx, dataY[ix]);
|
||||
|
||||
u.ctx.fillStyle = theme.colors.getContrastText(valueColor, 3);
|
||||
u.ctx.fillStyle = theme.colors.getContrastText(boxRect.fillColor, 3);
|
||||
u.ctx.fillText(formatValue(sidx, dataY[ix]), x, y);
|
||||
}
|
||||
}
|
||||
@ -455,8 +465,8 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
|
||||
return ySplits;
|
||||
},
|
||||
yValues: (u: uPlot, splits: number[]) => splits.map((v, i) => label(i + 1)),
|
||||
|
||||
yValues: (u: uPlot, splits: number[]) => splits.map((v, i) => label(i + 1)),
|
||||
yRange: [0, 1] as uPlot.Range.MinMax,
|
||||
|
||||
// pathbuilders
|
||||
@ -482,3 +492,8 @@ function getTextPositionOffet(rect: TimelineBoxRect, alignValue: TimelineValueAl
|
||||
textPadding
|
||||
);
|
||||
}
|
||||
|
||||
function getFillColor(fieldConfig: TimelineFieldConfig, color: string) {
|
||||
const opacityPercent = (fieldConfig.fillOpacity ?? 100) / 100;
|
||||
return tinycolor(color).setAlpha(opacityPercent).toString();
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { VizLegendOptions, GraphGradientMode, HideableFieldConfig, BarValueVisibility } from '@grafana/ui';
|
||||
import { VizLegendOptions, HideableFieldConfig, BarValueVisibility } from '@grafana/ui';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
@ -20,7 +20,6 @@ export type TimelineValueAlignment = 'center' | 'left' | 'right';
|
||||
export interface TimelineFieldConfig extends HideableFieldConfig {
|
||||
lineWidth?: number; // 0
|
||||
fillOpacity?: number; // 100
|
||||
gradientMode?: GraphGradientMode;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,8 +27,7 @@ export interface TimelineFieldConfig extends HideableFieldConfig {
|
||||
*/
|
||||
export const defaultTimelineFieldConfig: TimelineFieldConfig = {
|
||||
lineWidth: 1,
|
||||
fillOpacity: 80,
|
||||
gradientMode: GraphGradientMode.None,
|
||||
fillOpacity: 70,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -11,20 +11,13 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { UPlotConfigBuilder, FIXED_UNIT, SeriesVisibilityChangeMode, UPlotConfigPrepFn } from '@grafana/ui';
|
||||
import { TimelineCoreOptions, getConfig } from './timeline';
|
||||
import {
|
||||
AxisPlacement,
|
||||
GraphGradientMode,
|
||||
ScaleDirection,
|
||||
ScaleOrientation,
|
||||
} from '@grafana/ui/src/components/uPlot/config';
|
||||
import { AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/ui/src/components/uPlot/config';
|
||||
import { measureText } from '@grafana/ui/src/utils/measureText';
|
||||
|
||||
import { TimelineFieldConfig, TimelineOptions } from './types';
|
||||
|
||||
const defaultConfig: TimelineFieldConfig = {
|
||||
lineWidth: 0,
|
||||
fillOpacity: 80,
|
||||
gradientMode: GraphGradientMode.None,
|
||||
};
|
||||
|
||||
export function mapMouseEventToMode(event: React.MouseEvent): SeriesVisibilityChangeMode {
|
||||
@ -61,7 +54,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({
|
||||
return !(mode && field.display && mode.startsWith('continuous-'));
|
||||
};
|
||||
|
||||
const colorLookup = (seriesIdx: number, value: any) => {
|
||||
const getValueColor = (seriesIdx: number, value: any) => {
|
||||
const field = frame.fields[seriesIdx];
|
||||
|
||||
if (field.display) {
|
||||
@ -93,9 +86,8 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({
|
||||
alignValue,
|
||||
theme,
|
||||
label: (seriesIdx) => getFieldDisplayName(frame.fields[seriesIdx], frame),
|
||||
fill: colorLookup,
|
||||
stroke: colorLookup,
|
||||
colorLookup,
|
||||
getFieldConfig: (seriesIdx) => frame.fields[seriesIdx].config.custom,
|
||||
getValueColor,
|
||||
getTimeRange,
|
||||
// hardcoded formatter for state values
|
||||
formatValue: (seriesIdx, value) => formattedValueToString(frame.fields[seriesIdx].display!(value)),
|
||||
@ -170,17 +162,16 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({
|
||||
|
||||
field.state!.seriesIndex = seriesIndex++;
|
||||
|
||||
//const scaleKey = config.unit || FIXED_UNIT;
|
||||
//const colorMode = getFieldColorModeForField(field);
|
||||
|
||||
let { fillOpacity } = customConfig;
|
||||
// const scaleKey = config.unit || FIXED_UNIT;
|
||||
// const colorMode = getFieldColorModeForField(field);
|
||||
|
||||
builder.addSeries({
|
||||
scaleKey: FIXED_UNIT,
|
||||
pathBuilder: coreConfig.drawPaths,
|
||||
pointsBuilder: coreConfig.drawPoints,
|
||||
//colorMode,
|
||||
fillOpacity,
|
||||
lineWidth: customConfig.lineWidth,
|
||||
fillOpacity: customConfig.fillOpacity,
|
||||
theme,
|
||||
show: !customConfig.hideFrom?.viz,
|
||||
thresholds: config.thresholds,
|
||||
|
Loading…
Reference in New Issue
Block a user