Public
Edited
Sep 7, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
<div class="flex-parent-element">
<div class="flex-child-element magenta">
${createChart(finalJson)}
</div>
<div class="flex-child-element green">
<div id="app"></div>
</div>
</div>
Insert cell
function wrapperApp() {
React.useEffect(() => {
renderApp();
}, [randomImageByTopicsMap]);

return React.createElement("div", { id: "app" });
}
Insert cell
Insert cell
renderApp();
Insert cell
function renderApp() {
const randomId = Array.from(randomImageByTopicsMap.keys())[Math.floor(Math.random() * randomImageByTopicsMap.size)];
ReactDOM.render(
React.createElement(Gallery, { id: randomId }),
document.getElementById("app")
);
}
Insert cell
dispatch.on("circle-clicked", (id) => {
ReactDOM.render(
React.createElement(Gallery, { id }),
document.getElementById("app")
);
});
Insert cell
Insert cell
Insert cell
<style>
.flex-parent-element {
display: flex;
width: 100%;
align-items: center; /* add this */
}

.flex-child-element {
flex: 1;
/* border: 2px solid black; */
margin: 10px;
}

.flex-child-element:first-child {
margin-right: 10px;
flex-basis: 10%;
}
.flex-child-element.green {
height: 100%;
/* justify-content: center; */
}
</style>
Insert cell
<style>
.image-container {
position: relative;
overflow: hidden;
width: 100%;
height: 0;
padding-bottom: 60%; /* 4:3 aspect ratio */
}

.image-container img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}

