Skip to content

Commit

Permalink
Add Hover link and node feature (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
goliath-walker authored Mar 30, 2022
1 parent 5bc35e0 commit 521e0f0
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 29 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "joplin-plugin-link-graph-ui",
"version": "1.3.0",
"version": "1.4.0",
"scripts": {
"dist": "webpack --joplin-plugin-config buildMain && webpack --joplin-plugin-config buildExtraScripts && webpack --joplin-plugin-config createArchive",
"dev": "webpack --mode development --joplin-plugin-config buildMain && webpack --joplin-plugin-config buildExtraScripts && webpack --joplin-plugin-config createArchive",
Expand Down
2 changes: 1 addition & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": 1,
"id": "io.treymo.LinkGraph",
"app_min_version": "1.7",
"version": "1.3.0",
"version": "1.4.0",
"name": "Link Graph UI",
"description": "Visualize the connections between Joplin notes.",
"author": "Trey Mo",
Expand Down
112 changes: 101 additions & 11 deletions src/ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,22 @@ function update() {
}

function addMarkerEndDef(defs, distance) {
const style = `var(--distance-${distance}-primary-color, var(--distance-remaining-primary-color))`;
_addMarkerEndDef(defs, distance, style);
}

function _addMarkerEndDef(defs, name, style) {
defs
.append("marker")
.attr("id", `line-marker-end-${distance}`)
.attr("id", `line-marker-end-${name}`)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 20)
.attr("refY", 0)
.attr("markerWidth", 15)
.attr("markerHeight", 15)
.attr("markerUnits", "userSpaceOnUse")
.attr("orient", "auto")
.style("fill", `var(--distance-${distance}-primary-color, var(--distance-remaining-primary-color))`)
.style("fill", style)
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
}
Expand All @@ -54,11 +59,14 @@ function buildGraph(data) {
width = window.innerWidth;
height = window.innerHeight;

if (data.graphIsSelectionBased) {
if (data.graphIsSelectionBased)
document
.querySelector("#note_graph")
.classList.add("mode-selection-based-graph");
}
else
document
.querySelector("#note_graph")
.classList.remove("mode-selection-based-graph");

d3.select("#note_graph > svg").remove();
svg = d3
Expand Down Expand Up @@ -110,6 +118,8 @@ function buildGraph(data) {
}
// marker, if whole graph is shown
addMarkerEndDef(defs, "default");
// on hover marker
_addMarkerEndDef(defs, "hovered-link", "var(--hover-secondary-color");
}

//add zoom capabilities
Expand All @@ -135,7 +145,16 @@ function updateGraph(data) {
.data(data.edges)
.enter()
.append("line")
.classed("adjacent-line", (d) => d.focused);
.classed("adjacent-line", (d) => d.focused)
.attr("id", function (d) {
return domlinkId(d.source, d.target);
})
.on("mouseover", function (_ev, d) {
handleLinkHover(this, d, true);
})
.on("mouseout", function (_ev, d) {
handleLinkHover(this, d, false);
});

// provide distance classes for links
if (data.graphIsSelectionBased) {
Expand All @@ -150,15 +169,74 @@ function updateGraph(data) {
});
}

if (data.showLinkDirection) {
link.attr("marker-end", (d) => {
if (data.graphIsSelectionBased) {
const minDistance = minimalDistanceOfLink(d);
return `url(#line-marker-end-${minDistance})`;
} else return `url(#line-marker-end-default)`;
configureDistanceMarkerEnd(link);

function domNodeId(nodeId, withSharp) {
// dom id needs to start with [a-zA-Z], hence we prefix with "id-"
return `${withSharp ? "#" : ""}id-${nodeId}`;
}

function domlinkId(sourceNodeId, targetNodeId, withSharp) {
return `${withSharp ? "#" : ""}id-${sourceNodeId}-to-id-${targetNodeId}`;
}

function domNodeLabelId(nodeId, withSharp) {
return `${withSharp ? "#" : ""}id-label-${nodeId}`;
}

function handleLinkHover(linkSelector, linkData, isEntered) {
// link hover will also trigger source and target node as well as labels hover

// lines
linkSelector = d3.select(linkSelector);
linkSelector.classed("hovered-link", isEntered);
if (isEntered)
linkSelector.attr("marker-end", "url(#line-marker-end-hovered-link)");
else configureDistanceMarkerEnd(linkSelector);

// nodes
// at this point d.source/targets holds *reference* to node data
d3.select(domNodeId(linkData.source.id, true)).classed(
"hovered-node",
isEntered
);
d3.select(domNodeId(linkData.target.id, true)).classed(
"hovered-node",
isEntered
);

// node labels
d3.select(domNodeLabelId(linkData.source.id, true)).classed(
"hovered-node-label",
isEntered
);
d3.select(domNodeLabelId(linkData.target.id, true)).classed(
"hovered-node-label",
isEntered
);
}

function handleNodeHover(nodeId, isEntered) {
// node hover delegates to handleLinkHover
// for all incoming and outcoming links
d3.selectAll(
`line[id^=id-${nodeId}-to-id-],line[id$=-to-id-${nodeId}]`
).each(function (d, _i) {
handleLinkHover(this, d, isEntered);
});
}

function configureDistanceMarkerEnd(link) {
if (data.showLinkDirection) {
link.attr("marker-end", (d) => {
if (data.graphIsSelectionBased) {
const minDistance = minimalDistanceOfLink(d);
return `url(#line-marker-end-${minDistance})`;
} else return `url(#line-marker-end-default)`;
});
}
}

// Draw nodes.
var node = svg
.append("g")
Expand All @@ -171,13 +249,22 @@ function updateGraph(data) {
const circle = node.append("circle");

circle
.attr("id", function (d) {
return domNodeId(d.id, false);
})
.classed("current-note", (d) => d.id === data.currentNoteID)
.classed("adjacent-note", (d) => d.focused)
.on("click", function (_, i) {
webviewApi.postMessage({
name: "navigateTo",
id: i.id,
});
})
.on("mouseover", function (_evN, dN) {
handleNodeHover(dN.id, true);
})
.on("mouseout", function (_evN, dN) {
handleNodeHover(dN.id, false);
});

// provide distance classes for circles
Expand All @@ -193,6 +280,9 @@ function updateGraph(data) {

nodeLabel
.attr("class", "node-label")
.attr("id", function (d) {
return domNodeLabelId(d.id, false);
})
.attr("font-size", data.nodeFontSize + "px")
.text(function (d) {
return d.title;
Expand Down
97 changes: 81 additions & 16 deletions src/webview.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ button {
}

circle {
fill: var(--distance-remaining-primary-color);
stroke: var(--distance-remaining-secondary-color);
r: 10;
}

Expand All @@ -41,30 +39,33 @@ circle {

.current-note {
fill: var(--distance-0-primary-color);
stroke: var(--distance-0-secondary-color);
r: 18;
}

line {
stroke: var(--distance-remaining-primary-color);
}

line.distance-0 {
.adjacent-line {
stroke: var(--distance-0-primary-color);
stroke-width: 3px;
}

line.distance-1 {
stroke: var(--distance-1-primary-color);
.node-label {
fill: var(--joplin-color);
}

.adjacent-line {
stroke: var(--distance-0-primary-color);
stroke-width: 3px;
.hovered-node {
fill: var(--hover-primary-color) !important;
stroke: unset !important;
}

.node-label {
fill: var(--joplin-color);
.hovered-node-label {
fill: var(--hover-secondary-color) !important;
}

.hovered-link {
stroke: var(--hover-secondary-color) !important;
}

#note_graph {
Expand All @@ -75,19 +76,83 @@ line.distance-1 {
--distance-1-secondary-color: var(--joplin-color);
--distance-remaining-primary-color: var(--joplin-background-color-hover3);
--distance-remaining-secondary-color: var(--joplin-color4);

--hover-primary-color: #93f500;
--hover-secondary-color: var(--joplin-color-correct);
}

/*
* Make nodes far away from selected nodes less apparent:
* Remove stroke for circles > distance-2 in selection-based graphs
* WHOLE GRAPH MODE [START]
*/
#note_graph.mode-selection-based-graph circle:not(.distance-0):not(.distance-1):not(.distance-2) {
stroke: unset;

:not(#note_graph.mode-selection-based-graph) circle:not(.current-note):not(.adjacent-note) {
stroke: var(--distance-remaining-secondary-color);
stroke-width: 2;
fill: var(--distance-remaining-primary-color);
}

/*
* Mark links which are directed inwards/towards selected note
* WHOLE GRAPH MODE [END]
*/

/*
* DISTANCE-BASED GRAPH MODE [START]
*/

#note_graph {
/* distance colors for selection-based graph */
--distance-0-primary-color: var(--joplin-color);
--distance-0-secondary-color: var(--joplin-color4);
--distance-1-primary-color: var(--joplin-color4);
--distance-1-secondary-color: var(--joplin-color);
--distance-remaining-primary-color: var(--joplin-background-color-hover3);
--distance-remaining-secondary-color: var(--joplin-color4);
--distance-remaining-label-color: var(--joplin-color-faded);
}

#note_graph.mode-selection-based-graph line.distance-0 {
stroke: var(--distance-0-primary-color);
stroke-width: 3px;
}

#note_graph.mode-selection-based-graph line.distance-1 {
stroke: var(--distance-1-primary-color);
}

#note_graph.mode-selection-based-graph circle.distance-2 {
stroke: var(--distance-remaining-secondary-color);
stroke-width: 2;
}

#note_graph.mode-selection-based-graph circle:not(.distance-0):not(.distance-1):not(.distance-2) {
/*
* Make nodes far away from selected nodes less apparent:
* Remove stroke for circles > distance-2 in selection-based graphs
*/
stroke: unset;
fill: var(--distance-remaining-primary-color);
}

#note_graph.mode-selection-based-graph .inward-link {
/* Mark links which are directed inwards/towards selected note */
stroke-dasharray: 3;
}

#note_graph.mode-selection-based-graph text.node-label.distance-0 {
font-size: 150%;
font-weight: bold;
}

#note_graph.mode-selection-based-graph text.node-label.distance-1 {
font-size: 125%;
fill: var(--distance-1-primary-color);
}

#note_graph.mode-selection-based-graph text.node-label:not(.distance-0):not(.distance-1):not(.distance-2) {
font-size: 80%;
fill: var(--distance-remaining-label-color);
}

/*
* DISTANCE-BASED GRAPH MODE [END]
*/

0 comments on commit 521e0f0

Please sign in to comment.