DefaultMetricPoint.tsx 6.2 KB
Newer Older
1
import * as React from 'react';
2
import { Toggle, Stack } from '@fluentui/react';
3
import ReactEcharts from 'echarts-for-react';
4
5
import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { Trial } from '../../static/model/trial';
Lijiao's avatar
Lijiao committed
6
import { TooltipForAccuracy, EventMap } from '../../static/interface';
7
8
9
10
import 'echarts/lib/chart/scatter';
import 'echarts/lib/component/tooltip';
import 'echarts/lib/component/title';

Lijiao's avatar
Lijiao committed
11
12
13
14
15
16
const EmptyGraph = {
    grid: {
        left: '8%'
    },
    xAxis: {
        name: 'Trial',
17
        type: 'category'
Lijiao's avatar
Lijiao committed
18
19
20
    },
    yAxis: {
        name: 'Default metric',
21
        type: 'value'
Lijiao's avatar
Lijiao committed
22
23
    }
};
24

25
interface DefaultPointProps {
26
27
    trialIds: string[];
    visible: boolean;
28
29
30
}

interface DefaultPointState {
31
    bestCurveEnabled?: boolean | undefined;
32
    startY: number; // dataZoomY
Lijiao's avatar
Lijiao committed
33
    endY: number;
34
35
36
37
38
}

class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState> {
    constructor(props: DefaultPointProps) {
        super(props);
Lijiao's avatar
Lijiao committed
39
40
41
        this.state = {
            bestCurveEnabled: false,
            startY: 0, // dataZoomY
42
            endY: 100
Lijiao's avatar
Lijiao committed
43
        };
44
45
    }

46
    loadDefault = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
47
        this.setState({ bestCurveEnabled: checked });
48
    };
49

Lijiao's avatar
Lijiao committed
50
    shouldComponentUpdate(nextProps: DefaultPointProps): boolean {
51
        return nextProps.visible;
52
    }
53

54
55
56
    metricDataZoom = (e: EventMap): void => {
        if (e.batch !== undefined) {
            this.setState(() => ({
57
58
                startY: e.batch[0].start !== null ? e.batch[0].start : 0,
                endY: e.batch[0].end !== null ? e.batch[0].end : 100
59
60
            }));
        }
61
    };
62
63
64
65
66

    generateGraphConfig(maxSequenceId: number): any {
        const { startY, endY } = this.state;
        return {
            grid: {
67
                left: '8%'
68
69
70
71
            },
            tooltip: {
                trigger: 'item',
                enterable: true,
72
73
74
75
                position: (point: number[], data: TooltipForAccuracy): number[] => [
                    data.data[0] < maxSequenceId ? point[0] : point[0] - 300,
                    80
                ],
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
                formatter: (data: TooltipForAccuracy): React.ReactNode => {
                    return (
                        '<div class="tooldetailAccuracy">' +
                        '<div>Trial No.: ' +
                        data.data[0] +
                        '</div>' +
                        '<div>Trial ID: ' +
                        data.data[2] +
                        '</div>' +
                        '<div>Default metric: ' +
                        data.data[1] +
                        '</div>' +
                        '<div>Parameters: <pre>' +
                        JSON.stringify(data.data[3], null, 4) +
                        '</pre></div>' +
                        '</div>'
                    );
                }
94
95
96
97
98
99
100
101
102
103
104
105
106
            },
            dataZoom: [
                {
                    id: 'dataZoomY',
                    type: 'inside',
                    yAxisIndex: [0],
                    filterMode: 'empty',
                    start: startY,
                    end: endY
                }
            ],
            xAxis: {
                name: 'Trial',
107
                type: 'category'
108
109
110
111
            },
            yAxis: {
                name: 'Default metric',
                type: 'value',
112
                scale: true
113
            },
114
            series: undefined
115
116
117
118
        };
    }

    generateScatterSeries(trials: Trial[]): any {
119
        const data = trials.map(trial => [trial.sequenceId, trial.accuracy, trial.id, trial.description.parameters]);
Lijiao's avatar
Lijiao committed
120
121
122
        return {
            symbolSize: 6,
            type: 'scatter',
123
            data
Lijiao's avatar
Lijiao committed
124
125
        };
    }
126
127

    generateBestCurveSeries(trials: Trial[]): any {
Lijiao's avatar
Lijiao committed
128
        let best = trials[0];
129
        const data = [[best.sequenceId, best.accuracy, best.id, best.description.parameters]];
130

Lijiao's avatar
Lijiao committed
131
132
        for (let i = 1; i < trials.length; i++) {
            const trial = trials[i];
133
134
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const delta = trial.accuracy! - best.accuracy!;
135
            const better = EXPERIMENT.optimizeMode === 'minimize' ? delta < 0 : delta > 0;
136
            if (better) {
137
                data.push([trial.sequenceId, trial.accuracy, best.id, trial.description.parameters]);
138
139
                best = trial;
            } else {
140
                data.push([trial.sequenceId, best.accuracy, best.id, trial.description.parameters]);
Lijiao's avatar
Lijiao committed
141
142
            }
        }
143

Lijiao's avatar
Lijiao committed
144
145
146
        return {
            type: 'line',
            lineStyle: { color: '#FF6600' },
147
            data
Lijiao's avatar
Lijiao committed
148
149
150
151
        };
    }

    render(): React.ReactNode {
152
        const graph = this.generateGraph();
153
154
        const accNodata = graph === EmptyGraph ? 'No data' : '';
        const onEvents = { dataZoom: this.metricDataZoom };
155

156
157
        return (
            <div>
158
159
                <Stack horizontalAlign='end' className='default-metric'>
                    <Toggle label='Optimization curve' inlineLabel onChange={this.loadDefault} />
160
                </Stack>
161
                <div className='default-metric-graph'>
162
163
164
165
166
                    <ReactEcharts
                        option={graph}
                        style={{
                            width: '100%',
                            height: 402,
167
                            margin: '0 auto'
168
                        }}
169
                        theme='my_theme'
170
171
172
                        notMerge={true} // update now
                        onEvents={onEvents}
                    />
173
                    <div className='default-metric-noData'>{accNodata}</div>
174
                </div>
175
176
177
            </div>
        );
    }
178

Lijiao's avatar
Lijiao committed
179
    private generateGraph(): any {
180
181
182
183
        const trials = TRIALS.getTrials(this.props.trialIds).filter(trial => trial.sortable);
        if (trials.length === 0) {
            return EmptyGraph;
        }
Lijiao's avatar
Lijiao committed
184
        const graph = this.generateGraphConfig(trials[trials.length - 1].sequenceId);
185
        if (this.state.bestCurveEnabled) {
Lijiao's avatar
Lijiao committed
186
            (graph as any).series = [this.generateBestCurveSeries(trials), this.generateScatterSeries(trials)];
187
        } else {
Lijiao's avatar
Lijiao committed
188
            (graph as any).series = [this.generateScatterSeries(trials)];
189
190
191
192
193
194
        }
        return graph;
    }
}

export default DefaultPoint;