Skip to content

Commit

Permalink
Merge pull request #315 from amelioro/306-benefit-and-detriment-node-…
Browse files Browse the repository at this point in the history
…types

306 benefit and detriment node types
  • Loading branch information
keyserj authored Jan 22, 2024
2 parents 95d16c4 + 18b1709 commit 142fcea
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 27 deletions.
4 changes: 4 additions & 0 deletions src/common/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const nodeTypes = [
"problem",
"criterion",
"effect",
"benefit",
"detriment",
"solutionComponent",
"solution",

Expand Down Expand Up @@ -57,6 +59,8 @@ export const diagramNodeTypes: Record<DiagramType, NodeType[]> = {
"problem",
"criterion",
"effect",
"benefit",
"detriment",
"solutionComponent",
"solution",
"custom", // is a generic node but currently only seems worthwhile in topic
Expand Down
12 changes: 12 additions & 0 deletions src/db/migrations/20240122114058_add_benefits_detriments/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
BEGIN;

-- AlterEnum
BEGIN;
CREATE TYPE "NodeType_new" AS ENUM ('problem', 'solution', 'solutionComponent', 'criterion', 'effect', 'rootClaim', 'support', 'critique', 'question', 'answer', 'fact', 'source', 'custom');
ALTER TABLE "nodes" ALTER COLUMN "type" TYPE "NodeType_new" USING ("type"::text::"NodeType_new");
ALTER TYPE "NodeType" RENAME TO "NodeType_old";
ALTER TYPE "NodeType_new" RENAME TO "NodeType";
DROP TYPE "NodeType_old";
COMMIT;

COMMIT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
BEGIN;

ALTER TYPE "NodeType" ADD VALUE 'benefit';
ALTER TYPE "NodeType" ADD VALUE 'detriment';

COMMIT;
2 changes: 2 additions & 0 deletions src/db/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ enum NodeType {
problem
criterion
effect
benefit
detriment
solutionComponent
solution
Expand Down
3 changes: 3 additions & 0 deletions src/web/common/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// used material color tool to confirm accessibility (legibility) https://material.io/resources/color/#!/?view.left=1&view.right=1&primary.color=4ab74c&secondary.color=b74ab5
// used mui-theme-creator to see the colors in action and pick light & dark palettes https://bareynol.github.io/mui-theme-creator/?firstName=&lastName=&email=&password=
// color names from color-name.com
// used color calculator for complementary, analagous colors https://www.sessions.edu/color-calculator/

import {
type Theme as MaterialUITheme,
Expand Down Expand Up @@ -153,6 +154,8 @@ const sharedPalette = {
solutionComponent: augmentColor({ color: { main: primaryVariantLight } }),
criterion: augmentColor({ color: { main: "#4AB885" } }), // mint: analogous to solution; between solution & support because criteria are kind of like supports for solutions
effect: augmentColor({ color: { main: yellow[500] } }), // random yellow that looks decent: somewhat similar to green/solution but also goes well with lightning bolt icon
benefit: augmentColor({ color: { main: "#A0DC46" } }), // calculated analogous color to effect, solution, and support
detriment: augmentColor({ color: { main: "#DC477B", contrastText: "rgba(0, 0, 0, 0.87)" } }), // calculated analogous color to effect, problem, and critique

// explore - palette https://coolors.co/9e9e9e-f57c00-0288d1-04f06a-1c110a
question: augmentColor({ color: { main: grey[500] } }), // grey, ambiguous, uncertain
Expand Down
2 changes: 2 additions & 0 deletions src/web/topic/components/Diagram/Diagram.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const nodeTypes: Record<FlowNodeType, ComponentType<NodeProps>> = {
solutionComponent: buildNodeComponent("solutionComponent"),
criterion: buildNodeComponent("criterion"),
effect: buildNodeComponent("effect"),
benefit: buildNodeComponent("benefit"),
detriment: buildNodeComponent("detriment"),

// explore
question: buildNodeComponent("question"),
Expand Down
24 changes: 19 additions & 5 deletions src/web/topic/utils/edge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,29 @@ export const relations: Relation[] = exploreRelations.concat([
// topic relations
{ child: "problem", name: "causes", parent: "problem" },
{ child: "criterion", name: "criterionFor", parent: "problem" },
{ child: "effect", name: "addresses", parent: "problem" },
{ child: "effect", name: "createdBy", parent: "problem" },
{ child: "benefit", name: "createdBy", parent: "problem" },
{ child: "detriment", name: "createdBy", parent: "problem" },
{ child: "solutionComponent", name: "addresses", parent: "problem" },
{ child: "solution", name: "addresses", parent: "problem" },

{ child: "criterion", name: "relatesTo", parent: "effect" },
{ child: "criterion", name: "relatesTo", parent: "benefit" },
{ child: "criterion", name: "relatesTo", parent: "detriment" },

{ child: "effect", name: "embodies", parent: "criterion" },
{ child: "benefit", name: "embodies", parent: "criterion" },
{ child: "detriment", name: "relatesTo", parent: "criterion" },
{ child: "solutionComponent", name: "embodies", parent: "criterion" },
{ child: "solution", name: "embodies", parent: "criterion" },

{ child: "problem", name: "createdBy", parent: "effect" },
{ child: "solutionComponent", name: "creates", parent: "effect" },
{ child: "solution", name: "creates", parent: "effect" },
{ child: "solutionComponent", name: "creates", parent: "benefit" },
{ child: "solution", name: "creates", parent: "benefit" },
{ child: "solutionComponent", name: "creates", parent: "detriment" },
{ child: "solution", name: "creates", parent: "detriment" },

{ child: "problem", name: "createdBy", parent: "solutionComponent" },
{ child: "solution", name: "has", parent: "solutionComponent" },
Expand Down Expand Up @@ -141,21 +153,23 @@ type AddableNodes = {
const addableNodesFor: Record<NodeType, AddableNodes> = {
problem: {
parent: ["problem", "solution"],
child: ["problem", "criterion", "solution"],
child: ["problem", "effect", "benefit", "detriment", "criterion", "solution"],
},

// can't have multiple problems;
// could have multiple solutions but unintuitive to add from criterion because solution would be tied to parent & all sibling criteria
criterion: { parent: [], child: [] },

effect: { parent: [], child: ["problem"] },
effect: { parent: [], child: [] },
benefit: { parent: [], child: [] },
detriment: { parent: [], child: [] },

solutionComponent: {
parent: ["problem", "effect"],
parent: ["problem", "effect", "benefit", "detriment"],
child: ["problem"],
},
solution: {
parent: ["problem", "effect", "solutionComponent"], // could have criteria, but need to select a specific problem for it & that requires design
parent: ["problem", "effect", "benefit", "detriment", "solutionComponent"], // could have criteria, but need to select a specific problem for it & that requires design
child: ["problem", "solution"],
},

Expand Down
66 changes: 44 additions & 22 deletions src/web/topic/utils/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,44 @@ import ELK, { ElkNode, LayoutOptions } from "elkjs";
import { NodeType, nodeTypes } from "../../../common/node";
import { nodeHeightPx, nodeWidthPx } from "../components/Node/EditableNode.styles";
import { type Edge, type Node } from "./graph";
import { children, parents } from "./node";

export type Orientation = "DOWN" | "UP" | "RIGHT" | "LEFT";

const priorities = Object.fromEntries(nodeTypes.map((type, index) => [type, index.toString()])) as {
[type in NodeType]: string;
};

const elk = new ELK();

// sort by source priority, then target priority
const compareEdges = (edge1: Edge, edge2: Edge, nodes: Node[]) => {
const source1 = nodes.find((node) => node.id === edge1.source);
const source2 = nodes.find((node) => node.id === edge2.source);
const target1 = nodes.find((node) => node.id === edge1.target);
const target2 = nodes.find((node) => node.id === edge2.target);

if (!source1 || !source2 || !target1 || !target2)
throw new Error("Edge source or target not found");

const sourceCompare = Number(priorities[source1.type]) - Number(priorities[source2.type]);
if (sourceCompare !== 0) return sourceCompare;

return Number(priorities[target1.type]) - Number(priorities[target2.type]);
};

/**
* "null" means no partition; the node will be placed in any layer that makes sense based on edges.
* "[number]" means the node will be placed in a layer higher than nodes with lower [number], and lower than nodes with higher [number].
* "calculated" is a string that will error if it remains; it should be replaced before layout.
*/
const partitionOrders: { [type in NodeType]: string } = {
// topic
problem: "null",
criterion: "1",
effect: "2",
effect: "calculated",
benefit: "calculated",
detriment: "calculated",
solutionComponent: "3",
solution: "4",

Expand All @@ -29,26 +59,18 @@ const partitionOrders: { [type in NodeType]: string } = {
custom: "null",
};

const priorities = Object.fromEntries(nodeTypes.map((type, index) => [type, index.toString()])) as {
[type in NodeType]: string;
};

const elk = new ELK();

// sort by source priority, then target priority
const compareEdges = (edge1: Edge, edge2: Edge, nodes: Node[]) => {
const source1 = nodes.find((node) => node.id === edge1.source);
const source2 = nodes.find((node) => node.id === edge2.source);
const target1 = nodes.find((node) => node.id === edge1.target);
const target2 = nodes.find((node) => node.id === edge2.target);

if (!source1 || !source2 || !target1 || !target2)
throw new Error("Edge source or target not found");

const sourceCompare = Number(priorities[source1.type]) - Number(priorities[source2.type]);
if (sourceCompare !== 0) return sourceCompare;

return Number(priorities[target1.type]) - Number(priorities[target2.type]);
const calculatePartition = (node: Node, nodes: Node[], edges: Edge[]) => {
const topicGraph = { nodes, edges };

if (["effect", "benefit", "detriment"].includes(node.type)) {
const hasProblemSource = parents(node, topicGraph).some((parent) => parent.type === "problem");
const hasSolutionTarget = children(node, topicGraph).some((child) => child.type === "solution");
if (hasProblemSource && hasSolutionTarget) return "null";
else if (hasProblemSource) return "0";
else return "2";
} else {
return partitionOrders[node.type];
}
};

export interface NodePosition {
Expand Down Expand Up @@ -96,7 +118,7 @@ export const layout = async (
// solutions, components, effects; we might be able to improve that situation by modeling
// each problem within a nested node. Or maybe we could just do partitioning within
// a special "problem context view" rather than in the main topic diagram view.
"elk.partitioning.partition": partitionOrders[node.type],
"elk.partitioning.partition": calculatePartition(node, nodes, edges),
},
};
}),
Expand Down
10 changes: 10 additions & 0 deletions src/web/topic/utils/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
Check,
Code,
Extension,
Flood,
Info,
LocalFlorist,
PriorityHigh,
QuestionMark,
ThumbDown,
Expand Down Expand Up @@ -48,6 +50,14 @@ export const nodeDecorations: Record<FlowNodeType, NodeDecoration> = {
title: "Effect",
NodeIcon: Bolt,
},
benefit: {
title: "Benefit",
NodeIcon: LocalFlorist,
},
detriment: {
title: "Detriment",
NodeIcon: Flood,
},
solutionComponent: {
title: "Component",
NodeIcon: Widgets,
Expand Down

0 comments on commit 142fcea

Please sign in to comment.