mirror of https://github.com/cupcakearmy/ora.git
103 lines
2.6 KiB
Svelte
103 lines
2.6 KiB
Svelte
<script>
|
|
import * as d3 from 'd3'
|
|
import { map, max } from 'lodash'
|
|
import { onMount } from 'svelte'
|
|
|
|
let wrapper
|
|
|
|
export let data = []
|
|
|
|
function render() {
|
|
if (!wrapper) return
|
|
|
|
// Dynamic left padding depending on the labels
|
|
const longestKey = max(map(data, (d) => d.name.length))
|
|
const baseMargin = 35
|
|
const ml = Math.min(longestKey * 6.3, 120) + baseMargin
|
|
const margin = { left: ml, top: baseMargin, bottom: baseMargin, right: baseMargin }
|
|
const styles = window.getComputedStyle(wrapper)
|
|
const barHeight = 20
|
|
const width = parseInt(styles.width)
|
|
const height = Math.ceil(data.length * 1.25 * barHeight + (margin.top + margin.bottom))
|
|
|
|
const svg = d3.select(wrapper).attr('viewBox', [0, 0, width, height])
|
|
|
|
const yAxis = (g) =>
|
|
g.attr('transform', `translate(${margin.left},0)`).call(
|
|
d3
|
|
.axisLeft(y)
|
|
.tickFormat((i) => data[i].name)
|
|
.tickSizeOuter(0)
|
|
)
|
|
const xAxis = (g) =>
|
|
g
|
|
.attr('transform', `translate(0,${margin.top})`)
|
|
.call(d3.axisTop(x).ticks(width / 100, 's'))
|
|
.call((g) => g.select('.domain').remove())
|
|
|
|
const y = d3
|
|
.scaleBand()
|
|
.domain(d3.range(data.length))
|
|
.rangeRound([margin.bottom, height - margin.top])
|
|
.padding(0.2)
|
|
const x = d3
|
|
.scaleLinear()
|
|
.domain([0, d3.max(data, (d) => d.value)])
|
|
.range([margin.left, width - margin.right])
|
|
|
|
const format = x.tickFormat(20, 's')
|
|
|
|
// Bars
|
|
svg
|
|
.append('g')
|
|
.attr('fill', '#17bbac')
|
|
.selectAll('rect')
|
|
.data(data)
|
|
.join('rect')
|
|
.attr('x', x(0))
|
|
.attr('y', (d, i) => y(i))
|
|
.attr('width', (d) => x(d.value) - x(0))
|
|
.attr('height', y.bandwidth())
|
|
|
|
svg
|
|
.append('g')
|
|
.attr('fill', 'white')
|
|
.attr('text-anchor', 'end')
|
|
.attr('font-family', 'sans-serif')
|
|
.attr('font-size', 12)
|
|
.selectAll('text')
|
|
.data(data)
|
|
.join('text')
|
|
.attr('x', (d) => x(d.value))
|
|
.attr('y', (d, i) => y(i) + y.bandwidth() / 2)
|
|
.attr('dy', '0.35em')
|
|
.attr('dx', -4)
|
|
.text((d) => d.human)
|
|
.call((text) =>
|
|
text
|
|
.filter((d) => x(d.value) - x(0) < d.human.length * 8) // short bars
|
|
.attr('dx', +4)
|
|
.attr('fill', 'black')
|
|
.attr('text-anchor', 'start')
|
|
)
|
|
|
|
svg.append('g').call(xAxis)
|
|
|
|
svg.append('g').call(yAxis)
|
|
}
|
|
|
|
onMount(render)
|
|
</script>
|
|
|
|
<svg bind:this={wrapper} preserveAspectRatio="xMidYMid meet" />
|
|
|
|
<style>
|
|
svg {
|
|
width: 100%;
|
|
}
|
|
|
|
svg :global(*) {
|
|
font-family: monospace;
|
|
}
|
|
</style>
|