Public
Edited
Oct 7, 2023
Insert cell
Insert cell
Insert cell
{
const margin = { top: 30, right: 30, bottom: 200, left: 100 };
//const width // set automatically
const height = 800;

const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;

// Build X scales and axis:
const x = d3
.scaleBand()
.range([0, innerWidth])
.domain(dataset.varitySorted)
.padding(0.15);

// Build X scales and axis:
const y = d3
.scaleBand()
.range([innerHeight, 0])
.domain(dataset.siteSorted)
.padding(0.15);

// Build color scale
const colorScaleRelY_ = d3
.scaleLinear()
.range(["white", "#006600"])
.domain([-2, dataset.yieldRelYextent[1]]);

const colorScaleRelY = d3
.scaleSequential(d3.interpolateRdYlGn)
.domain([0, dataset.yieldRelYextent[1]]);

const triangle = (d) => {
let xBase = x(d.variety);
let yBase = y(d.site);
let width = x.bandwidth();
let height = y.bandwidth();
if (d.year === "1931") {
return `M ${xBase} ${yBase}
L ${xBase} ${yBase + height}
L ${xBase + width} ${yBase}
Z`;
} else {
return `M ${xBase} ${yBase + height}
L ${xBase + width} ${yBase}
L ${xBase + width} ${yBase + height}
Z`;
}
};

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

svg
.append("g")
.attr("id", "legend")
.call(
colorLegend(
colorScaleRelY,
dataset.averages.get("1931"),
"Bushels Per Acre 1931"
)
)
.call(transform(margin.left, margin.top + innerHeight + 120));
svg
.append("g")
.attr("id", "legend")
.call(
colorLegend(
colorScaleRelY,
dataset.averages.get("1932"),
"Bushels Per Acre 1932"
)
)
.call(transform(margin.left, margin.top + innerHeight + 50));

svg
.append("g")
.attr(
"transform",
"translate(" + margin.left + "," + (height - margin.bottom) + ")"
)
.call(d3.axisBottom(x));

svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(d3.axisLeft(y));

let rsvg = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

rsvg
.selectAll("path")
.data(dataset.values)
.join("path")
.attr("d", (d) => triangle(d))
.style("fill", (d) => colorScaleRelY(d.yieldRelY));

rsvg
.selectAll("rect")
.data(dataset.valuesAggregated)
.join("rect")
.attr("x", (d) => x(d.variety))
.attr("y", (d) => y(d.site))
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.attr("fill", "none")
.attr("stroke", (d) => colorScaleRelY(d.yieldRelY))
.attr("stroke-width", 12);

let overlayLegendG = svg.append("g").attr("id", "overlay-legend-markers");
overlayLegendG.call(transform(margin.left, margin.top));
overlayLegendG
.append("text")
.attr("x", x(dataset.varitySorted[dataset.varitySorted.length - 1]))
.attr("y", y(dataset.siteSorted[dataset.siteSorted.length - 1]) + 2)
.attr("font-family", "sans-serif")
.attr("font-size", 9)
.attr("fill", "#fff")
.text("Average");
overlayLegendG
.append("text")
.attr(
"x",
x(dataset.varitySorted[dataset.varitySorted.length - 1]) +
x.bandwidth() / 2
)
.attr(
"y",
y(dataset.siteSorted[dataset.siteSorted.length - 1]) + y.bandwidth() - 8
)
.attr("font-family", "sans-serif")
.attr("font-size", 9)
.attr("fill", "#fff")
.text("1931")
.attr("transform", (d) => {
var xRotate = x(dataset.varitySorted[dataset.varitySorted.length - 1]);
var yRotate =
y(dataset.siteSorted[dataset.siteSorted.length - 1]) + y.bandwidth();
return "rotate(-45," + xRotate + "," + yRotate + ")";
});
overlayLegendG
.append("text")
.attr(
"x",
x(dataset.varitySorted[dataset.varitySorted.length - 1]) +
x.bandwidth() / 2
)
.attr(
"y",
y(dataset.siteSorted[dataset.siteSorted.length - 1]) + y.bandwidth() + 8
)
.attr("font-family", "sans-serif")
.attr("font-size", 9)
.attr("fill", "#fff")
.text("1932")
.attr("transform", (d) => {
var xRotate = x(dataset.varitySorted[dataset.varitySorted.length - 1]);
var yRotate =
y(dataset.siteSorted[dataset.siteSorted.length - 1]) + y.bandwidth();
return "rotate(-45," + xRotate + "," + yRotate + ")";
});

return svg.node();
}
Insert cell
colorLegend = (colorScale, average, text) => {
let domain = colorScale.domain().map((d) => d * average);
let x = d3.scaleLinear().domain(domain).range([0, 600]).nice();
let noSlots = 100;
let markWidth = (domain[1] - domain[0]) / noSlots;
let marks = d3.range(domain[0], domain[1] - markWidth, markWidth);
console.log("Marks", marks);
let xAxis = d3.axisBottom(x);
return (g) => {
let gm = g.append("g");
gm.selectAll("rect")
.data(marks)
.join("rect")
.attr("x", (d) => x(d))
.attr("y", 0)
.attr("width", 20)
.attr("height", 20)
.attr("fill", (d) => colorScale(d / average));
g.append("text")
.attr("x", 100)
.attr("y", 50)
.attr("font-family", "sans-serif")
.attr("font-size", 12)
.attr("fill", "black")
.text(text);
let ag = g.append("g");
ag.call(xAxis).call(transform(0, 20));
//g.attr("transform", "translate(0,0)").call(xAxis);
};
}
Insert cell
transform = (x, y) => {
return (g) => {
g.attr("transform", "translate(" + x + "," + y + ")");
};
}
Insert cell
dataset = {
let x = {};
x.values = [...barley];
x.years = _.uniq(barley.map((d) => d.year));
x.average = d3.mean(barley, (d) => d.yield);

// Calculate the averages for each year
x.averages = d3.rollup(
barley,
(v) => d3.mean(v, (d) => d.yield),
(d) => d.year
);
x.averagesSite = d3.rollup(
barley,
(v) => d3.mean(v, (d) => d.yield),
(d) => d.site
);
x.averagesVariety = d3.rollup(
barley,
(v) => d3.mean(v, (d) => d.yield),
(d) => d.variety
);
x.siteSorted = _.uniq(barley.map((d) => d.site));
x.siteSorted.sort((a, b) => x.averagesSite.get(a) - x.averagesSite.get(b));
x.varitySorted = _.uniq(barley.map((d) => d.variety)).sort(
(a, b) => x.averagesVariety.get(a) - x.averagesVariety.get(b)
);

// calculate averags across all years for each combination of site and variety.
x.valuesAggregated = Array.from(
d3
.rollup(
barley,
(v) => ({
variety: v[0].variety,
site: v[0].site,
yieldRelY: d3.mean(v, (d) => d.yield) / x.average
}),
(d) => d.variety + "," + d.site
)
.values()
);

// add the yieldRelY property that is relative to the year average
x.values.forEach((d) => {
d.yieldRelY = d.yield / x.averages.get(d.year);
});
x.yieldRelYextent = d3.extent(x.values, (d) => d.yieldRelY);
return x;
}
Insert cell
/* viewof year = {
let years = _.uniq(barley.map((d) => d.year));
return Inputs.radio(years, { label: "Choose year", value: years[0] });
}
*/
Insert cell
import { Legend, Swatches } from "@d3/color-legend"
Insert cell
Insert cell
Insert cell
barley.json
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
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