diff --git a/src/app/credExplorer/WeightConfig.js b/src/app/credExplorer/WeightConfig.js
index 35e1e09..8b1ff60 100644
--- a/src/app/credExplorer/WeightConfig.js
+++ b/src/app/credExplorer/WeightConfig.js
@@ -7,6 +7,8 @@ import {type EdgeEvaluator} from "../../core/attribution/pagerank";
import {byEdgeType, byNodeType} from "./edgeWeights";
import {defaultStaticAdapters} from "../adapters/defaultPlugins";
import type {NodeType, EdgeType} from "../adapters/pluginAdapter";
+import {WeightSlider} from "./weights/WeightSlider";
+import {DirectionalitySlider} from "./weights/DirectionalitySlider";
type Props = {|
+onChange: (EdgeEvaluator) => void,
@@ -120,26 +122,23 @@ class EdgeConfig extends React.Component<{
this.props.edgeWeights,
({type}) => type.prefix
);
- return sortedWeights.map(({type, directionality, logWeight}) => (
-
- ));
+ return sortedWeights.map(({type, directionality, logWeight}) => {
+ const onChange = (value) => {
+ const edgeWeights = this.props.edgeWeights.filter(
+ (x) => x.type.prefix !== type.prefix
+ );
+ edgeWeights.push({type, logWeight: value, directionality});
+ this.props.onChange(edgeWeights);
+ };
+ return (
+
+ );
+ });
}
directionControls() {
@@ -147,26 +146,23 @@ class EdgeConfig extends React.Component<{
this.props.edgeWeights,
({type}) => type.prefix
);
- return sortedWeights.map(({type, directionality, logWeight}) => (
-
- ));
+ return sortedWeights.map(({type, directionality, logWeight}) => {
+ const onChange = (value: number) => {
+ const edgeWeights = this.props.edgeWeights.filter(
+ (x) => x.type.prefix !== type.prefix
+ );
+ edgeWeights.push({type, directionality: value, logWeight});
+ this.props.onChange(edgeWeights);
+ };
+ return (
+
+ );
+ });
}
render() {
return (
@@ -190,26 +186,23 @@ class NodeConfig extends React.Component<{
({type}) => type.prefix
);
- const controls = sortedWeights.map(({type, logWeight}) => (
-
- ));
+ const controls = sortedWeights.map(({type, logWeight}) => {
+ const onChange = (value) => {
+ const nodeWeights = this.props.nodeWeights.filter(
+ (x) => x.type.prefix !== type.prefix
+ );
+ nodeWeights.push({type, logWeight: value});
+ this.props.onChange(nodeWeights);
+ };
+ return (
+
+ );
+ });
return (
Node weights (in log space)
@@ -218,11 +211,3 @@ class NodeConfig extends React.Component<{
);
}
}
-
-function formatNumber(n: number) {
- let x = n.toFixed(1);
- if (!x.startsWith("-")) {
- x = "+" + x;
- }
- return x.replace("-", "\u2212");
-}
diff --git a/src/app/credExplorer/weights/DirectionalitySlider.js b/src/app/credExplorer/weights/DirectionalitySlider.js
new file mode 100644
index 0000000..4a45b5e
--- /dev/null
+++ b/src/app/credExplorer/weights/DirectionalitySlider.js
@@ -0,0 +1,39 @@
+// @flow
+
+import React from "react";
+
+function assertValidDirectionality(x: number) {
+ if (x < 0 || x > 1) {
+ throw new Error(
+ `directionality out of bounds: ${x} must be between 0 and 1`
+ );
+ }
+}
+
+export class DirectionalitySlider extends React.Component<{|
+ +directionality: number,
+ +name: string,
+ +onChange: (number) => void,
+|}> {
+ render() {
+ assertValidDirectionality(this.props.directionality);
+ return (
+
+ );
+ }
+}
diff --git a/src/app/credExplorer/weights/DirectionalitySlider.test.js b/src/app/credExplorer/weights/DirectionalitySlider.test.js
new file mode 100644
index 0000000..9b9d092
--- /dev/null
+++ b/src/app/credExplorer/weights/DirectionalitySlider.test.js
@@ -0,0 +1,89 @@
+// @flow
+
+import React from "react";
+import {shallow} from "enzyme";
+
+import {DirectionalitySlider} from "./DirectionalitySlider";
+
+require("../../testUtil").configureEnzyme();
+
+describe("app/credExplorer/weights/DirectionalitySlider", () => {
+ describe("DirectionalitySlider", () => {
+ function example() {
+ const onChange = jest.fn();
+ const element = shallow(
+
+ );
+ return {element, onChange};
+ }
+ it("sets slider to the provided weight", () => {
+ const {element} = example();
+ expect(element.find("input").props().value).toBe(0.5);
+ });
+ it("slider min is 0", () => {
+ const {element} = example();
+ expect(element.find("input").props().min).toBe(0);
+ });
+ it("slider max is 0", () => {
+ const {element} = example();
+ expect(element.find("input").props().max).toBe(1);
+ });
+ it("prints the provided weight", () => {
+ const {element} = example();
+ expect(
+ element
+ .find("span")
+ .at(0)
+ .text()
+ ).toBe("0.50");
+ });
+ it("displays the provided name", () => {
+ const {element} = example();
+ expect(
+ element
+ .find("span")
+ .at(1)
+ .text()
+ ).toBe("foo");
+ });
+ it("changes to the slider trigger the onChange", () => {
+ const {element, onChange} = example();
+ const input = element.find("input");
+ input.simulate("change", {target: {valueAsNumber: 0.99}});
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledWith(0.99);
+ });
+ it("errors if provided an out-of-bound directionality", () => {
+ function withDirectionality(d) {
+ return () =>
+ shallow(
+
+ );
+ }
+ expect(withDirectionality(-0.2)).toThrowError(
+ "directionality out of bounds"
+ );
+ expect(withDirectionality(2)).toThrowError(
+ "directionality out of bounds"
+ );
+ });
+ it("errors rather than providing an out-of-bound directionality", () => {
+ const {element} = example();
+ const input = element.find("input");
+ expect(() => {
+ input.simulate("change", {target: {valueAsNumber: -0.2}});
+ }).toThrowError("directionality out of bounds");
+ expect(() => {
+ input.simulate("change", {target: {valueAsNumber: 2.0}});
+ }).toThrowError("directionality out of bounds");
+ });
+ });
+});
diff --git a/src/app/credExplorer/weights/WeightSlider.js b/src/app/credExplorer/weights/WeightSlider.js
new file mode 100644
index 0000000..2e96a2a
--- /dev/null
+++ b/src/app/credExplorer/weights/WeightSlider.js
@@ -0,0 +1,36 @@
+// @flow
+
+import React from "react";
+
+export class WeightSlider extends React.Component<{|
+ +weight: number,
+ +name: string,
+ +onChange: (number) => void,
+|}> {
+ render() {
+ return (
+
+ );
+ }
+}
+
+export function formatWeight(n: number) {
+ let x = n.toFixed(1);
+ if (!x.startsWith("-")) {
+ x = "+" + x;
+ }
+ return x.replace("-", "\u2212");
+}
diff --git a/src/app/credExplorer/weights/WeightSlider.test.js b/src/app/credExplorer/weights/WeightSlider.test.js
new file mode 100644
index 0000000..44a24fb
--- /dev/null
+++ b/src/app/credExplorer/weights/WeightSlider.test.js
@@ -0,0 +1,61 @@
+// @flow
+
+import React from "react";
+import {shallow} from "enzyme";
+
+import {WeightSlider, formatWeight} from "./WeightSlider";
+
+require("../../testUtil").configureEnzyme();
+
+describe("app/credExplorer/weights/WeightSlider", () => {
+ describe("WeightSlider", () => {
+ function example() {
+ const onChange = jest.fn();
+ const element = shallow(
+
+ );
+ return {element, onChange};
+ }
+ it("sets slider to the provided weight", () => {
+ const {element} = example();
+ expect(element.find("input").props().value).toBe(3);
+ });
+ it("prints the provided weight", () => {
+ const {element} = example();
+ expect(
+ element
+ .find("span")
+ .at(0)
+ .text()
+ ).toBe(formatWeight(3));
+ });
+ it("displays the provided name", () => {
+ const {element} = example();
+ expect(
+ element
+ .find("span")
+ .at(1)
+ .text()
+ ).toBe("foo");
+ });
+ it("changes to the slider trigger the onChange", () => {
+ const {element, onChange} = example();
+ const input = element.find("input");
+ input.simulate("change", {target: {valueAsNumber: 7}});
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledWith(7);
+ });
+ });
+
+ describe("formatWeight", () => {
+ it("rounds to one decimal", () => {
+ expect(formatWeight(0.123)).toBe("+0.1");
+ });
+ it("adds a + to 0", () => {
+ expect(formatWeight(0)).toBe("+0.0");
+ });
+ it("adds a minus symbol to negative numbers", () => {
+ expect(formatWeight(-3)).toBe("\u22123.0");
+ });
+ });
+});