Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
ddc83c2a
Unverified
Commit
ddc83c2a
authored
Aug 14, 2018
by
Marcus Efraimsson
Committed by
GitHub
Aug 14, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #12897 from dehrax/12224-renderer
WIP Karma to Jest: heatmap renderer (refactor)
parents
77ee032e
53bab1a8
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
361 additions
and
670 deletions
+361
-670
public/app/plugins/panel/heatmap/rendering.ts
+361
-350
public/app/plugins/panel/heatmap/specs/renderer_specs.ts
+0
-320
No files found.
public/app/plugins/panel/heatmap/rendering.ts
View file @
ddc83c2a
...
...
@@ -19,56 +19,94 @@ let MIN_CARD_SIZE = 1,
Y_AXIS_TICK_PADDING
=
5
,
MIN_SELECTION_WIDTH
=
2
;
export
default
function
link
(
scope
,
elem
,
attrs
,
ctrl
)
{
let
data
,
timeRange
,
panel
,
heatmap
;
// $heatmap is JQuery object, but heatmap is D3
let
$heatmap
=
elem
.
find
(
'.heatmap-panel'
);
let
tooltip
=
new
HeatmapTooltip
(
$heatmap
,
scope
);
let
width
,
height
,
yScale
,
xScale
,
chartWidth
,
chartHeight
,
chartTop
,
chartBottom
,
yAxisWidth
,
xAxisHeight
,
cardPadding
,
cardRound
,
cardWidth
,
cardHeight
,
colorScale
,
opacityScale
,
mouseUpHandler
;
let
selection
=
{
active
:
false
,
x1
:
-
1
,
x2
:
-
1
,
};
let
padding
=
{
left
:
0
,
right
:
0
,
top
:
0
,
bottom
:
0
},
margin
=
{
left
:
25
,
right
:
15
,
top
:
10
,
bottom
:
20
},
dataRangeWidingFactor
=
DATA_RANGE_WIDING_FACTOR
;
ctrl
.
events
.
on
(
'render'
,
()
=>
{
render
();
ctrl
.
renderingCompleted
();
});
function
setElementHeight
()
{
export
default
function
rendering
(
scope
,
elem
,
attrs
,
ctrl
)
{
return
new
HeatmapRenderer
(
scope
,
elem
,
attrs
,
ctrl
);
}
export
class
HeatmapRenderer
{
width
:
number
;
height
:
number
;
yScale
:
any
;
xScale
:
any
;
chartWidth
:
number
;
chartHeight
:
number
;
chartTop
:
number
;
chartBottom
:
number
;
yAxisWidth
:
number
;
xAxisHeight
:
number
;
cardPadding
:
number
;
cardRound
:
number
;
cardWidth
:
number
;
cardHeight
:
number
;
colorScale
:
any
;
opacityScale
:
any
;
mouseUpHandler
:
any
;
data
:
any
;
panel
:
any
;
$heatmap
:
any
;
tooltip
:
HeatmapTooltip
;
heatmap
:
any
;
timeRange
:
any
;
selection
:
any
;
padding
:
any
;
margin
:
any
;
dataRangeWidingFactor
:
number
;
constructor
(
private
scope
,
private
elem
,
attrs
,
private
ctrl
)
{
// $heatmap is JQuery object, but heatmap is D3
this
.
$heatmap
=
this
.
elem
.
find
(
'.heatmap-panel'
);
this
.
tooltip
=
new
HeatmapTooltip
(
this
.
$heatmap
,
this
.
scope
);
this
.
selection
=
{
active
:
false
,
x1
:
-
1
,
x2
:
-
1
,
};
this
.
padding
=
{
left
:
0
,
right
:
0
,
top
:
0
,
bottom
:
0
};
this
.
margin
=
{
left
:
25
,
right
:
15
,
top
:
10
,
bottom
:
20
};
this
.
dataRangeWidingFactor
=
DATA_RANGE_WIDING_FACTOR
;
this
.
ctrl
.
events
.
on
(
'render'
,
this
.
onRender
.
bind
(
this
));
this
.
ctrl
.
tickValueFormatter
=
this
.
tickValueFormatter
.
bind
(
this
);
/////////////////////////////
// Selection and crosshair //
/////////////////////////////
// Shared crosshair and tooltip
appEvents
.
on
(
'graph-hover'
,
this
.
onGraphHover
.
bind
(
this
),
this
.
scope
);
appEvents
.
on
(
'graph-hover-clear'
,
this
.
onGraphHoverClear
.
bind
(
this
),
this
.
scope
);
// Register selection listeners
this
.
$heatmap
.
on
(
'mousedown'
,
this
.
onMouseDown
.
bind
(
this
));
this
.
$heatmap
.
on
(
'mousemove'
,
this
.
onMouseMove
.
bind
(
this
));
this
.
$heatmap
.
on
(
'mouseleave'
,
this
.
onMouseLeave
.
bind
(
this
));
}
onGraphHoverClear
()
{
this
.
clearCrosshair
();
}
onGraphHover
(
event
)
{
this
.
drawSharedCrosshair
(
event
.
pos
);
}
onRender
()
{
this
.
render
();
this
.
ctrl
.
renderingCompleted
();
}
setElementHeight
()
{
try
{
var
height
=
ctrl
.
height
||
panel
.
height
||
ctrl
.
row
.
height
;
var
height
=
this
.
ctrl
.
height
||
this
.
panel
.
height
||
this
.
ctrl
.
row
.
height
;
if
(
_
.
isString
(
height
))
{
height
=
parseInt
(
height
.
replace
(
'px'
,
''
),
10
);
}
height
-=
panel
.
legend
.
show
?
28
:
11
;
// bottom padding and space for legend
height
-=
this
.
panel
.
legend
.
show
?
28
:
11
;
// bottom padding and space for legend
$heatmap
.
css
(
'height'
,
height
+
'px'
);
this
.
$heatmap
.
css
(
'height'
,
height
+
'px'
);
return
true
;
}
catch
(
e
)
{
...
...
@@ -77,7 +115,7 @@ export default function link(scope, elem, attrs, ctrl) {
}
}
function
getYAxisWidth
(
elem
)
{
getYAxisWidth
(
elem
)
{
let
axis_text
=
elem
.
selectAll
(
'.axis-y text'
).
nodes
();
let
max_text_width
=
_
.
max
(
_
.
map
(
axis_text
,
text
=>
{
...
...
@@ -89,7 +127,7 @@ export default function link(scope, elem, attrs, ctrl) {
return
max_text_width
;
}
function
getXAxisHeight
(
elem
)
{
getXAxisHeight
(
elem
)
{
let
axis_line
=
elem
.
select
(
'.axis-x line'
);
if
(
!
axis_line
.
empty
())
{
let
axis_line_position
=
parseFloat
(
elem
.
select
(
'.axis-x line'
).
attr
(
'y2'
));
...
...
@@ -101,16 +139,16 @@ export default function link(scope, elem, attrs, ctrl) {
}
}
function
addXAxis
()
{
scope
.
xScale
=
xScale
=
d3
addXAxis
()
{
this
.
scope
.
xScale
=
this
.
xScale
=
d3
.
scaleTime
()
.
domain
([
t
imeRange
.
from
,
timeRange
.
to
])
.
range
([
0
,
chartWidth
]);
.
domain
([
t
his
.
timeRange
.
from
,
this
.
timeRange
.
to
])
.
range
([
0
,
this
.
chartWidth
]);
let
ticks
=
chartWidth
/
DEFAULT_X_TICK_SIZE_PX
;
let
grafanaTimeFormatter
=
ticksUtils
.
grafanaTimeFormat
(
ticks
,
t
imeRange
.
from
,
timeRange
.
to
);
let
ticks
=
this
.
chartWidth
/
DEFAULT_X_TICK_SIZE_PX
;
let
grafanaTimeFormatter
=
ticksUtils
.
grafanaTimeFormat
(
ticks
,
t
his
.
timeRange
.
from
,
this
.
timeRange
.
to
);
let
timeFormat
;
let
dashboardTimeZone
=
ctrl
.
dashboard
.
getTimezone
();
let
dashboardTimeZone
=
this
.
ctrl
.
dashboard
.
getTimezone
();
if
(
dashboardTimeZone
===
'utc'
)
{
timeFormat
=
d3
.
utcFormat
(
grafanaTimeFormatter
);
}
else
{
...
...
@@ -118,100 +156,100 @@ export default function link(scope, elem, attrs, ctrl) {
}
let
xAxis
=
d3
.
axisBottom
(
xScale
)
.
axisBottom
(
this
.
xScale
)
.
ticks
(
ticks
)
.
tickFormat
(
timeFormat
)
.
tickPadding
(
X_AXIS_TICK_PADDING
)
.
tickSize
(
chartHeight
);
.
tickSize
(
this
.
chartHeight
);
let
posY
=
margin
.
top
;
let
posX
=
yAxisWidth
;
heatmap
let
posY
=
this
.
margin
.
top
;
let
posX
=
this
.
yAxisWidth
;
this
.
heatmap
.
append
(
'g'
)
.
attr
(
'class'
,
'axis axis-x'
)
.
attr
(
'transform'
,
'translate('
+
posX
+
','
+
posY
+
')'
)
.
call
(
xAxis
);
// Remove horizontal line in the top of axis labels (called domain in d3)
heatmap
this
.
heatmap
.
select
(
'.axis-x'
)
.
select
(
'.domain'
)
.
remove
();
}
function
addYAxis
()
{
let
ticks
=
Math
.
ceil
(
chartHeight
/
DEFAULT_Y_TICK_SIZE_PX
);
let
tick_interval
=
ticksUtils
.
tickStep
(
data
.
heatmapStats
.
min
,
data
.
heatmapStats
.
max
,
ticks
);
let
{
y_min
,
y_max
}
=
wideYAxisRange
(
data
.
heatmapStats
.
min
,
data
.
heatmapStats
.
max
,
tick_interval
);
addYAxis
()
{
let
ticks
=
Math
.
ceil
(
this
.
chartHeight
/
DEFAULT_Y_TICK_SIZE_PX
);
let
tick_interval
=
ticksUtils
.
tickStep
(
this
.
data
.
heatmapStats
.
min
,
this
.
data
.
heatmapStats
.
max
,
ticks
);
let
{
y_min
,
y_max
}
=
this
.
wideYAxisRange
(
this
.
data
.
heatmapStats
.
min
,
this
.
data
.
heatmapStats
.
max
,
tick_interval
);
// Rewrite min and max if it have been set explicitly
y_min
=
panel
.
yAxis
.
min
!==
null
?
panel
.
yAxis
.
min
:
y_min
;
y_max
=
panel
.
yAxis
.
max
!==
null
?
panel
.
yAxis
.
max
:
y_max
;
y_min
=
this
.
panel
.
yAxis
.
min
!==
null
?
this
.
panel
.
yAxis
.
min
:
y_min
;
y_max
=
this
.
panel
.
yAxis
.
max
!==
null
?
this
.
panel
.
yAxis
.
max
:
y_max
;
// Adjust ticks after Y range widening
tick_interval
=
ticksUtils
.
tickStep
(
y_min
,
y_max
,
ticks
);
ticks
=
Math
.
ceil
((
y_max
-
y_min
)
/
tick_interval
);
let
decimalsAuto
=
ticksUtils
.
getPrecision
(
tick_interval
);
let
decimals
=
panel
.
yAxis
.
decimals
===
null
?
decimalsAuto
:
panel
.
yAxis
.
decimals
;
let
decimals
=
this
.
panel
.
yAxis
.
decimals
===
null
?
decimalsAuto
:
this
.
panel
.
yAxis
.
decimals
;
// Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js)
let
flot_tick_size
=
ticksUtils
.
getFlotTickSize
(
y_min
,
y_max
,
ticks
,
decimalsAuto
);
let
scaledDecimals
=
ticksUtils
.
getScaledDecimals
(
decimals
,
flot_tick_size
);
ctrl
.
decimals
=
decimals
;
ctrl
.
scaledDecimals
=
scaledDecimals
;
this
.
ctrl
.
decimals
=
decimals
;
this
.
ctrl
.
scaledDecimals
=
scaledDecimals
;
// Set default Y min and max if no data
if
(
_
.
isEmpty
(
data
.
buckets
))
{
if
(
_
.
isEmpty
(
this
.
data
.
buckets
))
{
y_max
=
1
;
y_min
=
-
1
;
ticks
=
3
;
decimals
=
1
;
}
data
.
yAxis
=
{
this
.
data
.
yAxis
=
{
min
:
y_min
,
max
:
y_max
,
ticks
:
ticks
,
};
scope
.
yScale
=
yScale
=
d3
this
.
scope
.
yScale
=
this
.
yScale
=
d3
.
scaleLinear
()
.
domain
([
y_min
,
y_max
])
.
range
([
chartHeight
,
0
]);
.
range
([
this
.
chartHeight
,
0
]);
let
yAxis
=
d3
.
axisLeft
(
yScale
)
.
axisLeft
(
this
.
yScale
)
.
ticks
(
ticks
)
.
tickFormat
(
tickValueFormatter
(
decimals
,
scaledDecimals
))
.
tickSizeInner
(
0
-
width
)
.
tickFormat
(
t
his
.
t
ickValueFormatter
(
decimals
,
scaledDecimals
))
.
tickSizeInner
(
0
-
this
.
width
)
.
tickSizeOuter
(
0
)
.
tickPadding
(
Y_AXIS_TICK_PADDING
);
heatmap
this
.
heatmap
.
append
(
'g'
)
.
attr
(
'class'
,
'axis axis-y'
)
.
call
(
yAxis
);
// Calculate Y axis width first, then move axis into visible area
let
posY
=
margin
.
top
;
let
posX
=
getYAxisWidth
(
heatmap
)
+
Y_AXIS_TICK_PADDING
;
heatmap
.
select
(
'.axis-y'
).
attr
(
'transform'
,
'translate('
+
posX
+
','
+
posY
+
')'
);
let
posY
=
this
.
margin
.
top
;
let
posX
=
this
.
getYAxisWidth
(
this
.
heatmap
)
+
Y_AXIS_TICK_PADDING
;
this
.
heatmap
.
select
(
'.axis-y'
).
attr
(
'transform'
,
'translate('
+
posX
+
','
+
posY
+
')'
);
// Remove vertical line in the right of axis labels (called domain in d3)
heatmap
this
.
heatmap
.
select
(
'.axis-y'
)
.
select
(
'.domain'
)
.
remove
();
}
// Wide Y values range and anjust to bucket size
function
wideYAxisRange
(
min
,
max
,
tickInterval
)
{
let
y_widing
=
(
max
*
(
dataRangeWidingFactor
-
1
)
-
min
*
(
dataRangeWidingFactor
-
1
))
/
2
;
wideYAxisRange
(
min
,
max
,
tickInterval
)
{
let
y_widing
=
(
max
*
(
this
.
dataRangeWidingFactor
-
1
)
-
min
*
(
this
.
dataRangeWidingFactor
-
1
))
/
2
;
let
y_min
,
y_max
;
if
(
tickInterval
===
0
)
{
y_max
=
max
*
dataRangeWidingFactor
;
y_min
=
min
-
min
*
(
dataRangeWidingFactor
-
1
);
y_max
=
max
*
this
.
dataRangeWidingFactor
;
y_min
=
min
-
min
*
(
this
.
dataRangeWidingFactor
-
1
);
tickInterval
=
(
y_max
-
y_min
)
/
2
;
}
else
{
y_max
=
Math
.
ceil
((
max
+
y_widing
)
/
tickInterval
)
*
tickInterval
;
...
...
@@ -226,89 +264,91 @@ export default function link(scope, elem, attrs, ctrl) {
return
{
y_min
,
y_max
};
}
function
addLogYAxis
()
{
let
log_base
=
panel
.
yAxis
.
logBase
;
let
{
y_min
,
y_max
}
=
adjustLogRange
(
data
.
heatmapStats
.
minLog
,
data
.
heatmapStats
.
max
,
log_base
);
addLogYAxis
()
{
let
log_base
=
this
.
panel
.
yAxis
.
logBase
;
let
{
y_min
,
y_max
}
=
this
.
adjustLogRange
(
this
.
data
.
heatmapStats
.
minLog
,
this
.
data
.
heatmapStats
.
max
,
log_base
);
y_min
=
panel
.
yAxis
.
min
&&
panel
.
yAxis
.
min
!==
'0'
?
adjustLogMin
(
panel
.
yAxis
.
min
,
log_base
)
:
y_min
;
y_max
=
panel
.
yAxis
.
max
!==
null
?
adjustLogMax
(
panel
.
yAxis
.
max
,
log_base
)
:
y_max
;
y_min
=
this
.
panel
.
yAxis
.
min
&&
this
.
panel
.
yAxis
.
min
!==
'0'
?
this
.
adjustLogMin
(
this
.
panel
.
yAxis
.
min
,
log_base
)
:
y_min
;
y_max
=
this
.
panel
.
yAxis
.
max
!==
null
?
this
.
adjustLogMax
(
this
.
panel
.
yAxis
.
max
,
log_base
)
:
y_max
;
// Set default Y min and max if no data
if
(
_
.
isEmpty
(
data
.
buckets
))
{
if
(
_
.
isEmpty
(
this
.
data
.
buckets
))
{
y_max
=
Math
.
pow
(
log_base
,
2
);
y_min
=
1
;
}
scope
.
yScale
=
yScale
=
d3
this
.
scope
.
yScale
=
this
.
yScale
=
d3
.
scaleLog
()
.
base
(
panel
.
yAxis
.
logBase
)
.
base
(
this
.
panel
.
yAxis
.
logBase
)
.
domain
([
y_min
,
y_max
])
.
range
([
chartHeight
,
0
]);
.
range
([
this
.
chartHeight
,
0
]);
let
domain
=
yScale
.
domain
();
let
tick_values
=
logScaleTickValues
(
domain
,
log_base
);
let
domain
=
this
.
yScale
.
domain
();
let
tick_values
=
this
.
logScaleTickValues
(
domain
,
log_base
);
let
decimalsAuto
=
ticksUtils
.
getPrecision
(
y_min
);
let
decimals
=
panel
.
yAxis
.
decimals
||
decimalsAuto
;
let
decimals
=
this
.
panel
.
yAxis
.
decimals
||
decimalsAuto
;
// Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js)
let
flot_tick_size
=
ticksUtils
.
getFlotTickSize
(
y_min
,
y_max
,
tick_values
.
length
,
decimalsAuto
);
let
scaledDecimals
=
ticksUtils
.
getScaledDecimals
(
decimals
,
flot_tick_size
);
ctrl
.
decimals
=
decimals
;
ctrl
.
scaledDecimals
=
scaledDecimals
;
this
.
ctrl
.
decimals
=
decimals
;
this
.
ctrl
.
scaledDecimals
=
scaledDecimals
;
data
.
yAxis
=
{
this
.
data
.
yAxis
=
{
min
:
y_min
,
max
:
y_max
,
ticks
:
tick_values
.
length
,
};
let
yAxis
=
d3
.
axisLeft
(
yScale
)
.
axisLeft
(
this
.
yScale
)
.
tickValues
(
tick_values
)
.
tickFormat
(
tickValueFormatter
(
decimals
,
scaledDecimals
))
.
tickSizeInner
(
0
-
width
)
.
tickFormat
(
t
his
.
t
ickValueFormatter
(
decimals
,
scaledDecimals
))
.
tickSizeInner
(
0
-
this
.
width
)
.
tickSizeOuter
(
0
)
.
tickPadding
(
Y_AXIS_TICK_PADDING
);
heatmap
this
.
heatmap
.
append
(
'g'
)
.
attr
(
'class'
,
'axis axis-y'
)
.
call
(
yAxis
);
// Calculate Y axis width first, then move axis into visible area
let
posY
=
margin
.
top
;
let
posX
=
getYAxisWidth
(
heatmap
)
+
Y_AXIS_TICK_PADDING
;
heatmap
.
select
(
'.axis-y'
).
attr
(
'transform'
,
'translate('
+
posX
+
','
+
posY
+
')'
);
let
posY
=
this
.
margin
.
top
;
let
posX
=
this
.
getYAxisWidth
(
this
.
heatmap
)
+
Y_AXIS_TICK_PADDING
;
this
.
heatmap
.
select
(
'.axis-y'
).
attr
(
'transform'
,
'translate('
+
posX
+
','
+
posY
+
')'
);
// Set first tick as pseudo 0
if
(
y_min
<
1
)
{
heatmap
this
.
heatmap
.
select
(
'.axis-y'
)
.
select
(
'.tick text'
)
.
text
(
'0'
);
}
// Remove vertical line in the right of axis labels (called domain in d3)
heatmap
this
.
heatmap
.
select
(
'.axis-y'
)
.
select
(
'.domain'
)
.
remove
();
}
function
addYAxisFromBuckets
()
{
const
tsBuckets
=
data
.
tsBuckets
;
addYAxisFromBuckets
()
{
const
tsBuckets
=
this
.
data
.
tsBuckets
;
scope
.
yScale
=
yScale
=
d3
this
.
scope
.
yScale
=
this
.
yScale
=
d3
.
scaleLinear
()
.
domain
([
0
,
tsBuckets
.
length
-
1
])
.
range
([
chartHeight
,
0
]);
.
range
([
this
.
chartHeight
,
0
]);
const
tick_values
=
_
.
map
(
tsBuckets
,
(
b
,
i
)
=>
i
);
const
decimalsAuto
=
_
.
max
(
_
.
map
(
tsBuckets
,
ticksUtils
.
getStringPrecision
));
const
decimals
=
panel
.
yAxis
.
decimals
===
null
?
decimalsAuto
:
panel
.
yAxis
.
decimals
;
ctrl
.
decimals
=
decimals
;
const
decimals
=
this
.
panel
.
yAxis
.
decimals
===
null
?
decimalsAuto
:
this
.
panel
.
yAxis
.
decimals
;
this
.
ctrl
.
decimals
=
decimals
;
let
tickValueFormatter
=
this
.
tickValueFormatter
.
bind
(
this
);
function
tickFormatter
(
valIndex
)
{
let
valueFormatted
=
tsBuckets
[
valIndex
];
if
(
!
_
.
isNaN
(
_
.
toNumber
(
valueFormatted
))
&&
valueFormatted
!==
''
)
{
...
...
@@ -319,59 +359,59 @@ export default function link(scope, elem, attrs, ctrl) {
}
const
tsBucketsFormatted
=
_
.
map
(
tsBuckets
,
(
v
,
i
)
=>
tickFormatter
(
i
));
data
.
tsBucketsFormatted
=
tsBucketsFormatted
;
this
.
data
.
tsBucketsFormatted
=
tsBucketsFormatted
;
let
yAxis
=
d3
.
axisLeft
(
yScale
)
.
axisLeft
(
this
.
yScale
)
.
tickValues
(
tick_values
)
.
tickFormat
(
tickFormatter
)
.
tickSizeInner
(
0
-
width
)
.
tickSizeInner
(
0
-
this
.
width
)
.
tickSizeOuter
(
0
)
.
tickPadding
(
Y_AXIS_TICK_PADDING
);
heatmap
this
.
heatmap
.
append
(
'g'
)
.
attr
(
'class'
,
'axis axis-y'
)
.
call
(
yAxis
);
// Calculate Y axis width first, then move axis into visible area
const
posY
=
margin
.
top
;
const
posX
=
getYAxisWidth
(
heatmap
)
+
Y_AXIS_TICK_PADDING
;
heatmap
.
select
(
'.axis-y'
).
attr
(
'transform'
,
'translate('
+
posX
+
','
+
posY
+
')'
);
const
posY
=
this
.
margin
.
top
;
const
posX
=
this
.
getYAxisWidth
(
this
.
heatmap
)
+
Y_AXIS_TICK_PADDING
;
this
.
heatmap
.
select
(
'.axis-y'
).
attr
(
'transform'
,
'translate('
+
posX
+
','
+
posY
+
')'
);
// Remove vertical line in the right of axis labels (called domain in d3)
heatmap
this
.
heatmap
.
select
(
'.axis-y'
)
.
select
(
'.domain'
)
.
remove
();
}
// Adjust data range to log base
function
adjustLogRange
(
min
,
max
,
logBase
)
{
adjustLogRange
(
min
,
max
,
logBase
)
{
let
y_min
,
y_max
;
y_min
=
data
.
heatmapStats
.
minLog
;
if
(
data
.
heatmapStats
.
minLog
>
1
||
!
data
.
heatmapStats
.
minLog
)
{
y_min
=
this
.
data
.
heatmapStats
.
minLog
;
if
(
this
.
data
.
heatmapStats
.
minLog
>
1
||
!
this
.
data
.
heatmapStats
.
minLog
)
{
y_min
=
1
;
}
else
{
y_min
=
adjustLogMin
(
data
.
heatmapStats
.
minLog
,
logBase
);
y_min
=
this
.
adjustLogMin
(
this
.
data
.
heatmapStats
.
minLog
,
logBase
);
}
// Adjust max Y value to log base
y_max
=
adjustLogMax
(
data
.
heatmapStats
.
max
,
logBase
);
y_max
=
this
.
adjustLogMax
(
this
.
data
.
heatmapStats
.
max
,
logBase
);
return
{
y_min
,
y_max
};
}
function
adjustLogMax
(
max
,
base
)
{
adjustLogMax
(
max
,
base
)
{
return
Math
.
pow
(
base
,
Math
.
ceil
(
ticksUtils
.
logp
(
max
,
base
)));
}
function
adjustLogMin
(
min
,
base
)
{
adjustLogMin
(
min
,
base
)
{
return
Math
.
pow
(
base
,
Math
.
floor
(
ticksUtils
.
logp
(
min
,
base
)));
}
function
logScaleTickValues
(
domain
,
base
)
{
logScaleTickValues
(
domain
,
base
)
{
let
domainMin
=
domain
[
0
];
let
domainMax
=
domain
[
1
];
let
tickValues
=
[];
...
...
@@ -393,8 +433,8 @@ export default function link(scope, elem, attrs, ctrl) {
return
tickValues
;
}
function
tickValueFormatter
(
decimals
,
scaledDecimals
=
null
)
{
let
format
=
panel
.
yAxis
.
format
;
tickValueFormatter
(
decimals
,
scaledDecimals
=
null
)
{
let
format
=
this
.
panel
.
yAxis
.
format
;
return
function
(
value
)
{
try
{
return
format
!==
'none'
?
kbn
.
valueFormats
[
format
](
value
,
decimals
,
scaledDecimals
)
:
value
;
...
...
@@ -405,181 +445,178 @@ export default function link(scope, elem, attrs, ctrl) {
};
}
ctrl
.
tickValueFormatter
=
tickValueFormatter
;
function
fixYAxisTickSize
()
{
heatmap
fixYAxisTickSize
()
{
this
.
heatmap
.
select
(
'.axis-y'
)
.
selectAll
(
'.tick line'
)
.
attr
(
'x2'
,
chartWidth
);
.
attr
(
'x2'
,
this
.
chartWidth
);
}
function
addAxes
()
{
chartHeight
=
height
-
margin
.
top
-
margin
.
bottom
;
chartTop
=
margin
.
top
;
chartBottom
=
chartTop
+
chartHeight
;
if
(
panel
.
dataFormat
===
'tsbuckets'
)
{
addYAxisFromBuckets
();
addAxes
()
{
this
.
chartHeight
=
this
.
height
-
this
.
margin
.
top
-
this
.
margin
.
bottom
;
this
.
chartTop
=
this
.
margin
.
top
;
this
.
chartBottom
=
this
.
chartTop
+
this
.
chartHeight
;
if
(
this
.
panel
.
dataFormat
===
'tsbuckets'
)
{
this
.
addYAxisFromBuckets
();
}
else
{
if
(
panel
.
yAxis
.
logBase
===
1
)
{
addYAxis
();
if
(
this
.
panel
.
yAxis
.
logBase
===
1
)
{
this
.
addYAxis
();
}
else
{
addLogYAxis
();
this
.
addLogYAxis
();
}
}
yAxisWidth
=
getYAxisWidth
(
heatmap
)
+
Y_AXIS_TICK_PADDING
;
chartWidth
=
width
-
yAxisWidth
-
margin
.
right
;
fixYAxisTickSize
();
this
.
yAxisWidth
=
this
.
getYAxisWidth
(
this
.
heatmap
)
+
Y_AXIS_TICK_PADDING
;
this
.
chartWidth
=
this
.
width
-
this
.
yAxisWidth
-
this
.
margin
.
right
;
this
.
fixYAxisTickSize
();
addXAxis
();
xAxisHeight
=
getXAxisHeight
(
heatmap
);
this
.
addXAxis
();
this
.
xAxisHeight
=
this
.
getXAxisHeight
(
this
.
heatmap
);
if
(
!
panel
.
yAxis
.
show
)
{
heatmap
if
(
!
this
.
panel
.
yAxis
.
show
)
{
this
.
heatmap
.
select
(
'.axis-y'
)
.
selectAll
(
'line'
)
.
style
(
'opacity'
,
0
);
}
if
(
!
panel
.
xAxis
.
show
)
{
heatmap
if
(
!
this
.
panel
.
xAxis
.
show
)
{
this
.
heatmap
.
select
(
'.axis-x'
)
.
selectAll
(
'line'
)
.
style
(
'opacity'
,
0
);
}
}
function
addHeatmapCanvas
()
{
let
heatmap_elem
=
$heatmap
[
0
];
addHeatmapCanvas
()
{
let
heatmap_elem
=
this
.
$heatmap
[
0
];
width
=
Math
.
floor
(
$heatmap
.
width
())
-
padding
.
right
;
height
=
Math
.
floor
(
$heatmap
.
height
())
-
padding
.
bottom
;
this
.
width
=
Math
.
floor
(
this
.
$heatmap
.
width
())
-
this
.
padding
.
right
;
this
.
height
=
Math
.
floor
(
this
.
$heatmap
.
height
())
-
this
.
padding
.
bottom
;
cardPadding
=
panel
.
cards
.
cardPadding
!==
null
?
panel
.
cards
.
cardPadding
:
CARD_PADDING
;
cardRound
=
panel
.
cards
.
cardRound
!==
null
?
panel
.
cards
.
cardRound
:
CARD_ROUND
;
this
.
cardPadding
=
this
.
panel
.
cards
.
cardPadding
!==
null
?
this
.
panel
.
cards
.
cardPadding
:
CARD_PADDING
;
this
.
cardRound
=
this
.
panel
.
cards
.
cardRound
!==
null
?
this
.
panel
.
cards
.
cardRound
:
CARD_ROUND
;
if
(
heatmap
)
{
heatmap
.
remove
();
if
(
this
.
heatmap
)
{
this
.
heatmap
.
remove
();
}
heatmap
=
d3
this
.
heatmap
=
d3
.
select
(
heatmap_elem
)
.
append
(
'svg'
)
.
attr
(
'width'
,
width
)
.
attr
(
'height'
,
height
);
.
attr
(
'width'
,
this
.
width
)
.
attr
(
'height'
,
this
.
height
);
}
function
addHeatmap
()
{
addHeatmapCanvas
();
addAxes
();
addHeatmap
()
{
this
.
addHeatmapCanvas
();
this
.
addAxes
();
if
(
panel
.
yAxis
.
logBase
!==
1
&&
panel
.
dataFormat
!==
'tsbuckets'
)
{
let
log_base
=
panel
.
yAxis
.
logBase
;
let
domain
=
yScale
.
domain
();
let
tick_values
=
logScaleTickValues
(
domain
,
log_base
);
data
.
buckets
=
mergeZeroBuckets
(
data
.
buckets
,
_
.
min
(
tick_values
));
if
(
this
.
panel
.
yAxis
.
logBase
!==
1
&&
this
.
panel
.
dataFormat
!==
'tsbuckets'
)
{
let
log_base
=
this
.
panel
.
yAxis
.
logBase
;
let
domain
=
this
.
yScale
.
domain
();
let
tick_values
=
this
.
logScaleTickValues
(
domain
,
log_base
);
this
.
data
.
buckets
=
mergeZeroBuckets
(
this
.
data
.
buckets
,
_
.
min
(
tick_values
));
}
let
cardsData
=
data
.
cards
;
let
maxValueAuto
=
data
.
cardStats
.
max
;
let
maxValue
=
panel
.
color
.
max
||
maxValueAuto
;
let
minValue
=
panel
.
color
.
min
||
0
;
let
cardsData
=
this
.
data
.
cards
;
let
maxValueAuto
=
this
.
data
.
cardStats
.
max
;
let
maxValue
=
this
.
panel
.
color
.
max
||
maxValueAuto
;
let
minValue
=
this
.
panel
.
color
.
min
||
0
;
let
colorScheme
=
_
.
find
(
ctrl
.
colorSchemes
,
{
value
:
panel
.
color
.
colorScheme
,
let
colorScheme
=
_
.
find
(
this
.
ctrl
.
colorSchemes
,
{
value
:
this
.
panel
.
color
.
colorScheme
,
});
colorScale
=
getColorScale
(
colorScheme
,
contextSrv
.
user
.
lightTheme
,
maxValue
,
minValue
);
opacityScale
=
getOpacityScale
(
panel
.
color
,
maxValue
);
setCardSize
();
this
.
colorScale
=
getColorScale
(
colorScheme
,
contextSrv
.
user
.
lightTheme
,
maxValue
,
minValue
);
this
.
opacityScale
=
getOpacityScale
(
this
.
panel
.
color
,
maxValue
);
this
.
setCardSize
();
let
cards
=
heatmap
.
selectAll
(
'.heatmap-card'
).
data
(
cardsData
);
let
cards
=
this
.
heatmap
.
selectAll
(
'.heatmap-card'
).
data
(
cardsData
);
cards
.
append
(
'title'
);
cards
=
cards
.
enter
()
.
append
(
'rect'
)
.
attr
(
'x'
,
getCardX
)
.
attr
(
'width'
,
getCardWidth
)
.
attr
(
'y'
,
getCardY
)
.
attr
(
'height'
,
getCardHeight
)
.
attr
(
'rx'
,
cardRound
)
.
attr
(
'ry'
,
cardRound
)
.
attr
(
'x'
,
this
.
getCardX
.
bind
(
this
)
)
.
attr
(
'width'
,
this
.
getCardWidth
.
bind
(
this
)
)
.
attr
(
'y'
,
this
.
getCardY
.
bind
(
this
)
)
.
attr
(
'height'
,
this
.
getCardHeight
.
bind
(
this
)
)
.
attr
(
'rx'
,
this
.
cardRound
)
.
attr
(
'ry'
,
this
.
cardRound
)
.
attr
(
'class'
,
'bordered heatmap-card'
)
.
style
(
'fill'
,
getCardColor
)
.
style
(
'stroke'
,
getCardColor
)
.
style
(
'fill'
,
this
.
getCardColor
.
bind
(
this
)
)
.
style
(
'stroke'
,
this
.
getCardColor
.
bind
(
this
)
)
.
style
(
'stroke-width'
,
0
)
.
style
(
'opacity'
,
getCardOpacity
);
.
style
(
'opacity'
,
this
.
getCardOpacity
.
bind
(
this
)
);
let
$cards
=
$heatmap
.
find
(
'.heatmap-card'
);
let
$cards
=
this
.
$heatmap
.
find
(
'.heatmap-card'
);
$cards
.
on
(
'mouseenter'
,
event
=>
{
tooltip
.
mouseOverBucket
=
true
;
highlightCard
(
event
);
t
his
.
t
ooltip
.
mouseOverBucket
=
true
;
this
.
highlightCard
(
event
);
})
.
on
(
'mouseleave'
,
event
=>
{
tooltip
.
mouseOverBucket
=
false
;
resetCardHighLight
(
event
);
t
his
.
t
ooltip
.
mouseOverBucket
=
false
;
this
.
resetCardHighLight
(
event
);
});
}
function
highlightCard
(
event
)
{
highlightCard
(
event
)
{
let
color
=
d3
.
select
(
event
.
target
).
style
(
'fill'
);
let
highlightColor
=
d3
.
color
(
color
).
darker
(
2
);
let
strokeColor
=
d3
.
color
(
color
).
brighter
(
4
);
let
current_card
=
d3
.
select
(
event
.
target
);
tooltip
.
originalFillColor
=
color
;
t
his
.
t
ooltip
.
originalFillColor
=
color
;
current_card
.
style
(
'fill'
,
highlightColor
.
toString
())
.
style
(
'stroke'
,
strokeColor
.
toString
())
.
style
(
'stroke-width'
,
1
);
}
function
resetCardHighLight
(
event
)
{
resetCardHighLight
(
event
)
{
d3
.
select
(
event
.
target
)
.
style
(
'fill'
,
tooltip
.
originalFillColor
)
.
style
(
'stroke'
,
tooltip
.
originalFillColor
)
.
style
(
'fill'
,
t
his
.
t
ooltip
.
originalFillColor
)
.
style
(
'stroke'
,
t
his
.
t
ooltip
.
originalFillColor
)
.
style
(
'stroke-width'
,
0
);
}
function
setCardSize
()
{
let
xGridSize
=
Math
.
floor
(
xScale
(
data
.
xBucketSize
)
-
xScale
(
0
));
let
yGridSize
=
Math
.
floor
(
yScale
(
yScale
.
invert
(
0
)
-
data
.
yBucketSize
));
setCardSize
()
{
let
xGridSize
=
Math
.
floor
(
this
.
xScale
(
this
.
data
.
xBucketSize
)
-
this
.
xScale
(
0
));
let
yGridSize
=
Math
.
floor
(
this
.
yScale
(
this
.
yScale
.
invert
(
0
)
-
this
.
data
.
yBucketSize
));
if
(
panel
.
yAxis
.
logBase
!==
1
)
{
let
base
=
panel
.
yAxis
.
logBase
;
let
splitFactor
=
data
.
yBucketSize
||
1
;
yGridSize
=
Math
.
floor
((
yScale
(
1
)
-
yScale
(
base
))
/
splitFactor
);
if
(
this
.
panel
.
yAxis
.
logBase
!==
1
)
{
let
base
=
this
.
panel
.
yAxis
.
logBase
;
let
splitFactor
=
this
.
data
.
yBucketSize
||
1
;
yGridSize
=
Math
.
floor
((
this
.
yScale
(
1
)
-
this
.
yScale
(
base
))
/
splitFactor
);
}
cardWidth
=
xGridSize
-
cardPadding
*
2
;
cardHeight
=
yGridSize
?
yGridSize
-
cardPadding
*
2
:
0
;
this
.
cardWidth
=
xGridSize
-
this
.
cardPadding
*
2
;
this
.
cardHeight
=
yGridSize
?
yGridSize
-
this
.
cardPadding
*
2
:
0
;
}
function
getCardX
(
d
)
{
getCardX
(
d
)
{
let
x
;
if
(
xScale
(
d
.
x
)
<
0
)
{
if
(
this
.
xScale
(
d
.
x
)
<
0
)
{
// Cut card left to prevent overlay
x
=
yAxisWidth
+
cardPadding
;
x
=
this
.
yAxisWidth
+
this
.
cardPadding
;
}
else
{
x
=
xScale
(
d
.
x
)
+
yAxisWidth
+
cardPadding
;
x
=
this
.
xScale
(
d
.
x
)
+
this
.
yAxisWidth
+
this
.
cardPadding
;
}
return
x
;
}
function
getCardWidth
(
d
)
{
getCardWidth
(
d
)
{
let
w
;
if
(
xScale
(
d
.
x
)
<
0
)
{
if
(
this
.
xScale
(
d
.
x
)
<
0
)
{
// Cut card left to prevent overlay
let
cutted_width
=
xScale
(
d
.
x
)
+
cardWidth
;
let
cutted_width
=
this
.
xScale
(
d
.
x
)
+
this
.
cardWidth
;
w
=
cutted_width
>
0
?
cutted_width
:
0
;
}
else
if
(
xScale
(
d
.
x
)
+
cardWidth
>
chartWidth
)
{
}
else
if
(
this
.
xScale
(
d
.
x
)
+
this
.
cardWidth
>
this
.
chartWidth
)
{
// Cut card right to prevent overlay
w
=
chartWidth
-
xScale
(
d
.
x
)
-
cardPadding
;
w
=
this
.
chartWidth
-
this
.
xScale
(
d
.
x
)
-
this
.
cardPadding
;
}
else
{
w
=
cardWidth
;
w
=
this
.
cardWidth
;
}
// Card width should be MIN_CARD_SIZE at least
...
...
@@ -587,138 +624,117 @@ export default function link(scope, elem, attrs, ctrl) {
return
w
;
}
function
getCardY
(
d
)
{
let
y
=
yScale
(
d
.
y
)
+
chartTop
-
cardHeight
-
cardPadding
;
if
(
panel
.
yAxis
.
logBase
!==
1
&&
d
.
y
===
0
)
{
y
=
chartBottom
-
cardHeight
-
cardPadding
;
getCardY
(
d
)
{
let
y
=
this
.
yScale
(
d
.
y
)
+
this
.
chartTop
-
this
.
cardHeight
-
this
.
cardPadding
;
if
(
this
.
panel
.
yAxis
.
logBase
!==
1
&&
d
.
y
===
0
)
{
y
=
this
.
chartBottom
-
this
.
cardHeight
-
this
.
cardPadding
;
}
else
{
if
(
y
<
chartTop
)
{
y
=
chartTop
;
if
(
y
<
this
.
chartTop
)
{
y
=
this
.
chartTop
;
}
}
return
y
;
}
function
getCardHeight
(
d
)
{
let
y
=
yScale
(
d
.
y
)
+
chartTop
-
cardHeight
-
cardPadding
;
let
h
=
cardHeight
;
getCardHeight
(
d
)
{
let
y
=
this
.
yScale
(
d
.
y
)
+
this
.
chartTop
-
this
.
cardHeight
-
this
.
cardPadding
;
let
h
=
this
.
cardHeight
;
if
(
panel
.
yAxis
.
logBase
!==
1
&&
d
.
y
===
0
)
{
return
cardHeight
;
if
(
this
.
panel
.
yAxis
.
logBase
!==
1
&&
d
.
y
===
0
)
{
return
this
.
cardHeight
;
}
// Cut card height to prevent overlay
if
(
y
<
chartTop
)
{
h
=
yScale
(
d
.
y
)
-
cardPadding
;
}
else
if
(
yScale
(
d
.
y
)
>
chartBottom
)
{
h
=
chartBottom
-
y
;
}
else
if
(
y
+
cardHeight
>
chartBottom
)
{
h
=
chartBottom
-
y
;
if
(
y
<
this
.
chartTop
)
{
h
=
this
.
yScale
(
d
.
y
)
-
this
.
cardPadding
;
}
else
if
(
this
.
yScale
(
d
.
y
)
>
this
.
chartBottom
)
{
h
=
this
.
chartBottom
-
y
;
}
else
if
(
y
+
this
.
cardHeight
>
this
.
chartBottom
)
{
h
=
this
.
chartBottom
-
y
;
}
// Height can't be more than chart height
h
=
Math
.
min
(
h
,
chartHeight
);
h
=
Math
.
min
(
h
,
this
.
chartHeight
);
// Card height should be MIN_CARD_SIZE at least
h
=
Math
.
max
(
h
,
MIN_CARD_SIZE
);
return
h
;
}
function
getCardColor
(
d
)
{
if
(
panel
.
color
.
mode
===
'opacity'
)
{
return
panel
.
color
.
cardColor
;
getCardColor
(
d
)
{
if
(
this
.
panel
.
color
.
mode
===
'opacity'
)
{
return
this
.
panel
.
color
.
cardColor
;
}
else
{
return
colorScale
(
d
.
count
);
return
this
.
colorScale
(
d
.
count
);
}
}
function
getCardOpacity
(
d
)
{
if
(
panel
.
color
.
mode
===
'opacity'
)
{
return
opacityScale
(
d
.
count
);
getCardOpacity
(
d
)
{
if
(
this
.
panel
.
color
.
mode
===
'opacity'
)
{
return
this
.
opacityScale
(
d
.
count
);
}
else
{
return
1
;
}
}
/////////////////////////////
// Selection and crosshair //
/////////////////////////////
// Shared crosshair and tooltip
appEvents
.
on
(
'graph-hover'
,
event
=>
{
drawSharedCrosshair
(
event
.
pos
);
},
scope
);
appEvents
.
on
(
'graph-hover-clear'
,
()
=>
{
clearCrosshair
();
},
scope
);
onMouseDown
(
event
)
{
this
.
selection
.
active
=
true
;
this
.
selection
.
x1
=
event
.
offsetX
;
function
onMouseDown
(
event
)
{
selection
.
active
=
true
;
selection
.
x1
=
event
.
offsetX
;
mouseUpHandler
=
function
()
{
onMouseUp
();
this
.
mouseUpHandler
=
()
=>
{
this
.
onMouseUp
();
};
$
(
document
).
one
(
'mouseup'
,
mouseUpHandler
);
$
(
document
).
one
(
'mouseup'
,
this
.
mouseUpHandler
.
bind
(
this
)
);
}
function
onMouseUp
()
{
$
(
document
).
unbind
(
'mouseup'
,
mouseUpHandler
);
mouseUpHandler
=
null
;
selection
.
active
=
false
;
onMouseUp
()
{
$
(
document
).
unbind
(
'mouseup'
,
this
.
mouseUpHandler
.
bind
(
this
)
);
this
.
mouseUpHandler
=
null
;
this
.
selection
.
active
=
false
;
let
selectionRange
=
Math
.
abs
(
selection
.
x2
-
selection
.
x1
);
if
(
selection
.
x2
>=
0
&&
selectionRange
>
MIN_SELECTION_WIDTH
)
{
let
timeFrom
=
xScale
.
invert
(
Math
.
min
(
selection
.
x1
,
selection
.
x2
)
-
yAxisWidth
);
let
timeTo
=
xScale
.
invert
(
Math
.
max
(
selection
.
x1
,
selection
.
x2
)
-
yAxisWidth
);
let
selectionRange
=
Math
.
abs
(
this
.
selection
.
x2
-
this
.
selection
.
x1
);
if
(
this
.
selection
.
x2
>=
0
&&
selectionRange
>
MIN_SELECTION_WIDTH
)
{
let
timeFrom
=
this
.
xScale
.
invert
(
Math
.
min
(
this
.
selection
.
x1
,
this
.
selection
.
x2
)
-
this
.
yAxisWidth
);
let
timeTo
=
this
.
xScale
.
invert
(
Math
.
max
(
this
.
selection
.
x1
,
this
.
selection
.
x2
)
-
this
.
yAxisWidth
);
ctrl
.
timeSrv
.
setTime
({
this
.
ctrl
.
timeSrv
.
setTime
({
from
:
moment
.
utc
(
timeFrom
),
to
:
moment
.
utc
(
timeTo
),
});
}
clearSelection
();
this
.
clearSelection
();
}
function
onMouseLeave
()
{
onMouseLeave
()
{
appEvents
.
emit
(
'graph-hover-clear'
);
clearCrosshair
();
this
.
clearCrosshair
();
}
function
onMouseMove
(
event
)
{
if
(
!
heatmap
)
{
onMouseMove
(
event
)
{
if
(
!
this
.
heatmap
)
{
return
;
}
if
(
selection
.
active
)
{
if
(
this
.
selection
.
active
)
{
// Clear crosshair and tooltip
clearCrosshair
();
tooltip
.
destroy
();
this
.
clearCrosshair
();
t
his
.
t
ooltip
.
destroy
();
selection
.
x2
=
limitSelection
(
event
.
offsetX
);
drawSelection
(
selection
.
x1
,
selection
.
x2
);
this
.
selection
.
x2
=
this
.
limitSelection
(
event
.
offsetX
);
this
.
drawSelection
(
this
.
selection
.
x1
,
this
.
selection
.
x2
);
}
else
{
emitGraphHoverEvent
(
event
);
drawCrosshair
(
event
.
offsetX
);
t
ooltip
.
show
(
event
,
data
);
this
.
emitGraphHoverEvent
(
event
);
this
.
drawCrosshair
(
event
.
offsetX
);
t
his
.
tooltip
.
show
(
event
,
this
.
data
);
}
}
function
emitGraphHoverEvent
(
event
)
{
let
x
=
xScale
.
invert
(
event
.
offsetX
-
yAxisWidth
).
valueOf
();
let
y
=
yScale
.
invert
(
event
.
offsetY
);
emitGraphHoverEvent
(
event
)
{
let
x
=
this
.
xScale
.
invert
(
event
.
offsetX
-
this
.
yAxisWidth
).
valueOf
();
let
y
=
this
.
yScale
.
invert
(
event
.
offsetY
);
let
pos
=
{
pageX
:
event
.
pageX
,
pageY
:
event
.
pageY
,
...
...
@@ -730,105 +746,100 @@ export default function link(scope, elem, attrs, ctrl) {
};
// Set minimum offset to prevent showing legend from another panel
pos
.
panelRelY
=
Math
.
max
(
event
.
offsetY
/
height
,
0.001
);
pos
.
panelRelY
=
Math
.
max
(
event
.
offsetY
/
this
.
height
,
0.001
);
// broadcast to other graph panels that we are hovering
appEvents
.
emit
(
'graph-hover'
,
{
pos
:
pos
,
panel
:
panel
});
appEvents
.
emit
(
'graph-hover'
,
{
pos
:
pos
,
panel
:
this
.
panel
});
}
function
limitSelection
(
x2
)
{
x2
=
Math
.
max
(
x2
,
yAxisWidth
);
x2
=
Math
.
min
(
x2
,
chartWidth
+
yAxisWidth
);
limitSelection
(
x2
)
{
x2
=
Math
.
max
(
x2
,
this
.
yAxisWidth
);
x2
=
Math
.
min
(
x2
,
this
.
chartWidth
+
this
.
yAxisWidth
);
return
x2
;
}
function
drawSelection
(
posX1
,
posX2
)
{
if
(
heatmap
)
{
heatmap
.
selectAll
(
'.heatmap-selection'
).
remove
();
drawSelection
(
posX1
,
posX2
)
{
if
(
this
.
heatmap
)
{
this
.
heatmap
.
selectAll
(
'.heatmap-selection'
).
remove
();
let
selectionX
=
Math
.
min
(
posX1
,
posX2
);
let
selectionWidth
=
Math
.
abs
(
posX1
-
posX2
);
if
(
selectionWidth
>
MIN_SELECTION_WIDTH
)
{
heatmap
this
.
heatmap
.
append
(
'rect'
)
.
attr
(
'class'
,
'heatmap-selection'
)
.
attr
(
'x'
,
selectionX
)
.
attr
(
'width'
,
selectionWidth
)
.
attr
(
'y'
,
chartTop
)
.
attr
(
'height'
,
chartHeight
);
.
attr
(
'y'
,
this
.
chartTop
)
.
attr
(
'height'
,
this
.
chartHeight
);
}
}
}
function
clearSelection
()
{
selection
.
x1
=
-
1
;
selection
.
x2
=
-
1
;
clearSelection
()
{
this
.
selection
.
x1
=
-
1
;
this
.
selection
.
x2
=
-
1
;
if
(
heatmap
)
{
heatmap
.
selectAll
(
'.heatmap-selection'
).
remove
();
if
(
this
.
heatmap
)
{
this
.
heatmap
.
selectAll
(
'.heatmap-selection'
).
remove
();
}
}
function
drawCrosshair
(
position
)
{
if
(
heatmap
)
{
heatmap
.
selectAll
(
'.heatmap-crosshair'
).
remove
();
drawCrosshair
(
position
)
{
if
(
this
.
heatmap
)
{
this
.
heatmap
.
selectAll
(
'.heatmap-crosshair'
).
remove
();
let
posX
=
position
;
posX
=
Math
.
max
(
posX
,
yAxisWidth
);
posX
=
Math
.
min
(
posX
,
chartWidth
+
yAxisWidth
);
posX
=
Math
.
max
(
posX
,
this
.
yAxisWidth
);
posX
=
Math
.
min
(
posX
,
this
.
chartWidth
+
this
.
yAxisWidth
);
heatmap
this
.
heatmap
.
append
(
'g'
)
.
attr
(
'class'
,
'heatmap-crosshair'
)
.
attr
(
'transform'
,
'translate('
+
posX
+
',0)'
)
.
append
(
'line'
)
.
attr
(
'x1'
,
1
)
.
attr
(
'y1'
,
chartTop
)
.
attr
(
'y1'
,
this
.
chartTop
)
.
attr
(
'x2'
,
1
)
.
attr
(
'y2'
,
chartBottom
)
.
attr
(
'y2'
,
this
.
chartBottom
)
.
attr
(
'stroke-width'
,
1
);
}
}
function
drawSharedCrosshair
(
pos
)
{
if
(
heatmap
&&
ctrl
.
dashboard
.
graphTooltip
!==
0
)
{
let
posX
=
xScale
(
pos
.
x
)
+
yAxisWidth
;
drawCrosshair
(
posX
);
drawSharedCrosshair
(
pos
)
{
if
(
this
.
heatmap
&&
this
.
ctrl
.
dashboard
.
graphTooltip
!==
0
)
{
let
posX
=
this
.
xScale
(
pos
.
x
)
+
this
.
yAxisWidth
;
this
.
drawCrosshair
(
posX
);
}
}
function
clearCrosshair
()
{
if
(
heatmap
)
{
heatmap
.
selectAll
(
'.heatmap-crosshair'
).
remove
();
clearCrosshair
()
{
if
(
this
.
heatmap
)
{
this
.
heatmap
.
selectAll
(
'.heatmap-crosshair'
).
remove
();
}
}
function
render
()
{
data
=
ctrl
.
data
;
panel
=
ctrl
.
panel
;
t
imeRange
=
ctrl
.
range
;
render
()
{
this
.
data
=
this
.
ctrl
.
data
;
this
.
panel
=
this
.
ctrl
.
panel
;
t
his
.
timeRange
=
this
.
ctrl
.
range
;
if
(
!
setElementHeight
()
||
!
data
)
{
if
(
!
this
.
setElementHeight
()
||
!
this
.
data
)
{
return
;
}
// Draw default axes and return if no data
if
(
_
.
isEmpty
(
data
.
buckets
))
{
addHeatmapCanvas
();
addAxes
();
if
(
_
.
isEmpty
(
this
.
data
.
buckets
))
{
this
.
addHeatmapCanvas
();
this
.
addAxes
();
return
;
}
addHeatmap
();
scope
.
yAxisWidth
=
yAxisWidth
;
scope
.
xAxisHeight
=
xAxisHeight
;
scope
.
chartHeight
=
chartHeight
;
scope
.
chartWidth
=
chartWidth
;
scope
.
chartTop
=
chartTop
;
this
.
addHeatmap
();
this
.
scope
.
yAxisWidth
=
this
.
yAxisWidth
;
this
.
scope
.
xAxisHeight
=
this
.
xAxisHeight
;
this
.
scope
.
chartHeight
=
this
.
chartHeight
;
this
.
scope
.
chartWidth
=
this
.
chartWidth
;
this
.
scope
.
chartTop
=
this
.
chartTop
;
}
// Register selection listeners
$heatmap
.
on
(
'mousedown'
,
onMouseDown
);
$heatmap
.
on
(
'mousemove'
,
onMouseMove
);
$heatmap
.
on
(
'mouseleave'
,
onMouseLeave
);
}
public/app/plugins/panel/heatmap/specs/renderer_specs.ts
deleted
100644 → 0
View file @
77ee032e
import
{
describe
,
beforeEach
,
it
,
sinon
,
expect
,
angularMocks
}
from
'../../../../../test/lib/common'
;
import
'../module'
;
import
angular
from
'angular'
;
import
$
from
'jquery'
;
import
helpers
from
'test/specs/helpers'
;
import
TimeSeries
from
'app/core/time_series2'
;
import
moment
from
'moment'
;
import
{
Emitter
}
from
'app/core/core'
;
import
rendering
from
'../rendering'
;
import
{
convertToHeatMap
,
convertToCards
,
histogramToHeatmap
,
calculateBucketSize
}
from
'../heatmap_data_converter'
;
describe
(
'grafanaHeatmap'
,
function
()
{
beforeEach
(
angularMocks
.
module
(
'grafana.core'
));
function
heatmapScenario
(
desc
,
func
,
elementWidth
=
500
)
{
describe
(
desc
,
function
()
{
var
ctx
:
any
=
{};
ctx
.
setup
=
function
(
setupFunc
)
{
beforeEach
(
angularMocks
.
module
(
function
(
$provide
)
{
$provide
.
value
(
'timeSrv'
,
new
helpers
.
TimeSrvStub
());
})
);
beforeEach
(
angularMocks
.
inject
(
function
(
$rootScope
,
$compile
)
{
var
ctrl
:
any
=
{
colorSchemes
:
[
{
name
:
'Oranges'
,
value
:
'interpolateOranges'
,
invert
:
'dark'
,
},
{
name
:
'Reds'
,
value
:
'interpolateReds'
,
invert
:
'dark'
},
],
events
:
new
Emitter
(),
height
:
200
,
panel
:
{
heatmap
:
{},
cards
:
{
cardPadding
:
null
,
cardRound
:
null
,
},
color
:
{
mode
:
'spectrum'
,
cardColor
:
'#b4ff00'
,
colorScale
:
'linear'
,
exponent
:
0.5
,
colorScheme
:
'interpolateOranges'
,
fillBackground
:
false
,
},
legend
:
{
show
:
false
,
},
xBucketSize
:
1000
,
xBucketNumber
:
null
,
yBucketSize
:
1
,
yBucketNumber
:
null
,
xAxis
:
{
show
:
true
,
},
yAxis
:
{
show
:
true
,
format
:
'short'
,
decimals
:
null
,
logBase
:
1
,
splitFactor
:
null
,
min
:
null
,
max
:
null
,
removeZeroValues
:
false
,
},
tooltip
:
{
show
:
true
,
seriesStat
:
false
,
showHistogram
:
false
,
},
highlightCards
:
true
,
},
renderingCompleted
:
sinon
.
spy
(),
hiddenSeries
:
{},
dashboard
:
{
getTimezone
:
sinon
.
stub
().
returns
(
'utc'
),
},
range
:
{
from
:
moment
.
utc
(
'01 Mar 2017 10:00:00'
,
'DD MMM YYYY HH:mm:ss'
),
to
:
moment
.
utc
(
'01 Mar 2017 11:00:00'
,
'DD MMM YYYY HH:mm:ss'
),
},
};
var
scope
=
$rootScope
.
$new
();
scope
.
ctrl
=
ctrl
;
ctx
.
series
=
[];
ctx
.
series
.
push
(
new
TimeSeries
({
datapoints
:
[[
1
,
1422774000000
],
[
2
,
1422774060000
]],
alias
:
'series1'
,
})
);
ctx
.
series
.
push
(
new
TimeSeries
({
datapoints
:
[[
2
,
1422774000000
],
[
3
,
1422774060000
]],
alias
:
'series2'
,
})
);
ctx
.
data
=
{
heatmapStats
:
{
min
:
1
,
max
:
3
,
minLog
:
1
,
},
xBucketSize
:
ctrl
.
panel
.
xBucketSize
,
yBucketSize
:
ctrl
.
panel
.
yBucketSize
,
};
setupFunc
(
ctrl
,
ctx
);
let
logBase
=
ctrl
.
panel
.
yAxis
.
logBase
;
let
bucketsData
;
if
(
ctrl
.
panel
.
dataFormat
===
'tsbuckets'
)
{
bucketsData
=
histogramToHeatmap
(
ctx
.
series
);
}
else
{
bucketsData
=
convertToHeatMap
(
ctx
.
series
,
ctx
.
data
.
yBucketSize
,
ctx
.
data
.
xBucketSize
,
logBase
);
}
ctx
.
data
.
buckets
=
bucketsData
;
let
{
cards
,
cardStats
}
=
convertToCards
(
bucketsData
);
ctx
.
data
.
cards
=
cards
;
ctx
.
data
.
cardStats
=
cardStats
;
let
elemHtml
=
`
<div class="heatmap-wrapper">
<div class="heatmap-canvas-wrapper">
<div class="heatmap-panel" style='width:
${
elementWidth
}
px'></div>
</div>
</div>`
;
var
element
=
angular
.
element
(
elemHtml
);
$compile
(
element
)(
scope
);
scope
.
$digest
();
ctrl
.
data
=
ctx
.
data
;
ctx
.
element
=
element
;
rendering
(
scope
,
$
(
element
),
[],
ctrl
);
ctrl
.
events
.
emit
(
'render'
);
})
);
};
func
(
ctx
);
});
}
heatmapScenario
(
'default options'
,
function
(
ctx
)
{
ctx
.
setup
(
function
(
ctrl
)
{
ctrl
.
panel
.
yAxis
.
logBase
=
1
;
});
it
(
'should draw correct Y axis'
,
function
()
{
var
yTicks
=
getTicks
(
ctx
.
element
,
'.axis-y'
);
expect
(
yTicks
).
to
.
eql
([
'1'
,
'2'
,
'3'
]);
});
it
(
'should draw correct X axis'
,
function
()
{
var
xTicks
=
getTicks
(
ctx
.
element
,
'.axis-x'
);
let
expectedTicks
=
[
formatTime
(
'01 Mar 2017 10:00:00'
),
formatTime
(
'01 Mar 2017 10:15:00'
),
formatTime
(
'01 Mar 2017 10:30:00'
),
formatTime
(
'01 Mar 2017 10:45:00'
),
formatTime
(
'01 Mar 2017 11:00:00'
),
];
expect
(
xTicks
).
to
.
eql
(
expectedTicks
);
});
});
heatmapScenario
(
'when logBase is 2'
,
function
(
ctx
)
{
ctx
.
setup
(
function
(
ctrl
)
{
ctrl
.
panel
.
yAxis
.
logBase
=
2
;
});
it
(
'should draw correct Y axis'
,
function
()
{
var
yTicks
=
getTicks
(
ctx
.
element
,
'.axis-y'
);
expect
(
yTicks
).
to
.
eql
([
'1'
,
'2'
,
'4'
]);
});
});
heatmapScenario
(
'when logBase is 10'
,
function
(
ctx
)
{
ctx
.
setup
(
function
(
ctrl
,
ctx
)
{
ctrl
.
panel
.
yAxis
.
logBase
=
10
;
ctx
.
series
.
push
(
new
TimeSeries
({
datapoints
:
[[
10
,
1422774000000
],
[
20
,
1422774060000
]],
alias
:
'series3'
,
})
);
ctx
.
data
.
heatmapStats
.
max
=
20
;
});
it
(
'should draw correct Y axis'
,
function
()
{
var
yTicks
=
getTicks
(
ctx
.
element
,
'.axis-y'
);
expect
(
yTicks
).
to
.
eql
([
'1'
,
'10'
,
'100'
]);
});
});
heatmapScenario
(
'when logBase is 32'
,
function
(
ctx
)
{
ctx
.
setup
(
function
(
ctrl
)
{
ctrl
.
panel
.
yAxis
.
logBase
=
32
;
ctx
.
series
.
push
(
new
TimeSeries
({
datapoints
:
[[
10
,
1422774000000
],
[
100
,
1422774060000
]],
alias
:
'series3'
,
})
);
ctx
.
data
.
heatmapStats
.
max
=
100
;
});
it
(
'should draw correct Y axis'
,
function
()
{
var
yTicks
=
getTicks
(
ctx
.
element
,
'.axis-y'
);
expect
(
yTicks
).
to
.
eql
([
'1'
,
'32'
,
'1.0 K'
]);
});
});
heatmapScenario
(
'when logBase is 1024'
,
function
(
ctx
)
{
ctx
.
setup
(
function
(
ctrl
)
{
ctrl
.
panel
.
yAxis
.
logBase
=
1024
;
ctx
.
series
.
push
(
new
TimeSeries
({
datapoints
:
[[
2000
,
1422774000000
],
[
300000
,
1422774060000
]],
alias
:
'series3'
,
})
);
ctx
.
data
.
heatmapStats
.
max
=
300000
;
});
it
(
'should draw correct Y axis'
,
function
()
{
var
yTicks
=
getTicks
(
ctx
.
element
,
'.axis-y'
);
expect
(
yTicks
).
to
.
eql
([
'1'
,
'1 K'
,
'1.0 Mil'
]);
});
});
heatmapScenario
(
'when Y axis format set to "none"'
,
function
(
ctx
)
{
ctx
.
setup
(
function
(
ctrl
)
{
ctrl
.
panel
.
yAxis
.
logBase
=
1
;
ctrl
.
panel
.
yAxis
.
format
=
'none'
;
ctx
.
data
.
heatmapStats
.
max
=
10000
;
});
it
(
'should draw correct Y axis'
,
function
()
{
var
yTicks
=
getTicks
(
ctx
.
element
,
'.axis-y'
);
expect
(
yTicks
).
to
.
eql
([
'0'
,
'2000'
,
'4000'
,
'6000'
,
'8000'
,
'10000'
,
'12000'
]);
});
});
heatmapScenario
(
'when Y axis format set to "second"'
,
function
(
ctx
)
{
ctx
.
setup
(
function
(
ctrl
)
{
ctrl
.
panel
.
yAxis
.
logBase
=
1
;
ctrl
.
panel
.
yAxis
.
format
=
's'
;
ctx
.
data
.
heatmapStats
.
max
=
3600
;
});
it
(
'should draw correct Y axis'
,
function
()
{
var
yTicks
=
getTicks
(
ctx
.
element
,
'.axis-y'
);
expect
(
yTicks
).
to
.
eql
([
'0 ns'
,
'17 min'
,
'33 min'
,
'50 min'
,
'1.11 hour'
]);
});
});
heatmapScenario
(
'when data format is Time series buckets'
,
function
(
ctx
)
{
ctx
.
setup
(
function
(
ctrl
,
ctx
)
{
ctrl
.
panel
.
dataFormat
=
'tsbuckets'
;
const
series
=
[
{
alias
:
'1'
,
datapoints
:
[[
1000
,
1422774000000
],
[
200000
,
1422774060000
]],
},
{
alias
:
'2'
,
datapoints
:
[[
3000
,
1422774000000
],
[
400000
,
1422774060000
]],
},
{
alias
:
'3'
,
datapoints
:
[[
2000
,
1422774000000
],
[
300000
,
1422774060000
]],
},
];
ctx
.
series
=
series
.
map
(
s
=>
new
TimeSeries
(
s
));
ctx
.
data
.
tsBuckets
=
series
.
map
(
s
=>
s
.
alias
).
concat
(
''
);
ctx
.
data
.
yBucketSize
=
1
;
let
xBucketBoundSet
=
series
[
0
].
datapoints
.
map
(
dp
=>
dp
[
1
]);
ctx
.
data
.
xBucketSize
=
calculateBucketSize
(
xBucketBoundSet
);
});
it
(
'should draw correct Y axis'
,
function
()
{
var
yTicks
=
getTicks
(
ctx
.
element
,
'.axis-y'
);
expect
(
yTicks
).
to
.
eql
([
'1'
,
'2'
,
'3'
,
''
]);
});
});
});
function
getTicks
(
element
,
axisSelector
)
{
return
element
.
find
(
axisSelector
)
.
find
(
'text'
)
.
map
(
function
()
{
return
this
.
textContent
;
})
.
get
();
}
function
formatTime
(
timeStr
)
{
let
format
=
'HH:mm'
;
return
moment
.
utc
(
timeStr
,
'DD MMM YYYY HH:mm:ss'
).
format
(
format
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment