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
dfb9419f
Commit
dfb9419f
authored
Oct 31, 2018
by
Torkel Ödegaard
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'graph-legend-to-react'
parents
baea76c4
36cd7381
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
716 additions
and
439 deletions
+716
-439
public/app/core/components/colorpicker/SeriesColorPicker.tsx
+64
-33
public/app/core/components/colorpicker/SeriesColorPickerPopover.tsx
+70
-0
public/app/core/core.ts
+1
-1
public/app/features/dashboard/panel_model.ts
+1
-0
public/app/features/panel/panel_ctrl.ts
+0
-5
public/app/plugins/panel/graph/Legend/Legend.tsx
+321
-0
public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx
+196
-0
public/app/plugins/panel/graph/graph.ts
+37
-16
public/app/plugins/panel/graph/legend.ts
+0
-306
public/app/plugins/panel/graph/module.ts
+13
-49
public/app/plugins/panel/graph/series_overrides_ctrl.ts
+1
-1
public/sass/components/_panel_graph.scss
+12
-28
No files found.
public/app/core/components/colorpicker/SeriesColorPicker.tsx
View file @
dfb9419f
import
React
from
'react'
;
import
{
ColorPickerPopover
}
from
'./ColorPickerPopover'
;
import
{
react2AngularDirective
}
from
'app/core/utils/react2angular'
;
import
ReactDOM
from
'react-dom'
;
import
Drop
from
'tether-drop'
;
import
{
SeriesColorPickerPopover
}
from
'./SeriesColorPickerPopover'
;
export
interface
Props
{
series
:
any
;
onColorChange
:
(
color
:
string
)
=>
void
;
onToggleAxis
:
()
=>
void
;
export
interface
SeriesColorPickerProps
{
color
:
string
;
yaxis
?:
number
;
optionalClass
?:
string
;
onColorChange
:
(
newColor
:
string
)
=>
void
;
onToggleAxis
?:
()
=>
void
;
}
export
class
SeriesColorPicker
extends
React
.
Component
<
Props
,
any
>
{
export
class
SeriesColorPicker
extends
React
.
Component
<
SeriesColorPickerProps
>
{
pickerElem
:
any
;
colorPickerDrop
:
any
;
static
defaultProps
=
{
optionalClass
:
''
,
yaxis
:
undefined
,
onToggleAxis
:
()
=>
{},
};
constructor
(
props
)
{
super
(
props
);
this
.
onColorChange
=
this
.
onColorChange
.
bind
(
this
);
this
.
onToggleAxis
=
this
.
onToggleAxis
.
bind
(
this
);
}
onColorChange
(
color
)
{
this
.
props
.
onColorChange
(
color
);
}
onToggleAxis
()
{
this
.
props
.
onToggleAxis
();
componentWillUnmount
()
{
this
.
destroyDrop
();
}
renderAxisSelection
()
{
const
leftButtonClass
=
this
.
props
.
series
.
yaxis
===
1
?
'btn-success'
:
'btn-inverse'
;
const
rightButtonClass
=
this
.
props
.
series
.
yaxis
===
2
?
'btn-success'
:
'btn-inverse'
;
onClickToOpen
=
()
=>
{
if
(
this
.
colorPickerDrop
)
{
this
.
destroyDrop
();
}
return
(
<
div
className=
"p-b-1"
>
<
label
className=
"small p-r-1"
>
Y Axis:
</
label
>
<
button
onClick=
{
this
.
onToggleAxis
}
className=
{
'btn btn-small '
+
leftButtonClass
}
>
Left
</
button
>
<
button
onClick=
{
this
.
onToggleAxis
}
className=
{
'btn btn-small '
+
rightButtonClass
}
>
Right
</
button
>
</
div
>
const
{
color
,
yaxis
,
onColorChange
,
onToggleAxis
}
=
this
.
props
;
const
dropContent
=
(
<
SeriesColorPickerPopover
color=
{
color
}
yaxis=
{
yaxis
}
onColorChange=
{
onColorChange
}
onToggleAxis=
{
onToggleAxis
}
/>
);
const
dropContentElem
=
document
.
createElement
(
'div'
);
ReactDOM
.
render
(
dropContent
,
dropContentElem
);
const
drop
=
new
Drop
({
target
:
this
.
pickerElem
,
content
:
dropContentElem
,
position
:
'top center'
,
classes
:
'drop-popover'
,
openOn
:
'hover'
,
hoverCloseDelay
:
200
,
remove
:
true
,
tetherOptions
:
{
constraints
:
[{
to
:
'scrollParent'
,
attachment
:
'none both'
}],
},
});
drop
.
on
(
'close'
,
this
.
closeColorPicker
.
bind
(
this
));
this
.
colorPickerDrop
=
drop
;
this
.
colorPickerDrop
.
open
();
};
closeColorPicker
()
{
setTimeout
(()
=>
{
this
.
destroyDrop
();
},
100
);
}
destroyDrop
()
{
if
(
this
.
colorPickerDrop
&&
this
.
colorPickerDrop
.
tether
)
{
this
.
colorPickerDrop
.
destroy
();
this
.
colorPickerDrop
=
null
;
}
}
render
()
{
const
{
optionalClass
,
children
}
=
this
.
props
;
return
(
<
div
className=
"graph-legend-popover"
>
{
this
.
props
.
series
.
yaxis
&&
this
.
renderAxisSelection
()
}
<
ColorPickerPopover
color=
{
this
.
props
.
series
.
color
}
onColorSelect=
{
this
.
onColorChange
}
/>
<
div
className=
{
optionalClass
}
ref=
{
e
=>
(
this
.
pickerElem
=
e
)
}
onClick=
{
this
.
onClickToOpen
}
>
{
children
}
</
div
>
);
}
}
react2AngularDirective
(
'seriesColorPicker'
,
SeriesColorPicker
,
[
'series'
,
'onColorChange'
,
'onToggleAxis'
]);
public/app/core/components/colorpicker/SeriesColorPickerPopover.tsx
0 → 100644
View file @
dfb9419f
import
React
from
'react'
;
import
{
ColorPickerPopover
}
from
'./ColorPickerPopover'
;
import
{
react2AngularDirective
}
from
'app/core/utils/react2angular'
;
export
interface
SeriesColorPickerPopoverProps
{
color
:
string
;
yaxis
?:
number
;
onColorChange
:
(
color
:
string
)
=>
void
;
onToggleAxis
?:
()
=>
void
;
}
export
class
SeriesColorPickerPopover
extends
React
.
PureComponent
<
SeriesColorPickerPopoverProps
,
any
>
{
render
()
{
return
(
<
div
className=
"graph-legend-popover"
>
{
this
.
props
.
yaxis
&&
<
AxisSelector
yaxis=
{
this
.
props
.
yaxis
}
onToggleAxis=
{
this
.
props
.
onToggleAxis
}
/>
}
<
ColorPickerPopover
color=
{
this
.
props
.
color
}
onColorSelect=
{
this
.
props
.
onColorChange
}
/>
</
div
>
);
}
}
interface
AxisSelectorProps
{
yaxis
:
number
;
onToggleAxis
:
()
=>
void
;
}
interface
AxisSelectorState
{
yaxis
:
number
;
}
export
class
AxisSelector
extends
React
.
PureComponent
<
AxisSelectorProps
,
AxisSelectorState
>
{
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
yaxis
:
this
.
props
.
yaxis
,
};
this
.
onToggleAxis
=
this
.
onToggleAxis
.
bind
(
this
);
}
onToggleAxis
()
{
this
.
setState
({
yaxis
:
this
.
state
.
yaxis
===
2
?
1
:
2
,
});
this
.
props
.
onToggleAxis
();
}
render
()
{
const
leftButtonClass
=
this
.
state
.
yaxis
===
1
?
'btn-success'
:
'btn-inverse'
;
const
rightButtonClass
=
this
.
state
.
yaxis
===
2
?
'btn-success'
:
'btn-inverse'
;
return
(
<
div
className=
"p-b-1"
>
<
label
className=
"small p-r-1"
>
Y Axis:
</
label
>
<
button
onClick=
{
this
.
onToggleAxis
}
className=
{
'btn btn-small '
+
leftButtonClass
}
>
Left
</
button
>
<
button
onClick=
{
this
.
onToggleAxis
}
className=
{
'btn btn-small '
+
rightButtonClass
}
>
Right
</
button
>
</
div
>
);
}
}
react2AngularDirective
(
'seriesColorPickerPopover'
,
SeriesColorPickerPopover
,
[
'series'
,
'onColorChange'
,
'onToggleAxis'
,
]);
public/app/core/core.ts
View file @
dfb9419f
...
...
@@ -14,7 +14,7 @@ import './components/jsontree/jsontree';
import
'./components/code_editor/code_editor'
;
import
'./utils/outline'
;
import
'./components/colorpicker/ColorPicker'
;
import
'./components/colorpicker/SeriesColorPicker'
;
import
'./components/colorpicker/SeriesColorPicker
Popover
'
;
import
'./components/colorpicker/spectrum_picker'
;
import
'./services/search_srv'
;
import
'./services/ng_react'
;
...
...
public/app/features/dashboard/panel_model.ts
View file @
dfb9419f
...
...
@@ -133,6 +133,7 @@ export class PanelModel {
}
destroy
()
{
this
.
events
.
emit
(
'panel-teardown'
);
this
.
events
.
removeAllListeners
();
}
}
public/app/features/panel/panel_ctrl.ts
View file @
dfb9419f
...
...
@@ -48,11 +48,6 @@ export class PanelCtrl {
}
$scope
.
$on
(
'component-did-mount'
,
()
=>
this
.
panelDidMount
());
$scope
.
$on
(
'$destroy'
,
()
=>
{
this
.
events
.
emit
(
'panel-teardown'
);
this
.
events
.
removeAllListeners
();
});
}
panelDidMount
()
{
...
...
public/app/plugins/panel/graph/Legend/Legend.tsx
0 → 100644
View file @
dfb9419f
import
_
from
'lodash'
;
import
React
,
{
PureComponent
}
from
'react'
;
import
{
TimeSeries
}
from
'app/core/core'
;
import
CustomScrollbar
from
'app/core/components/CustomScrollbar/CustomScrollbar'
;
import
{
LegendItem
,
LEGEND_STATS
}
from
'./LegendSeriesItem'
;
interface
LegendProps
{
seriesList
:
TimeSeries
[];
optionalClass
?:
string
;
}
interface
LegendEventHandlers
{
onToggleSeries
?:
(
hiddenSeries
)
=>
void
;
onToggleSort
?:
(
sortBy
,
sortDesc
)
=>
void
;
onToggleAxis
?:
(
series
:
TimeSeries
)
=>
void
;
onColorChange
?:
(
series
:
TimeSeries
,
color
:
string
)
=>
void
;
}
interface
LegendComponentEventHandlers
{
onToggleSeries
?:
(
series
,
event
)
=>
void
;
onToggleSort
?:
(
sortBy
,
sortDesc
)
=>
void
;
onToggleAxis
?:
(
series
:
TimeSeries
)
=>
void
;
onColorChange
?:
(
series
:
TimeSeries
,
color
:
string
)
=>
void
;
}
interface
LegendDisplayProps
{
hiddenSeries
:
any
;
hideEmpty
?:
boolean
;
hideZero
?:
boolean
;
alignAsTable
?:
boolean
;
rightSide
?:
boolean
;
sideWidth
?:
number
;
}
interface
LegendValuesProps
{
values
?:
boolean
;
min
?:
boolean
;
max
?:
boolean
;
avg
?:
boolean
;
current
?:
boolean
;
total
?:
boolean
;
}
interface
LegendSortProps
{
sort
?:
'min'
|
'max'
|
'avg'
|
'current'
|
'total'
;
sortDesc
?:
boolean
;
}
export
type
GraphLegendProps
=
LegendProps
&
LegendDisplayProps
&
LegendValuesProps
&
LegendSortProps
&
LegendEventHandlers
;
export
type
LegendComponentProps
=
LegendProps
&
LegendDisplayProps
&
LegendValuesProps
&
LegendSortProps
&
LegendComponentEventHandlers
;
interface
LegendState
{
hiddenSeries
:
{
[
seriesAlias
:
string
]:
boolean
};
}
export
class
GraphLegend
extends
PureComponent
<
GraphLegendProps
,
LegendState
>
{
static
defaultProps
:
Partial
<
GraphLegendProps
>
=
{
values
:
false
,
min
:
false
,
max
:
false
,
avg
:
false
,
current
:
false
,
total
:
false
,
alignAsTable
:
false
,
rightSide
:
false
,
sort
:
undefined
,
sortDesc
:
false
,
optionalClass
:
''
,
onToggleSeries
:
()
=>
{},
onToggleSort
:
()
=>
{},
onToggleAxis
:
()
=>
{},
onColorChange
:
()
=>
{},
};
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
hiddenSeries
:
this
.
props
.
hiddenSeries
,
};
}
sortLegend
()
{
let
seriesList
=
[...
this
.
props
.
seriesList
]
||
[];
if
(
this
.
props
.
sort
)
{
seriesList
=
_
.
sortBy
(
seriesList
,
series
=>
{
let
sort
=
series
.
stats
[
this
.
props
.
sort
];
if
(
sort
===
null
)
{
sort
=
-
Infinity
;
}
return
sort
;
});
if
(
this
.
props
.
sortDesc
)
{
seriesList
=
seriesList
.
reverse
();
}
}
return
seriesList
;
}
onToggleSeries
=
(
series
,
event
)
=>
{
let
hiddenSeries
=
{
...
this
.
state
.
hiddenSeries
};
if
(
event
.
ctrlKey
||
event
.
metaKey
||
event
.
shiftKey
)
{
if
(
hiddenSeries
[
series
.
alias
])
{
delete
hiddenSeries
[
series
.
alias
];
}
else
{
hiddenSeries
[
series
.
alias
]
=
true
;
}
}
else
{
hiddenSeries
=
this
.
toggleSeriesExclusiveMode
(
series
);
}
this
.
setState
({
hiddenSeries
:
hiddenSeries
});
this
.
props
.
onToggleSeries
(
hiddenSeries
);
};
toggleSeriesExclusiveMode
(
series
)
{
const
hiddenSeries
=
{
...
this
.
state
.
hiddenSeries
};
if
(
hiddenSeries
[
series
.
alias
])
{
delete
hiddenSeries
[
series
.
alias
];
}
// check if every other series is hidden
const
alreadyExclusive
=
this
.
props
.
seriesList
.
every
(
value
=>
{
if
(
value
.
alias
===
series
.
alias
)
{
return
true
;
}
return
hiddenSeries
[
value
.
alias
];
});
if
(
alreadyExclusive
)
{
// remove all hidden series
this
.
props
.
seriesList
.
forEach
(
value
=>
{
delete
hiddenSeries
[
value
.
alias
];
});
}
else
{
// hide all but this serie
this
.
props
.
seriesList
.
forEach
(
value
=>
{
if
(
value
.
alias
===
series
.
alias
)
{
return
;
}
hiddenSeries
[
value
.
alias
]
=
true
;
});
}
return
hiddenSeries
;
}
render
()
{
const
{
optionalClass
,
rightSide
,
sideWidth
,
sort
,
sortDesc
,
hideEmpty
,
hideZero
,
values
,
min
,
max
,
avg
,
current
,
total
,
}
=
this
.
props
;
const
seriesValuesProps
=
{
values
,
min
,
max
,
avg
,
current
,
total
};
const
hiddenSeries
=
this
.
state
.
hiddenSeries
;
const
seriesHideProps
=
{
hideEmpty
,
hideZero
};
const
sortProps
=
{
sort
,
sortDesc
};
const
seriesList
=
this
.
sortLegend
().
filter
(
series
=>
!
series
.
hideFromLegend
(
seriesHideProps
));
const
legendClass
=
`
${
this
.
props
.
alignAsTable
?
'graph-legend-table'
:
''
}
${
optionalClass
}
`
;
// Set min-width if side style and there is a value, otherwise remove the CSS property
// Set width so it works with IE11
const
width
:
any
=
rightSide
&&
sideWidth
?
sideWidth
:
undefined
;
const
ieWidth
:
any
=
rightSide
&&
sideWidth
?
sideWidth
-
1
:
undefined
;
const
legendStyle
:
React
.
CSSProperties
=
{
minWidth
:
width
,
width
:
ieWidth
,
};
const
legendProps
:
LegendComponentProps
=
{
seriesList
:
seriesList
,
hiddenSeries
:
hiddenSeries
,
onToggleSeries
:
this
.
onToggleSeries
,
onToggleAxis
:
this
.
props
.
onToggleAxis
,
onToggleSort
:
this
.
props
.
onToggleSort
,
onColorChange
:
this
.
props
.
onColorChange
,
...
seriesValuesProps
,
...
sortProps
,
};
return
(
<
div
className=
{
`graph-legend-content ${legendClass}`
}
style=
{
legendStyle
}
>
{
this
.
props
.
alignAsTable
?
<
LegendTable
{
...
legendProps
}
/>
:
<
LegendSeriesList
{
...
legendProps
}
/>
}
</
div
>
);
}
}
class
LegendSeriesList
extends
PureComponent
<
LegendComponentProps
>
{
render
()
{
const
{
seriesList
,
hiddenSeries
,
values
,
min
,
max
,
avg
,
current
,
total
}
=
this
.
props
;
const
seriesValuesProps
=
{
values
,
min
,
max
,
avg
,
current
,
total
};
return
seriesList
.
map
((
series
,
i
)
=>
(
<
LegendItem
// This trick required because TimeSeries.id is not unique (it's just TimeSeries.alias).
// In future would be good to make id unique across the series list.
key=
{
`${series.id}-${i}`
}
series=
{
series
}
hidden=
{
hiddenSeries
[
series
.
alias
]
}
{
...
seriesValuesProps
}
onLabelClick=
{
this
.
props
.
onToggleSeries
}
onColorChange=
{
this
.
props
.
onColorChange
}
onToggleAxis=
{
this
.
props
.
onToggleAxis
}
/>
));
}
}
class
LegendTable
extends
PureComponent
<
Partial
<
LegendComponentProps
>>
{
onToggleSort
=
stat
=>
{
let
sortDesc
=
this
.
props
.
sortDesc
;
let
sortBy
=
this
.
props
.
sort
;
if
(
stat
!==
sortBy
)
{
sortDesc
=
null
;
}
// if already sort ascending, disable sorting
if
(
sortDesc
===
false
)
{
sortBy
=
null
;
sortDesc
=
null
;
}
else
{
sortDesc
=
!
sortDesc
;
sortBy
=
stat
;
}
this
.
props
.
onToggleSort
(
sortBy
,
sortDesc
);
};
render
()
{
const
seriesList
=
this
.
props
.
seriesList
;
const
{
values
,
min
,
max
,
avg
,
current
,
total
,
sort
,
sortDesc
,
hiddenSeries
}
=
this
.
props
;
const
seriesValuesProps
=
{
values
,
min
,
max
,
avg
,
current
,
total
};
return
(
<
table
>
<
colgroup
>
<
col
style=
{
{
width
:
'100%'
}
}
/>
</
colgroup
>
<
thead
>
<
tr
>
<
th
style=
{
{
textAlign
:
'left'
}
}
/>
{
LEGEND_STATS
.
map
(
statName
=>
seriesValuesProps
[
statName
]
&&
(
<
LegendTableHeaderItem
key=
{
statName
}
statName=
{
statName
}
sort=
{
sort
}
sortDesc=
{
sortDesc
}
onClick=
{
this
.
onToggleSort
}
/>
)
)
}
</
tr
>
</
thead
>
<
tbody
>
{
seriesList
.
map
((
series
,
i
)
=>
(
<
LegendItem
key=
{
`${series.id}-${i}`
}
asTable=
{
true
}
series=
{
series
}
hidden=
{
hiddenSeries
[
series
.
alias
]
}
onLabelClick=
{
this
.
props
.
onToggleSeries
}
onColorChange=
{
this
.
props
.
onColorChange
}
onToggleAxis=
{
this
.
props
.
onToggleAxis
}
{
...
seriesValuesProps
}
/>
))
}
</
tbody
>
</
table
>
);
}
}
interface
LegendTableHeaderProps
{
statName
:
string
;
onClick
?:
(
statName
:
string
)
=>
void
;
}
class
LegendTableHeaderItem
extends
PureComponent
<
LegendTableHeaderProps
&
LegendSortProps
>
{
onClick
=
()
=>
this
.
props
.
onClick
(
this
.
props
.
statName
);
render
()
{
const
{
statName
,
sort
,
sortDesc
}
=
this
.
props
;
return
(
<
th
className=
"pointer"
onClick=
{
this
.
onClick
}
>
{
statName
}
{
sort
===
statName
&&
<
span
className=
{
sortDesc
?
'fa fa-caret-down'
:
'fa fa-caret-up'
}
/>
}
</
th
>
);
}
}
export
class
Legend
extends
PureComponent
<
GraphLegendProps
>
{
render
()
{
return
(
<
CustomScrollbar
>
<
GraphLegend
{
...
this
.
props
}
/>
</
CustomScrollbar
>
);
}
}
export
default
Legend
;
public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx
0 → 100644
View file @
dfb9419f
import
React
,
{
PureComponent
}
from
'react'
;
import
classNames
from
'classnames'
;
import
{
TimeSeries
}
from
'app/core/core'
;
import
{
SeriesColorPicker
}
from
'app/core/components/colorpicker/SeriesColorPicker'
;
export
const
LEGEND_STATS
=
[
'min'
,
'max'
,
'avg'
,
'current'
,
'total'
];
export
interface
LegendLabelProps
{
series
:
TimeSeries
;
asTable
?:
boolean
;
hidden
?:
boolean
;
onLabelClick
?:
(
series
,
event
)
=>
void
;
onColorChange
?:
(
series
,
color
:
string
)
=>
void
;
onToggleAxis
?:
(
series
)
=>
void
;
}
export
interface
LegendValuesProps
{
values
?:
boolean
;
min
?:
boolean
;
max
?:
boolean
;
avg
?:
boolean
;
current
?:
boolean
;
total
?:
boolean
;
}
type
LegendItemProps
=
LegendLabelProps
&
LegendValuesProps
;
interface
LegendItemState
{
yaxis
:
number
;
}
export
class
LegendItem
extends
PureComponent
<
LegendItemProps
,
LegendItemState
>
{
static
defaultProps
=
{
asTable
:
false
,
hidden
:
false
,
onLabelClick
:
()
=>
{},
onColorChange
:
()
=>
{},
onToggleAxis
:
()
=>
{},
};
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
yaxis
:
this
.
props
.
series
.
yaxis
,
};
}
onLabelClick
=
e
=>
this
.
props
.
onLabelClick
(
this
.
props
.
series
,
e
);
onToggleAxis
=
()
=>
{
const
yaxis
=
this
.
state
.
yaxis
===
2
?
1
:
2
;
const
info
=
{
alias
:
this
.
props
.
series
.
alias
,
yaxis
:
yaxis
};
this
.
setState
({
yaxis
:
yaxis
});
this
.
props
.
onToggleAxis
(
info
);
};
onColorChange
=
color
=>
{
this
.
props
.
onColorChange
(
this
.
props
.
series
,
color
);
// Because of PureComponent nature it makes only shallow props comparison and changing of series.color doesn't run
// component re-render. In this case we can't rely on color, selected by user, because it may be overwritten
// by series overrides. So we need to use forceUpdate() to make sure we have proper series color.
this
.
forceUpdate
();
};
renderLegendValues
()
{
const
{
series
,
asTable
}
=
this
.
props
;
const
legendValueItems
=
[];
for
(
const
valueName
of
LEGEND_STATS
)
{
if
(
this
.
props
[
valueName
])
{
const
valueFormatted
=
series
.
formatValue
(
series
.
stats
[
valueName
]);
legendValueItems
.
push
(
<
LegendValue
key=
{
valueName
}
valueName=
{
valueName
}
value=
{
valueFormatted
}
asTable=
{
asTable
}
/>
);
}
}
return
legendValueItems
;
}
render
()
{
const
{
series
,
values
,
asTable
,
hidden
}
=
this
.
props
;
const
seriesOptionClasses
=
classNames
({
'graph-legend-series-hidden'
:
hidden
,
'graph-legend-series--right-y'
:
series
.
yaxis
===
2
,
});
const
valueItems
=
values
?
this
.
renderLegendValues
()
:
[];
const
seriesLabel
=
(
<
LegendSeriesLabel
label=
{
series
.
alias
}
color=
{
series
.
color
}
yaxis=
{
this
.
state
.
yaxis
}
onLabelClick=
{
this
.
onLabelClick
}
onColorChange=
{
this
.
onColorChange
}
onToggleAxis=
{
this
.
onToggleAxis
}
/>
);
if
(
asTable
)
{
return
(
<
tr
className=
{
`graph-legend-series ${seriesOptionClasses}`
}
>
<
td
>
{
seriesLabel
}
</
td
>
{
valueItems
}
</
tr
>
);
}
else
{
return
(
<
div
className=
{
`graph-legend-series ${seriesOptionClasses}`
}
>
{
seriesLabel
}
{
valueItems
}
</
div
>
);
}
}
}
interface
LegendSeriesLabelProps
{
label
:
string
;
color
:
string
;
yaxis
?:
number
;
onLabelClick
?:
(
event
)
=>
void
;
}
class
LegendSeriesLabel
extends
PureComponent
<
LegendSeriesLabelProps
&
LegendSeriesIconProps
>
{
static
defaultProps
=
{
yaxis
:
undefined
,
onLabelClick
:
()
=>
{},
};
render
()
{
const
{
label
,
color
,
yaxis
}
=
this
.
props
;
const
{
onColorChange
,
onToggleAxis
}
=
this
.
props
;
return
[
<
LegendSeriesIcon
key=
"icon"
color=
{
color
}
yaxis=
{
yaxis
}
onColorChange=
{
onColorChange
}
onToggleAxis=
{
onToggleAxis
}
/>,
<
a
className=
"graph-legend-alias pointer"
title=
{
label
}
key=
"label"
onClick=
{
e
=>
this
.
props
.
onLabelClick
(
e
)
}
>
{
label
}
</
a
>,
];
}
}
interface
LegendSeriesIconProps
{
color
:
string
;
yaxis
?:
number
;
onColorChange
?:
(
color
:
string
)
=>
void
;
onToggleAxis
?:
()
=>
void
;
}
interface
LegendSeriesIconState
{
color
:
string
;
}
function
SeriesIcon
(
props
)
{
return
<
i
className=
"fa fa-minus pointer"
style=
{
{
color
:
props
.
color
}
}
/>;
}
class
LegendSeriesIcon
extends
PureComponent
<
LegendSeriesIconProps
,
LegendSeriesIconState
>
{
static
defaultProps
=
{
yaxis
:
undefined
,
onColorChange
:
()
=>
{},
onToggleAxis
:
()
=>
{},
};
render
()
{
return
(
<
SeriesColorPicker
optionalClass=
"graph-legend-icon"
yaxis=
{
this
.
props
.
yaxis
}
color=
{
this
.
props
.
color
}
onColorChange=
{
this
.
props
.
onColorChange
}
onToggleAxis=
{
this
.
props
.
onToggleAxis
}
>
<
SeriesIcon
color=
{
this
.
props
.
color
}
/>
</
SeriesColorPicker
>
);
}
}
interface
LegendValueProps
{
value
:
string
;
valueName
:
string
;
asTable
?:
boolean
;
}
function
LegendValue
(
props
:
LegendValueProps
)
{
const
value
=
props
.
value
;
const
valueName
=
props
.
valueName
;
if
(
props
.
asTable
)
{
return
<
td
className=
{
`graph-legend-value ${valueName}`
}
>
{
value
}
</
td
>;
}
return
<
div
className=
{
`graph-legend-value ${valueName}`
}
>
{
value
}
</
div
>;
}
public/app/plugins/panel/graph/graph.ts
View file @
dfb9419f
...
...
@@ -20,6 +20,9 @@ import { EventManager } from 'app/features/annotations/all';
import
{
convertToHistogramData
}
from
'./histogram'
;
import
{
alignYLevel
}
from
'./align_yaxes'
;
import
config
from
'app/core/config'
;
import
React
from
'react'
;
import
ReactDOM
from
'react-dom'
;
import
{
Legend
,
GraphLegendProps
}
from
'./Legend/Legend'
;
import
{
GraphCtrl
}
from
'./module'
;
...
...
@@ -35,6 +38,7 @@ class GraphElement {
panelWidth
:
number
;
eventManager
:
EventManager
;
thresholdManager
:
ThresholdManager
;
legendElem
:
HTMLElement
;
constructor
(
private
scope
,
private
elem
,
private
timeSrv
)
{
this
.
ctrl
=
scope
.
ctrl
;
...
...
@@ -50,7 +54,7 @@ class GraphElement {
});
// panel events
this
.
ctrl
.
events
.
on
(
'panel-teardown'
,
this
.
onPanel
t
eardown
.
bind
(
this
));
this
.
ctrl
.
events
.
on
(
'panel-teardown'
,
this
.
onPanel
T
eardown
.
bind
(
this
));
/**
* Split graph rendering into two parts.
...
...
@@ -63,13 +67,14 @@ class GraphElement {
// global events
appEvents
.
on
(
'graph-hover'
,
this
.
onGraphHover
.
bind
(
this
),
scope
);
appEvents
.
on
(
'graph-hover-clear'
,
this
.
onGraphHoverClear
.
bind
(
this
),
scope
);
this
.
elem
.
bind
(
'plotselected'
,
this
.
onPlotSelected
.
bind
(
this
));
this
.
elem
.
bind
(
'plotclick'
,
this
.
onPlotClick
.
bind
(
this
));
scope
.
$on
(
'$destroy'
,
this
.
onScopeDestroy
.
bind
(
this
));
// get graph legend element
if
(
this
.
elem
&&
this
.
elem
.
parent
)
{
this
.
legendElem
=
this
.
elem
.
parent
().
find
(
'.graph-legend'
)[
0
];
}
}
onRender
(
renderData
)
{
...
...
@@ -82,7 +87,26 @@ class GraphElement {
const
graphHeight
=
this
.
elem
.
height
();
updateLegendValues
(
this
.
data
,
this
.
panel
,
graphHeight
);
this
.
ctrl
.
events
.
emit
(
'render-legend'
);
const
{
values
,
min
,
max
,
avg
,
current
,
total
}
=
this
.
panel
.
legend
;
const
{
alignAsTable
,
rightSide
,
sideWidth
,
sort
,
sortDesc
,
hideEmpty
,
hideZero
}
=
this
.
panel
.
legend
;
const
legendOptions
=
{
alignAsTable
,
rightSide
,
sideWidth
,
sort
,
sortDesc
,
hideEmpty
,
hideZero
};
const
valueOptions
=
{
values
,
min
,
max
,
avg
,
current
,
total
};
const
legendProps
:
GraphLegendProps
=
{
seriesList
:
this
.
data
,
hiddenSeries
:
this
.
ctrl
.
hiddenSeries
,
...
legendOptions
,
...
valueOptions
,
onToggleSeries
:
this
.
ctrl
.
onToggleSeries
,
onToggleSort
:
this
.
ctrl
.
onToggleSort
,
onColorChange
:
this
.
ctrl
.
onColorChange
,
onToggleAxis
:
this
.
ctrl
.
onToggleAxis
,
};
const
legendReactElem
=
React
.
createElement
(
Legend
,
legendProps
);
ReactDOM
.
render
(
legendReactElem
,
this
.
legendElem
,
()
=>
this
.
onLegendRenderingComplete
());
}
onLegendRenderingComplete
()
{
this
.
render_panel
();
}
onGraphHover
(
evt
)
{
...
...
@@ -99,17 +123,20 @@ class GraphElement {
this
.
tooltip
.
show
(
evt
.
pos
);
}
onPanel
t
eardown
()
{
onPanel
T
eardown
()
{
this
.
thresholdManager
=
null
;
if
(
this
.
plot
)
{
this
.
plot
.
destroy
();
this
.
plot
=
null
;
}
}
onLegendRenderingComplete
()
{
this
.
render_panel
();
this
.
tooltip
.
destroy
();
this
.
elem
.
off
();
this
.
elem
.
remove
();
console
.
log
(
'react unmount'
);
ReactDOM
.
unmountComponentAtNode
(
this
.
legendElem
);
}
onGraphHoverClear
(
event
,
info
)
{
...
...
@@ -157,12 +184,6 @@ class GraphElement {
}
}
onScopeDestroy
()
{
this
.
tooltip
.
destroy
();
this
.
elem
.
off
();
this
.
elem
.
remove
();
}
shouldAbortRender
()
{
if
(
!
this
.
data
)
{
return
true
;
...
...
public/app/plugins/panel/graph/legend.ts
deleted
100644 → 0
View file @
baea76c4
This diff is collapsed.
Click to expand it.
public/app/plugins/panel/graph/module.ts
View file @
dfb9419f
import
'./graph'
;
import
'./legend'
;
import
'./series_overrides_ctrl'
;
import
'./thresholds_form'
;
...
...
@@ -244,67 +243,32 @@ class GraphCtrl extends MetricsPanelCtrl {
}
}
changeSeriesColor
(
series
,
color
)
{
onColorChange
=
(
series
,
color
)
=>
{
series
.
setColor
(
color
);
this
.
panel
.
aliasColors
[
series
.
alias
]
=
series
.
color
;
this
.
render
();
}
}
;
toggleSeries
(
serie
,
event
)
{
if
(
event
.
ctrlKey
||
event
.
metaKey
||
event
.
shiftKey
)
{
if
(
this
.
hiddenSeries
[
serie
.
alias
])
{
delete
this
.
hiddenSeries
[
serie
.
alias
];
}
else
{
this
.
hiddenSeries
[
serie
.
alias
]
=
true
;
}
}
else
{
this
.
toggleSeriesExclusiveMode
(
serie
);
}
onToggleSeries
=
hiddenSeries
=>
{
this
.
hiddenSeries
=
hiddenSeries
;
this
.
render
();
}
toggleSeriesExclusiveMode
(
serie
)
{
const
hidden
=
this
.
hiddenSeries
;
if
(
hidden
[
serie
.
alias
])
{
delete
hidden
[
serie
.
alias
];
}
// check if every other series is hidden
const
alreadyExclusive
=
_
.
every
(
this
.
seriesList
,
value
=>
{
if
(
value
.
alias
===
serie
.
alias
)
{
return
true
;
}
return
hidden
[
value
.
alias
];
});
if
(
alreadyExclusive
)
{
// remove all hidden series
_
.
each
(
this
.
seriesList
,
value
=>
{
delete
this
.
hiddenSeries
[
value
.
alias
];
});
}
else
{
// hide all but this serie
_
.
each
(
this
.
seriesList
,
value
=>
{
if
(
value
.
alias
===
serie
.
alias
)
{
return
;
}
};
this
.
hiddenSeries
[
value
.
alias
]
=
true
;
});
}
}
onToggleSort
=
(
sortBy
,
sortDesc
)
=>
{
this
.
panel
.
legend
.
sort
=
sortBy
;
this
.
panel
.
legend
.
sortDesc
=
sortDesc
;
this
.
render
();
};
toggleAxis
(
info
)
{
onToggleAxis
=
info
=>
{
let
override
=
_
.
find
(
this
.
panel
.
seriesOverrides
,
{
alias
:
info
.
alias
});
if
(
!
override
)
{
override
=
{
alias
:
info
.
alias
};
this
.
panel
.
seriesOverrides
.
push
(
override
);
}
info
.
yaxis
=
override
.
yaxis
=
info
.
yaxis
===
2
?
1
:
2
;
override
.
yaxis
=
info
.
yaxis
;
this
.
render
();
}
}
;
addSeriesOverride
(
override
)
{
this
.
panel
.
seriesOverrides
.
push
(
override
||
{});
...
...
public/app/plugins/panel/graph/series_overrides_ctrl.ts
View file @
dfb9419f
...
...
@@ -53,7 +53,7 @@ export function SeriesOverridesCtrl($scope, $element, popoverSrv) {
element
:
$element
.
find
(
'.dropdown'
)[
0
],
position
:
'top center'
,
openOn
:
'click'
,
template
:
'<series-color-picker series="series" onColorChange="colorSelected" />'
,
template
:
'<series-color-picker
-popover
series="series" onColorChange="colorSelected" />'
,
model
:
{
autoClose
:
true
,
colorSelected
:
$scope
.
colorSelected
,
...
...
public/sass/components/_panel_graph.scss
View file @
dfb9419f
...
...
@@ -14,7 +14,7 @@
.graph-legend-series
{
display
:
block
;
padding-left
:
0
px
;
padding-left
:
4
px
;
}
.graph-legend-table
.graph-legend-series
{
...
...
@@ -52,9 +52,6 @@
padding-top
:
6px
;
position
:
relative
;
// fix for Firefox (white stripe on the right of scrollbar)
width
:
calc
(
100%
-
1px
);
.popover-content
{
padding
:
0
;
}
...
...
@@ -62,15 +59,6 @@
.graph-legend-content
{
position
:
relative
;
// fix for Firefox (white stripe on the right of scrollbar)
width
:
calc
(
100%
-
1px
);
}
.graph-legend-scroll
{
position
:
relative
;
overflow
:
auto
!
important
;
padding
:
1px
;
}
.graph-legend-icon
{
...
...
@@ -82,8 +70,8 @@
.graph-legend-icon
,
.graph-legend-alias
,
.graph-legend-value
{
display
:
inline
;
cursor
:
pointer
;
float
:
left
;
white-space
:
nowrap
;
font-size
:
85%
;
text-align
:
left
;
...
...
@@ -120,6 +108,11 @@
}
}
// Don't move series to the right if legend is on the right as well
.graph-panel--legend-right
.graph-legend-series--right-y
{
float
:
left
;
}
.graph-legend-value
{
padding-left
:
6px
;
}
...
...
@@ -128,7 +121,8 @@
.body--phantomjs
{
.graph-panel--legend-right
{
.graph-legend
{
display
:
inline-block
;
display
:
block
;
max-width
:
min-content
;
}
.graph-panel__chart
{
...
...
@@ -138,24 +132,14 @@
.graph-legend-table
{
display
:
table
;
width
:
auto
;
.graph-legend-scroll
{
display
:
table
;
}
}
}
}
.graph-legend-table
{
tbody
{
display
:
block
;
position
:
relative
;
overflow-y
:
auto
;
overflow-x
:
hidden
;
padding-bottom
:
1px
;
padding-right
:
5px
;
padding-left
:
5px
;
}
padding-bottom
:
1px
;
padding-right
:
5px
;
padding-left
:
5px
;
.graph-legend-series
{
display
:
table-row
;
...
...
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