Published
Edited
Mar 29, 2022
Importers
4 stars
Insert cell
Insert cell
GridView([{"name":"Ant"}, {"name":"Bat"}, {"name":"Cat"}, {"name":"Dog"}, {"name":"Eel"}, {"name":"Fly"}], "name", {})
Insert cell
selectedNum
Insert cell
viewof selectedNum = GridView(getSampleData(16), "name", {"startSelectIdx":2})
Insert cell
viewof selectedImage = GridView(getSampleData(9), null, {imgLabel: "img", cols: 3, width: 600, height:400, fill: "#ff00ff"})
Insert cell
selectedImage
Insert cell
function GridView(data, label, {
width = window.innerWidth - 20,
height = 0,
cols = 0,
selectableText = false,
fill = "#DDDDDD",
strokeColor = "#000000",
strokeWidth = 2,
selectedFill = "#FFFFFF",
selectedStroke = "#00FF00",
fontSize = 14,
fontColor = "black",
startSelectIdx = -1,
imgLabel = null,
imgShadeAlpha = 0,
cellW = 100,
cellH = 50,
//bgcolor = "#ffffff", TODO: figure out fill on images https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
onClick = () =>{}
} = {})
{

var halfStroke = strokeWidth / 2.0;

if (width == 0) {
width = cellW * cols + strokeWidth;
} else {
if (cols == 0) {
cols = Math.floor((width - strokeWidth) / cellW)
} else {
cellW = Math.floor(width / cols - (strokeWidth));
}
}

var rows = Math.ceil(data.length / cols);

var selectedIdx = -1;

if (height == 0) {
height = rows * cellH + strokeWidth;
} else {
cellH = Math.floor(height / rows - (strokeWidth));
}

let svg = d3.create("svg")
.attr("width", width)
.attr("height", height)

function getX(i) { return i < 0 ? 0 : (i % cols) * (cellW) + halfStroke}
function getY(i) { return i < 0 ? 0 : (Math.floor(i / cols)) * (cellH) + halfStroke}

function setSelectedIdx(idx) {
selectedIdx = idx == selectedIdx ? -1 : idx;
var d = (selectedIdx < 0 || selectedIdx >= data.length) ? null : data[idx];
console.log("d is " + d)
onClick(d, selectedIdx);
svg.property("value", d).dispatch("input");
refresh();
}
const index = d3.local();
let refresh = () => {
if (imgLabel != null) {
svg.selectAll(".myimg")
.data(data)
.attr("x", (d,i) => {
return getX(i);
})
.enter() // new
.append("svg:image")
.attr("class","myimg")
.attr("xlink:href", (d) => d[imgLabel])
.style("background-color", "#ff00ff")
.attr("width", (d,i) => cellW)
.attr("height", (d,i) => cellH)
.attr("x", (d,i) => getX(i))
.attr("y", (d,i) => getY(i))
.exit()
.remove()
}

svg.selectAll(".mycell")
.data(data)
.attr("x", (d,i) => {
return getX(i);
})
.attr("fill", (d, i) => selectedIdx == i ? selectedFill : fill)
.style("stroke", strokeColor)
.enter() // new
.append("rect")
.attr("class","mycell")
.attr("fill", (d, i) => selectedIdx == i ? selectedFill : fill)
.attr("fill-opacity", imgLabel == null ? 1 : imgShadeAlpha)
.attr("width", (d,i) => cellW)
.attr("height", (d,i) => cellH)
.attr("x", (d,i) => getX(i))
.attr("y", (d,i) => getY(i))
.each(function(d, i) {
index.set(this, i); // Store index in local variable.
})
.on("click", function (e, d) {
setSelectedIdx(index.get(this));
})
.style("stroke-width", strokeWidth)
.style("stroke", (d) => strokeColor)
.append("title").html((d)=> JSON.stringify(d).replaceAll(",","&#013;&#010;"))
.exit()
.remove()
svg.selectAll(".mytext")
.data(data, d => d)
.enter()
.append("text")
.attr("class", "mytext")
.attr("x", (d,i) => getX(i) + (cellW / 2))
.attr("y", (d, i) => getY(i) + (cellH / 2))
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("fill",fontColor)
.attr("pointer-events", selectableText ? "default" : "none")
.attr("font-size", fontSize)
.attr("font-family", "monospace")
.text((d)=> {
var txt = d[label] ?? ""
if (txt.length > 16)
txt = txt.substring(0,12) + "…";
return txt;
})
.exit()
.remove()

svg.select(".selectbox")
.attr("x", getX(selectedIdx))
.attr("y", getY(selectedIdx))
.attr("visibility", selectedIdx >= 0 ? "visible" : "hidden")
} // end of refresh

refresh();

if (startSelectIdx != -1) {
setSelectedIdx(startSelectIdx);
}
var selectBox = svg.append("rect")
.attr("class", "selectbox")
.attr("x", getX(selectedIdx))
.attr("y", getY(selectedIdx))
.attr("width", cellW)
.attr("height", cellH)
.attr("visibility", selectedIdx >= 0 ? "visible" : "hidden")
.style("stroke", selectedStroke)
.style("stroke-width", strokeWidth)
.attr("fill", "none");

return Object.assign(svg.node(), {value: selectedIdx == -1 ? null : data[selectedIdx]});
}
Insert cell
function getSampleData(numItems) {
var data = [];
for (var i = 0; i < numItems; i++) {
data.push({name:i.toString(), count:i, img: "https://picsum.photos/200?cachebust=" + i.toString()});
}

return data
}
Insert cell
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