Skip to content

Instantly share code, notes, and snippets.

@betafcc
Last active November 18, 2018 13:40
Show Gist options
  • Save betafcc/369e2a5cd6849d67134a393ce4972065 to your computer and use it in GitHub Desktop.
Save betafcc/369e2a5cd6849d67134a393ce4972065 to your computer and use it in GitHub Desktop.
[
{"sign":"Aries","count":2328,"sum_of_votes":39017358},
{"sign":"Taurus","count":2281,"sum_of_votes":51056695},
{"sign":"Gemini","count":2301,"sum_of_votes":42472945},
{"sign":"Cancer","count":2230,"sum_of_votes":41106817},
{"sign":"Leo","count":2297,"sum_of_votes":35133564},
{"sign":"Virgo","count":2287,"sum_of_votes":26846077},
{"sign":"Libra","count":2292,"sum_of_votes":41968051},
{"sign":"Scorpio","count":2167,"sum_of_votes":56140744},
{"sign":"Sagittarius","count":2019,"sum_of_votes":32702931},
{"sign":"Capricorn","count":2033,"sum_of_votes":26875115},
{"sign":"Aquarius","count":2058,"sum_of_votes":61465912},
{"sign":"Pisces","count":2172,"sum_of_votes":101108395}
]
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vega-embed/3.23.1/vega-embed.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vega/4.3.0/vega.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vega-lite/2.6.0/vega-lite.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vega-embed/3.24.1/vega-embed.min.js"></script>
</head>
<body>
<div id="vis"></div>
<script src="util.js"></script>
<script>(async () => {
const data = (await (await fetch('./data.json')).json()).map((el, i) => ({...el, symbol: '♈♉♊♋♌♍♎♏♐♑♒♓'[i]}))
const values = features({data, column: 'sum_of_votes'})
let config = {
radius: 200,
background: '#F3E9E4',
pallete: ['#D2475E', '#E18A1F', '#EEE31C', '#70C2B9'],
}
config = {
...config,
innerRadius: config.radius * 4 / 5,
strokeWidth: 5,
colorAxis: {
field: 'sign',
type: 'nominal',
sort: null,
legend: null,
scale: {range: config.pallete},
},
textScale: {domain: [-config.radius, config.radius], nice: false},
}
config.textSize = (config.radius - config.innerRadius) / 2
config.textRadius = (config.radius + config.innerRadius) / 2 - 2
config.tooltip = [
{field: 'sign', type: 'nominal', title: 'Signo'},
{field: 'count', type: 'quantitative', title: 'Número de candidatos'},
{field: 'sum_of_votes', type: 'quantitative', title: 'Total de votos', format: ','},
]
const spec = {
background: config.background,
data: {values},
config: {view: {stroke: ''}, axis: {domainColor: null, tickColor: null}},
// layer: [{}],
hconcat: [
{
width: config.radius * 2,
height: config.radius * 2,
layer: [
{
mark: {type: 'geoshape', stroke: config.background, strokeWidth: 5},
projection: {type: 'azimuthalEquidistant', rotate: [0, 90, 0]},
encoding: {
tooltip: config.tooltip,
color: config.colorAxis,
}
},
{
data: {values: [{}]},
mark: 'circle',
encoding: {
color: {value: config.background},
size: {value: (config.innerRadius * 2) ** 2},
opacity: {value: 1},
}
},
{
mark: {type: 'text', size: config.textSize, baseline: 'middle', align: 'center', color: 'white'},
transform: [{
as: 'x',
calculate: `${config.textRadius} * sin((PI / 180) * (datum.geometry.coordinates[0][1][0] + datum.geometry.coordinates[0][2][0]) / 2)`
}, {
as: 'y',
calculate: `${config.textRadius} * cos((PI / 180) * (datum.geometry.coordinates[0][1][0] + datum.geometry.coordinates[0][2][0]) / 2)`
}],
encoding: {
x: {field: 'x', type: 'quantitative', scale: config.textScale, axis: null},
y: {field: 'y', type: 'quantitative', scale: config.textScale, axis: null},
text: {field: 'symbol', type: 'nominal'},
}
}
],
},
{
mark: 'bar',
width: config.radius * 2,
height: config.radius * 2,
encoding: {
color: config.colorAxis,
x: {field: 'sum_of_votes', type: 'quantitative', title: null, axis: {grid: false, tickCount: 0}},
y: {field: 'sign', type: 'nominal', title: null, sort: {field: 'sum_of_votes', op: 'mean', order: 'descending'}},
tooltip: config.tooltip,
}
}
],
}
vegaEmbed('#vis', spec, {defaultStyle: true, renderer: 'svg'})
})();</script>
</body>
</html>
const features = ({data: [...rows], column}) =>
geometries(rows.map(r => r[column]))
.map((el, i) => ({
...rows[i],
type: 'Feature',
geometry: el
}))
const geometries = ([...xs]) =>
xs
// map to angles
.map(linearScale({
domain: [0, xs.reduce((a, b) => a + b)],
range: [0, 360],
}))
// cumulative sum
.reduce((acc, n, i) => acc.concat(acc[i] + n), [0])
// take pairs
.reduce((acc, n, i, arr) => [...acc, [arr[i - 1], n]], []).slice(1)
// to geojson geometry
.map(([startAngle, endAngle]) => ({
type: 'Polygon',
coordinates: [[
[0, 90],
[endAngle, -89],
[startAngle, -89],
[0, 90],
]]
}))
const linearScale = ({domain: [xi, xf], range: [yi, yf]}) => {
const a = (yi - yf) / (xi - xf)
const b = yi - a * xi
return x => a * x + b
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment