chart = {
let mx, my, mouseX = 0, mouseY = 0
let beta = startY, alpha = startX
const drag = d3.drag().on('drag', dragged).on('start', (evt) => {
mx = evt.x
my = evt.y
}).on('end', (evt) => {
mouseX = evt.x - mx + mouseX
mouseY = evt.y - my + mouseY
})
const svgNode = d3.select(DOM.svg(width, 900))
const grid3d = d33d._3d().shape('GRID', 61).origin(origin).scale(scale).rotateY(beta).rotateX(alpha)
const yScale3d = d33d._3d().shape('LINE_STRIP').origin(origin).scale(scale).rotateY(beta).rotateX(alpha)
const point3d = d33d._3d().origin(origin).scale(scale).rotateY(beta).rotateX(alpha).x((d) => d.s).y((d) => d.r).z((d) => d.p)
const key = (d) => d.id
const svg = svgNode.call(drag).append('g')
const render = () => {
const xGrid = svg.selectAll('path.grid').data(grid3d.rotateY(beta).rotateX(alpha)(grid), key)
xGrid.enter().append('path').attr('class', '_3d grid').merge(xGrid)
.attr('stroke', 'black')
.attr('stroke-width', 0.08)
.attr('fill', '#ffffff')
.attr('fill-opacity', 0.2)
.attr('d', grid3d.draw)
xGrid.exit().remove()
const points = svg.selectAll('circle').data(point3d.rotateY(beta).rotateX(alpha)(scatter), key)
points.enter().append('circle').attr('class', '_3d').merge(points)
.attr('r', 3)
.attr('fill', (d) => {
if (d.total < 50) return 'red'
if (d.dist < 45) return 'green'
if (d.dist < 50) return 'orange'
return 'blue'
})
.attr('fill-opacity', (d) => d.total > 50 ? 1 : 0.05)
.attr('cx', (d) => d.projected.x)
.attr('cy', (d) => d.projected.y)
points.exit().remove();
const yScale = svg.selectAll('path.yScale').data(yScale3d.rotateY(beta).rotateX(alpha)([runDuration]))
yScale.enter().append('path').attr('class', '_3d yScale').merge(yScale)
.attr('stroke', 'black')
.attr('stroke-width', .5)
.attr('d', yScale3d.draw)
yScale.exit().remove()
const pad = (n) => `${n}`.padStart(2, '0')
const yText = svg.selectAll('text.yText').data(runDuration)
yText.enter().append('text').attr('class', '_3d yText').merge(yText)
.attr('dx', '.3em')
.attr('x', (d) => d.projected.x)
.attr('y', (d) => d.projected.y)
.each((d) => { d.centroid = { ...d.rotated } })
.text((d) => {
if ((1 + d[1]) % 3) return ''
const sec = (110 - d[1]) * 10
return `${pad(Math.floor(sec / 60))}:${pad(sec % 60)}`
})
yText.exit().remove()
d3.selectAll('._3d').sort(d33d._3d().sort)
}
function dragged(evt) {
beta = (evt.x - mx + mouseX) * Math.PI / 230 + startY
alpha = (evt.y - my + mouseY) * Math.PI / 230 + startX
render()
}
render()
return svgNode.node()
}