DefaultMetricPoint.tsx 10.2 KB
Newer Older
1
import * as React from 'react';
2
import { Stack, Dropdown, Toggle, IDropdownOption } from '@fluentui/react';
3
import ReactEcharts from 'echarts-for-react';
Lijiaoa's avatar
Lijiaoa committed
4
5
6
7
import { Trial } from '@model/trial';
import { EXPERIMENT, TRIALS } from '@static/datamodel';
import { TooltipForAccuracy, EventMap } from '@static/interface';
import { reformatRetiariiParameter } from '@static/function';
8
import { gap15 } from '@components/fluent/ChildrenGap';
Lijiaoa's avatar
Lijiaoa committed
9
import { optimizeModeValue } from './optimizeMode';
10
11
12
13
import 'echarts/lib/chart/scatter';
import 'echarts/lib/component/tooltip';
import 'echarts/lib/component/title';

14
15
// this file is for overview page and detail page's Default metric graph

Lijiao's avatar
Lijiao committed
16
17
18
19
20
21
const EmptyGraph = {
    grid: {
        left: '8%'
    },
    xAxis: {
        name: 'Trial',
22
        type: 'category'
Lijiao's avatar
Lijiao committed
23
24
25
    },
    yAxis: {
        name: 'Default metric',
26
        type: 'value'
Lijiao's avatar
Lijiao committed
27
28
    }
};
29

30
interface DefaultPointProps {
31
    trialIds: string[];
32
33
    chartHeight: number;
    hasBestCurve: boolean;
34
    changeExpandRowIDs: Function;
35
36
37
}

interface DefaultPointState {
38
    bestCurveEnabled?: boolean | undefined;
39
    startY: number; // dataZoomY
Lijiao's avatar
Lijiao committed
40
    endY: number;
41
    userSelectOptimizeMode: string;
42
    userSelectAccuracyNumberKey: string;
43
44
45
46
47
}

class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState> {
    constructor(props: DefaultPointProps) {
        super(props);
Lijiao's avatar
Lijiao committed
48
49
50
        this.state = {
            bestCurveEnabled: false,
            startY: 0, // dataZoomY
51
            endY: 100,
52
53
            userSelectOptimizeMode: optimizeModeValue(EXPERIMENT.optimizeMode),
            userSelectAccuracyNumberKey: 'default'
Lijiao's avatar
Lijiao committed
54
        };
55
56
    }

57
    loadDefault = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
58
        this.setState({ bestCurveEnabled: checked });
59
    };
60

61
62
63
    metricDataZoom = (e: EventMap): void => {
        if (e.batch !== undefined) {
            this.setState(() => ({
64
65
                startY: e.batch[0].start !== null ? e.batch[0].start : 0,
                endY: e.batch[0].end !== null ? e.batch[0].end : 100
66
67
            }));
        }
68
    };
69

70
    pointClick = (params: any): void => {
71
72
73
        // [hasBestCurve: true]: is detail page, otherwise, is overview page
        const { hasBestCurve } = this.props;
        if (!hasBestCurve) {
74
75
76
77
78
            this.props.changeExpandRowIDs(params.data[2], 'chart');
        }
    };

    generateGraphConfig(_maxSequenceId: number): any {
79
        const { startY, endY, userSelectAccuracyNumberKey } = this.state;
80
        const { hasBestCurve } = this.props;
81
82
        return {
            grid: {
83
                left: '8%'
84
85
86
            },
            tooltip: {
                trigger: 'item',
87
                enterable: hasBestCurve,
88
                confine: true, // confirm always show tooltip box rather than hidden by background
89
90
91
92
                formatter: (data: TooltipForAccuracy): React.ReactNode => `
                    <div class="tooldetailAccuracy">
                        <div>Trial No.: ${data.data[0]}</div>
                        <div>Trial ID: ${data.data[2]}</div>
93
                        <div>${userSelectAccuracyNumberKey}: ${data.data[1]}</div>
94
95
96
97
98
                        <div>Parameters: <pre>${JSON.stringify(
                            reformatRetiariiParameter(data.data[3]),
                            null,
                            4
                        )}</pre></div>
99
100
                    </div>
                `
101
102
103
104
105
106
107
108
109
110
111
112
113
            },
            dataZoom: [
                {
                    id: 'dataZoomY',
                    type: 'inside',
                    yAxisIndex: [0],
                    filterMode: 'empty',
                    start: startY,
                    end: endY
                }
            ],
            xAxis: {
                name: 'Trial',
114
                type: 'category'
115
116
117
118
            },
            yAxis: {
                name: 'Default metric',
                type: 'value',
119
                scale: true
120
            },
121
            series: undefined
122
123
124
        };
    }

125
126
127
128
129
130
131
    private formatAccuracy(accuracy: number | undefined): number {
        if (accuracy === undefined || isNaN(accuracy) || !isFinite(accuracy)) {
            return 0;
        }
        return accuracy;
    }

132
    generateScatterSeries(trials: Trial[]): any {
133
134
135
136
137
138
139
140
        const { userSelectAccuracyNumberKey } = this.state;
        let data;
        if (trials[0].accuracyNumberTypeDictKeys.length > 1) {
            // dict type final results
            data = trials.map(trial => [
                trial.sequenceId,
                trial.acc === undefined ? 0 : this.formatAccuracy(trial.acc[userSelectAccuracyNumberKey]),
                trial.id,
141
                trial.parameter
142
143
144
145
146
147
            ]);
        } else {
            data = trials.map(trial => [
                trial.sequenceId,
                this.formatAccuracy(trial.accuracy),
                trial.id,
148
                trial.parameter
149
150
            ]);
        }
151

Lijiao's avatar
Lijiao committed
152
153
154
        return {
            symbolSize: 6,
            type: 'scatter',
155
            data
Lijiao's avatar
Lijiao committed
156
157
        };
    }
