Skip to content

Commit 4ad0679

Browse files
graphierosautofix-ci[bot]coderabbitai[bot]
authored
feat(a11y): use seeded patterns in bar charts (#2157)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 9ae5629 commit 4ad0679

File tree

5 files changed

+726
-5
lines changed

5 files changed

+726
-5
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script setup lang="ts">
2+
/**
3+
* This component is designed to create various patterns from seeds, and included
4+
* inside vue-data-ui charts in the #pattern slots.
5+
* Using patterns helps users with vision deficency (like achromatopsia) to distinguish
6+
* series in the context of data visualisation.
7+
*/
8+
import { computed } from 'vue'
9+
import { createSeededSvgPattern, type ChartPatternSlotProps } from '~/utils/charts'
10+
11+
const props = defineProps<ChartPatternSlotProps>()
12+
13+
const pattern = computed(() =>
14+
createSeededSvgPattern(props.seed, {
15+
foregroundColor: props.foregroundColor,
16+
backgroundColor: props.color ?? props.fallbackColor,
17+
minimumSize: props.minSize,
18+
maximumSize: props.maxSize,
19+
}),
20+
)
21+
</script>
22+
23+
<template>
24+
<pattern
25+
:id
26+
patternUnits="userSpaceOnUse"
27+
:width="pattern.width"
28+
:height="pattern.height"
29+
:patternTransform="`rotate(${pattern.rotation})`"
30+
v-html="pattern.contentMarkup"
31+
/>
32+
</template>

app/components/Compare/FacetBarChart.vue

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { ref, computed } from 'vue'
33
import { VueUiHorizontalBar } from 'vue-data-ui/vue-ui-horizontal-bar'
44
import type { VueUiHorizontalBarConfig, VueUiHorizontalBarDatasetItem } from 'vue-data-ui'
55
import { getFrameworkColor, isListedFramework } from '~/utils/frameworks'
6+
import { createChartPatternSlotMarkup } from '~/utils/charts'
67
import { drawSmallNpmxLogoAndTaglineWatermark } from '~/composables/useChartWatermark'
8+
79
import {
810
loadFile,
911
insertLineBreaks,
@@ -213,13 +215,40 @@ const config = computed<VueUiHorizontalBarConfig>(() => {
213215
backdropFilter: false,
214216
backgroundColor: 'transparent',
215217
customFormat: ({ datapoint }) => {
216-
const name = datapoint?.name?.replace(/\n/g, '<br>')
218+
const name = datapoint?.name?.replace(/\n/g, '<br>') ?? ''
219+
const safeSeriesIndex = (datapoint?.absoluteIndex as number) ?? 0
220+
const patternId = `tooltip-pattern-${safeSeriesIndex}`
221+
const usePattern = safeSeriesIndex !== 0
222+
223+
const patternMarkup = usePattern
224+
? createChartPatternSlotMarkup({
225+
id: patternId,
226+
seed: safeSeriesIndex,
227+
foregroundColor: colors.value.bg!,
228+
fallbackColor: 'transparent',
229+
maxSize: 24,
230+
minSize: 16,
231+
})
232+
: ''
233+
234+
const markerMarkup = usePattern
235+
? `
236+
<rect x="0" y="0" width="20" height="20" rx="3" fill="${datapoint?.color ?? 'transparent'}" />
237+
<rect x="0" y="0" width="20" height="20" rx="3" fill="url(#${patternId})" />
238+
`
239+
: `
240+
<rect x="0" y="0" width="20" height="20" rx="3" fill="${datapoint?.color ?? 'transparent'}" />
241+
`
242+
217243
return `
218244
<div class="font-mono p-3 border border-border rounded-md bg-[var(--bg)]/10 backdrop-blur-md">
219245
<div class="grid grid-cols-[12px_minmax(0,1fr)_max-content] items-center gap-x-3">
220246
<div class="w-3 h-3">
221-
<svg viewBox="0 0 2 2" class="w-full h-full">
222-
<rect x="0" y="0" width="2" height="2" rx="0.3" fill="${datapoint?.color}" />
247+
<svg viewBox="0 0 20 20" class="w-full h-full" aria-hidden="true">
248+
<defs>
249+
${patternMarkup}
250+
</defs>
251+
${markerMarkup}
223252
</svg>
224253
</div>
225254
<span class="text-3xs uppercase tracking-wide text-[var(--fg)]/70 truncate">
@@ -230,7 +259,7 @@ const config = computed<VueUiHorizontalBarConfig>(() => {
230259
</span>
231260
</div>
232261
</div>
233-
`
262+
`
234263
},
235264
},
236265
},
@@ -243,8 +272,19 @@ const config = computed<VueUiHorizontalBarConfig>(() => {
243272
<div class="font-mono facet-bar">
244273
<ClientOnly v-if="dataset.length">
245274
<VueUiHorizontalBar :key="chartKey" :dataset :config class="[direction:ltr]">
275+
<template #pattern="{ patternId, seriesIndex }">
276+
<ChartPatternSlot
277+
v-if="seriesIndex != 0"
278+
:id="patternId"
279+
:seed="seriesIndex"
280+
:foreground-color="colors.bg!"
281+
fallback-color="transparent"
282+
:max-size="24"
283+
:min-size="16"
284+
/>
285+
</template>
286+
246287
<template #svg="{ svg }">
247-
<!-- Inject npmx logo & tagline during SVG and PNG print -->
248288
<g
249289
v-if="svg.isPrintingSvg || svg.isPrintingImg"
250290
v-html="
@@ -261,15 +301,19 @@ const config = computed<VueUiHorizontalBarConfig>(() => {
261301
<span v-if="isOpen" class="i-lucide:x w-6 h-6" aria-hidden="true" />
262302
<span v-else class="i-lucide:ellipsis-vertical w-6 h-6" aria-hidden="true" />
263303
</template>
304+
264305
<template #optionCsv>
265306
<span class="text-fg-subtle font-mono pointer-events-none">CSV</span>
266307
</template>
308+
267309
<template #optionImg>
268310
<span class="text-fg-subtle font-mono pointer-events-none">PNG</span>
269311
</template>
312+
270313
<template #optionSvg>
271314
<span class="text-fg-subtle font-mono pointer-events-none">SVG</span>
272315
</template>
316+
273317
<template #optionAltCopy>
274318
<span
275319
class="w-6 h-6"

0 commit comments

Comments
 (0)