Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
nni
Commits
88a225f8
Unverified
Commit
88a225f8
authored
Oct 14, 2020
by
Yuge Zhang
Committed by
GitHub
Oct 14, 2020
Browse files
Chevron icon before table row and TableList refactoring (#2900)
parent
842f6cdb
Changes
11
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
870 additions
and
1064 deletions
+870
-1064
src/webui/.eslintrc
src/webui/.eslintrc
+1
-0
src/webui/src/components/TrialsDetail.tsx
src/webui/src/components/TrialsDetail.tsx
+8
-121
src/webui/src/components/modals/ChangeColumnComponent.tsx
src/webui/src/components/modals/ChangeColumnComponent.tsx
+48
-81
src/webui/src/components/modals/Compare.tsx
src/webui/src/components/modals/Compare.tsx
+143
-152
src/webui/src/components/public-child/ExpandableDetails.tsx
src/webui/src/components/public-child/ExpandableDetails.tsx
+22
-0
src/webui/src/components/public-child/PaginationTable.tsx
src/webui/src/components/public-child/PaginationTable.tsx
+120
-0
src/webui/src/components/trial-detail/Para.tsx
src/webui/src/components/trial-detail/Para.tsx
+51
-6
src/webui/src/components/trial-detail/TableList.tsx
src/webui/src/components/trial-detail/TableList.tsx
+465
-698
src/webui/src/static/interface.ts
src/webui/src/static/interface.ts
+1
-0
src/webui/src/static/model/trial.ts
src/webui/src/static/model/trial.ts
+10
-5
src/webui/src/static/style/table.scss
src/webui/src/static/style/table.scss
+1
-1
No files found.
src/webui/.eslintrc
View file @
88a225f8
...
...
@@ -28,6 +28,7 @@
"@typescript-eslint/no-inferrable-types": 0,
"@typescript-eslint/no-use-before-define": [2, "nofunc"],
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }],
"arrow-parens": [2, "as-needed"],
"no-inner-declarations": 0,
"no-empty": 2,
...
...
src/webui/src/components/TrialsDetail.tsx
View file @
88a225f8
import
*
as
React
from
'
react
'
;
import
{
Stack
,
StackItem
,
Pivot
,
PivotItem
,
Dropdown
,
IDropdownOption
,
DefaultButton
}
from
'
@fluentui/react
'
;
import
{
Stack
,
Pivot
,
PivotItem
}
from
'
@fluentui/react
'
;
import
{
EXPERIMENT
,
TRIALS
}
from
'
../static/datamodel
'
;
import
{
Trial
}
from
'
../static/model/trial
'
;
import
{
AppContext
}
from
'
../App
'
;
import
{
Title
}
from
'
./overview/Title
'
;
import
{
TitleContext
}
from
'
./overview/TitleContext
'
;
import
DefaultPoint
from
'
./trial-detail/DefaultMetricPoint
'
;
import
Duration
from
'
./trial-detail/Duration
'
;
import
Para
from
'
./trial-detail/Para
'
;
...
...
@@ -13,18 +10,8 @@ import TableList from './trial-detail/TableList';
import
'
../static/style/trialsDetail.scss
'
;
import
'
../static/style/search.scss
'
;
const
searchOptions
=
[
{
key
:
'
id
'
,
text
:
'
Id
'
},
{
key
:
'
Trial No.
'
,
text
:
'
Trial No.
'
},
{
key
:
'
status
'
,
text
:
'
Status
'
},
{
key
:
'
parameters
'
,
text
:
'
Parameters
'
}
];
interface
TrialDetailState
{
tablePageSize
:
number
;
// table components val
whichChart
:
string
;
searchType
:
string
;
searchFilter
:
(
trial
:
Trial
)
=>
boolean
;
}
class
TrialsDetail
extends
React
.
Component
<
{},
TrialDetailState
>
{
...
...
@@ -39,71 +26,22 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
tablePageSize
:
20
,
whichChart
:
'
Default metric
'
,
searchType
:
'
id
'
,
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-function-return-type
searchFilter
:
trial
=>
true
whichChart
:
'
Default metric
'
};
}
// search a trial by trial No. | trial id | Parameters | Status
searchTrial
=
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
):
void
=>
{
const
targetValue
=
event
.
target
.
value
;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let
filter
=
(
trial
:
Trial
):
boolean
=>
true
;
if
(
!
targetValue
.
trim
())
{
this
.
setState
({
searchFilter
:
filter
});
return
;
}
switch
(
this
.
state
.
searchType
)
{
case
'
id
'
:
filter
=
(
trial
):
boolean
=>
trial
.
info
.
id
.
toUpperCase
().
includes
(
targetValue
.
toUpperCase
());
break
;
case
'
Trial No.
'
:
filter
=
(
trial
):
boolean
=>
trial
.
info
.
sequenceId
.
toString
()
===
targetValue
;
break
;
case
'
status
'
:
filter
=
(
trial
):
boolean
=>
trial
.
info
.
status
.
toUpperCase
().
includes
(
targetValue
.
toUpperCase
());
break
;
case
'
parameters
'
:
// TODO: support filters like `x: 2` (instead of `"x": 2`)
filter
=
(
trial
):
boolean
=>
JSON
.
stringify
(
trial
.
info
.
hyperParameters
,
null
,
4
).
includes
(
targetValue
);
break
;
default
:
alert
(
`Unexpected search filter
${
this
.
state
.
searchType
}
`
);
}
this
.
setState
({
searchFilter
:
filter
});
};
handleTablePageSizeSelect
=
(
event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
:
IDropdownOption
|
undefined
):
void
=>
{
if
(
item
!==
undefined
)
{
this
.
setState
({
tablePageSize
:
item
.
text
===
'
all
'
?
-
1
:
parseInt
(
item
.
text
,
10
)
});
}
};
handleWhichTabs
=
(
item
:
any
):
void
=>
{
this
.
setState
({
whichChart
:
item
.
props
.
headerText
});
};
updateSearchFilterType
=
(
event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
:
IDropdownOption
|
undefined
):
void
=>
{
// clear input value and re-render table
if
(
item
!==
undefined
)
{
if
(
this
.
searchInput
!==
null
)
{
this
.
searchInput
.
value
=
''
;
}
this
.
setState
(()
=>
({
searchType
:
item
.
key
.
toString
()
}));
}
};
render
():
React
.
ReactNode
{
const
{
tablePageSize
,
whichChart
,
searchType
}
=
this
.
state
;
const
source
=
TRIALS
.
filter
(
this
.
state
.
searchFilter
);
const
trialIds
=
TRIALS
.
filter
(
this
.
state
.
searchFilter
).
map
(
trial
=>
trial
.
id
);
const
{
whichChart
}
=
this
.
state
;
const
source
=
TRIALS
.
toArray
(
);
const
trialIds
=
TRIALS
.
toArray
(
).
map
(
trial
=>
trial
.
id
);
return
(
<
AppContext
.
Consumer
>
{
(
value
):
React
.
ReactNode
=>
(
{
(
_
value
):
React
.
ReactNode
=>
(
<
React
.
Fragment
>
<
div
className
=
'trial'
id
=
'tabsty'
>
<
Pivot
...
...
@@ -144,61 +82,10 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
</
Pivot
>
</
div
>
{
/* trial table list */
}
<
div
className
=
'bulletedList'
style
=
{
{
marginTop
:
18
}
}
>
<
Stack
className
=
'title'
>
<
TitleContext
.
Provider
value
=
{
{
text
:
'
Trial jobs
'
,
icon
:
'
BulletedList
'
}
}
>
<
Title
/>
</
TitleContext
.
Provider
>
</
Stack
>
<
Stack
horizontal
className
=
'allList'
>
<
StackItem
grow
=
{
50
}
>
<
DefaultButton
text
=
'Compare'
className
=
'allList-compare'
// use child-component tableList's function, the function is in child-component.
onClick
=
{
():
void
=>
{
if
(
this
.
tableList
)
{
this
.
tableList
.
compareBtn
();
}
}
}
/>
</
StackItem
>
<
StackItem
grow
=
{
50
}
>
<
Stack
horizontal
horizontalAlign
=
'end'
className
=
'allList'
>
<
DefaultButton
className
=
'allList-button-gap'
text
=
'Add column'
onClick
=
{
():
void
=>
{
if
(
this
.
tableList
)
{
this
.
tableList
.
addColumn
();
}
}
}
/>
<
Dropdown
selectedKey
=
{
searchType
}
options
=
{
searchOptions
}
onChange
=
{
this
.
updateSearchFilterType
}
styles
=
{
{
root
:
{
width
:
150
}
}
}
/>
<
input
type
=
'text'
className
=
'allList-search-input'
placeholder
=
{
`Search by
${
this
.
state
.
searchType
}
`
}
onChange
=
{
this
.
searchTrial
}
style
=
{
{
width
:
230
}
}
ref
=
{
(
text
):
any
=>
(
this
.
searchInput
=
text
)
}
/>
</
Stack
>
</
StackItem
>
</
Stack
>
<
div
style
=
{
{
backgroundColor
:
'
#fff
'
}
}
>
<
TableList
pageSize
=
{
tablePageSize
}
tableSource
=
{
source
.
map
(
trial
=>
trial
.
tableRecord
)
}
columnList
=
{
value
.
columnList
}
changeColumn
=
{
value
.
changeColumn
}
tableSource
=
{
source
}
trialsUpdateBroadcast
=
{
this
.
context
.
trialsUpdateBroadcast
}
// TODO: change any to specific type
ref
=
{
(
tabList
):
any
=>
(
this
.
tableList
=
tabList
)
}
/>
</
div
>
</
React
.
Fragment
>
...
...
src/webui/src/components/modals/ChangeColumnComponent.tsx
View file @
88a225f8
import
*
as
React
from
'
react
'
;
import
{
Dialog
,
DialogType
,
DialogFooter
,
Checkbox
,
PrimaryButton
,
DefaultButton
}
from
'
@fluentui/react
'
;
import
{
OPERATION
}
from
'
../../static/const
'
;
interface
ChangeColumnState
{
userSelectColumnList
:
string
[];
originSelectColumnList
:
string
[];
// buffer, not saved yet
currentSelected
:
string
[];
}
interface
ChangeColumnProps
{
isHideDialog
:
boolean
;
showColumn
:
string
[];
// all column List
selectedColumn
:
string
[];
// user selected column list
changeColumn
:
(
val
:
string
[])
=>
void
;
hideShowColumnDialog
:
()
=>
void
;
allColumns
:
SimpleColumn
[];
// all column List
selectedColumns
:
string
[];
// user selected column list
onSelectedChange
:
(
val
:
string
[])
=>
void
;
onHideDialog
:
()
=>
void
;
minSelected
?:
number
;
}
interface
SimpleColumn
{
key
:
string
;
// key for management
name
:
string
;
// name to display
}
interface
CheckBoxItems
{
...
...
@@ -20,12 +24,12 @@ interface CheckBoxItems {
checked
:
boolean
;
onChange
:
()
=>
void
;
}
class
ChangeColumnComponent
extends
React
.
Component
<
ChangeColumnProps
,
ChangeColumnState
>
{
constructor
(
props
:
ChangeColumnProps
)
{
super
(
props
);
this
.
state
=
{
userSelectColumnList
:
this
.
props
.
selectedColumn
,
originSelectColumnList
:
this
.
props
.
selectedColumn
currentSelected
:
this
.
props
.
selectedColumns
};
}
...
...
@@ -38,97 +42,50 @@ class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeCol
label
:
string
,
val
?:
boolean
):
void
=>
{
const
source
:
string
[]
=
JSON
.
parse
(
JSON
.
stringify
(
this
.
state
.
userSelectColumnList
))
;
const
source
:
string
[]
=
[...
this
.
state
.
currentSelected
]
;
if
(
val
===
true
)
{
if
(
!
source
.
includes
(
label
))
{
source
.
push
(
label
);
this
.
setState
(
()
=>
({
userSelectColumnList
:
source
})
)
;
this
.
setState
(
{
currentSelected
:
source
});
}
}
else
{
if
(
source
.
includes
(
label
))
{
// remove from source
const
result
=
source
.
filter
(
item
=>
item
!==
label
);
this
.
setState
(()
=>
({
userSelectColumnList
:
result
}));
}
// remove from source
const
result
=
source
.
filter
(
item
=>
item
!==
label
);
this
.
setState
({
currentSelected
:
result
});
}
};
saveUserSelectColumn
=
():
void
=>
{
const
{
userSelectColumnList
}
=
this
.
state
;
const
{
showColumn
}
=
this
.
props
;
// sort by Trial No. | ID | Duration | Start Time | End Time | ...
const
sortColumn
:
string
[]
=
[];
/**
*
* TODO: use this function to refactor sort column
* search space might orderless
showColumn.map(item => {
userSelectColumnList.map(key => {
if (item === key || key.includes('search space')) {
if (!sortColumn.includes(key)) {
sortColumn.push(key);
}
}
});
});
*/
// push ![Operation] ![search space] column
showColumn
.
map
(
item
=>
{
userSelectColumnList
.
map
(
key
=>
{
if
(
item
===
key
&&
item
!==
OPERATION
)
{
sortColumn
.
push
(
key
);
}
});
});
// push search space key
userSelectColumnList
.
map
(
index
=>
{
if
(
index
.
includes
(
'
search space
'
))
{
if
(
!
sortColumn
.
includes
(
index
))
{
sortColumn
.
push
(
index
);
}
}
});
// push Operation
if
(
userSelectColumnList
.
includes
(
OPERATION
))
{
sortColumn
.
push
(
OPERATION
);
}
this
.
props
.
changeColumn
(
sortColumn
);
this
.
hideDialog
();
// hide dialog
};
hideDialog
=
():
void
=>
{
this
.
props
.
hideShowColumnDialog
();
const
{
currentSelected
}
=
this
.
state
;
const
{
allColumns
,
onSelectedChange
}
=
this
.
props
;
const
selectedColumns
=
allColumns
.
map
(
column
=>
column
.
key
).
filter
(
key
=>
currentSelected
.
includes
(
key
));
onSelectedChange
(
selectedColumns
);
this
.
hideDialog
();
};
// user exit dialog
cancelOption
=
():
void
=>
{
// reset select column
const
{
originSelectColumnList
}
=
this
.
state
;
this
.
setState
({
userSelectColumnList
:
originSelectColumnList
},
()
=>
{
this
.
setState
({
currentSelected
:
this
.
props
.
selectedColumns
},
()
=>
{
this
.
hideDialog
();
});
};
private
hideDialog
=
():
void
=>
{
this
.
props
.
onHideDialog
();
};
render
():
React
.
ReactNode
{
const
{
showColumn
,
isHideDialog
}
=
this
.
props
;
const
{
userSelectColumnList
}
=
this
.
state
;
const
renderOptions
:
Array
<
CheckBoxItems
>
=
[];
showColumn
.
map
(
item
=>
{
if
(
userSelectColumnList
.
includes
(
item
))
{
// selected column name
renderOptions
.
push
({
label
:
item
,
checked
:
true
,
onChange
:
this
.
makeChangeHandler
(
item
)
});
}
else
{
renderOptions
.
push
({
label
:
item
,
checked
:
false
,
onChange
:
this
.
makeChangeHandler
(
item
)
});
}
});
const
{
allColumns
,
minSelected
}
=
this
.
props
;
const
{
currentSelected
}
=
this
.
state
;
return
(
<
div
>
<
Dialog
hidden
=
{
isHideDialog
}
// required field!
hidden
=
{
false
}
dialogContentProps
=
{
{
type
:
DialogType
.
largeHeader
,
title
:
'
C
hange tabl
e column
'
,
subText
:
'
You can chose which columns you w
ant
to see
in the table
.
'
title
:
'
C
ustomiz
e column
s
'
,
subText
:
'
You can cho
o
se which columns you w
ish
to see.
'
}
}
modalProps
=
{
{
isBlocking
:
false
,
...
...
@@ -136,12 +93,22 @@ class ChangeColumnComponent extends React.Component<ChangeColumnProps, ChangeCol
}
}
>
<
div
className
=
'columns-height'
>
{
renderOptions
.
map
(
item
=>
{
return
<
Checkbox
key
=
{
item
.
label
}
{
...
item
}
styles
=
{
{
root
:
{
marginBottom
:
8
}
}
}
/>;
})
}
{
allColumns
.
map
(
item
=>
(
<
Checkbox
key
=
{
item
.
key
}
label
=
{
item
.
name
}
checked
=
{
currentSelected
.
includes
(
item
.
key
)
}
onChange
=
{
this
.
makeChangeHandler
(
item
.
key
)
}
styles
=
{
{
root
:
{
marginBottom
:
8
}
}
}
/>
))
}
</
div
>
<
DialogFooter
>
<
PrimaryButton
text
=
'Save'
onClick
=
{
this
.
saveUserSelectColumn
}
/>
<
PrimaryButton
text
=
'Save'
onClick
=
{
this
.
saveUserSelectColumn
}
disabled
=
{
currentSelected
.
length
<
(
minSelected
===
undefined
?
1
:
minSelected
)
}
/>
<
DefaultButton
text
=
'Cancel'
onClick
=
{
this
.
cancelOption
}
/>
</
DialogFooter
>
</
Dialog
>
...
...
src/webui/src/components/modals/Compare.tsx
View file @
88a225f8
import
*
as
React
from
'
react
'
;
import
{
renderToString
}
from
'
react-dom/server
'
;
import
{
Stack
,
Modal
,
IconButton
,
IDragOptions
,
ContextualMenu
}
from
'
@fluentui/react
'
;
import
ReactEcharts
from
'
echarts-for-react
'
;
import
IntermediateVal
from
'
../public-child/IntermediateVal
'
;
import
{
TRIALS
}
from
'
../../static/datamodel
'
;
import
{
TableRecord
,
Intermedia
,
TooltipForIntermediate
}
from
'
../../static/interface
'
;
import
{
TooltipForIntermediate
,
TableObj
,
SingleAxis
}
from
'
../../static/interface
'
;
import
{
contentStyles
,
iconButtonStyles
}
from
'
../buttons/ModalTheme
'
;
import
'
../../static/style/compare.scss
'
;
import
{
convertDuration
,
parseMetrics
}
from
'
../../static/function
'
;
import
{
EXPERIMENT
,
TRIALS
}
from
'
../../static/datamodel
'
;
function
_getWebUIWidth
():
number
{
return
window
.
innerWidth
;
}
const
dragOptions
:
IDragOptions
=
{
moveMenuItemText
:
'
Move
'
,
...
...
@@ -13,79 +18,81 @@ const dragOptions: IDragOptions = {
menu
:
ContextualMenu
};
// the modal of trial compare
// TODO: this should be refactored to the common modules
// copied from trial.ts
function
_parseIntermediates
(
trial
:
TableObj
):
number
[]
{
const
intermediates
:
number
[]
=
[];
for
(
const
metric
of
trial
.
intermediates
)
{
if
(
metric
===
undefined
)
{
break
;
}
const
parsedMetric
=
parseMetrics
(
metric
.
data
);
if
(
typeof
parsedMetric
===
'
object
'
)
{
// TODO: should handle more types of metric keys
intermediates
.
push
(
parsedMetric
.
default
);
}
else
{
intermediates
.
push
(
parsedMetric
);
}
}
return
intermediates
;
}
interface
Item
{
id
:
string
;
sequenceId
:
number
;
duration
:
string
;
parameters
:
Map
<
string
,
any
>
;
metrics
:
Map
<
string
,
any
>
;
intermediates
:
number
[];
}
interface
CompareProps
{
compareStacks
:
Array
<
TableRecord
>
;
cancelFunc
:
()
=>
void
;
trials
:
TableObj
[];
title
:
string
;
showDetails
:
boolean
;
onHideDialog
:
()
=>
void
;
}
class
Compare
extends
React
.
Component
<
CompareProps
,
{}
>
{
public
_isCompareMount
!
:
boolean
;
constructor
(
props
:
CompareProps
)
{
super
(
props
);
}
intermediate
=
():
React
.
ReactNode
=>
{
const
{
compareStacks
}
=
this
.
props
;
const
trialIntermediate
:
Array
<
Intermedia
>
=
[];
const
idsList
:
string
[]
=
[];
compareStacks
.
forEach
(
element
=>
{
const
trial
=
TRIALS
.
getTrial
(
element
.
id
);
trialIntermediate
.
push
({
name
:
element
.
id
,
data
:
trial
.
description
.
intermediate
,
type
:
'
line
'
,
hyperPara
:
trial
.
description
.
parameters
});
idsList
.
push
(
element
.
id
);
});
// find max intermediate number
trialIntermediate
.
sort
((
a
,
b
)
=>
{
return
b
.
data
.
length
-
a
.
data
.
length
;
});
const
legend
:
string
[]
=
[];
// max length
const
length
=
trialIntermediate
[
0
]
!==
undefined
?
trialIntermediate
[
0
].
data
.
length
:
0
;
const
xAxis
:
number
[]
=
[];
trialIntermediate
.
forEach
(
element
=>
{
legend
.
push
(
element
.
name
);
});
for
(
let
i
=
1
;
i
<=
length
;
i
++
)
{
xAxis
.
push
(
i
);
}
private
_generateTooltipSummary
(
row
:
Item
,
metricKey
:
string
):
string
{
return
renderToString
(
<
div
className
=
'tooldetailAccuracy'
>
<
div
>
Trial ID:
{
row
.
id
}
</
div
>
<
div
>
Default metric:
{
row
.
metrics
.
get
(
metricKey
)
||
'
N/A
'
}
</
div
>
</
div
>
);
}
private
_intermediates
(
items
:
Item
[],
metricKey
:
string
):
React
.
ReactNode
{
// Precondition: make sure `items` is not empty
const
xAxisMax
=
Math
.
max
(...
items
.
map
(
item
=>
item
.
intermediates
.
length
));
const
xAxis
=
Array
(
xAxisMax
)
.
fill
(
0
)
.
map
((
_
,
i
)
=>
i
+
1
);
// [1, 2, 3, ..., xAxisMax]
const
dataForEchart
=
items
.
map
(
item
=>
({
name
:
item
.
id
,
data
:
item
.
intermediates
,
type
:
'
line
'
}));
const
legend
=
dataForEchart
.
map
(
item
=>
item
.
name
);
const
option
=
{
tooltip
:
{
trigger
:
'
item
'
,
enterable
:
true
,
position
:
function
(
point
:
number
[],
data
:
TooltipForIntermediate
):
number
[]
{
position
:
(
point
:
number
[],
data
:
TooltipForIntermediate
):
[
number
,
number
]
=>
{
if
(
data
.
dataIndex
<
length
/
2
)
{
return
[
point
[
0
],
80
];
}
else
{
return
[
point
[
0
]
-
300
,
80
];
}
},
formatter
:
function
(
data
:
TooltipForIntermediate
):
React
.
ReactNode
{
const
trialId
=
data
.
seriesName
;
let
obj
=
{};
const
temp
=
trialIntermediate
.
find
(
key
=>
key
.
name
===
trialId
);
if
(
temp
!==
undefined
)
{
obj
=
temp
.
hyperPara
;
}
return
(
'
<div class="tooldetailAccuracy">
'
+
'
<div>Trial ID:
'
+
trialId
+
'
</div>
'
+
'
<div>Intermediate:
'
+
data
.
data
+
'
</div>
'
+
'
<div>Parameters:
'
+
'
<pre>
'
+
JSON
.
stringify
(
obj
,
null
,
4
)
+
'
</pre>
'
+
'
</div>
'
+
'
</div>
'
);
formatter
:
(
data
:
TooltipForIntermediate
):
string
=>
{
const
item
=
items
.
find
(
k
=>
k
.
id
===
data
.
seriesName
)
as
Item
;
return
this
.
_generateTooltipSummary
(
item
,
metricKey
);
}
},
grid
:
{
...
...
@@ -96,12 +103,11 @@ class Compare extends React.Component<CompareProps, {}> {
legend
:
{
type
:
'
scroll
'
,
right
:
40
,
left
:
idsList
.
length
>
6
?
80
:
null
,
data
:
idsList
left
:
legend
.
length
>
6
?
80
:
null
,
data
:
legend
},
xAxis
:
{
type
:
'
category
'
,
// name: '# Intermediate',
boundaryGap
:
false
,
data
:
xAxis
},
...
...
@@ -110,7 +116,7 @@ class Compare extends React.Component<CompareProps, {}> {
name
:
'
Metric
'
,
scale
:
true
},
series
:
trialIntermediate
series
:
dataForEchart
};
return
(
<
ReactEcharts
...
...
@@ -119,108 +125,92 @@ class Compare extends React.Component<CompareProps, {}> {
notMerge
=
{
true
}
// update now
/>
);
};
// render table column ---
initColumn
=
():
React
.
ReactNode
=>
{
const
idList
:
string
[]
=
[];
const
sequenceIdList
:
number
[]
=
[];
const
durationList
:
number
[]
=
[];
}
const
compareStacks
=
this
.
props
.
compareStacks
.
map
(
tableRecord
=>
TRIALS
.
getTrial
(
tableRecord
.
id
));
private
_renderRow
(
key
:
string
,
rowName
:
string
,
className
:
string
,
items
:
Item
[],
formatter
:
(
item
:
Item
)
=>
string
):
React
.
ReactNode
{
return
(
<
tr
key
=
{
key
}
>
<
td
className
=
'column'
>
{
rowName
}
</
td
>
{
items
.
map
(
item
=>
(
<
td
className
=
{
className
}
key
=
{
item
.
id
}
>
{
formatter
(
item
)
}
</
td
>
))
}
</
tr
>
);
}
const
parameterList
:
Array
<
object
>
=
[];
let
parameterKeys
:
string
[]
=
[];
if
(
compareStacks
.
length
!==
0
)
{
parameterKeys
=
Object
.
keys
(
compareStacks
[
0
].
description
.
parameters
);
}
compareStacks
.
forEach
(
temp
=>
{
idList
.
push
(
temp
.
id
);
sequenceIdList
.
push
(
temp
.
sequenceId
)
;
durationList
.
push
(
temp
.
duration
)
;
parameterList
.
push
(
temp
.
description
.
parameters
);
});
let
isComplexSearchSpace
;
i
f
(
parameterList
.
length
>
0
)
{
isComplexSearchSpace
=
typeof
parameterList
[
0
][
parameterKeys
[
0
]]
===
'
object
'
?
true
:
false
;
private
_overlapKeys
(
s
:
Map
<
string
,
any
>
[]):
string
[]
{
// Calculate the overlapped keys for multiple
const
intersection
:
string
[]
=
[];
for
(
const
i
of
s
[
0
].
keys
())
{
let
inAll
=
true
;
for
(
const
t
of
s
)
{
if
(
!
Array
.
from
(
t
.
keys
()).
includes
(
i
))
{
inAll
=
false
;
break
;
}
}
if
(
inAll
)
{
i
ntersection
.
push
(
i
);
}
}
const
width
=
this
.
getWebUIWidth
();
let
scrollClass
;
return
intersection
;
}
// render table column ---
private
_columns
(
items
:
Item
[]):
React
.
ReactNode
{
// Precondition: make sure `items` is not empty
const
width
=
_getWebUIWidth
();
let
scrollClass
:
string
=
''
;
if
(
width
>
1200
)
{
scrollClass
=
i
dList
.
length
>
3
?
'
flex
'
:
''
;
scrollClass
=
i
tems
.
length
>
3
?
'
flex
'
:
''
;
}
else
if
(
width
<
700
)
{
scrollClass
=
i
dList
.
length
>
1
?
'
flex
'
:
''
;
scrollClass
=
i
tems
.
length
>
1
?
'
flex
'
:
''
;
}
else
{
scrollClass
=
i
dList
.
length
>
2
?
'
flex
'
:
''
;
scrollClass
=
i
tems
.
length
>
2
?
'
flex
'
:
''
;
}
const
parameterKeys
=
this
.
_overlapKeys
(
items
.
map
(
item
=>
item
.
parameters
));
const
metricKeys
=
this
.
_overlapKeys
(
items
.
map
(
item
=>
item
.
metrics
));
return
(
<
table
className
=
{
`compare-modal-table
${
scrollClass
}
`
}
>
<
tbody
>
<
tr
>
<
td
className
=
'column'
>
Id
</
td
>
{
Object
.
keys
(
idList
).
map
(
key
=>
(
<
td
className
=
'value idList'
key
=
{
key
}
>
{
idList
[
key
]
}
</
td
>
))
}
</
tr
>
<
tr
>
<
td
className
=
'column'
>
Trial No.
</
td
>
{
Object
.
keys
(
sequenceIdList
).
map
(
key
=>
(
<
td
className
=
'value idList'
key
=
{
key
}
>
{
sequenceIdList
[
key
]
}
</
td
>
))
}
</
tr
>
<
tr
>
<
td
className
=
'column'
>
Default metric
</
td
>
{
Object
.
keys
(
compareStacks
).
map
(
index
=>
(
<
td
className
=
'value'
key
=
{
index
}
>
<
IntermediateVal
trialId
=
{
compareStacks
[
index
].
id
}
/>
</
td
>
))
}
</
tr
>
<
tr
>
<
td
className
=
'column'
>
duration
</
td
>
{
Object
.
keys
(
durationList
).
map
(
index
=>
(
<
td
className
=
'value'
key
=
{
index
}
>
{
durationList
[
index
]
}
</
td
>
))
}
</
tr
>
{
isComplexSearchSpace
?
null
:
Object
.
keys
(
parameterKeys
).
map
(
index
=>
(
<
tr
key
=
{
index
}
>
<
td
className
=
'column'
key
=
{
index
}
>
{
parameterKeys
[
index
]
}
</
td
>
{
Object
.
keys
(
parameterList
).
map
(
key
=>
(
<
td
key
=
{
key
}
className
=
'value'
>
{
parameterList
[
key
][
parameterKeys
[
index
]]
}
</
td
>
))
}
</
tr
>
))
}
{
this
.
_renderRow
(
'
id
'
,
'
ID
'
,
'
value idList
'
,
items
,
item
=>
item
.
id
)
}
{
this
.
_renderRow
(
'
trialnum
'
,
'
Trial No.
'
,
'
value
'
,
items
,
item
=>
item
.
sequenceId
.
toString
())
}
{
this
.
_renderRow
(
'
duration
'
,
'
Duration
'
,
'
value
'
,
items
,
item
=>
item
.
duration
)
}
{
parameterKeys
.
map
(
k
=>
this
.
_renderRow
(
`space_
${
k
}
`
,
k
,
'
value
'
,
items
,
item
=>
item
.
parameters
.
get
(
k
))
)
}
{
metricKeys
.
map
(
k
=>
this
.
_renderRow
(
`metrics_
${
k
}
`
,
`Metric:
${
k
}
`
,
'
value
'
,
items
,
item
=>
item
.
metrics
.
get
(
k
))
)
}
</
tbody
>
</
table
>
);
};
getWebUIWidth
=
():
number
=>
{
return
window
.
innerWidth
;
};
componentDidMount
():
void
{
this
.
_isCompareMount
=
true
;
}
componentWillUnmount
():
void
{
this
.
_isCompareMount
=
false
;
}
render
():
React
.
ReactNode
{
const
{
cancelFunc
}
=
this
.
props
;
const
{
onHideDialog
,
trials
,
title
,
showDetails
}
=
this
.
props
;
const
flatten
=
(
m
:
Map
<
SingleAxis
,
any
>
):
Map
<
string
,
any
>
=>
{
return
new
Map
(
Array
.
from
(
m
).
map
(([
key
,
value
])
=>
[
key
.
baseName
,
value
]));
};
const
inferredSearchSpace
=
TRIALS
.
inferredSearchSpace
(
EXPERIMENT
.
searchSpaceNew
);
const
items
:
Item
[]
=
trials
.
map
(
trial
=>
({
id
:
trial
.
id
,
sequenceId
:
trial
.
sequenceId
,
duration
:
convertDuration
(
trial
.
duration
),
parameters
:
flatten
(
trial
.
parameters
(
inferredSearchSpace
)),
metrics
:
flatten
(
trial
.
metrics
(
TRIALS
.
inferredMetricSpace
())),
intermediates
:
_parseIntermediates
(
trial
)
}));
const
metricKeys
=
this
.
_overlapKeys
(
items
.
map
(
item
=>
item
.
metrics
));
const
defaultMetricKey
=
!
metricKeys
||
metricKeys
.
includes
(
'
default
'
)
?
'
default
'
:
metricKeys
[
0
];
return
(
<
Modal
...
...
@@ -229,22 +219,23 @@ class Compare extends React.Component<CompareProps, {}> {
className
=
'compare-modal'
allowTouchBodyScroll
=
{
true
}
dragOptions
=
{
dragOptions
}
onDismiss
=
{
onHideDialog
}
>
<
div
>
<
div
className
=
{
contentStyles
.
header
}
>
<
span
>
Compare trials
</
span
>
<
span
>
{
title
}
</
span
>
<
IconButton
styles
=
{
iconButtonStyles
}
iconProps
=
{
{
iconName
:
'
Cancel
'
}
}
ariaLabel
=
'Close popup modal'
onClick
=
{
cancelFunc
}
onClick
=
{
onHideDialog
}
/>
</
div
>
<
Stack
className
=
'compare-modal-intermediate'
>
{
this
.
intermediate
(
)
}
{
this
.
_
intermediate
s
(
items
,
defaultMetricKey
)
}
<
Stack
className
=
'compare-yAxis'
>
# Intermediate result
</
Stack
>
</
Stack
>
<
Stack
>
{
this
.
initColumn
(
)
}
</
Stack
>
{
showDetails
&&
<
Stack
>
{
this
.
_columns
(
items
)
}
</
Stack
>
}
</
div
>
</
Modal
>
);
...
...
src/webui/src/components/public-child/ExpandableDetails.tsx
0 → 100644
View file @
88a225f8
import
*
as
React
from
'
react
'
;
import
{
DetailsRow
,
IDetailsRowBaseProps
}
from
'
@fluentui/react
'
;
import
OpenRow
from
'
../public-child/OpenRow
'
;
interface
ExpandableDetailsProps
{
detailsProps
:
IDetailsRowBaseProps
;
isExpand
:
boolean
;
}
class
ExpandableDetails
extends
React
.
Component
<
ExpandableDetailsProps
,
{}
>
{
render
():
React
.
ReactNode
{
const
{
detailsProps
,
isExpand
}
=
this
.
props
;
return
(
<
div
>
<
DetailsRow
{
...
detailsProps
}
/>
{
isExpand
&&
<
OpenRow
trialId
=
{
detailsProps
.
item
.
id
}
/>
}
</
div
>
);
}
}
export
default
ExpandableDetails
;
src/webui/src/components/public-child/PaginationTable.tsx
0 → 100644
View file @
88a225f8
import
*
as
React
from
'
react
'
;
import
{
DetailsList
,
Dropdown
,
Icon
,
IDetailsListProps
,
IDropdownOption
,
IStackTokens
,
Stack
}
from
'
@fluentui/react
'
;
import
ReactPaginate
from
'
react-paginate
'
;
interface
PaginationTableState
{
itemsPerPage
:
number
;
currentPage
:
number
;
itemsOnPage
:
any
[];
// this needs to be stored in state to prevent re-rendering
}
const
horizontalGapStackTokens
:
IStackTokens
=
{
childrenGap
:
20
,
padding
:
10
};
function
_currentTableOffset
(
perPage
:
number
,
currentPage
:
number
,
source
:
any
[]):
number
{
return
perPage
===
-
1
?
0
:
Math
.
min
(
currentPage
,
Math
.
floor
((
source
.
length
-
1
)
/
perPage
))
*
perPage
;
}
function
_obtainPaginationSlice
(
perPage
:
number
,
currentPage
:
number
,
source
:
any
[]):
any
[]
{
if
(
perPage
===
-
1
)
{
return
source
;
}
else
{
const
offset
=
_currentTableOffset
(
perPage
,
currentPage
,
source
);
return
source
.
slice
(
offset
,
offset
+
perPage
);
}
}
class
PaginationTable
extends
React
.
PureComponent
<
IDetailsListProps
,
PaginationTableState
>
{
constructor
(
props
:
IDetailsListProps
)
{
super
(
props
);
this
.
state
=
{
itemsPerPage
:
20
,
currentPage
:
0
,
itemsOnPage
:
[]
};
}
private
_onItemsPerPageSelect
(
event
:
React
.
FormEvent
<
HTMLDivElement
>
,
item
:
IDropdownOption
|
undefined
):
void
{
if
(
item
!==
undefined
)
{
const
{
items
}
=
this
.
props
;
// use current offset to calculate the next `current_page`
const
currentOffset
=
_currentTableOffset
(
this
.
state
.
itemsPerPage
,
this
.
state
.
currentPage
,
items
);
const
itemsPerPage
=
item
.
key
as
number
;
const
currentPage
=
Math
.
floor
(
currentOffset
/
itemsPerPage
);
this
.
setState
({
itemsPerPage
:
itemsPerPage
,
currentPage
:
currentPage
,
itemsOnPage
:
_obtainPaginationSlice
(
itemsPerPage
,
currentPage
,
this
.
props
.
items
)
});
}
}
private
_onPageSelect
(
event
:
any
):
void
{
const
currentPage
=
event
.
selected
;
this
.
setState
({
currentPage
:
currentPage
,
itemsOnPage
:
_obtainPaginationSlice
(
this
.
state
.
itemsPerPage
,
currentPage
,
this
.
props
.
items
)
});
}
componentDidUpdate
(
prevProps
:
IDetailsListProps
):
void
{
if
(
prevProps
.
items
!==
this
.
props
.
items
)
{
this
.
setState
({
itemsOnPage
:
_obtainPaginationSlice
(
this
.
state
.
itemsPerPage
,
this
.
state
.
currentPage
,
this
.
props
.
items
)
});
}
}
render
():
React
.
ReactNode
{
const
{
itemsPerPage
,
itemsOnPage
}
=
this
.
state
;
const
detailListProps
=
{
...
this
.
props
,
items
:
itemsOnPage
};
const
itemsCount
=
this
.
props
.
items
.
length
;
const
pageCount
=
itemsPerPage
===
-
1
?
1
:
Math
.
ceil
(
itemsCount
/
itemsPerPage
);
const
perPageOptions
=
[
{
key
:
10
,
text
:
'
10 items per page
'
},
{
key
:
20
,
text
:
'
20 items per page
'
},
{
key
:
50
,
text
:
'
50 items per page
'
},
{
key
:
-
1
,
text
:
'
All items
'
}
];
return
(
<
div
>
<
DetailsList
{
...
detailListProps
}
/>
<
Stack
horizontal
horizontalAlign
=
'end'
verticalAlign
=
'baseline'
styles
=
{
{
root
:
{
padding
:
10
}
}
}
tokens
=
{
horizontalGapStackTokens
}
>
<
Dropdown
selectedKey
=
{
itemsPerPage
}
options
=
{
perPageOptions
}
onChange
=
{
this
.
_onItemsPerPageSelect
.
bind
(
this
)
}
styles
=
{
{
dropdown
:
{
width
:
150
}
}
}
/>
<
ReactPaginate
previousLabel
=
{
<
Icon
aria
-
hidden
=
{
true
}
iconName
=
'ChevronLeft'
/>
}
nextLabel
=
{
<
Icon
aria
-
hidden
=
{
true
}
iconName
=
'ChevronRight'
/>
}
breakLabel
=
{
'
...
'
}
breakClassName
=
{
'
break
'
}
pageCount
=
{
pageCount
}
marginPagesDisplayed
=
{
2
}
pageRangeDisplayed
=
{
2
}
onPageChange
=
{
this
.
_onPageSelect
.
bind
(
this
)
}
containerClassName
=
{
itemsCount
===
0
?
'
pagination hidden
'
:
'
pagination
'
}
subContainerClassName
=
{
'
pages pagination
'
}
disableInitialCallback
=
{
false
}
activeClassName
=
{
'
active
'
}
/>
</
Stack
>
</
div
>
);
}
}
export
default
PaginationTable
;
src/webui/src/components/trial-detail/Para.tsx
View file @
88a225f8
import
*
as
d3
from
'
d3
'
;
import
{
Dropdown
,
IDropdownOption
,
Stack
}
from
'
@fluentui/react
'
;
import
{
Dropdown
,
IDropdownOption
,
Stack
,
DefaultButton
}
from
'
@fluentui/react
'
;
import
ParCoords
from
'
parcoord-es
'
;
import
'
parcoord-es/dist/parcoords.css
'
;
import
*
as
React
from
'
react
'
;
...
...
@@ -9,12 +9,16 @@ import { filterByStatus } from '../../static/function';
import
{
TableObj
,
SingleAxis
,
MultipleAxes
}
from
'
../../static/interface
'
;
import
'
../../static/style/button.scss
'
;
import
'
../../static/style/para.scss
'
;
import
ChangeColumnComponent
from
'
../modals/ChangeColumnComponent
'
;
interface
ParaState
{
dimName
:
string
[];
selectedPercent
:
string
;
primaryMetricKey
:
string
;
noChart
:
boolean
;
customizeColumnsDialogVisible
:
boolean
;
availableDimensions
:
string
[];
chosenDimensions
:
string
[];
}
interface
ParaProps
{
...
...
@@ -45,7 +49,10 @@ class Para extends React.Component<ParaProps, ParaState> {
dimName
:
[],
primaryMetricKey
:
'
default
'
,
selectedPercent
:
'
1
'
,
noChart
:
true
noChart
:
true
,
customizeColumnsDialogVisible
:
false
,
availableDimensions
:
[],
chosenDimensions
:
[]
};
}
...
...
@@ -82,11 +89,24 @@ class Para extends React.Component<ParaProps, ParaState> {
}
render
():
React
.
ReactNode
{
const
{
selectedPercent
,
noChart
}
=
this
.
state
;
const
{
selectedPercent
,
noChart
,
customizeColumnsDialogVisible
,
availableDimensions
,
chosenDimensions
}
=
this
.
state
;
return
(
<
div
className
=
'parameter'
>
<
Stack
horizontal
className
=
'para-filter'
horizontalAlign
=
'end'
>
<
DefaultButton
text
=
'Add/Remove axes'
onClick
=
{
():
void
=>
{
this
.
setState
({
customizeColumnsDialogVisible
:
true
});
}
}
styles
=
{
{
root
:
{
marginRight
:
10
}
}
}
/>
<
Dropdown
selectedKey
=
{
selectedPercent
}
onChange
=
{
this
.
percentNum
}
...
...
@@ -101,6 +121,21 @@ class Para extends React.Component<ParaProps, ParaState> {
/>
{
this
.
finalKeysDropdown
()
}
</
Stack
>
{
customizeColumnsDialogVisible
&&
availableDimensions
.
length
>
0
&&
(
<
ChangeColumnComponent
selectedColumns
=
{
chosenDimensions
}
allColumns
=
{
availableDimensions
.
map
(
dim
=>
({
key
:
dim
,
name
:
dim
}))
}
onSelectedChange
=
{
(
selected
:
string
[]):
void
=>
{
this
.
setState
({
chosenDimensions
:
selected
},
()
=>
{
this
.
renderParallelCoordinates
();
});
}
}
onHideDialog
=
{
():
void
=>
{
this
.
setState
({
customizeColumnsDialogVisible
:
false
});
}
}
minSelected
=
{
2
}
/>
)
}
<
div
className
=
'parcoords'
style
=
{
this
.
chartMulineStyle
}
ref
=
{
this
.
paraRef
}
/>
{
noChart
&&
<
div
className
=
'nodata'
>
No data
</
div
>
}
</
div
>
...
...
@@ -143,13 +178,13 @@ class Para extends React.Component<ParaProps, ParaState> {
private
renderParallelCoordinates
():
void
{
const
{
searchSpace
}
=
this
.
props
;
const
percent
=
parseFloat
(
this
.
state
.
selectedPercent
);
const
{
primaryMetricKey
}
=
this
.
state
;
const
{
primaryMetricKey
,
chosenDimensions
}
=
this
.
state
;
const
inferredSearchSpace
=
TRIALS
.
inferredSearchSpace
(
searchSpace
);
const
inferredMetricSpace
=
TRIALS
.
inferredMetricSpace
();
let
convertedTrials
=
this
.
getTrialsAsObjectList
(
inferredSearchSpace
,
inferredMetricSpace
);
const
dimensions
:
[
any
,
any
][]
=
[];
const
dimensions
:
[
string
,
any
][]
=
[];
let
colorDim
:
string
|
undefined
=
undefined
,
colorScale
:
any
=
undefined
;
// treat every axis as numeric to fit for brush
...
...
@@ -213,7 +248,11 @@ class Para extends React.Component<ParaProps, ParaState> {
}
this
.
pcs
.
data
(
convertedTrials
)
.
dimensions
(
dimensions
.
reduce
((
obj
,
entry
)
=>
({
...
obj
,
[
entry
[
0
]]:
entry
[
1
]
}),
{}));
.
dimensions
(
dimensions
.
filter
(([
d
,
_
])
=>
chosenDimensions
.
length
===
0
||
chosenDimensions
.
includes
(
d
))
.
reduce
((
obj
,
entry
)
=>
({
...
obj
,
[
entry
[
0
]]:
entry
[
1
]
}),
{})
);
if
(
firstRun
)
{
this
.
pcs
.
margin
(
this
.
innerChartMargins
)
...
...
@@ -230,6 +269,12 @@ class Para extends React.Component<ParaProps, ParaState> {
if
(
firstRun
)
{
this
.
setState
({
noChart
:
false
});
}
// set new available dims
this
.
setState
({
availableDimensions
:
dimensions
.
map
(
e
=>
e
[
0
]),
chosenDimensions
:
chosenDimensions
.
length
===
0
?
dimensions
.
map
(
e
=>
e
[
0
])
:
chosenDimensions
});
}
private
getTrialsAsObjectList
(
inferredSearchSpace
:
MultipleAxes
,
inferredMetricSpace
:
MultipleAxes
):
{}[]
{
...
...
src/webui/src/components/trial-detail/TableList.tsx
View file @
88a225f8
This diff is collapsed.
Click to expand it.
src/webui/src/static/interface.ts
View file @
88a225f8
...
...
@@ -33,6 +33,7 @@ interface TableObj {
color
?:
string
;
startTime
?:
number
;
endTime
?:
number
;
intermediates
:
(
MetricDataRecord
|
undefined
)[];
parameters
(
axes
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
;
metrics
(
axes
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
;
}
...
...
src/webui/src/static/model/trial.ts
View file @
88a225f8
...
...
@@ -60,7 +60,7 @@ function inferTrialParameters(
class
Trial
implements
TableObj
{
private
metricsInitialized
:
boolean
=
false
;
private
infoField
:
TrialJobInfo
|
undefined
;
p
rivate
intermediates
:
(
MetricDataRecord
|
undefined
)[]
=
[];
p
ublic
intermediates
:
(
MetricDataRecord
|
undefined
)[]
=
[];
public
final
:
MetricDataRecord
|
undefined
;
private
finalAcc
:
number
|
undefined
;
...
...
@@ -224,24 +224,29 @@ class Trial implements TableObj {
}
public
parameters
(
axes
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
{
const
ret
=
new
Map
<
SingleAxis
,
any
>
(
Array
.
from
(
axes
.
axes
.
values
()).
map
(
k
=>
[
k
,
null
]));
if
(
this
.
info
===
undefined
||
this
.
info
.
hyperParameters
===
undefined
)
{
throw
new
Map
()
;
throw
ret
;
}
else
{
const
tempHyper
=
this
.
info
.
hyperParameters
;
let
params
=
JSON
.
parse
(
tempHyper
[
tempHyper
.
length
-
1
]).
parameters
;
if
(
typeof
params
===
'
string
'
)
{
params
=
JSON
.
parse
(
params
);
}
const
[
result
,
unexpectedEntries
]
=
inferTrialParameters
(
params
,
axes
);
const
[
updated
,
unexpectedEntries
]
=
inferTrialParameters
(
params
,
axes
);
if
(
unexpectedEntries
.
size
)
{
throw
unexpectedEntries
;
}
return
result
;
for
(
const
[
k
,
v
]
of
updated
)
{
ret
.
set
(
k
,
v
);
}
return
ret
;
}
}
public
metrics
(
space
:
MultipleAxes
):
Map
<
SingleAxis
,
any
>
{
const
ret
=
new
Map
<
SingleAxis
,
any
>
();
// set default value: null
const
ret
=
new
Map
<
SingleAxis
,
any
>
(
Array
.
from
(
space
.
axes
.
values
()).
map
(
k
=>
[
k
,
null
]));
const
unexpectedEntries
=
new
Map
<
string
,
any
>
();
if
(
this
.
acc
===
undefined
)
{
return
ret
;
...
...
src/webui/src/static/style/table.scss
View file @
88a225f8
...
...
@@ -58,7 +58,7 @@
}
.detail-table
{
padding
:
5px
0
0
0
;
padding
-top
:
5px
;
}
.columns-height
{
...
...
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