Public
Edited
Mar 14, 2023
Fork of Swimmer Plot
Insert cell
Insert cell
Insert cell
swimmerplot = {

var svg = d3.select(DOM.svg(width, height))

let shape_size = 30
let line_size = 15
// circle data and annotations
let swimmer = svg.selectAll("swimmer-plot")
.data(data)
.enter()
.append('g')
.attr('class', 'swimmer-plot')
// each swimmer plot has to be below the prior one by as many swimmer lanes as the prior
// each plot g (three in this example) is moved down enough to accomodate all shapes and arrow lines
//.attr('transform', (d,i) => `translate(0,${moveDown(i)*shape_size + moveDown(i)*line_size})`)
// TODO Alternate background color between white and a super light gray
//.append('rect')
//.attr('width', '100%')
//.attr('height', (d,i) => height - moveDown(i)*(50+(moveDown(i)*20)))
//.attr('opacity', 0.02)

swimmer.selectAll("main-shape-lane")
.data(d => d.swimmers)
.enter()
.append("g")
.attr('class', 'main-shape-lane')
// each main shape lane needs to be moved down the height of a shape
// adding 30 above to account for text
.attr('transform', (d,i) => `translate(${0},${i*shape_size+30})`)
.selectAll('path')
.data(d => d)
.enter()
.append('path')
.attr('transform', (d,i) => `translate(${xScale(d.x)},0)`)
.attr("d", d => shape(d.type))
.attr('fill', 'none')
.attr("fill", d => d.risk === 2 ? colorScale(d.risk) : "none")
.attr('stroke', d => typeColorScale(d.type))
.attr('stroke-width', 2)

// the black numbers

let fontsize = 12
swimmer.selectAll("black-dot-lane")
.data(d => d.blackDots)
.enter()
.append("g")
.attr("transform", d => `translate(${xScale(d.x)}, ${fontsize})`)
.append("text")
.attr("font-size", fontsize)
.text(d => d.value)
.attr('text-anchor', 'middle')

// line lanes need to start at the bottom under big shapes
let lines = swimmer.selectAll("line-lanes")
.data(d => d.lines)
.enter()
.append("g")
.attr('class', 'line-lanes')
.attr('transform', (d,i) => `translate(${0},${i*10-line_size})`)
lines.selectAll('line-lanes')
.data(d => d)
.enter()
.append("line")
.attr('stroke', d => typeColorScale(d.type))
.attr("x1", d => xScale(d.x_start))
.attr("y1", shape_size + line_size + 20)
.attr("x2", d => xScale(d.x_end))
.attr("y2", shape_size+ line_size + 20)
.attr('stroke-width', 1)
lines.selectAll('g')
.data(d => d)
.enter()
.append("path")
.attr("d", d3.symbol().type(d3.symbolTriangle).size(20))
.attr('stroke', d => typeColorScale(d.type))
.attr('fill', d => typeColorScale(d.type))
.attr('transform', (d,i) => `translate(${xScale(d.x_end)},${shape_size + line_size + 20})rotate(90)`)

return svg.node()
}
Insert cell
shape_size = 30
Insert cell
line_size = 40
Insert cell
Insert cell
// todo calculate total height dynamically
height = 2*shape_size + 2*line_size
Insert cell
Insert cell
xScale = d3.scaleLinear()
.domain(d3.extent(data.map(d => d.swimmers).flat().flat(), d => d.x))
.range([margin.left, width - margin.right])
Insert cell
colorScale = d3.scaleOrdinal().domain([0,1,2]).range([color.green, color.yellow, color.red])
Insert cell
typeColorScale = d3.scaleOrdinal().domain(['p', 'k']).range(['black', 'gray', 'lightgray'])
Insert cell
typeColorScale1 = d3.scaleOrdinal().domain(['p', 'k']).range(['orange', 'blue', 'green'])
Insert cell
moveDown = (n) => {
let n_lanes = data.map(d => d.swimmers).map(d => d.length).splice(0,n).reduce((partialSum, a) => partialSum + a, 0)
return n_lanes*shape_size + n_lanes*line_size
}
Insert cell
shape = (expr) => {
switch(expr) {
case 'p':
return d3.symbols.map((symbol, i) => d3.symbol(symbol, 400)())[0]
break;
case 'k':
return d3.symbols.map((symbol, i) => d3.symbol(symbol, 200)())[5]
case 'd':
return d3.symbols.map((symbol, i) => d3.symbol(symbol, 400)())[3]
default:
console.log(`value out of bounds`);
}
}
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