block by emeeks 9bc7e3f505b25908a26fd8045656b490

Treemaps with time

Full Screen

An experiment with encoding the change over time of a value in a treemap. Each rectangle represents the imports to the United States from the countries shown in 2015. Within each rectangle, the “corners” from a rectangle of the same ratio are drawn according to the value of imports from an earlier decade (2005, 1995, 1985) and these corners are connected to form a vector of change. This works pretty well for progressive growth. It “overflows” a bit when an earlier value is higher than the current but other than that it suffers a bit for representing change that’s negative.

Found the data for this chart via cmgiven’s cool Treemap Bar Chart

Data Sources: Census

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>
    svg {
        height: 1000px;
        width: 1000px;
    }
</style>
<body>
    <svg
    height="1000"
    width="1000"
    />
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
d3.json('data.json', initialize)

const greys = d3.scaleLinear().domain([0,4]).range(["#1b211d", "#ddda93"])

function initialize(data) {

    const importsByYear = [
        data.children[2].children[0],
        data.children[1].children[0],
        data.children[0].children[0],
        data.children[3].children[0]
    ]

    const compare = importsByYear[0]
    years = ["2015","2005","1995","1985"]

    importsByYear.forEach((year,i) => {
            year.children.forEach((continent, ci) => {
                continent.children.forEach((country, coi) => {
                    const compareIndex = compare.children[ci].children.map(d => d.country).indexOf(country.country)
                    const comparePoint = compare.children[ci].children[compareIndex]
                    if (comparePoint && !comparePoint.priorValues) {
                        comparePoint.priorValues = []
                    }
                    if (comparePoint) {
                        comparePoint.priorValues.push(country.adj_value)
                    }
                })
                continent.children = continent.children.filter(d => d.adj_value > 10000)
            })
    })

    console.log(data)

    const treemap = d3.treemap()
        .size([1000,800])
        .padding(10)

    var root = d3.hierarchy(importsByYear[0])

    root.sum(function (d) { return d.adj_value })

    const treemapRendered = treemap(root)

    d3.select("svg")
        .selectAll("g.treecell")
        .data(treemapRendered.descendants())
        .enter()
        .append("g")
        .attr("class", "treecell")

    d3.selectAll("g.treecell")
        .append("rect")
        .attr("transform", d => `translate(${d.x0},${d.y0})`)
        .attr("width", d => d.x1 - d.x0)
        .attr("height", d => d.y1 - d.y0)
        .style("fill", "#e3cbbf")
        .style("stroke", "#977d5a")
        .style("stroke-width", "1px")

    d3.selectAll("g.treecell")
        .each(function (d) {
            d3.select(this)
                .append("text")
                .text(d.data.country)
                .attr("transform", d => `translate(${d.x0 + ((d.x1 - d.x0) / 2)},${d.y0})`)
                .style("text-anchor", "middle")
        
            if (d.data && d.data.priorValues) {
            const data = squareCorners(d.data.adj_value,d.data.priorValues,d.x0,d.x1,d.y0,d.y1)
            
                d3.select(this)
                    .selectAll("path.cornervector")
                    .data(data)
                    .enter()
                    .append("path")
                    .attr("class", "cornervector")
                    .attr("d", (p,i) => cornerVectors(p,i,data,d.x1,d.y1))
                    .style("fill", (p,i) => greys(i))

                d3.select(this)
                    .selectAll("path.corner")
                    .data(data)
                    .enter()
                    .append("path")
                    .attr("d", p => corner(p))
                    .style("stroke", "#151513")
                    .style("stroke-width", "2px")
                    .style("fill", "none")

            }
        })

}

function squareCorners(initialValue, priorValues, x0, x1, y0, y1) {
    const priorPercent = priorValues.map(d => d / initialValue)
    const weightedMid = (d0, d1, weight) => d1 - (d1 - d0) * weight
    return priorPercent.map(d => {
        const midX = weightedMid(x0,x1,d)
        const midY = weightedMid(y0,y1,d)
        return [Math.max(x0 - 4, midX), Math.max(y0 - 4, midY), x1 - midX, y1 - midY]
        })
}

function cornerVectors(c,ci,data,x1,y1) {
    const lastCorner = ci === 0 ? data[ci] : data[ci - 1]

    return `M${lastCorner[0]},${lastCorner[1] + lastCorner[3] * .1}L${lastCorner[0]},${lastCorner[1]}L${lastCorner[0] + lastCorner[2] * .1},${lastCorner[1]}L${c[0] + c[2] * .1},${c[1]}L${c[0]},${c[1]}L${c[0]},${c[1] + c[3] * .1}Z`
}

function corner(coordinates) {
    return `M${coordinates[0]},${coordinates[1] + coordinates[3] * .12}L${coordinates[0]},${coordinates[1]}L${coordinates[0] + coordinates[2] * .12},${coordinates[1]}`
}

</script>
</body>