Public
Edited
Jan 11, 2023
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
orderedWordCloud = (theWidth, data, termColor, exent, id) => {
// setup the wrapper svg
const innerWidth = theWidth - (2 * config.padding);
const svg = d3.create("svg")
.attr('height', config.height)
.attr('width', theWidth)
.attr('id', id || 'ordered-word-cloud')
.attr('class', 'word-cloud');

// start hieght calculations
let y = config.height;
let wordNodes;
const wordListHeight = config.height - (2 * config.padding);
const wordWrapper = svg.append('g')
.attr('transform', `translate(${2 * config.padding},0)`);
const sizeRange = { min: config.minFontSize, max: config.maxFontSize };
const fullExtent = exent || d3.extent(data, d => d.tfnorm)

// start layout loop
while ((y >= wordListHeight) && (sizeRange.max > sizeRange.min)) {
wordNodes = wordWrapper.selectAll('text') // one text per term
.data(data, d => d.term)
.enter()
.append('text') // for incoming data
.attr('class', '')
.attr('fill', termColor)
.attr('font-family', 'Lato, Helvetica, sans')
.classed('word', true)
.classed('hide', d => d.display === false)
.classed('show', d => d.display !== false)
.classed('selected', d => d.term === config.selectedTerm)
.attr('font-size', d => fontSizeComputer(d, fullExtent, sizeRange))
.text(d => d.term)
.attr('font-weight', 'bold')
.on('mouseover', (d) => {
const { event } = d3;
d3.select(event.target).attr('fill', config.linkColor)
.attr('cursor', 'pointer');
})
.on('mouseout', () => {
const { event } = d3;
d3.select(event.target).attr('fill', config.textColor)
.attr('cursor', 'arrow');
});

// Layout
y = 0;
const leftHeight = listCloudLayout(wordNodes, innerWidth, fullExtent, sizeRange);
y = Math.max(y, leftHeight);
sizeRange.max -= 1;
}
// we need to return a DOM element for observable to render
return svg.node();
}
Insert cell
listCloudLayout = (wordNodes, width, extent, sizeRange) => {
const canvasContext2d = DOM.context2d(300, 300);
let x = 0;
if (typeof (wordNodes) === 'undefined') {
return x;
}
wordNodes.attr('x', (d) => {
const fs = fontSizeComputer(d, extent, sizeRange);
canvasContext2d.font = `bold ${fs}px Lato`; // crazy hack for IE compat, instead of simply this.getComputedTextLength()
const metrics = canvasContext2d.measureText(d.term);
const textLength = metrics.width+4; // give it a little horizontal spacing between words
let lastX = x;
if (x + textLength + 10 > width) { // TODO: replace 10 with state property for padding
lastX = 0;
}
x = lastX + textLength + (0.5 * fs);
return lastX;
});
let y = -0.5 * sizeRange.max;
let lastAdded = 0;
wordNodes.attr('y', (d, index, data) => { // need closure here for d3.select to work right on the element
const xPosition = d3.select(data[index]).attr('x');
if (xPosition === '0') { // WTF does this come out as a string???!?!?!?!
const height = 1.2 * fontSizeComputer(d, extent, sizeRange);
y += height;
y = Math.max(y, height);
lastAdded = height;
}
return y;
});
return y + lastAdded;
}

Insert cell
fontSizeComputer = (term, extent, sizeRange) => {
const size = sizeRange.min + (((sizeRange.max - sizeRange.min)
* (Math.log(term.tfnorm) - Math.log(extent[0]))) / (Math.log(extent[1]) - Math.log(extent[0])));
return size;
}
Insert cell
Insert cell
// scale across all data, not one side or other, so visual comparisons make sense
extent = d3.extent(leftData.concat(rightData), d => d.tfnorm)
Insert cell
rightData = {
const cleanedData = rightRawData
.filter(r => r.term.length > 2)
.sort((x, y) => d3.descending(x.count, y.count))
.slice(0, config.maxTerms);
const total = cleanedData.map(r => r.count).reduce((a, b) => a + b, 0);
return cleanedData.map(r => ({ ...r, tfnorm: r.count/total }));
}
Insert cell
leftData = {
const cleanedData = leftRawData
.filter(r => r.term.length > 2)
.sort((x, y) => d3.descending(x.count, y.count))
.slice(0, config.maxTerms);
const total = cleanedData.map(r => r.count).reduce((a, b) => a + b, 0);
return cleanedData.map(r => ({ ...r, tfnorm: r.count/total }));
}
Insert cell
leftRawData = FileAttachment("left-top-terms@1.csv").csv({typed: true});
Insert cell
rightRawData = FileAttachment("right-top-terms@1.csv").csv({typed: true});
Insert cell
Insert cell
config = ({
width: 600,
height: 250,
maxTerms: 100,
maxFontSize: 30,
minFontSize: 12,
padding: 0,
selectedTerm: null
})
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