Published
Edited
Jan 20, 2021
Importers
2 stars
Insert cell
Insert cell
Insert cell
{
const height = 200
const margin = {left: 20, top: 20, right: 20, bottom: 20}
const domainX = [2, 2.5]
const domainY = [6.5, 4.5]
const rangeX = [margin.left, width - margin.right]
const rangeY = [margin.top, height - margin.bottom]
const scale = fitViewBox(domainX, domainY, rangeX, rangeY)
const svg = d3.select(DOM.svg(width, height))
.style('background', colors.lightgray)
svg.append('polyline')
.attr('points', [
margin.left, margin.top,
width - margin.right, margin.top,
width - margin.right, height - margin.bottom,
margin.left, height - margin.bottom,
])
.attr('fill', colors.white)
svg.append('polygon')
.attr('points', scale.points([2,5, 3,5, 3,6, 2,6]))
.attr('stroke', colors.black)
.attr('fill', 'none')
.attr('vector-effect', 'non-scaling-stroke')
svg.append('polygon')
.attr('points', scale.points([domainX[0],domainY[0], domainX[1],domainY[0], domainX[1],domainY[1], domainX[0],domainY[1]]))
.attr('stroke', colors.red)
.attr('stroke-dasharray', [6, 2])
.attr('stroke-width', 2)
.attr('fill', 'none')
.attr('vector-effect', 'non-scaling-stroke')
svg.call(d3.zoom()
.extent([[0, 0], [width, height]])
.scaleExtent([0.1, 8])
.on('zoom', zoomed));

function zoomed({transform}) {
svg.selectAll('polygon').attr('transform', transform);
}
return svg.node()
}
Insert cell
Insert cell
function fitViewBox (domainX, domainY, rangeX, rangeY) {
const lenDomainX = Math.abs(domainX[1] - domainX[0])
const lenDomainY = Math.abs(domainY[1] - domainY[0])
const lenRangeX = Math.abs(rangeX[1] - rangeX[0])
const lenRangeY = Math.abs(rangeY[1] - rangeY[0])
const factorX = lenDomainX / lenRangeX
const factorY = lenDomainY / lenRangeY
const factor = Math.max(factorX, factorY)
const contentX = lenDomainX / factor
const contentY = lenDomainY / factor
const marginX = (lenRangeX - contentX) / 2
const marginY = (lenRangeY - contentY) / 2
let x0 = d3.min(domainX) - marginX * factor
let x1 = d3.max(domainX) + marginX * factor
if (domainX[0] > domainX[1]) {
[x0, x1] = [x1, x0]
}
let y0 = d3.min(domainY) - marginY * factor
let y1 = d3.max(domainY) + marginY * factor
if (domainY[0] > domainY[1]) {
[y0, y1] = [y1, y0]
}
const scaleX = d3.scaleLinear()
.domain([x0, x1])
.range(rangeX)
const scaleY = d3.scaleLinear()
.domain([y0, y1])
.range(rangeY)
const scale = (x, y) => [scaleX(x), scaleY(y)]
const lengthX = value => Math.abs(scaleX(value) - scaleX(0))
const lengthY = value => Math.abs(scaleY(value) - scaleY(0))
scale.x = scaleX
scale.y = scaleY
scale.lengthX = lengthX
scale.lengthY = lengthY
scale.points = coordinates => coordinates.map((x, i) => i % 2 === 0 ? scaleX(x) : scaleY(x))
scale.ellipse = (selection, options) => {
selection.attr('cx', scaleX(options.cx))
selection.attr('cy', scaleY(options.cy))
selection.attr('rx', lengthX(options.rx))
selection.attr('ry', lengthY(options.ry))
}
scale.line = (selection, options) => {
selection.attr('x1', scaleX(options.x1))
selection.attr('y1', scaleY(options.y1))
selection.attr('x2', scaleX(options.x2))
selection.attr('y2', scaleY(options.y2))
}
return scale
}
Insert cell
Insert cell
Insert cell
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