function radar (data, {
bind = null,
f = d3.format('.1f'),
margin = { top: 100, right: 100, bottom: 50, left: 100 },
height = 300,
width = 300,
factorLegend = 0.95,
radians = 2 * Math.PI,
labelKey = 'label',
keyValues = {
label: 'label',
value: 'value',
},
strokeWidth = 8,
strokeColour = '#0d75ff',
axisColour = '#D9D9D9',
axisLabelColour = '#c7c7c7',
dataLabelColour = '#454545',
outerRadius = height / 2 - 10,
bgdCol = '#f1f2f0'
} = {}) {
const w = width + margin.left + margin.right
const h = height + margin.top + margin.bottom
const angle = d3.scaleLinear()
.range([0, 2 * Math.PI])
const radius = d3.scaleLinear()
.range([0, outerRadius])
const line = d3.lineRadial()
.curve(d3.curveCardinalClosed)
.angle((d, i) => angle(i))
.radius(d => radius(d.value))
bind.selectAll('svg').remove();
const svg = bind.append('svg')
.attr('width', w)
.attr('height', h)
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
// extend the domain slightly to match the range of [0, 2π].
angle.domain([0, d3.max(data, (d, i) => i + 1)])
radius.domain([0, 1])
// axis
g.append('g')
.selectAll('g.axis')
.data(d3.range(angle.domain()[1]))
.join('g')
.attr('class', 'axis')
.append('line')
.attr('x1', width / 2)
.attr('y1', height / 2)
.attr('x2', (d, i) => (width / 2) * (1 - factorLegend * Math.sin((i * radians) / 3)))
.attr('y2', (d, i) => (height / 2) * (1 - factorLegend * Math.cos((i * radians) / 3)))
.attr('stroke', axisColour)
.attr('stroke-width', 2)
// line
g.append('g')
.attr('transform', `translate(${width / 2}, ${height / 2})`)
.selectAll('path.layer')
.data([data])
.join('path')
.attr('class', 'layer')
.attr('d', d => line(d))
.attr('stroke', strokeColour)
.attr('stroke-width', strokeWidth)
.attr('fill', 'none')
const labelX = (d, i) => (width / 2) * (1 - factorLegend * Math.sin((i * radians) / 3)) - 20 * Math.sin((i * radians) / 3)
const labelY = (d, i) => (height / 2) * (1 - Math.cos((i * radians) / 3)) - 20 * Math.cos((i * radians) / 3)
// category labels
g.append('g')
.selectAll('g.label')
.data(data)
.join('text')
.attr('class', 'label')
.attr('x', (d, i) => labelX(d, i))
.attr('y', (d, i) => labelY(d, i))
.attr('transform', (d, i) => (i === 0) ? 'translate(0, -10)' : 'translate(0, -30)')
.attr('dy', '1.5em')
.attr('text-anchor', 'middle')
.attr('fill', axisLabelColour)
.text(d => d[keyValues.label])
// value labels
g.append('g')
.selectAll('g.value')
.data(data)
.join('text')
.attr('class', 'label')
.attr('x', (d, i) => labelX(d, i))
.attr('y', (d, i) => labelY(d, i))
.attr('transform', (d, i) => (i === 0) ? 'translate(0, -30)' : 'translate(0, -10)')
.attr('dy', '1.5em')
.attr('text-anchor', 'middle')
.attr('fill', dataLabelColour)
.text(d => f(d[keyValues.value]))
return svg;
}