Display a node/edge type weight configuration UI (#487)
Summary: This commit adds sliders for each node and edge type (hard-coded for now), and hooks them up to the cred explorer so that re-running PageRank uses the newly induced edge evaluator. Paired with @decentralion. Test Plan: We will add tests later. We promise! In the meantime, the results that appear when you drag a slider and re-run PageRank seem appropriate. For instance, changing the “Git blob” node type from `0.0` to `-10.0` results in the Git blobs not dominating the whole view. wchargin-branch: configurable-weights-ui
This commit is contained in:
parent
1749676459
commit
b5ddead3c2
|
@ -10,6 +10,8 @@ import {Graph} from "../../core/graph";
|
|||
import {pagerank, type PagerankResult} from "../../core/attribution/pagerank";
|
||||
import {PagerankTable} from "./PagerankTable";
|
||||
import type {PluginAdapter} from "../pluginAdapter";
|
||||
import {type EdgeEvaluator} from "../../core/attribution/pagerank";
|
||||
import {WeightConfig} from "./WeightConfig";
|
||||
|
||||
type Props = {};
|
||||
type State = {
|
||||
|
@ -24,6 +26,7 @@ type State = {
|
|||
|},
|
||||
+pagerankResult: ?PagerankResult,
|
||||
|},
|
||||
edgeEvaluator: ?EdgeEvaluator,
|
||||
};
|
||||
|
||||
const REPO_OWNER_KEY = "repoOwner";
|
||||
|
@ -36,6 +39,7 @@ export default class App extends React.Component<Props, State> {
|
|||
repoOwner: "",
|
||||
repoName: "",
|
||||
data: {graphWithMetadata: null, pagerankResult: null},
|
||||
edgeEvaluator: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -47,6 +51,7 @@ export default class App extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {edgeEvaluator} = this.state;
|
||||
const {graphWithMetadata, pagerankResult} = this.state.data;
|
||||
return (
|
||||
<div>
|
||||
|
@ -82,20 +87,14 @@ export default class App extends React.Component<Props, State> {
|
|||
<br />
|
||||
<button onClick={() => this.loadData()}>Load data</button>
|
||||
<button
|
||||
disabled={graphWithMetadata == null}
|
||||
disabled={graphWithMetadata == null || edgeEvaluator == null}
|
||||
onClick={() => {
|
||||
setTimeout(() => {
|
||||
if (graphWithMetadata == null) {
|
||||
throw new Error(
|
||||
"graphWithMetadata: " + String(graphWithMetadata)
|
||||
);
|
||||
if (graphWithMetadata == null || edgeEvaluator == null) {
|
||||
throw new Error("Unexpected null value");
|
||||
}
|
||||
const {graph} = graphWithMetadata;
|
||||
const edgeWeight = (_unused_Edge) => ({
|
||||
toWeight: 1,
|
||||
froWeight: 1,
|
||||
});
|
||||
const pagerankResult = pagerank(graph, edgeWeight, {
|
||||
const pagerankResult = pagerank(graph, edgeEvaluator, {
|
||||
verbose: true,
|
||||
});
|
||||
const data = {graphWithMetadata, pagerankResult};
|
||||
|
@ -120,6 +119,7 @@ export default class App extends React.Component<Props, State> {
|
|||
) : (
|
||||
<p>Graph not loaded.</p>
|
||||
)}
|
||||
<WeightConfig onChange={(ee) => this.setState({edgeEvaluator: ee})} />
|
||||
<PagerankTable
|
||||
graph={graphWithMetadata ? graphWithMetadata.graph : null}
|
||||
adapters={graphWithMetadata ? graphWithMetadata.adapters : null}
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
// @flow
|
||||
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
type EdgeAddressT,
|
||||
type NodeAddressT,
|
||||
EdgeAddress,
|
||||
NodeAddress,
|
||||
} from "../../core/graph";
|
||||
|
||||
import {type EdgeEvaluator} from "../../core/attribution/pagerank";
|
||||
import {byEdgeType, byNodeType} from "./edgeWeights";
|
||||
|
||||
// Hacks...
|
||||
import * as GithubNode from "../../plugins/github/nodes";
|
||||
import * as GithubEdge from "../../plugins/github/edges";
|
||||
import * as GitNode from "../../plugins/git/nodes";
|
||||
import * as GitEdge from "../../plugins/git/edges";
|
||||
|
||||
type Props = {
|
||||
onChange: (EdgeEvaluator) => void,
|
||||
};
|
||||
|
||||
type UserEdgeWeight = {|+logWeight: number, +directionality: number|};
|
||||
type UserNodeWeight = number /* in log space */;
|
||||
|
||||
const defaultEdgeWeights = (): Map<EdgeAddressT, UserEdgeWeight> =>
|
||||
new Map()
|
||||
.set(GithubEdge._Prefix.authors, {logWeight: 0, directionality: 0.5})
|
||||
.set(GithubEdge._Prefix.mergedAs, {logWeight: 0, directionality: 0.5})
|
||||
.set(GithubEdge._Prefix.references, {logWeight: 0, directionality: 0.5})
|
||||
.set(GithubEdge._Prefix.hasParent, {logWeight: 0, directionality: 0.5})
|
||||
.set(GitEdge._Prefix.hasTree, {logWeight: 0, directionality: 0.5})
|
||||
.set(GitEdge._Prefix.hasParent, {logWeight: 0, directionality: 0.5})
|
||||
.set(GitEdge._Prefix.includes, {logWeight: 0, directionality: 0.5})
|
||||
.set(GitEdge._Prefix.becomes, {logWeight: 0, directionality: 0.5})
|
||||
.set(GitEdge._Prefix.hasContents, {logWeight: 0, directionality: 0.5});
|
||||
|
||||
const defaultNodeWeights = (): Map<NodeAddressT, UserNodeWeight> =>
|
||||
new Map()
|
||||
.set(GithubNode._Prefix.repo, 0)
|
||||
.set(GithubNode._Prefix.issue, 0)
|
||||
.set(GithubNode._Prefix.pull, 0)
|
||||
.set(GithubNode._Prefix.review, 0)
|
||||
.set(GithubNode._Prefix.comment, 0)
|
||||
.set(GithubNode._Prefix.userlike, 0)
|
||||
.set(GitNode._Prefix.blob, 0)
|
||||
.set(GitNode._Prefix.commit, 0)
|
||||
.set(GitNode._Prefix.tree, 0)
|
||||
.set(GitNode._Prefix.treeEntry, 0);
|
||||
|
||||
type State = {
|
||||
edgeWeights: Map<EdgeAddressT, UserEdgeWeight>,
|
||||
nodeWeights: Map<NodeAddressT, UserNodeWeight>,
|
||||
};
|
||||
|
||||
export class WeightConfig extends React.Component<Props, State> {
|
||||
constructor(props: Props): void {
|
||||
super(props);
|
||||
this.state = {
|
||||
edgeWeights: defaultEdgeWeights(),
|
||||
nodeWeights: defaultNodeWeights(),
|
||||
};
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div style={{display: "flex", flex: 1}}>
|
||||
<EdgeConfig
|
||||
edgeWeights={this.state.edgeWeights}
|
||||
onChange={(ew) => this.setState({edgeWeights: ew}, () => this.fire())}
|
||||
/>
|
||||
<NodeConfig
|
||||
nodeWeights={this.state.nodeWeights}
|
||||
onChange={(nw) => this.setState({nodeWeights: nw}, () => this.fire())}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fire();
|
||||
}
|
||||
|
||||
fire() {
|
||||
const {edgeWeights, nodeWeights} = this.state;
|
||||
const edgePrefixes = Array.from(edgeWeights.entries()).map(
|
||||
([prefix, {logWeight, directionality}]) => ({
|
||||
prefix,
|
||||
weight: 2 ** logWeight,
|
||||
directionality,
|
||||
})
|
||||
);
|
||||
const nodePrefixes = Array.from(nodeWeights.entries()).map(
|
||||
([prefix, logWeight]) => ({prefix, weight: 2 ** logWeight})
|
||||
);
|
||||
const edgeEvaluator = byNodeType(byEdgeType(edgePrefixes), nodePrefixes);
|
||||
this.props.onChange(edgeEvaluator);
|
||||
}
|
||||
}
|
||||
|
||||
class EdgeConfig extends React.Component<{
|
||||
edgeWeights: Map<EdgeAddressT, UserEdgeWeight>,
|
||||
onChange: (Map<EdgeAddressT, UserEdgeWeight>) => void,
|
||||
}> {
|
||||
render() {
|
||||
const controls = [];
|
||||
for (const [key, currentValue] of this.props.edgeWeights.entries()) {
|
||||
controls.push(
|
||||
<label style={{display: "block"}} key={key}>
|
||||
<input
|
||||
type="range"
|
||||
min={-10}
|
||||
max={10}
|
||||
step={0.1}
|
||||
value={currentValue.logWeight}
|
||||
onChange={(e) => {
|
||||
const value: number = e.target.valueAsNumber;
|
||||
const edgeWeights = new Map(this.props.edgeWeights);
|
||||
const oldValue = edgeWeights.get(key);
|
||||
if (oldValue == null) {
|
||||
throw new Error(key);
|
||||
}
|
||||
edgeWeights.set(key, {...oldValue, logWeight: value});
|
||||
this.props.onChange(edgeWeights);
|
||||
}}
|
||||
/>{" "}
|
||||
{formatNumber(currentValue.logWeight)}{" "}
|
||||
{JSON.stringify(EdgeAddress.toParts(key))}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h2>Edge weights (in log space)</h2>
|
||||
{controls}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NodeConfig extends React.Component<{
|
||||
nodeWeights: Map<NodeAddressT, UserNodeWeight>,
|
||||
onChange: (Map<NodeAddressT, UserNodeWeight>) => void,
|
||||
}> {
|
||||
render() {
|
||||
const controls = [];
|
||||
for (const [key, currentValue] of this.props.nodeWeights.entries()) {
|
||||
controls.push(
|
||||
<label style={{display: "block"}} key={key}>
|
||||
<input
|
||||
type="range"
|
||||
min={-10}
|
||||
max={10}
|
||||
step={0.1}
|
||||
value={currentValue}
|
||||
onChange={(e) => {
|
||||
const value: number = e.target.valueAsNumber;
|
||||
const nodeWeights = new Map(this.props.nodeWeights);
|
||||
nodeWeights.set(key, value);
|
||||
this.props.onChange(nodeWeights);
|
||||
}}
|
||||
/>{" "}
|
||||
{formatNumber(currentValue)}{" "}
|
||||
{JSON.stringify(NodeAddress.toParts(key))}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h2>Node weights (in log space)</h2>
|
||||
{controls}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function formatNumber(n: number) {
|
||||
let x = n.toFixed(1);
|
||||
if (!x.startsWith("-")) {
|
||||
x = "+" + x;
|
||||
}
|
||||
return x.replace("-", "\u2212");
|
||||
}
|
Loading…
Reference in New Issue