Public
Edited
Mar 8, 2023
Fork of Dot tile v1
1 star
Insert cell
Insert cell
Insert cell
function dotTileV3 (data, {
title = null,
w = 300,
radius = 12,
startAtZero = true, // be able to start at min value for very close values away from zero
xPadding = 20, // padding each side of the line/range (will change the length of the line)
bgdCol = '#e4f1e6',
bgdList = '#f2f8f3', // background of the demographic list
listLabels = ['%', 'Demographic', 'Count'], // pass in empty array if labels shouldn't show
lineCol = '#688e70',
circleFill = '#f59994',
circleTextFill = bgdCol,
titleCol = '#394437',
titlePadding = 35, // y drop for title
offsetHeight = titlePadding + 140,// based on title to create a visual center
h = (data.length * 35) + offsetHeight
} = {}) {

const min = (startAtZero) ? 0 : d3.min(data, d => d.value);
const max = d3.max(data, d => d.value);

// have the data sorted high to low for ease
// add an id for each datum
const dataDecending = _.sortBy(data, 'value')
.map((d, i) => {
const obj = d;
obj.id = `ref-${i}`;
return obj
})

// set up scales for horizontal placement
const xScale = d3.scaleLinear()
.domain([min, max * 1.1]) // slight padding on values
.range([xPadding, w - (xPadding * 2)]);

const svg = DOM.svg(w, h);
const sel = d3.select(svg);

// background colour
sel.append('rect')
.attr('width', w)
.attr('height', h)
.attr('rx', 20)
.attr('fill', bgdCol);

// append the title
if (title !==null) {
sel.append('text')
.attr('y', titlePadding)
.attr('x', w/2)
.attr('text-anchor', 'middle')
.style('fill', titleCol)
.text(title);
}

// show labels if they exist
if (!_.isEmpty(listLabels)) {
// TODO remove some of the repetition encapsulate into a function or css style
sel.append('text')
.style('font-size', '11px')
.attr('text-anchor', 'end')
.attr('y', offsetHeight - 40)
.attr('x', xPadding + 35)
.style('fill', titleCol)
.text(listLabels[0]);
sel.append('text')
.style('font-size', '10px')
.attr('text-anchor', 'middle')
.attr('y', offsetHeight - 40)
.attr('x', w/2)
.style('text-transform', 'uppercase')
.style('letter-spacing', '2px')
.style('fill', titleCol)
.text(listLabels[1]);
sel.append('text')
.style('font-size', '10px')
.attr('y', offsetHeight - 40)
.attr('text-anchor', 'end')
.attr('x', w - xPadding)
.style('text-transform', 'uppercase')
.style('letter-spacing', '2px')
.style('fill', titleCol)
.text(listLabels[2]);
}

// have line for values
sel.append('line')
.attr('x1', xPadding)
.attr('y1', offsetHeight/2 -10)
.attr('x2', w - xPadding)
.attr('y2', offsetHeight/2 -10)
.attr('stroke', lineCol)
.attr('stroke-dasharray', 2);


// add circles
sel.selectAll('circle')
.data(dataDecending)
.join('circle')
.attr('class', d => `${d.id} item`)
.attr('fill', circleFill)
.attr('cx', d => xScale(d.value))
.attr('cy', offsetHeight/2 -10)
.attr('r', radius)
.attr('stroke', bgdCol)
.style('opacity', 0.9)
.on('mouseover', function (e, d) {
mouseover(e, d)
})

const list = sel.selectAll('g')
// pass in sorted data so can apply values one above and below (index order matches visual order)
.data(_.sortBy(data, d => d.value))
.join('g')
.attr('class', d => `${d.id} item`)
.attr('transform', (d, i) => {
return `translate(${0}, ${i * 35 + offsetHeight})`
});

list.append('rect')
.attr('width', w - (xPadding))
.attr('x', xPadding/2)
.attr('height', 30)
.attr('y', -20)
.attr('rx', 10)
.attr('fill', bgdList)
.style('cursor', 'pointer')
.on('mouseover', function (e, d) {
mouseover(e, d)
})


list.append('text')
.style('font-size', '13px')
.attr('text-anchor', 'end')
.attr('x', xPadding + 35)
.text(d => d.value);
list.append('text')
.style('font-size', '13px')
.attr('text-anchor', 'middle')
.attr('x', w/2)
.text(d => d.label);
list.append('text')
.style('font-size', '13px')
.attr('text-anchor', 'end')
.attr('x', w - xPadding)
.text(d => d.population);

function mouseover(e, d) {
sel.selectAll(`.item`).style('opacity', 0.4);
sel.selectAll(`.${d.id}`).style('opacity', 1);
}

return svg;

}
Insert cell
Insert cell
Insert cell
<hr>
<link href="https://fonts.googleapis.com/css?family=Space+Mono" rel="stylesheet">
<style>
text {
font-family:'Space Mono',monospace;
fill: #130C0E;
}
</style>
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