@ -1,4 +1,11 @@
import { FieldColorMode , FieldColorModeId , GrafanaTheme2 , ThresholdsConfig , ThresholdsMode } from '@grafana/data' ;
import {
colorManipulator ,
FieldColorMode ,
FieldColorModeId ,
GrafanaTheme2 ,
ThresholdsConfig ,
ThresholdsMode ,
} from '@grafana/data' ;
import tinycolor from 'tinycolor2' ;
import uPlot from 'uplot' ;
import { getCanvasContext } from '../../../utils/measureText' ;
@ -11,8 +18,8 @@ export function getOpacityGradientFn(
const ctx = getCanvasContext ( ) ;
const gradient = ctx . createLinearGradient ( 0 , plot . bbox . top , 0 , plot . bbox . top + plot . bbox . height ) ;
gradient . addColorStop ( 0 , tinycolor ( color ) . setAlpha ( opacity ) . toRgbString ( ) ) ;
gradient . addColorStop ( 1 , tinycolor ( color ) . setAlpha ( 0 ) . toRgbString ( ) ) ;
gradient . addColorStop ( 0 , colorManipulator . alpha ( color , opacity ) ) ;
gradient . addColorStop ( 1 , colorManipulator . alpha ( color , 0 ) ) ;
return gradient ;
} ;
@ -39,10 +46,132 @@ export function getHueGradientFn(
return gradient ;
} ;
}
/ * *
* Experimental & quick and dirty test
* Not being used
* /
export enum GradientDirection {
'Right' = 0 ,
'Up' = 1 ,
}
type ValueStop = [ value : number , color : string ] ;
type ScaleValueStops = ValueStop [ ] ;
export function scaleGradient (
u : uPlot ,
scaleKey : string ,
dir : GradientDirection ,
scaleStops : ScaleValueStops ,
discrete = false
) {
let scale = u . scales [ scaleKey ] ;
// we want the stop below or at the scaleMax
// and the stop below or at the scaleMin, else the stop above scaleMin
let minStopIdx : number | null = null ;
let maxStopIdx : number | null = null ;
for ( let i = 0 ; i < scaleStops . length ; i ++ ) {
let stopVal = scaleStops [ i ] [ 0 ] ;
if ( stopVal <= scale . min ! || minStopIdx == null ) {
minStopIdx = i ;
}
maxStopIdx = i ;
if ( stopVal >= scale . max ! ) {
break ;
}
}
if ( minStopIdx === maxStopIdx ) {
return scaleStops [ minStopIdx ! ] [ 1 ] ;
}
let minStopVal = scaleStops [ minStopIdx ! ] [ 0 ] ;
let maxStopVal = scaleStops [ maxStopIdx ! ] [ 0 ] ;
if ( minStopVal === - Infinity ) {
minStopVal = scale . min ! ;
}
if ( maxStopVal === Infinity ) {
maxStopVal = scale . max ! ;
}
let minStopPos = Math . round ( u . valToPos ( minStopVal , scaleKey , true ) ) ;
let maxStopPos = Math . round ( u . valToPos ( maxStopVal , scaleKey , true ) ) ;
let range = minStopPos - maxStopPos ;
let x0 , y0 , x1 , y1 ;
if ( dir === GradientDirection . Up ) {
x0 = x1 = 0 ;
y0 = minStopPos ;
y1 = maxStopPos ;
} else {
y0 = y1 = 0 ;
x0 = minStopPos ;
x1 = maxStopPos ;
}
let ctx = getCanvasContext ( ) ;
let grd = ctx . createLinearGradient ( x0 , y0 , x1 , y1 ) ;
let prevColor : string ;
for ( let i = minStopIdx ! ; i <= maxStopIdx ! ; i ++ ) {
let s = scaleStops [ i ] ;
let stopPos =
i === minStopIdx ? minStopPos : i === maxStopIdx ? maxStopPos : Math.round ( u . valToPos ( s [ 0 ] , scaleKey , true ) ) ;
let pct = ( minStopPos - stopPos ) / range ;
if ( discrete && i > minStopIdx ! ) {
grd . addColorStop ( pct , prevColor ! ) ;
}
grd . addColorStop ( pct , ( prevColor = s [ 1 ] ) ) ;
}
return grd ;
}
export function getDataRange ( plot : uPlot , scaleKey : string ) {
let sc = plot . scales [ scaleKey ] ;
let min = Infinity ;
let max = - Infinity ;
plot . series . forEach ( ( ser , seriesIdx ) = > {
if ( ser . show && ser . scale === scaleKey ) {
// uPlot skips finding data min/max when a scale has a pre-defined range
if ( ser . min == null ) {
let data = plot . data [ seriesIdx ] ;
for ( let i = 0 ; i < data . length ; i ++ ) {
if ( data [ i ] != null ) {
min = Math . min ( min , data [ i ] ! ) ;
max = Math . max ( max , data [ i ] ! ) ;
}
}
} else {
min = Math . min ( min , ser . min ! ) ;
max = Math . max ( max , ser . max ! ) ;
}
}
} ) ;
if ( max === min ) {
min = sc . min ! ;
max = sc . max ! ;
}
return [ min , max ] ;
}
export function getScaleGradientFn (
opacity : number ,
theme : GrafanaTheme2 ,
@ -58,68 +187,41 @@ export function getScaleGradientFn(
}
return ( plot : uPlot , seriesIdx : number ) = > {
// A uplot bug (I think) where this is called before there is bbox
// Color used for cursor highlight, not sure what to do here as this is called before we have bbox
// and only once so same color is used for all points
if ( plot . bbox . top == null ) {
return theme . colors . text . primary ;
}
const ctx = getCanvasContext ( ) ;
const gradient = ctx . createLinearGradient ( 0 , plot . bbox . top , 0 , plot . bbox . top + plot . bbox . height ) ;
const canvasHeight = plot . bbox . height ;
const series = plot . series [ seriesIdx ] ;
const scale = plot . scales [ series . scale ! ] ;
const scaleMin = scale . min ? ? 0 ;
const scaleMax = scale . max ? ? 100 ;
const scaleRange = scaleMax - scaleMin ;
const addColorStop = ( value : number , color : string ) = > {
const pos = plot . valToPos ( value , series . scale ! , true ) ;
// when above range we get negative values here
if ( pos < 0 ) {
return ;
}
const percent = Math . max ( pos / canvasHeight , 0 ) ;
const realColor = tinycolor ( theme . visualization . getColorByName ( color ) ) . setAlpha ( opacity ) . toString ( ) ;
const colorStopPos = Math . min ( percent , 1 ) ;
let scaleKey = plot . series [ seriesIdx ] . scale ! ;
gradient . addColorStop ( colorStopPos , realColor ) ;
} ;
let gradient : CanvasGradient | string = '' ;
if ( colorMode . id === FieldColorModeId . Thresholds ) {
for ( let idx = 0 ; idx < thresholds . steps . length ; idx ++ ) {
const step = thresholds . steps [ idx ] ;
if ( thresholds . mode === ThresholdsMode . Absolute ) {
const value = step . value === - Infinity ? scaleMin : step.value ;
addColorStop ( value , step . color ) ;
if ( thresholds . steps . length > idx + 1 ) {
// to make the gradient discrete
addColorStop ( thresholds . steps [ idx + 1 ] . value - 0.00000001 , step . color ) ;
}
} else {
const percent = step . value === - Infinity ? 0 : step.value ;
const realValue = ( percent / 100 ) * scaleRange ;
addColorStop ( realValue , step . color ) ;
// to make the gradient discrete
if ( thresholds . steps . length > idx + 1 ) {
// to make the gradient discrete
const nextValue = ( thresholds . steps [ idx + 1 ] . value / 100 ) * scaleRange - 0.0000001 ;
addColorStop ( nextValue , step . color ) ;
}
}
if ( thresholds . mode === ThresholdsMode . Absolute ) {
const valueStops = thresholds . steps . map (
( step ) = >
[ step . value , colorManipulator . alpha ( theme . visualization . getColorByName ( step . color ) , opacity ) ] as ValueStop
) ;
gradient = scaleGradient ( plot , scaleKey , GradientDirection . Up , valueStops , true ) ;
} else {
const [ min , max ] = getDataRange ( plot , scaleKey ) ;
const range = max - min ;
const valueStops = thresholds . steps . map (
( step ) = >
[
min + range * ( step . value / 100 ) ,
colorManipulator . alpha ( theme . visualization . getColorByName ( step . color ) , opacity ) ,
] as ValueStop
) ;
gradient = scaleGradient ( plot , scaleKey , GradientDirection . Up , valueStops , true ) ;
}
} else if ( colorMode . getColors ) {
const colors = colorMode . getColors ( theme ) ;
const stepValue = ( scaleMax - scaleMin ) / colors . length ;
for ( let idx = 0 ; idx < colors . length ; idx ++ ) {
addColorStop ( scaleMin + stepValue * idx , colors [ idx ] ) ;
}
const [ min , max ] = getDataRange ( plot , scaleKey ) ;
const range = max - min ;
const valueStops = colors . map (
( color , i ) = >
[
min + range * ( i / ( colors . length - 1 ) ) ,
colorManipulator . alpha ( theme . visualization . getColorByName ( color ) , opacity ) ,
] as ValueStop
) ;
gradient = scaleGradient ( plot , scaleKey , GradientDirection . Up , valueStops , false ) ;
}
return gradient ;