Skip to content

Commit

Permalink
Add new compact experiment view (mlflow#546)
Browse files Browse the repository at this point in the history
* WIP splitting into two table views

* WIP refactor

* WIP; metrics & params appear as block elements

* WIP got toggle to work

* More WIP

* Add docs

* Fix lint & other obvious things before more major refactor

* WIP

* UI tweaks

* Don't pass unnecessary props to table components

* Fix react warnings

* Fix toggle (broke it by fixing react warnings)

* Add highlighting of sorted cells + toggle on header cell

* WIP; added truncation to table cells & headers, will try to simplify CSS some

* Fix warnings & linter, simplify some CSS

* Fix metric filtering for compact view

* Small refactoring to eliminate some duplicate code, store inline styles in constants

* Address comments

* Address some offline review comments
* Sort doesn’t need to have the name, or could say “sort by”
* Toggle should just be two icons (e.g. grid and column)
* Let metric & param values flow next to each other

* Try some suggestions, like highlighting names on hover

* Remove changing hover on exit

* WIP addressing some comments

* More changes (updates to highlighting style etc). TODO: get tab stop alignment to work

* Small refactor: share styles across containers for metric/param cell content. Also reuse background color for selected
experiment for hovered metrics/params

* Fix hover titles for toggle between compact/grid view

* Fix width of param/metric key-value pairs

* Address comments:
* Use consistent icon for sorting
* Change background color on highlight to yellow
* Add padding to highlight boxes

* Increase font size of caret + fix vertical alignment

* Simplify CSS / remove dead code

* WIP addressing comments

* Undo unnecessary package-lock changes

* Remove unused styles, refactor styles from ExperimentViewUtil into a static variable so they can
be shared with the multi-column table, fix lint
  • Loading branch information
smurching authored and andrewmchen committed Oct 3, 2018
1 parent 53de566 commit f758154
Show file tree
Hide file tree
Showing 9 changed files with 3,995 additions and 3,510 deletions.
6,724 changes: 3,365 additions & 3,359 deletions mlflow/server/js/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions mlflow/server/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"bytes": "3.0.0",
"classnames": "^2.2.6",
"cookie": "0.3.1",
"dateformat": "3.0.3",
"draft-js": "^0.10.5",
Expand Down
7 changes: 7 additions & 0 deletions mlflow/server/js/src/components/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,10 @@ table th {
.modal-footer .btn+.btn {
margin-left: 16px;
}

.unselectable {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
20 changes: 20 additions & 0 deletions mlflow/server/js/src/components/ExperimentRunsSortToggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";
import PropTypes from "prop-types";

export default class ExperimentRunsSortToggle extends React.Component {
static propTypes = {
children: PropTypes.node,
bsRole: PropTypes.string,
bsClass: PropTypes.string,
};

render() {
// eslint-disable-next-line no-unused-vars
const {bsRole, bsClass, ...otherProps} = this.props;
return (
<span {...otherProps}>
{this.props.children}
</span>
);
}
}
89 changes: 89 additions & 0 deletions mlflow/server/js/src/components/ExperimentRunsTableCompact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Table from 'react-bootstrap/es/Table';
import ExperimentViewUtil from "./ExperimentViewUtil";

const styles = {
sortArrow: {
marginLeft: "2px",
},
sortContainer: {
minHeight: "18px",
},
sortToggle: {
cursor: "pointer",
},
sortKeyName: {
display: "inline-block"
},
};

/**
* Compact table view for displaying runs associated with an experiment. Renders metrics/params in
* a single table cell per run (as opposed to one cell per metric/param).
*/
class ExperimentRunsTableCompact extends Component {
static propTypes = {
rows: PropTypes.arrayOf(PropTypes.object),
onCheckAll: PropTypes.func.isRequired,
isAllChecked: PropTypes.func.isRequired,
onSortBy: PropTypes.func.isRequired,
sortState: PropTypes.object.isRequired,
};

getSortInfo(isMetric, isParam) {
const { sortState, onSortBy } = this.props;
const sortIcon = sortState.ascending ?
<i className="fas fa-caret-up" style={styles.sortArrow}/> :
<i className="fas fa-caret-down" style={styles.sortArrow}/>;
if (sortState.isMetric === isMetric && sortState.isParam === isParam) {
return (
<span
style={styles.sortToggle}
onClick={() => onSortBy(isMetric, isParam, sortState.key)}
>
<span style={styles.sortKeyName} className="run-table-container">
(sort: {sortState.key}
</span>
{sortIcon}
<span>)</span>
</span>);
}
return undefined;
}

render() {
const { rows, onCheckAll, isAllChecked, onSortBy, sortState } = this.props;
const headerCells = [ExperimentViewUtil.getSelectAllCheckbox(onCheckAll, isAllChecked())];
ExperimentViewUtil.getRunMetadataHeaderCells(onSortBy, sortState)
.forEach((headerCell) => headerCells.push(headerCell));
return (
<Table hover>
<colgroup span="7"/>
<colgroup span="1"/>
<colgroup span="1"/>
<tbody>
<tr>
{headerCells}
<th className="top-row left-border" scope="colgroup"
colSpan="1">
<div>Parameters</div>
<div style={styles.sortContainer} className="unselectable">
{this.getSortInfo(false, true)}
</div>
</th>
<th className="top-row left-border" scope="colgroup"
colSpan="1">
<div>Metrics</div>
<div style={styles.sortContainer} className="unselectable">
{this.getSortInfo(true, false)}
</div>
</th>
</tr>
{rows.map(row => <tr key={row.key}>{row.contents}</tr>)}
</tbody>
</Table>);
}
}

export default ExperimentRunsTableCompact;
109 changes: 109 additions & 0 deletions mlflow/server/js/src/components/ExperimentRunsTableMultiColumn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Table from 'react-bootstrap/es/Table';
import ExperimentViewUtil from './ExperimentViewUtil';
import classNames from 'classnames';

/**
* Table view for displaying runs associated with an experiment. Renders each metric and param
* value associated with a run in its own column.
*/
class ExperimentRunsTableMultiColumn extends Component {
static propTypes = {
rows: PropTypes.arrayOf(PropTypes.object),
paramKeyList: PropTypes.arrayOf(PropTypes.string),
metricKeyList: PropTypes.arrayOf(PropTypes.string),
onCheckAll: PropTypes.func.isRequired,
isAllChecked: PropTypes.func.isRequired,
onSortBy: PropTypes.func.isRequired,
sortState: PropTypes.object.isRequired,
};

render() {
const { paramKeyList, metricKeyList, rows, onCheckAll, isAllChecked, onSortBy,
sortState } = this.props;
const columns = [ExperimentViewUtil.getSelectAllCheckbox(onCheckAll, isAllChecked())];
ExperimentViewUtil.getRunMetadataHeaderCells(onSortBy, sortState)
.forEach((cell) => columns.push(cell));
this.getMetricParamHeaderCells(paramKeyList, metricKeyList, onSortBy, sortState)
.forEach((cell) => columns.push(cell));
return (<Table hover>
<colgroup span="7"/>
<colgroup span={paramKeyList.length}/>
<colgroup span={metricKeyList.length}/>
<tbody>
<tr>
<th className="top-row" scope="colgroup" colSpan="5"></th>
<th
className="top-row left-border"
scope="colgroup"
colSpan={paramKeyList.length}
>
Parameters
</th>
<th className="top-row left-border" scope="colgroup"
colSpan={metricKeyList.length}>Metrics
</th>
</tr>
<tr>
{columns}
</tr>
{rows.map(row => <tr key={row.key}>{row.contents}</tr>)}
</tbody>
</Table>);
}

getMetricParamHeaderCells(
paramKeyList,
metricKeyList,
onSortBy,
sortState) {
const numParams = paramKeyList.length;
const numMetrics = metricKeyList.length;
const columns = [];

const getHeaderCell = (isParam, key, i) => {
const isMetric = !isParam;
const sortIcon = ExperimentViewUtil.getSortIcon(sortState, isMetric, isParam, key);
const className = classNames("bottom-row", "sortable", { "left-border": i === 0 });
const elemKey = (isParam ? "param-" : "metric-") + key;
return (
<th
key={elemKey} className={className}
onClick={() => onSortBy(isMetric, isParam, key)}
>
<span
style={styles.metricParamNameContainer}
className="run-table-container"
>
{key}
</span>
<span style={ExperimentViewUtil.styles.sortIconContainer}>{sortIcon}</span>
</th>);
};

paramKeyList.forEach((paramKey, i) => {
columns.push(getHeaderCell(true, paramKey, i));
});
if (numParams === 0) {
columns.push(<th key="meta-param-empty" className="bottom-row left-border">(n/a)</th>);
}

metricKeyList.forEach((metricKey, i) => {
columns.push(getHeaderCell(false, metricKey, i));
});
if (numMetrics === 0) {
columns.push(<th key="meta-metric-empty" className="bottom-row left-border">(n/a)</th>);
}
return columns;
}
}

const styles = {
metricParamNameContainer: {
verticalAlign: "middle",
display: "inline-block",
},
};

export default ExperimentRunsTableMultiColumn;
56 changes: 41 additions & 15 deletions mlflow/server/js/src/components/ExperimentView.css
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,17 @@ span.error-message {

.metric-filler-bg {
position: relative;
background-color: #f7f7f7;
width: 50px;
display: inline-block;
}

.metric-filler-fg {
background-color: #def1ff;
position: absolute;
left: -3px;
top: -1px;
height: 22px;
display: inline-block;
}

.metric-text {
Expand All @@ -161,22 +164,45 @@ span.error-message {
user-select: none;
}

.ExperimentView th.sortable::after {
content: " ";
font-size: 0;
display: inline-block;
margin: 0 0 0 4px;
border-style: solid;
border-color: transparent;
border-width: 0 5px 8.7px 5px;
.ExperimentView .metric-param-sort-toggle {
color: #888;
cursor: pointer;
}

.ExperimentView th.sorted.asc::after {
border-color: transparent transparent #888 transparent;
border-width: 0 5px 8.7px 5px;
.ExperimentView .metric-param-name {
font-weight: bold;
}

.ExperimentView .metric-param-value {
margin-left: 4px;
}

.ExperimentView .highlighted {
border-radius: 5px;
background-color: rgba(255, 242, 172, 0.5);
color: black;
}

/** Container with a max width & text-truncation for use in the experiment runs table */
.ExperimentView .run-table-container {
max-width: 240px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: top;
}


.ExperimentView .metric-param-container-cell {
min-width: 280px;
}

.ExperimentView .metric-param-cell {
display: inline-block;
width: 250px;
padding: 2px;
}

.ExperimentView th.sorted.desc::after {
border-color: #888 transparent transparent transparent;
border-width: 8.7px 5px 0 5px;
.ExperimentView .metric-param-content {
padding: 4px;
}
Loading

0 comments on commit f758154

Please sign in to comment.