/
TimelineExplorer.js
195 lines (186 loc) · 5.82 KB
/
TimelineExplorer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// @flow
import React from "react";
import deepEqual from "lodash.isequal";
import {type Weights, copy as weightsCopy} from "../analysis/weights";
import {type NodeAddressT} from "../core/graph";
import {TimelineCred} from "../analysis/timeline/timelineCred";
import {type TimelineCredParameters} from "../analysis/timeline/params";
import {TimelineCredView} from "./TimelineCredView";
import Link from "../webutil/Link";
import {WeightConfig} from "./weights/WeightConfig";
import {WeightsFileManager} from "./weights/WeightsFileManager";
import {type PluginDeclaration} from "../analysis/pluginDeclaration";
export type Props = {
projectId: string,
initialTimelineCred: TimelineCred,
};
export type State = {
timelineCred: TimelineCred,
weights: Weights,
alpha: number,
intervalDecay: number,
loading: boolean,
showWeightConfig: boolean,
selectedNodeTypePrefix: NodeAddressT | null,
};
/**
* TimelineExplorer allows displaying, exploring, and re-calculating TimelineCred.
*
* It basically wraps a TimelineCredView with some additional features and options:
* - allows changing the weights and re-calculating cred with new weights
* - allows saving/loading weights
* - displays the string
*/
export class TimelineExplorer extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
const timelineCred = props.initialTimelineCred;
const {alpha, intervalDecay, weights} = timelineCred.params();
this.state = {
timelineCred,
alpha,
intervalDecay,
// Set the weights to a copy, to ensure we don't mutate the weights in the
// initialTimelineCred. This enables e.g. disabling the analyze button
// when the parameters are unchanged.
weights: weightsCopy(weights),
loading: false,
showWeightConfig: false,
selectedNodeTypePrefix: null,
};
}
params(): TimelineCredParameters {
const {alpha, intervalDecay, weights} = this.state;
// Set the weights to a copy, to ensure that the weights we pass into e.g.
// analyzeCred are a distinct reference from the one we keep in our state.
return {alpha, intervalDecay, weights: weightsCopy(weights)};
}
async analyzeCred() {
this.setState({loading: true});
const timelineCred = await this.state.timelineCred.reanalyze(this.params());
this.setState({timelineCred, loading: false});
}
renderConfigurationRow() {
const {showWeightConfig} = this.state;
const weightFileManager = (
<WeightsFileManager
weights={this.state.weights}
onWeightsChange={(weights: Weights) => {
this.setState({weights});
}}
/>
);
const weightConfig = (
<WeightConfig
declarations={this.state.timelineCred.plugins()}
nodeTypeWeights={this.state.weights.nodeTypeWeights}
edgeTypeWeights={this.state.weights.edgeTypeWeights}
onNodeWeightChange={(prefix, weight) => {
this.setState(({weights}) => {
weights.nodeTypeWeights.set(prefix, weight);
return {weights};
});
}}
onEdgeWeightChange={(prefix, weight) => {
this.setState(({weights}) => {
weights.edgeTypeWeights.set(prefix, weight);
return {weights};
});
}}
/>
);
const paramsUpToDate = deepEqual(
this.params(),
this.state.timelineCred.params()
);
const analyzeButton = (
<button
disabled={this.state.loading || paramsUpToDate}
onClick={() => this.analyzeCred()}
>
re-compute cred
</button>
);
return (
<div>
<div style={{marginTop: 30, display: "flex"}}>
<span style={{paddingLeft: 30}}>
cred for {this.props.projectId}
<Link to={`/prototype/${this.props.projectId}/`}>(legacy)</Link>
</span>
<span style={{flexGrow: 1}} />
{this.renderFilterSelect()}
<span style={{flexGrow: 1}} />
<button
onClick={() => {
this.setState(({showWeightConfig}) => ({
showWeightConfig: !showWeightConfig,
}));
}}
>
{showWeightConfig
? "Hide weight configuration"
: "Show weight configuration"}
</button>
{analyzeButton}
</div>
{showWeightConfig && (
<div style={{marginTop: 10}}>
<span>Upload/Download weights:</span>
{weightFileManager}
{weightConfig}
</div>
)}
</div>
);
}
renderFilterSelect() {
const optionGroup = (declaration: PluginDeclaration) => {
const header = (
<option
key={declaration.nodePrefix}
value={declaration.nodePrefix}
style={{fontWeight: "bold"}}
>
{declaration.name}
</option>
);
const entries = declaration.nodeTypes.map((type) => (
<option key={type.prefix} value={type.prefix}>
{"\u2003" + type.name}
</option>
));
return [header, ...entries];
};
return (
<label>
<span style={{marginLeft: "5px"}}>Showing: </span>
<select
value={this.state.selectedNodeTypePrefix}
onChange={(e) =>
this.setState({selectedNodeTypePrefix: e.target.value})
}
>
<option key={null} value={null}>
All users
</option>
{this.state.timelineCred.plugins().map(optionGroup)}
</select>
</label>
);
}
render() {
const timelineCredView = (
<TimelineCredView
timelineCred={this.state.timelineCred}
selectedNodeFilter={this.state.selectedNodeTypePrefix}
/>
);
return (
<div style={{width: 900, margin: "0 auto"}}>
{this.renderConfigurationRow()}
{timelineCredView}
</div>
);
}
}