Public
Edited
Feb 15, 2023
Insert cell
Insert cell
Insert cell
animalsounds = FileAttachment("animal-sounds@1.csv").csv({typed: true})
Insert cell
inlineChart(animalsounds, {
x: d => d["Animal"]
, y: d => d["Sound"]
, z: d => d["Subject"]
, yDomain: [0, 100]
, colors:["#00a2dd", "#003e5a", "#CC4425"]
})
Insert cell
function inlineChart(data, {
x = ([x]) => x, // given d in data, returns the (temporal) x-value
y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
z = () => 1, // given d in data, returns the (categorical) z-value
marginTop = 50, // top margin, in pixels
marginRight = 50, // right margin, in pixels
marginBottom = 120, // bottom margin, in pixels
marginLeft = 40, // left margin, in pixels
gapTop = 0,
gapRight = 0,
gapBottom = 30,
gapLeft = 30,
width = 450, // outer width, in pixels
height = 500, // outer height, in pixels
barwidth = 40,
xType = d3.scaleBand, // type of x-scale
xDomain, // [xmin, xmax]
xRange = [marginLeft, width - marginRight - gapLeft - gapRight], // [left, right]
yType = d3.scaleLinear, // type of y-scale
yDomain, // [ymin, ymax]
yRange = [height - marginBottom, marginTop], // [bottom, top]
zDomain, // array of z-values
colors = d3.schemeCategory10, // stroke color of line
} = {}){
const X = d3.map(data, x);
const Y = d3.map(data, y);
const Z = d3.map(data, z);

if (xDomain === undefined) xDomain = X;
if (yDomain === undefined) yDomain = [0, d3.max(Y)];
if (zDomain === undefined) zDomain = Z;

zDomain = new d3.InternSet(zDomain);
const xScale = xType(xDomain, xRange);
const yScale = yType(yDomain, yRange);
const color = d3.scaleOrdinal(zDomain, colors);
const xAxis = d3.axisBottom(xScale).ticks(0).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(5).tickSizeOuter(10);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("font-family", "Times New Roman")
;
const plot = svg.append("g")
.attr("class", "plot")
.attr("transform", `translate(${marginLeft + gapLeft}, ${marginTop + gapTop})`)
, gxaxis = plot
.selectAll("g.x-axis")
.data([0])
.join("g").attr("class", "x-axis")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis)
, gyaxis = plot
.selectAll("g.y-axis")
.data([0])
.join("g").attr("class", "y-axis")
.attr("transform", `translate(${marginLeft}, 0)`)
.call(yAxis)
;

let xwidth = xScale.bandwidth()
;
plot.selectAll("g.series")
.data(X)
.join("g").attr("class", "series")
.attr("transform", (d, i) => `translate(${xScale(X[i])}, ${ yScale(Y[i])})`)
.each(function(d, i){
let h = yScale(0) - yScale(Y[i])
, bclr = color(Z[i])
, fclr = pickBestContrast(bclr, ["#ffff", "#000000"])
;
d3.select(this)
.selectAll("rect")
.data([0])
.join("rect")
.attr("fill", color(Z[i]))
.attr("x", (xwidth - barwidth) / 2)
.attr("y", 0)
.attr("height", h)
.attr("width", barwidth);

d3.select(this)
.selectAll("text.band-label")
.data([0])
.join("text").attr("class", "band-label")
.attr("transform", `translate(${xwidth / 2}, ${h / 2}) rotate(-90) `)
.attr("text-anchor", "middle")
.attr("fill", fclr)
.text(d)

if(!/donkey/i.test(d)){
d3.select(this)
.selectAll("text.value-label")
.data([0])
.join("text").attr("class", "value-label")
.attr("transform", `translate(${xwidth / 2}, ${ yScale(100) })`)
.attr("dy", "-3.5em")
.attr("text-anchor", "middle")
.text(Y[i])
}
else{
const ovrflw = yScale(Y[i]) - yScale(100)
, sy = d3.symbol(d3.symbolTriangle, 16)
, offset = 10
, overflow = d3.select(this)
.selectAll("g.overflow")
.data([0])
.join("g").attr("class", "overflow")
.attr("fill", "#fff")
.attr("stroke", bclr)
.attr("transform", `translate(${(xwidth - barwidth) / 2}, ${-ovrflw})`)
;
overflow.selectAll("rect")
.data([0]).join("rect")
.attr("width", barwidth)
.attr("height", ovrflw)
.attr("stroke-dasharray", `0,${barwidth},${ovrflw + barwidth + ovrflw} `)
overflow
.selectAll("g.jagged")
.data([d3.quantize(d3.interpolateNumber(1, 10), 4)])
.join("g").attr("class", "jagged")
.attr("transform", `translate(${(offset) / 2}, ${ovrflw})`)
.attr("fill", bclr)
.selectAll("path")
.data(d => d)
.join("path")
.attr("transform", (d, i) => `translate(${offset * i}, -2)`)
.attr("d", sy());

let vwsymbl = overflow
.selectAll("g.value-w-symbol")
.data(["gte"])
.join("g").attr("class", "value-w-symbol")
.attr("fill", "#000")
.attr("stroke", "#000")
;
vwsymbl.call(g => g.append("use")
.attr("href", d => `#${d}`)
.attr("width", 15).attr("height", 15)
)

vwsymbl.call(g => g.append("text")
.attr("dx", "1em").attr("dy", "0.75em")
.text(Y[i]));

vwsymbl.attr("transform", `translate(${barwidth / 2 - 18}, ${ovrflw - 30})`)
}


});

plot.selectAll("g.x-axis")
.attr("font-size", null)
.attr("font-family", null)
.selectAll("text.axis-label")
.data(["Species"])
.join("text").attr("class", "axis-label")
.attr("text-rendering", "geometricPrecision")
.attr("fill", "#000")
.attr("transform", `translate(${(xRange[1]) / 2}, ${gapBottom})`)
.attr("text-anchor", "start")
.text(d => d);

plot.selectAll("g.y-axis")
.attr("font-size", null)
.attr("font-family", null)
.selectAll("text.axis-label")
.data(["Sound (in decibels)"])
.join("text").attr("class", "axis-label")
.attr("text-rendering", "geometricPrecision")
.attr("fill", "#000")
.attr("transform", `translate(${-gapLeft}, ${(yScale(0)) / 2}) rotate(-90)`)
.attr("dy", "-0.5em")
.attr("text-anchor", "middle")
.text(d => d);
//cleanup
plot.selectAll("g.x-axis")
.selectAll("g.tick").remove();
return svg.node();
}
Insert cell
pickBestContrast = (backColor, forecolors) => {
let fmap = forecolors.map(d => {
let cr = contrast(backColor, d);
return {
d, cr, contrast: cr > 4.5
};
}).filter(d => d.contrast).sort((a, b) => b.cr - a.cr)
;
return fmap && fmap[0] && fmap[0].d;
};
Insert cell
contrast = (backcolor, forecolor) => {
const bcl = lrgb_luminance(rgb_lrgb(d3.rgb(backcolor)))
, fcl = lrgb_luminance(rgb_lrgb(d3.rgb(forecolor)))
, c = (Math.max(bcl, fcl) + 0.05) / (Math.min(bcl, fcl) + 0.05)
;
return c;
};
Insert cell
lrgb_luminance = ([r, g, b]) => {
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}



Insert cell
rgb_lrgb = ({ r, g, b }) => {
return [r / 255, g / 255, b / 255].map(rgb_lrgb1);
}
Insert cell
rgb_lrgb1 = (v) => {
return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
}
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