158
159

    generateBestCurveSeries(trials: Trial[]): any {
160
        const { userSelectOptimizeMode, userSelectAccuracyNumberKey } = this.state;
Lijiao's avatar
Lijiao committed
161
        let best = trials[0];
162
163
164
165
166
        const data = [
            [
                best.sequenceId,
                best.acc === undefined ? 0 : this.formatAccuracy(best.acc[userSelectAccuracyNumberKey]),
                best.id,
167
                best.parameter
168
169
            ]
        ];
Lijiao's avatar
Lijiao committed
170
171
        for (let i = 1; i < trials.length; i++) {
            const trial = trials[i];
172
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
173
            const delta = trial.acc![userSelectAccuracyNumberKey] - best.acc![userSelectAccuracyNumberKey];
174
            const better = userSelectOptimizeMode === 'minimize' ? delta < 0 : delta > 0;
175
            if (better) {
176
177
178
179
                data.push([
                    trial.sequenceId,
                    trial.acc === undefined ? 0 : this.formatAccuracy(trial.acc[userSelectAccuracyNumberKey]),
                    best.id,
180
                    trial.parameter
181
                ]);
182
183
                best = trial;
            } else {
184
185
186
187
                data.push([
                    trial.sequenceId,
                    best.acc === undefined ? 0 : this.formatAccuracy(best.acc[userSelectAccuracyNumberKey]),
                    best.id,
188
                    trial.parameter
189
                ]);
Lijiao's avatar
Lijiao committed
190
191
            }
        }
192

Lijiao's avatar
Lijiao committed
193
194
195
        return {
            type: 'line',
            lineStyle: { color: '#FF6600' },
196
            data
Lijiao's avatar
Lijiao committed
197
198
199
        };
    }

200
201
202
203
204
205
    updateUserOptimizeMode = (event: React.FormEvent<HTMLDivElement>, item?: IDropdownOption): void => {
        if (item !== undefined) {
            this.setState({ userSelectOptimizeMode: item.key.toString() });
        }
    };

206
207
208
209
210
211
212
213
214
215
216
217
218
219
    // final result keys dropdown click event
    updateTrialfinalResultKeys = (event: React.FormEvent<HTMLDivElement>, item?: IDropdownOption): void => {
        if (item !== undefined) {
            this.setState(
                {
                    userSelectAccuracyNumberKey: item.key.toString()
                },
                () => {
                    this.generateGraph();
                }
            );
        }
    };

Lijiao's avatar
Lijiao committed
220
    render(): React.ReactNode {
221
        const { hasBestCurve, chartHeight } = this.props;
222
223
        const { userSelectOptimizeMode, userSelectAccuracyNumberKey } = this.state;
        const trials = TRIALS.getTrials(this.props.trialIds).filter(trial => trial.sortable);
224
        const graph = this.generateGraph();
225
        const accNodata = graph === EmptyGraph ? 'No data' : '';
226
        const onEvents = { dataZoom: this.metricDataZoom, click: this.pointClick };
227
228
229
230
        let dictDropdown: string[] = [];
        if (trials.length > 0) {
            dictDropdown = trials[0].accuracyNumberTypeDictKeys;
        }
231
232
        return (
            <div>
233
                {hasBestCurve && (
234
                    <Stack horizontal reversed tokens={gap15} className='default-metric'>
235
                        <Toggle label='Optimization curve' inlineLabel onChange={this.loadDefault} />
236
237
238
239
240
241
242
243
244
245
                        <Dropdown
                            selectedKey={userSelectOptimizeMode}
                            onChange={this.updateUserOptimizeMode}
                            options={[
                                { key: 'maximize', text: 'Maximize' },
                                { key: 'minimize', text: 'Minimize' }
                            ]}
                            styles={{ dropdown: { width: 100 } }}
                            className='para-filter-percent'
                        />
246
247
248
249
250
251
252
253
254
                        {dictDropdown.length > 1 && (
                            <Dropdown
                                selectedKey={userSelectAccuracyNumberKey}
                                onChange={this.updateTrialfinalResultKeys}
                                options={dictDropdown.map(item => ({ key: item, text: item }))}
                                styles={{ dropdown: { width: 100 } }}
                                className='para-filter-percent'
                            />
                        )}
255
256
                    </Stack>
                )}
257
                <div className='default-metric-graph graph'>
258
259
260
261
                    <ReactEcharts
                        option={graph}
                        style={{
                            width: '100%',
262
                            height: chartHeight,
263
                            margin: '0 auto'
264
                        }}
265
                        theme='nni_theme'
266
267
268
                        notMerge={true} // update now
                        onEvents={onEvents}
                    />
Lijiaoa's avatar
Lijiaoa committed
269
                    <div className='default-metric-noData fontColor333'>{accNodata}</div>
270
                </div>
271
272
273
            </div>
        );
    }
274

Lijiao's avatar
Lijiao committed
275
    private generateGraph(): any {
276
277
278
279
        const trials = TRIALS.getTrials(this.props.trialIds).filter(trial => trial.sortable);
        if (trials.length === 0) {
            return EmptyGraph;
        }
Lijiao's avatar
Lijiao committed
280
        const graph = this.generateGraphConfig(trials[trials.length - 1].sequenceId);
281
        if (this.state.bestCurveEnabled) {
Lijiao's avatar
Lijiao committed
282
            (graph as any).series = [this.generateBestCurveSeries(trials), this.generateScatterSeries(trials)];
283
        } else {
Lijiao's avatar
Lijiao committed
284
            (graph as any).series = [this.generateScatterSeries(trials)];
285
286
287
288
289
290
        }
        return graph;
    }
}

export default DefaultPoint;