.image-container .caption {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
padding: 0px;
box-sizing: border-box;
max-height: 50px;
overflow-y: scroll;
}
</style>
Insert cell
Insert cell
function createChart(finalJson) {
const root = d3.hierarchy(finalJson);

root.x0 = dy / 2;
root.y0 = 0;
root.descendants().forEach((d, i) => {
d.id = i;
d._children = d.children;
if (d.depth && d.data.name.length !== 7) d.children = null;
});

const svg = d3.create("svg")
.attr("viewBox", [-margin.left, -margin.top, width, dx])
.style("font", "25px sans-serif")
.style("user-select", "none");

const linkStrokeWidthBase = 3;
const gLink = svg.append("g")
.attr("fill", "none")
.attr("stroke-opacity", 0.7)
.attr("stroke-width", linkStrokeWidthBase);


const gNode = svg.append("g")
.attr("cursor", "pointer")
.attr("pointer-events", "all");
// function selectImage(node) {
// // console.log(node.data);
// let imgTopic = getImgTid(node.data.name);
// if (imgTopic !== null) {
// let idxofImage = getIndexOfObjectWithTopic(imgTopic);
// dispatch.call("circle-clicked", null, idxofImage);
// }
// }

function selectImage(node) {
// console.log(node.data);
let imgTopic = getImgTid(node.data.name);
if (imgTopic !== null) {
// let idxofImage = getIndexOfObjectWithTopic(imgTopic);
dispatch.call("circle-clicked", null, imgTopic);
}
}

const updateCircles = (id) => {
// console.log(id);
gNode.selectAll("circle").attr("fill", d => {
if (d.id === id) {
return "red";
} else {
return d._children ? "#555" : "#999";
}
});
};

function update(source) {
const duration = d3.event && d3.event.altKey ? 2500 : 250;
const nodes = root.descendants().reverse();
const links = root.links();

// Compute the new tree layout.
tree(root);

let left = root;
let right = root;
root.eachBefore(node => {
if (node.x < left.x) left = node;
if (node.x > right.x) right = node;
});

const height = right.x - left.x + margin.top + margin.bottom;

const transition = svg.transition()
.duration(duration)
.attr("viewBox", [-margin.left, left.x - margin.top, width, height])
.tween("resize", window.ResizeObserver ? null : () => () => svg.dispatch("toggle"));


// Update the nodes…
const node = gNode.selectAll("g")
.data(nodes, d => d.id);

// Enter any new nodes at the parent's previous position.
const nodeEnter = node.enter().append("g")
.attr("transform", d => `translate(${source.y0},${source.x0})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0)
.on("click", (event, d) => {
// console.log(d.data.labels);
d.children = d.children ? null : d._children;
update(d);
selectImage(d);
updateCircles(d.id);
})
// Add mouseover and mouseout events
.on("mouseover", function (event, d) {
// Get all ancestors of the node
const ancestors = d.ancestors();
// Select all links on the path to the node and make them thicker
gLink.selectAll("path")
.filter(link => ancestors.includes(link.source) && ancestors.includes(link.target))
.attr("stroke-width", 6)
.attr("stroke", "red")
.attr("stroke-opacity", 1);
})
.on("mouseout", function (event, d) {
// Reset the stroke width of all links
gLink.selectAll("path")
.attr("stroke-width", linkStrokeWidthBase)
// .attr("stroke-width", d => d.target.data.similarity ?
// simWidthScale(d.target.data.similarity) : avgWidthScale(avgSimilarity[d.target.data.id]))
.attr("stroke", d => d.target.data.similarity ?
simColorScale(d.target.data.similarity) : avgColorScale(avgSimilarity[d.target.data.id]))
.attr("stroke-opacity", 0.7);
});

nodeEnter.append("circle")
.attr("r", 6)
.attr("fill", d => d._children ? "#555" : "#999")
.attr("stroke-width", 15);

nodeEnter.append("text")
.attr("dy", "-0.31em")
// .attr("dy", d => d._children ? "-0.31em" : "0.31em")
// .attr("x", d => d._children ? -25 : 6)
.attr("x", -40)
// .attr("x", 3)
.attr("text-anchor", "start")
// .attr("text-anchor", d => d._children ? "end" : "start")
// .text(d => d.data.name)
.text(d => d.data.labels ? d.data.labels.slice(0, 2).join(' ') : "")
.clone(true).lower()
.attr("stroke-linejoin", "round")
.attr("stroke-width", 5)
.attr("stroke", "white");
// Add title element to show labels information
nodeEnter.append("title")
.text(d => {
let labelStr = d.data.labels ? d.data.labels.join(", ") : "";
let simStr = d.data.similarity ? "\nSimilarity: " + d.data.similarity : "";
let imgTopicStr = d.data.img_tid ? ", Image Topic#: " + d.data.img_tid : "";
return labelStr + simStr + imgTopicStr;
});


// Transition nodes to their new position.
const nodeUpdate = node.merge(nodeEnter).transition(transition)
.attr("transform", d => `translate(${d.y},${d.x})`)
.attr("fill-opacity", 1)
.attr("stroke-opacity", 1);

// Transition exiting nodes to the parent's new position.
const nodeExit = node.exit().transition(transition).remove()
.attr("transform", d => `translate(${source.y},${source.x})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0);

const diagonal = d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x);
// Update the links…
const link = gLink.selectAll("path")
.data(links, d => d.target.id);

// Enter any new links at the parent's previous position.
const linkEnter = link.enter().append("path")
.attr("d", d => {
// console.log(avgSimilarity[d.target.data.id]);
// avgColorScale(0.15)
const o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
// simColorScale(d.target.data.similarity)
}).attr("stroke", d => d.target.data.similarity ?
simColorScale(d.target.data.similarity) : avgColorScale(avgSimilarity[d.target.data.id]))
.attr("stroke-width", linkStrokeWidthBase)
// .attr("stroke-width", d => d.target.data.similarity ?
// simWidthScale(d.target.data.similarity) : avgWidthScale(avgSimilarity[d.target.data.id]))
.attr("fill", "none")
.attr("stroke-linejoin", "round");

// Transition links to their new position.
link.merge(linkEnter).transition(transition)
.attr("d", diagonal)
.attr("stroke", d => d.target.data.similarity ?
simColorScale(d.target.data.similarity) : avgColorScale(avgSimilarity[d.target.data.id]))
.attr("stroke-width", linkStrokeWidthBase);
// .attr("stroke-width", d => d.target.data.similarity ? simWidthScale(d.target.data.similarity) : avgWidthScale(avgSimilarity[d.target.data.id]));


// Transition exiting nodes to the parent's new position.
link.exit().transition(transition).remove()
.attr("d", d => {
const o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
});

// Stash the old positions for transition.
root.eachBefore(d => {
d.x0 = d.x;
d.y0 = d.y;
});
}

update(root);

dispatchImg.on("img-clicked", (imageTopic) => {
console.log(imageTopic);

// const matchingNodes = findNodesWithImgTid(root, imageTopic);
// console.log(matchingNodes);
// updateCircles(id);
});
return svg.node();
}
Insert cell
margin = ({top: 25, right: 50, bottom: 10, left: 7.5})
Insert cell
dy = width / 2.4
Insert cell
dx = 50
Insert cell
dispatch = d3.dispatch("circle-clicked");

Insert cell
dispatchImg = d3.dispatch("img-clicked");
Insert cell
diagonal = d3.linkHorizontal().x(d => d.y).y(d => d.x)
Insert cell
tree = d3.tree().nodeSize([dx, dy])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// margin = ({top: 10, right: 120, bottom: 10, left: 40})
Insert cell
d3 = require("d3@6")
Insert cell
import {render, component, jsx, memo, forwardRef, React, ReactDOM, createElement, Children, createRef, createContext, lazy, Fragment, StrictMode, Suspense, cloneElement, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useDebugValue, createPortal, __SECRET_SWITCHER} from "@j-f1/react"
Insert cell
import {Range, pack} from "@esperanc/range-input-variations"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more