in ts/webui/src/components/trial-detail/Para.tsx [174:274]
private renderParallelCoordinates(): void {
const { searchSpace } = this.props;
const percent = parseFloat(this.state.selectedPercent);
const { primaryMetricKey, chosenDimensions } = this.state;
const inferredSearchSpace = TRIALS.inferredSearchSpace(searchSpace);
const inferredMetricSpace = TRIALS.inferredMetricSpace();
let convertedTrials = this.getTrialsAsObjectList(inferredSearchSpace, inferredMetricSpace);
const dimensions: [string, any][] = [];
let colorDim: string | undefined = undefined,
colorScale: any = undefined;
// treat every axis as numeric to fit for brush
for (const [k, v] of inferredSearchSpace.axes) {
dimensions.push([
k,
{
type: 'number',
yscale: this.convertToD3Scale(v)
}
]);
}
for (const [k, v] of inferredMetricSpace.axes) {
const scale = this.convertToD3Scale(v);
if (k === primaryMetricKey && scale !== undefined && scale.interpolate) {
// set color for primary metrics
// `colorScale` is used to produce a color range, while `scale` is to produce a pixel range
colorScale = this.convertToD3Scale(v, false);
convertedTrials.sort((a, b) => (EXPERIMENT.optimizeMode === 'minimize' ? a[k] - b[k] : b[k] - a[k]));
// filter top trials
if (percent != 1) {
const keptTrialNum = Math.max(Math.ceil(convertedTrials.length * percent), 1);
convertedTrials = convertedTrials.slice(0, keptTrialNum);
const domain = d3.extent(convertedTrials, item => item[k]);
scale.domain([domain[0], domain[1]]);
colorScale.domain([domain[0], domain[1]]);
if (colorScale !== undefined) {
colorScale.domain(domain);
}
}
// reverse the converted trials to show the top ones upfront
convertedTrials.reverse();
const assignColors = (scale: any): void => {
scale.range([0, 1]); // fake a range to perform invert
const [scaleMin, scaleMax] = scale.domain();
const pivot = scale.invert(0.5);
scale
.domain([scaleMin, pivot, scaleMax])
.range(['#90EE90', '#FFC400', '#CA0000'])
.interpolate(d3.interpolateHsl);
};
assignColors(colorScale);
colorDim = k;
}
dimensions.push([
k,
{
type: 'number',
yscale: scale
}
]);
}
if (convertedTrials.length === 0 || dimensions.length <= 1) {
return;
}
const firstRun = this.pcs === undefined;
if (firstRun) {
this.pcs = ParCoords()(this.paraRef.current);
}
this.pcs
.data(convertedTrials)
.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)
.alphaOnBrushed(0.2)
.smoothness(0.1)
.brushMode('1D-axes')
.reorderable()
.interactive();
}
if (colorScale !== undefined) {
this.pcs.color(d => (colorScale as any)(d[colorDim as any]));
}
this.pcs.render();
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
});
}