chart = {
const margin = { top: 50, bottom: 50, left: 50, right: 100},
height = 450,
width = 550,
colors = {
domain: ['setosa','versicolor','virginica'],
range: ['rgb(60,200,200)','rgb(150,100,200)','rgb(200,150,60)']
}
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0,0,width,height])
const x = d3.scaleLinear()
.domain(d3.extent(data, d => d.sepalLength))
.range([margin.left, width - margin.right])
.nice()
const y = d3.scaleLinear()
.domain(d3.extent(data, d => d.sepalWidth))
.range([height - margin.bottom, margin.top])
.nice()
const color = d3.scaleOrdinal()
.domain(colors.domain)
.range(colors.range)
const xAxis = g => g.attr("transform", `translate(0, ${height - margin.bottom})`).call(d3.axisBottom(x))
const yAxis = g => g.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
svg.append("g")
.call(xAxis)
.selectAll(".tick line").clone()
.attr("y1", margin.top + margin.bottom - height)
.attr("stroke", "lightgrey")
svg.append("g")
.call(yAxis)
.selectAll(".tick line").clone()
.attr("x1", width - margin.left - margin.right)
.attr("stroke", "lightgrey")
// Create the legend
const legend = svg.append("g").attr("transform", `translate(${width - margin.right + 20}, ${margin.top + 10})`)
// legend title
legend.append("text")
.text("species")
.attr("font-size", "0.7em")
.attr("font-weight", "bold")
.attr("font-family", "sans-serif")
// legend circles
legend.selectAll(".legend-mark")
.data(colors.domain)
.join("circle")
.attr("class", "legend-mark")
.attr("cx", 5)
.attr("cy", (d, i) => (i + 1) * 15)
.attr("fill", d => color(d))
.attr("r", 5)
.attr("opacity", 0.5)
// legend names
legend.selectAll(".legend-name")
.data(colors.domain)
.join("text")
.attr("class", "legend-name")
.attr("x", 15)
.attr("y", (d, i) => (i + 1) * 15)
.attr("text-anchor", "start")
.attr("alignment-baseline", "middle")
.attr("font-size", "0.7em")
.attr("font-family", "sans-serif")
.text(d => d)
const points = svg.selectAll(".scatter")
.data(data)
.join("circle")
.attr("class", "scatter")
.attr("cx", d => x(d.sepalLength))
.attr("cy", d => y(d.sepalWidth))
.attr("fill", d => color(d.species))
.attr("r", 3)
.attr("opacity", 0.5)
const lines = svg.selectAll(".line")
.data(colors.domain
.map(d => (
[
...leastSquaresData(data.filter(f => f.species === d), 'sepalLength', 'sepalWidth')[0], {species: d}
])))
.join("line")
.attr("class", "line")
.attr("x1", d => x(d[0].x))
.attr("x2", d => x(d[1].x))
.attr("y1", d => y(d[0].y))
.attr("y2", d => y(d[1].y))
.attr("stroke", d => color(d[2].species))
.attr("stroke-width", 2)
const overall_line = svg.selectAll(".total-line")
.data([leastSquaresData(data, 'sepalLength', 'sepalWidth')[0]])
.join("line")
.attr("class", "total-line")
.attr("x1", d => x(d[0].x))
.attr("x2", d => x(d[1].x))
.attr("y1", d => y(d[0].y))
.attr("y2", d => y(d[1].y))
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("stroke-dasharray", "5 4")
svg.append("text") // add a y-axis label
.attr("transform", "rotate(-90)")
.attr("alignment-baseline", "center")
.attr("text-anchor", "middle")
.attr("x", -height/2.0) // mind-bendingly x/y swapped because of rotate(-90)
.attr("y", margin.left - 30)
.attr("font-size", "0.7em")
.attr("font-weight", "bold")
.attr("font-family", "sans-serif")
.text("sepalWidth")
svg.append("text") // add an x-axis label
.attr("alignment-baseline", "center")
.attr("text-anchor", "middle")
.attr("x", (width - margin.left)/2.0)
.attr("y", height - margin.bottom + 30)
.attr("font-size", "0.7em")
.attr("font-weight", "bold")
.attr("font-family", "sans-serif")
.text("sepalLength")
return svg.node()
}