Skip to content

Commit

Permalink
feat: add vertical bar chart component
Browse files Browse the repository at this point in the history
  • Loading branch information
brc-dd committed Mar 3, 2025
1 parent 637ae1c commit 01de6fc
Show file tree
Hide file tree
Showing 22 changed files with 780 additions and 15 deletions.
1 change: 1 addition & 0 deletions config/vite.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const baseConfig = {
'@vueuse/core',
'body-scroll-lock',
'dayjs',
'd3',
'file-saver',
'fuse.js',
'lodash-es',
Expand Down
2 changes: 1 addition & 1 deletion docs/.vitepress/theme/components/Showcase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const computedStory = computed(() => {
</div>
</template>

<style scoped>
<style scoped lang="postcss">
.Showcase {
margin: 0 -12px;
border: 1px solid var(--c-divider);
Expand Down
2 changes: 1 addition & 1 deletion docs/components/grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ If you need to adjust the overall grid layout based on different screen sizes or
</SGrid>
</template>
<style scoped>
<style scoped lang="postcss">
.grid {
grid-template-columns: 1fr;
gap: 16px;
Expand Down
2 changes: 1 addition & 1 deletion docs/components/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const options = useTable({
})
</script>

<style scoped>
<style scoped lang="postcss">
.table :deep(.col-name) { --table-col-width: 128px; }
.table :deep(.col-group) { --table-col-width: 128px; }
.table :deep(.col-status) { --table-col-width: 128px; }
Expand Down
11 changes: 11 additions & 0 deletions lib/components/SChart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
defineProps<{
height: string
}>()
</script>

<template>
<div class="SChart" :style="{ height }">
<slot />
</div>
</template>
167 changes: 167 additions & 0 deletions lib/components/SChartBarVertical.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<script setup lang="ts">
import { useElementSize } from '@vueuse/core'
import * as d3 from 'd3'
import { ref, watch } from 'vue'
export type KV = { key: string; value: number }
const props = defineProps<{
data: KV[]
margins?: { top: number; right: number; bottom: number; left: number }
ticks?: number
tooltip?: (d: KV) => string
}>()
const chartRef = ref<HTMLElement>()
const { width, height } = useElementSize(chartRef)
// Function to render the chart
function renderChart({ clientWidth, clientHeight }: { clientWidth: number; clientHeight: number }) {
if (!chartRef.value) { return }
// Clear any existing SVG
d3.select(chartRef.value).selectAll('*').remove()
// Set dimensions and margins
const margin = { top: 30, right: 30, bottom: 60, left: 60, ...props.margins }
const width = clientWidth - margin.left - margin.right
const height = clientHeight - margin.top - margin.bottom
// Create SVG
const svg = d3
.select(chartRef.value)
.append('svg')
.attr('width', '100%')
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
// X scale
const x = d3
.scaleBand()
.domain(props.data.map((d) => d.key))
.range([0, width])
.padding(0.4)
// Y scale
const y = d3
.scaleLinear()
.domain([0, d3.max(props.data, (d) => d.value)!])
.nice()
.range([height, 0])
// Add X axis
svg
.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x))
.selectAll('text')
.style('fill', 'var(--c-text-1)')
.style('font-size', '14px')
.style('text-anchor', 'middle')
// Remove X axis line
svg.select('.domain').remove()
// Add Y axis
svg
.append('g')
.call(d3.axisLeft(y).ticks(props.ticks || 5))
.selectAll('text')
.style('fill', 'var(--c-text-1)')
.style('font-size', '14px')
// Remove Y axis line
svg.select('.domain').remove()
// Add horizontal grid lines
svg
.selectAll('line.grid')
.data(y.ticks(props.ticks || 5))
.enter()
.append('line')
.attr('class', 'grid')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', (d) => y(d))
.attr('y2', (d) => y(d))
.attr('stroke', 'var(--c-divider)')
.attr('stroke-dasharray', '2,2')
// Create a tooltip
const Tooltip = d3.select(chartRef.value).append('div').attr('class', 'tooltip')
// Add bars
svg
.selectAll('rect')
.data(props.data)
.enter()
.append('rect')
.attr('x', (d) => x(d.key)!)
.attr('y', (d) => y(d.value))
.attr('width', x.bandwidth())
.attr('height', (d) => height - y(d.value))
.attr('fill', 'var(--c-fg-info-1)')
.attr('rx', 2)
.attr('ry', 2)
.on('mouseover', () => {
Tooltip.style('opacity', 1)
})
.on('mousemove', (event, d) => {
Tooltip.html(props.tooltip ? props.tooltip(d) : `${d.key}: ${d.value}`)
.style('transform', `translate3d(${event.pageX + 10}px,${event.pageY + 10}px,0)`)
.style('--max-tooltip-width', `${Tooltip.text().length}ch`)
.style('--min-tooltip-width', `${Math.max(...Tooltip.text().split(' ').map((t) => t.length))}ch`)
.style('--available-width', `${clientWidth - event.pageX + 24}px`)
})
.on('mouseleave', () => {
Tooltip.style('opacity', 0)
})
}
watch(
[width, height],
([clientWidth, clientHeight]) => {
if (!clientWidth || !clientHeight) { return }
renderChart({ clientWidth, clientHeight })
},
{ immediate: true }
)
</script>

<template>
<div class="SChartBar" ref="chartRef" />
</template>

<style scoped lang="postcss">
.SChartBar {
width: 100%;
height: 100%;
font-feature-settings: 'tnum' 1;
}
/* Hide axis lines */
:deep(.domain) {
display: none;
}
/* Style the ticks */
:deep(.tick line) {
display: none;
}
:deep(.tooltip) {
opacity: 0;
background-color: var(--c-bg-elv-2);
border: 1px solid var(--c-divider);
border-radius: 6px;
padding: 6px;
position: absolute;
top: 0;
left: 0;
transition: transform 0.4s ease-out, opacity 0.25s 0.75s;
pointer-events: none;
width: min(var(--max-tooltip-width), max(var(--min-tooltip-width), var(--available-width)));
width: calc-size(auto, min(size, max(var(--min-tooltip-width), var(--available-width))));
}
</style>
9 changes: 9 additions & 0 deletions lib/styles/utilities.css
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@
.s-max-w-lg { max-width: 960px; }
.s-max-w-xl { max-width: 1216px; }

.s-h-256 { height: 256px; }
.s-h-320 { height: 320px; }
.s-h-512 { height: 512px; }
.s-h-full { height: 100%; }

.s-aspect-16-9 { aspect-ratio: 16 / 9; }
.s-aspect-4-3 { aspect-ratio: 4 / 3; }
.s-aspect-1-1 { aspect-ratio: 1 / 1; }

/**
* Typography
* -------------------------------------------------------------------------- */
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@
"@tanstack/vue-virtual": "3.0.0-beta.62",
"@tinyhttp/content-disposition": "^2.2.2",
"@tinyhttp/cookie": "^2.1.1",
"@types/d3": "^7.4.3",
"@types/file-saver": "^2.0.7",
"@types/qs": "^6.9.18",
"d3": "^7.9.0",
"file-saver": "^2.0.5",
"magic-string": "^0.30.17",
"ofetch": "^1.4.1",
Expand Down
Loading

0 comments on commit 01de6fc

Please sign in to comment.