Published
Edited
Oct 30, 2019
1 fork
Insert cell
Insert cell
Insert cell
mutable inspectData =''
Insert cell
mutable inspectSelector = ''
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const root = treemap(prepped_tree);
let focus = root;

const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("font", "11px sans-serif");

// const shadow = DOM.uid("shadow");

// svg.append("filter")
// .attr("id", shadow.id)
// .append("feDropShadow")
// .attr("flood-opacity", 0.8)
// .attr("dx", 0)
// .attr("stdDeviation", 1.2);

const nestedTreeData = d3
.nest()
.key(d => d.height)
.entries(root.descendants());
// mutable inspectMe = nestedTreeData;

// These are only rectangles
// We create group and start the selection/data join up here
const node = svg
.selectAll("g")
.data(nestedTreeData) // first we start on the highest level up
.join("g")
.attr("class", "filterGroup")
// .attr("filter", shadow)
.selectAll("g")
.attr("class", d => "groupHeight_" + d.height) // this class targets groups by dept/class/item
.data(d => d.values) // ??
.join("g")
.attr("class", d => "treeHeight_" + d.height)
.attr("transform", d => `translate(${d.x0},${d.y0})`);

// then we append to the variable labels
const labels = svg
.append("g")
.attr('class', 'nodelabel')
.selectAll("g")
.data(nestedTreeData) // first we start on the highest level up
.join("g")
.attr("class", "labelgroup")
.selectAll("g")
.data(d => d.values) //
.join("g")
.attr("class", d => "treeHeight_" + d.height)
.attr("transform", d => `translate(${d.x0},${d.y0})`);

const labelHandler = d => {
// Hides the label for individual objects
// and only shows labels for classifications and departments...
mutable inspectData = d; // console.log(d);
if (d.height >= 1) {
return d.data.key;
} else {
return '';
}
};

// assigning it to a mutable makes it possible for us to inspect
mutable inspectSelector = labels
.append("text")
.join("text")
.attr("class", d => "treeHeightLabel_" + d.height)
.attr("y", 11)
.text(d => {
return labelHandler(d);
});

node.append("title").text(
d =>
`${d
.ancestors()
.reverse()
.map(d => d.data.key)
.join("/")}\n${format(d.data.title)}`
);

const rect = node
.append("rect") // for now, filters leaves out of display because too cluttered
.attr("id", d => (d.nodeUid = DOM.uid("node")).id)
.attr("fill", d => color(d.height))
.attr("width", d => d.x1 - d.x0)
.attr("height", d => d.y1 - d.y0)
// ---- Interactivity starts here
.on("mouseover", function(d) {
// unsure why we have to use "function(d)" for it to work
console.log(this);
console.log(d3.select(this));
d3.select(this).attr("fill", "blue");
mouseoverHelper(d);
})
.on("click", clicked)
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(250) // try these
.attr("fill", d => color(d.height));
d3.select("#tooltip").remove();
});

node
.append("clipPath")
.attr("id", d => (d.clipUid = DOM.uid("clip")).id)
.append("use");
// .attr("xlink:href", d => d.nodeUid.href); this is broken, our data is missing .clipUid

const text = node
.append("text")
.attr("clip-path", d => d.clipUid) // temp
.selectAll("tspan")
.data(d => d)
.join("tspan")
.attr("fill-opacity", (d, i, nodes) =>
i === nodes.length - 1 ? 0.7 : null
)
.text(d => helperTitleNoOverplot(d)); /// TO EMMA: this might be wrong

// node.filter(d => d.children).selectAll("tspan")
// .attr("dx", 3)
// .attr("y", 13);

const tspan = node
.filter(d => !d.children)
.selectAll("tspan")
.attr("x", 3)
.attr(
"y",
(d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`
);

function clicked(p) {
focus = focus === p ? (p = p.parent) : p;

root.each(
d =>
(d.target = {
x0: ((d.x0 - p.x0) / (p.x1 - p.x0)) * height, // calculate the new view
x1: ((d.x1 - p.x0) / (p.x1 - p.x0)) * height,
y0: d.y0 - p.y0,
y1: d.y1 - p.y0
})
);

const t = node
.transition()
.duration(750)
.attr("transform", d => `translate(${d.target.y0},${d.target.x0})`);

rect.transition(t).attr("height", d => rectHeight(d.target));
text.transition(t).attr("fill-opacity", d => +labelVisible(d.target));
tspan.transition(t).attr("fill-opacity", d => labelVisible(d.target) * 0.7);
}

function rectHeight(d) {
return d.x1 - d.x0 - Math.min(1, (d.x1 - d.x0) / 6);
}

function labelVisible(d) {
return d.y1 <= width && d.y0 >= 0 && d.x1 - d.x0 > 4;
}

return svg.node();
}
Insert cell
//
// Logic helper function; ensures that
// we don't render titles for classifications with only a few

helperTitleNoOverplot = d => {
const classificationThreshold = 4;
console.log(d.parent.children.length);
// if d's parent doesn't have enough classification categories,
if (d.parent.children.length < classificationThreshold) {
// don't draw the titles...
return '';
} else {
// else, just draw the title normally
return d.data.title;
}
}
Insert cell
mouseoverHelper = d => {
// console.log(d);
mutable inspectCurrent = d;
// This conditional prevents the viewer from rendering an ugly "undefined"
// let's edit this so that you can mouseover any level
if (d.height > 0) { // the mouseover is not on a leaf node
mutable mouseoverTitle = "";
mutable mouseoverClassification = "";
mutable mouseoverArtist = "";
mutable mouseoverCulture = "";
mutable mouseoverDate = "";
} else {
mutable mouseoverDepartment = d.data.department;
mutable mouseoverTitle = d.data.title;
mutable mouseoverClassification = d.data.classification;
mutable mouseoverArtist = d.data.artistname;
mutable mouseoverCulture = d.data.culture;
mutable mouseoverDate = d.data.begindate;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
byDepartmentClassification = d3.nest()
.key(d => d.department)
.key(d => d.classification)
.entries(rowdata)
Insert cell
// Pass thingy into hierarchy
hier = d3.hierarchy(prepped_tree, funcChildren)
Insert cell
treemap = data => d3.treemap()
.tile(d3[tile])
.size([width, height])
.padding(vPadding)
.paddingTop(vPaddingTop)
.round(true)
(d3.hierarchy(data, funcChildren)
// running .sum in this way will count the nodes, similar to .count
// ideally we want to filter...
// .sum(d => (d.children ? d.parent : 1)).value
// changed sum -> count, counting leaves to generate values
// for now this didn't do much, just changing the output graph a little, rectangles fill up properly
.count(d => 1)
)

Insert cell
Insert cell
// When we run treemap, this function wraps up stuff
// and mutates the input, computing values that
// will be passed into root for the chart all the way above...
// The code below is called in chart but you can remove it from this here.
// This line is only included so we can inspect the output above...
treemap(prepped_tree);
Insert cell
// There is probably an easier way to do this in JS
// but this way uses a constructor
obj = function(name, children) {
this.name = name;
this.values = children; }
Insert cell
prepped_tree = new obj("metStuff", byDepartmentClassification);
Insert cell
// This function is used to choose where the children are located
funcChildren = d => d.values
Insert cell
funcChildren(prepped_tree)
Insert cell
2000
Insert cell
height = 900
Insert cell
format = d3.format(",d")
Insert cell
color = d3.scaleSequential([8, 0], d3.interpolateViridis)
Insert cell
d3 = require("d3@5")
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more