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
9dbf54eb
Commit
9dbf54eb
authored
Nov 25, 2020
by
Ryan McKinley
Committed by
GitHub
Nov 25, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
GraphNG: support x != time in library (#29353)
parent
0b451486
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
126 additions
and
49 deletions
+126
-49
packages/grafana-data/src/transformations/matchers.ts
+2
-6
packages/grafana-data/src/transformations/matchers/ids.ts
+3
-1
packages/grafana-data/src/transformations/matchers/simpleFieldMatcher.ts
+42
-0
packages/grafana-data/src/utils/Registry.ts
+1
-1
packages/grafana-ui/src/components/GraphNG/GraphNG.tsx
+28
-17
packages/grafana-ui/src/components/GraphNG/utils.ts
+50
-24
No files found.
packages/grafana-data/src/transformations/matchers.ts
View file @
9dbf54eb
...
...
@@ -11,6 +11,7 @@ import {
FrameMatcher
,
}
from
'../types/transformations'
;
import
{
Registry
}
from
'../utils/Registry'
;
import
{
getSimpleFieldMatchers
}
from
'./matchers/simpleFieldMatcher'
;
/**
* Registry that contains all of the built in field matchers.
...
...
@@ -21,6 +22,7 @@ export const fieldMatchers = new Registry<FieldMatcherInfo>(() => {
...
getFieldPredicateMatchers
(),
// Predicates
...
getFieldTypeMatchers
(),
// by type
...
getFieldNameMatchers
(),
// by name
...
getSimpleFieldMatchers
(),
// first
];
});
...
...
@@ -43,9 +45,6 @@ export const frameMatchers = new Registry<FrameMatcherInfo>(() => {
*/
export
function
getFieldMatcher
(
config
:
MatcherConfig
):
FieldMatcher
{
const
info
=
fieldMatchers
.
get
(
config
.
id
);
if
(
!
info
)
{
throw
new
Error
(
'Unknown Matcher: '
+
config
.
id
);
}
return
info
.
get
(
config
.
options
);
}
...
...
@@ -56,8 +55,5 @@ export function getFieldMatcher(config: MatcherConfig): FieldMatcher {
*/
export
function
getFrameMatchers
(
config
:
MatcherConfig
):
FrameMatcher
{
const
info
=
frameMatchers
.
get
(
config
.
id
);
if
(
!
info
)
{
throw
new
Error
(
'Unknown Matcher: '
+
config
.
id
);
}
return
info
.
get
(
config
.
options
);
}
packages/grafana-data/src/transformations/matchers/ids.ts
View file @
9dbf54eb
...
...
@@ -13,7 +13,9 @@ export enum MatcherID {
export
enum
FieldMatcherID
{
// Specific Types
numeric
=
'numeric'
,
time
=
'time'
,
time
=
'time'
,
// Can be multiple times
first
=
'first'
,
firstTimeField
=
'firstTimeField'
,
// Only the first fime field
// With arguments
byType
=
'byType'
,
...
...
packages/grafana-data/src/transformations/matchers/simpleFieldMatcher.ts
0 → 100644
View file @
9dbf54eb
import
{
Field
,
FieldType
,
DataFrame
}
from
'../../types/dataFrame'
;
import
{
FieldMatcherID
}
from
'./ids'
;
import
{
FieldMatcherInfo
}
from
'../../types/transformations'
;
const
firstFieldMatcher
:
FieldMatcherInfo
=
{
id
:
FieldMatcherID
.
first
,
name
:
'First Field'
,
description
:
'The first field in the frame'
,
get
:
(
type
:
FieldType
)
=>
{
return
(
field
:
Field
,
frame
:
DataFrame
,
allFrames
:
DataFrame
[])
=>
{
return
field
===
frame
.
fields
[
0
];
};
},
getOptionsDisplayText
:
()
=>
{
return
`First field`
;
},
};
const
firstTimeFieldMatcher
:
FieldMatcherInfo
=
{
id
:
FieldMatcherID
.
firstTimeField
,
name
:
'First time field'
,
description
:
'The first field of type time in a frame'
,
get
:
(
type
:
FieldType
)
=>
{
return
(
field
:
Field
,
frame
:
DataFrame
,
allFrames
:
DataFrame
[])
=>
{
return
field
.
type
===
FieldType
.
time
&&
field
===
frame
.
fields
.
find
(
f
=>
f
.
type
===
FieldType
.
time
);
};
},
getOptionsDisplayText
:
()
=>
{
return
`First time field`
;
},
};
/**
* Registry Initialization
*/
export
function
getSimpleFieldMatchers
():
FieldMatcherInfo
[]
{
return
[
firstFieldMatcher
,
firstTimeFieldMatcher
];
}
packages/grafana-data/src/utils/Registry.ts
View file @
9dbf54eb
...
...
@@ -69,7 +69,7 @@ export class Registry<T extends RegistryItem> {
get
(
id
:
string
):
T
{
const
v
=
this
.
getIfExists
(
id
);
if
(
!
v
)
{
throw
new
Error
(
'Undefined: '
+
id
);
throw
new
Error
(
`"
${
id
}
" not found in:
${
this
.
list
().
map
(
v
=>
v
.
id
)}
`
);
}
return
v
;
}
...
...
packages/grafana-ui/src/components/GraphNG/GraphNG.tsx
View file @
9dbf54eb
...
...
@@ -3,13 +3,13 @@ import {
compareDataFrameStructures
,
DataFrame
,
FieldConfig
,
FieldMatcher
,
FieldType
,
formattedValueToString
,
getFieldColorModeForField
,
getFieldDisplayName
,
getTimeField
,
}
from
'@grafana/data'
;
import
{
mergeTimeSeriesData
}
from
'./utils'
;
import
{
alignDataFrames
}
from
'./utils'
;
import
{
UPlotChart
}
from
'../uPlot/Plot'
;
import
{
PlotProps
}
from
'../uPlot/types'
;
import
{
AxisPlacement
,
GraphFieldConfig
,
GraphMode
,
PointMode
}
from
'../uPlot/config'
;
...
...
@@ -22,9 +22,15 @@ import { useRevision } from '../uPlot/hooks';
const
defaultFormatter
=
(
v
:
any
)
=>
(
v
==
null
?
'-'
:
v
.
toFixed
(
1
));
export
interface
XYFieldMatchers
{
x
:
FieldMatcher
;
y
:
FieldMatcher
;
}
export
interface
GraphNGProps
extends
Omit
<
PlotProps
,
'data'
|
'config'
>
{
data
:
DataFrame
[];
legend
?:
LegendOptions
;
fields
?:
XYFieldMatchers
;
// default will assume timeseries data
}
const
defaultConfig
:
GraphFieldConfig
=
{
...
...
@@ -35,6 +41,7 @@ const defaultConfig: GraphFieldConfig = {
export
const
GraphNG
:
React
.
FC
<
GraphNGProps
>
=
({
data
,
fields
,
children
,
width
,
height
,
...
...
@@ -43,7 +50,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
timeZone
,
...
plotProps
})
=>
{
const
alignedFrameWithGapTest
=
useMemo
(()
=>
mergeTimeSeriesData
(
data
),
[
data
]);
const
alignedFrameWithGapTest
=
useMemo
(()
=>
alignDataFrames
(
data
,
fields
),
[
data
,
fields
]);
if
(
alignedFrameWithGapTest
==
null
)
{
return
(
...
...
@@ -66,28 +73,32 @@ export const GraphNG: React.FC<GraphNGProps> = ({
const
configBuilder
=
useMemo
(()
=>
{
const
builder
=
new
UPlotConfigBuilder
();
let
{
timeIndex
}
=
getTimeField
(
alignedFrame
);
if
(
timeIndex
===
undefined
)
{
timeIndex
=
0
;
// assuming first field represents x-domain
// X is the first field in the alligned frame
const
xField
=
alignedFrame
.
fields
[
0
];
if
(
xField
.
type
===
FieldType
.
time
)
{
builder
.
addScale
({
scaleKey
:
'x'
,
isTime
:
true
,
});
builder
.
addAxis
({
scaleKey
:
'x'
,
isTime
:
true
,
placement
:
AxisPlacement
.
Bottom
,
timeZone
,
theme
,
});
}
else
{
// Not time!
builder
.
addScale
({
scaleKey
:
'x'
,
isTime
:
true
,
});
builder
.
addAxis
({
scaleKey
:
'x'
,
placement
:
AxisPlacement
.
Bottom
,
theme
,
});
}
builder
.
addAxis
({
scaleKey
:
'x'
,
isTime
:
true
,
placement
:
AxisPlacement
.
Bottom
,
timeZone
,
theme
,
});
let
seriesIdx
=
0
;
const
legendItems
:
LegendItem
[]
=
[];
...
...
@@ -96,7 +107,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
const
config
=
field
.
config
as
FieldConfig
<
GraphFieldConfig
>
;
const
customConfig
=
config
.
custom
||
defaultConfig
;
if
(
i
===
timeIndex
||
field
.
type
!==
FieldType
.
number
)
{
if
(
field
===
xField
||
field
.
type
!==
FieldType
.
number
)
{
continue
;
}
...
...
packages/grafana-ui/src/components/GraphNG/utils.ts
View file @
9dbf54eb
import
{
DataFrame
,
FieldType
,
getTimeField
,
ArrayVector
,
NullValueMode
,
getFieldDisplayName
,
Field
,
fieldMatchers
,
FieldMatcherID
,
}
from
'@grafana/data'
;
import
{
AlignedFrameWithGapTest
}
from
'../uPlot/types'
;
import
uPlot
,
{
AlignedData
,
AlignedDataWithGapTest
}
from
'uplot'
;
import
{
XYFieldMatchers
}
from
'./GraphNG'
;
// the results ofter passing though data
export
interface
XYDimensionFields
{
x
:
Field
[];
y
:
Field
[];
}
export
function
mapDimesions
(
match
:
XYFieldMatchers
,
frame
:
DataFrame
,
frames
?:
DataFrame
[]):
XYDimensionFields
{
const
out
:
XYDimensionFields
=
{
x
:
[],
y
:
[],
};
for
(
const
field
of
frame
.
fields
)
{
if
(
match
.
x
(
field
,
frame
,
frames
??
[]))
{
out
.
x
.
push
(
field
);
}
if
(
match
.
y
(
field
,
frame
,
frames
??
[]))
{
out
.
y
.
push
(
field
);
}
}
return
out
;
}
/**
* Returns a single DataFrame with:
* - A shared time column
* - only numeric fields
*
* The input expects all frames to have a time field with values in ascending order
*
* @alpha
*/
export
function
mergeTimeSeriesData
(
frames
:
DataFrame
[]
):
AlignedFrameWithGapTest
|
null
{
export
function
alignDataFrames
(
frames
:
DataFrame
[],
fields
?:
XYFieldMatchers
):
AlignedFrameWithGapTest
|
null
{
const
valuesFromFrames
:
AlignedData
[]
=
[];
const
sourceFields
:
Field
[]
=
[];
// Default to timeseries config
if
(
!
fields
)
{
fields
=
{
x
:
fieldMatchers
.
get
(
FieldMatcherID
.
firstTimeField
).
get
({}),
y
:
fieldMatchers
.
get
(
FieldMatcherID
.
numeric
).
get
({}),
};
}
for
(
const
frame
of
frames
)
{
const
{
timeField
}
=
getTimeField
(
frame
);
if
(
!
timeField
)
{
continue
;
const
dims
=
mapDimesions
(
fields
,
frame
,
frames
);
if
(
!
(
dims
.
x
.
length
&&
dims
.
y
.
length
))
{
continue
;
// both x and y matched something!
}
if
(
dims
.
x
.
length
>
1
)
{
throw
new
Error
(
'Only a single x field is supported'
);
}
// Add the first X axis
if
(
!
sourceFields
.
length
)
{
sourceFields
.
push
(
dims
.
x
[
0
]);
}
const
alignedData
:
AlignedData
=
[
timeField
.
values
.
toArray
(),
// The x axis (time)
dims
.
x
[
0
]
.
values
.
toArray
(),
// The x axis (time)
];
// find numeric fields
for
(
const
field
of
frame
.
fields
)
{
if
(
field
.
type
!==
FieldType
.
number
)
{
continue
;
}
// Add the Y values
for
(
const
field
of
dims
.
y
)
{
let
values
=
field
.
values
.
toArray
();
if
(
field
.
config
.
nullValueMode
===
NullValueMode
.
AsZero
)
{
values
=
values
.
map
(
v
=>
(
v
===
null
?
0
:
v
));
}
alignedData
.
push
(
values
);
// Add the first time field
if
(
sourceFields
.
length
<
1
)
{
sourceFields
.
push
(
timeField
);
}
// This will cache an appropriate field name in the field state
getFieldDisplayName
(
field
,
frame
,
frames
);
sourceFields
.
push
(
field
);
}
// Timeseries has tima and at least one number
if
(
alignedData
.
length
>
1
)
{
valuesFromFrames
.
push
(
alignedData
);
}
valuesFromFrames
.
push
(
alignedData
);
}
if
(
valuesFromFrames
.
length
===
0
)
{
...
...
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