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 {pagerank, type PagerankResult} from "../../core/attribution/pagerank";
|
||||||
import {PagerankTable} from "./PagerankTable";
|
import {PagerankTable} from "./PagerankTable";
|
||||||
import type {PluginAdapter} from "../pluginAdapter";
|
import type {PluginAdapter} from "../pluginAdapter";
|
||||||
|
import {type EdgeEvaluator} from "../../core/attribution/pagerank";
|
||||||
|
import {WeightConfig} from "./WeightConfig";
|
||||||
|
|
||||||
type Props = {};
|
type Props = {};
|
||||||
type State = {
|
type State = {
|
||||||
|
@ -24,6 +26,7 @@ type State = {
|
||||||
|},
|
|},
|
||||||
+pagerankResult: ?PagerankResult,
|
+pagerankResult: ?PagerankResult,
|
||||||
|},
|
|},
|
||||||
|
edgeEvaluator: ?EdgeEvaluator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const REPO_OWNER_KEY = "repoOwner";
|
const REPO_OWNER_KEY = "repoOwner";
|
||||||
|
@ -36,6 +39,7 @@ export default class App extends React.Component<Props, State> {
|
||||||
repoOwner: "",
|
repoOwner: "",
|
||||||
repoName: "",
|
repoName: "",
|
||||||
data: {graphWithMetadata: null, pagerankResult: null},
|
data: {graphWithMetadata: null, pagerankResult: null},
|
||||||
|
edgeEvaluator: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +51,7 @@ export default class App extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {edgeEvaluator} = this.state;
|
||||||
const {graphWithMetadata, pagerankResult} = this.state.data;
|
const {graphWithMetadata, pagerankResult} = this.state.data;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -82,20 +87,14 @@ export default class App extends React.Component<Props, State> {
|
||||||
<br />
|
<br />
|
||||||
<button onClick={() => this.loadData()}>Load data</button>
|
<button onClick={() => this.loadData()}>Load data</button>
|
||||||
<button
|
<button
|
||||||
disabled={graphWithMetadata == null}
|
disabled={graphWithMetadata == null || edgeEvaluator == null}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (graphWithMetadata == null) {
|
if (graphWithMetadata == null || edgeEvaluator == null) {
|
||||||
throw new Error(
|
throw new Error("Unexpected null value");
|
||||||
"graphWithMetadata: " + String(graphWithMetadata)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const {graph} = graphWithMetadata;
|
const {graph} = graphWithMetadata;
|
||||||
const edgeWeight = (_unused_Edge) => ({
|
const pagerankResult = pagerank(graph, edgeEvaluator, {
|
||||||
toWeight: 1,
|
|
||||||
froWeight: 1,
|
|
||||||
});
|
|
||||||
const pagerankResult = pagerank(graph, edgeWeight, {
|
|
||||||
verbose: true,
|
verbose: true,
|
||||||
});
|
});
|
||||||
const data = {graphWithMetadata, pagerankResult};
|
const data = {graphWithMetadata, pagerankResult};
|
||||||
|
@ -120,6 +119,7 @@ export default class App extends React.Component<Props, State> {
|
||||||
) : (
|
) : (
|
||||||
<p>Graph not loaded.</p>
|
<p>Graph not loaded.</p>
|
||||||
)}
|
)}
|
||||||
|
<WeightConfig onChange={(ee) => this.setState({edgeEvaluator: ee})} />
|
||||||
<PagerankTable
|
<PagerankTable
|
||||||
graph={graphWithMetadata ? graphWithMetadata.graph : null}
|
graph={graphWithMetadata ? graphWithMetadata.graph : null}
|
||||||
adapters={graphWithMetadata ? graphWithMetadata.adapters : 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