Skip to content

Instantly share code, notes, and snippets.

@erikvullings
Created April 29, 2021 21:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erikvullings/10b64c0208510d0a01fe346247d6c691 to your computer and use it in GitHub Desktop.
Save erikvullings/10b64c0208510d0a01fe346247d6c691 to your computer and use it in GitHub Desktop.
Radar or spider chart for mithril

A simple SVG-based radar or spider chart for mithril without any external dependencies, based on the source code found here.

Check out the example on Flems.

const columns = {
price: 'Price',
useful: 'Usefulness',
design: 'Design',
battery: 'Battery Capacity',
camera: 'Camera Quality'
}
export const data = [
{
// iphone
color: '#edc951',
price: 1,
battery: .7,
design: 1,
useful: .9,
camera: .9
}, {
// galaxy
color: '#cc333f',
price: .8,
battery: 1,
design: .6,
useful: .8,
camera: 1
}, {
// nexus
color: '#00a0b0',
price: .5,
battery: .8,
design: .7,
useful: .6,
camera: .6
}
]
const RadarChart = () => {
return {
view: () => {
return m('div#demo', [
m('svg', {
id: 'demo-target',
xlmns: 'http://www.w3.org/2000/svg',
version: 1,
viewBox: '0 0 130 130',
}, render(columns, data, {
shapeProps: (data) => ({
className: 'shape',
fill: data.color,
})
}))
]);
}
}
}
m.render(document.body, m(RadarChart))
/**
* @source: https://github.com/derhuerst/svg-radar-chart
*/
const round = v => Math.round(v * 10000) / 10000
const polarToX = (angle, distance) => Math.cos(angle - Math.PI / 2) * distance
const polarToY = (angle, distance) => Math.sin(angle - Math.PI / 2) * distance
const points = (points) => {
return points
.map(point => point[0].toFixed(4) + ',' + point[1].toFixed(4))
.join(' ')
}
const noSmoothing = (points) => {
let d = 'M' + points[0][0].toFixed(4) + ',' + points[0][1].toFixed(4)
for (let i = 1; i < points.length; i++) {
d += 'L' + points[i][0].toFixed(4) + ',' + points[i][1].toFixed(4)
}
return d + 'z'
}
const axis = (opt) => (col) => {
return m('polyline', Object.assign(opt.axisProps(col), {
points: points([
[0, 0], [
polarToX(col.angle, opt.chartSize / 2),
polarToY(col.angle, opt.chartSize / 2)
]
])
}))
}
const shape = (columns, opt) => (data, i) => {
return m('path', Object.assign(opt.shapeProps(data), {
d: opt.smoothing(columns.map((col) => {
const val = data[col.key]
if ('number' !== typeof val) {
throw new Error(`Data set ${i} is invalid.`)
}
return [
polarToX(col.angle, val * opt.chartSize / 2),
polarToY(col.angle, val * opt.chartSize / 2)
]
}))
}))
}
const scale = (opt, value) => {
return m('circle', Object.assign(opt.scaleProps(value), {
cx: 0, cy: 0, r: value * opt.chartSize / 2
}))
}
const caption = (opt) => (col) => {
return m('text', Object.assign(opt.captionProps(col), {
x: polarToX(col.angle, opt.size / 2 * .95).toFixed(4),
y: polarToY(col.angle, opt.size / 2 * .95).toFixed(4),
dy: (opt.captionProps(col).fontSize || 2) / 2
}), col.caption)
}
const defaults = {
size: 100, // size of the chart (including captions)
axes: true, // show axes?
scales: 3, // show scale circles?
captions: true, // show captions?
captionsPosition: 1.2, // where on the axes are the captions?
smoothing: noSmoothing, // shape smoothing function
axisProps: () => ({className: 'axis'}),
scaleProps: () => ({className: 'scale', fill: 'none'}),
shapeProps: () => ({className: 'shape'}),
captionProps: () => ({
className: 'caption',
textAnchor: 'middle', fontSize: 3,
fontFamily: 'sans-serif'
})
}
const render = (columns, data, opt = {}) => {
if ('object' !== typeof columns || Array.isArray(columns)) {
throw new Error('columns must be an object')
}
if (!Array.isArray(data)) {
throw new Error('data must be an array')
}
opt = Object.assign({}, defaults, opt)
opt.chartSize = opt.size / opt.captionsPosition
columns = Object.keys(columns).map((key, i, all) => ({
key, caption: columns[key],
angle: Math.PI * 2 * i / all.length
}))
const groups = [
m('g', data.map(shape(columns, opt)))
]
if (opt.captions) groups.push(m('g', columns.map(caption(opt))))
if (opt.axes) groups.unshift(m('g', columns.map(axis(opt))))
if (opt.scales > 0) {
const scales = []
for (let i = opt.scales; i > 0; i--) {
scales.push(scale(opt, i / opt.scales))
}
groups.unshift(m('g', scales))
}
const delta = (opt.size / 2).toFixed(4)
return m('g', {
transform: `translate(${delta},${delta})`
}, groups)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment