Skip to content

Commit c8d7026

Browse files
committed
feat: add compare scatter chart
1 parent ba3042e commit c8d7026

File tree

9 files changed

+1775
-78
lines changed

9 files changed

+1775
-78
lines changed

app/components/Compare/FacetScatterChart.vue

Lines changed: 490 additions & 0 deletions
Large diffs are not rendered by default.

app/composables/useChartWatermark.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,27 +132,35 @@ export function drawSmallNpmxLogoAndTaglineWatermark({
132132
colors,
133133
translateFn,
134134
logoWidth = 36,
135+
taglineFontSize = 8,
136+
offsetYTagline = 0,
137+
offsetXTagline = 0,
138+
offsetYLogo = 0,
135139
}: {
136140
svg: Record<string, any>
137141
colors: WatermarkColors
138142
translateFn: (key: string) => string
139143
logoWidth?: number
144+
taglineFontSize?: number
145+
offsetYTagline?: number
146+
offsetXTagline?: number
147+
offsetYLogo?: number
140148
}) {
141149
if (!svg.height) return
142150

143151
const npmxLogoWidthToHeight = 2.64
144152
const npmxLogoHeight = logoWidth / npmxLogoWidthToHeight
145153
const offsetX = 6
146-
const watermarkY = svg.height - npmxLogoHeight
154+
const watermarkY = svg.height - npmxLogoHeight + offsetYLogo
147155
const taglineY = svg.height - 3
148156

149157
return `
150158
${generateWatermarkLogo({ x: offsetX, y: watermarkY, width: logoWidth, height: npmxLogoHeight, fill: colors.fg })}
151159
<text
152160
fill="${colors.fgSubtle}"
153-
x="${logoWidth + offsetX * 2}"
154-
y="${taglineY}"
155-
font-size="8"
161+
x="${logoWidth + offsetX * 2 + offsetXTagline}"
162+
y="${taglineY + offsetYTagline}"
163+
font-size="${taglineFontSize}"
156164
text-anchor="start"
157165
>
158166
${translateFn('tagline')}

app/composables/useFacetSelection.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export interface FacetInfoWithLabels extends Omit<FacetInfo, 'id'> {
44
label: string
55
description: string
66
chartable: boolean
7+
chartable_scatter: boolean
8+
formatter?: (value: number) => string
79
}
810

911
// Get facets in a category (excluding coming soon)
@@ -21,73 +23,104 @@ function getFacetsInCategory(category: string): ComparisonFacet[] {
2123
*/
2224
export function useFacetSelection(queryParam = 'facets') {
2325
const { t } = useI18n()
26+
const compactNumberFormatter = useCompactNumberFormatter()
27+
const bytesFormatter = useBytesFormatter()
2428

2529
const facetLabels = computed(
26-
(): Record<ComparisonFacet, { label: string; description: string; chartable: boolean }> => ({
30+
(): Record<
31+
ComparisonFacet,
32+
{
33+
label: string
34+
description: string
35+
chartable: boolean
36+
chartable_scatter: boolean
37+
formatter?: (value: number) => string
38+
}
39+
> => ({
2740
downloads: {
2841
label: t(`compare.facets.items.downloads.label`),
2942
description: t(`compare.facets.items.downloads.description`),
30-
chartable: true,
43+
chartable: true, // TODO: rename to chartable_bar
44+
chartable_scatter: true,
45+
formatter: v => compactNumberFormatter.value.format(v),
3146
},
3247
totalLikes: {
3348
label: t(`compare.facets.items.totalLikes.label`),
3449
description: t(`compare.facets.items.totalLikes.description`),
3550
chartable: true,
51+
chartable_scatter: true,
52+
formatter: v => compactNumberFormatter.value.format(v),
3653
},
3754
packageSize: {
3855
label: t(`compare.facets.items.packageSize.label`),
3956
description: t(`compare.facets.items.packageSize.description`),
4057
chartable: true,
58+
chartable_scatter: true,
59+
formatter: v => bytesFormatter.format(v),
4160
},
4261
installSize: {
4362
label: t(`compare.facets.items.installSize.label`),
4463
description: t(`compare.facets.items.installSize.description`),
4564
chartable: true,
65+
chartable_scatter: true,
66+
formatter: v => bytesFormatter.format(v),
4667
},
4768
moduleFormat: {
4869
label: t(`compare.facets.items.moduleFormat.label`),
4970
description: t(`compare.facets.items.moduleFormat.description`),
5071
chartable: false,
72+
chartable_scatter: false,
5173
},
5274
types: {
5375
label: t(`compare.facets.items.types.label`),
5476
description: t(`compare.facets.items.types.description`),
5577
chartable: false,
78+
chartable_scatter: false,
5679
},
5780
engines: {
5881
label: t(`compare.facets.items.engines.label`),
5982
description: t(`compare.facets.items.engines.description`),
6083
chartable: false,
84+
chartable_scatter: false,
6185
},
6286
vulnerabilities: {
6387
label: t(`compare.facets.items.vulnerabilities.label`),
6488
description: t(`compare.facets.items.vulnerabilities.description`),
6589
chartable: false,
90+
chartable_scatter: true,
91+
formatter: v => compactNumberFormatter.value.format(v),
6692
},
6793
lastUpdated: {
6894
label: t(`compare.facets.items.lastUpdated.label`),
6995
description: t(`compare.facets.items.lastUpdated.description`),
7096
chartable: false,
97+
chartable_scatter: true,
7198
},
7299
license: {
73100
label: t(`compare.facets.items.license.label`),
74101
description: t(`compare.facets.items.license.description`),
75102
chartable: false,
103+
chartable_scatter: false,
76104
},
77105
dependencies: {
78106
label: t(`compare.facets.items.dependencies.label`),
79107
description: t(`compare.facets.items.dependencies.description`),
80108
chartable: true,
109+
chartable_scatter: true,
110+
formatter: v => compactNumberFormatter.value.format(v),
81111
},
82112
totalDependencies: {
83113
label: t(`compare.facets.items.totalDependencies.label`),
84114
description: t(`compare.facets.items.totalDependencies.description`),
85115
chartable: true,
116+
chartable_scatter: true,
117+
formatter: v => compactNumberFormatter.value.format(v),
86118
},
87119
deprecated: {
88120
label: t(`compare.facets.items.deprecated.label`),
89121
description: t(`compare.facets.items.deprecated.description`),
90122
chartable: false,
123+
chartable_scatter: false,
91124
},
92125
}),
93126
)
@@ -100,6 +133,8 @@ export function useFacetSelection(queryParam = 'facets') {
100133
label: facetLabels.value[facet].label,
101134
description: facetLabels.value[facet].description,
102135
chartable: facetLabels.value[facet].chartable,
136+
chartable_scatter: facetLabels.value[facet].chartable_scatter,
137+
formatter: facetLabels.value[facet].formatter ?? undefined,
103138
}
104139
}
105140

@@ -224,6 +259,7 @@ export function useFacetSelection(queryParam = 'facets') {
224259
isAllSelected,
225260
isNoneSelected,
226261
allFacets: ALL_FACETS,
262+
facetLabels,
227263
// Facet info with i18n
228264
getCategoryLabel,
229265
facetsByCategory,

app/pages/compare.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import { NO_DEPENDENCY_ID } from '~/composables/usePackageComparison'
33
import { useRouteQuery } from '@vueuse/router'
44
import FacetBarChart from '~/components/Compare/FacetBarChart.vue'
5-
import FacetQuadrantChart from '~/components/Compare/FacetQuadrantChart.vue'
65
import type { CommandPaletteContextCommandInput } from '~/types/command-palette'
6+
import FacetScatterChart from '~/components/Compare/FacetScatterChart.vue'
77
88
definePageMeta({
99
name: 'compare',
@@ -424,7 +424,7 @@ useSeoMeta({
424424
</div>
425425
</TabPanel>
426426

427-
<!-- Charts: per-facet bars & quadrant -->
427+
<!-- Charts: per-facet bars & scatter -->
428428
<TabPanel value="charts" panel-id="comparison-panel-charts">
429429
<div
430430
v-if="selectedFacets.some(facet => facet.chartable)"
@@ -447,8 +447,8 @@ useSeoMeta({
447447
<p v-else class="py-12 text-center text-fg-subtle">
448448
{{ $t('compare.packages.no_chartable_data') }}
449449
</p>
450-
<div class="max-w-[450px] mx-auto">
451-
<FacetQuadrantChart
450+
<div>
451+
<FacetScatterChart
452452
v-if="packages.length"
453453
:packages-data="packagesData"
454454
:packages="packages.filter(p => p !== NO_DEPENDENCY_ID)"

app/utils/charts.ts

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import type {
22
AltCopyArgs,
33
VueUiHorizontalBarConfig,
44
VueUiHorizontalBarDatapoint,
5-
VueUiQuadrantConfig,
6-
VueUiQuadrantDatapoint,
5+
VueUiScatterConfig,
6+
VueUiScatterSeries,
77
VueUiXyConfig,
88
VueUiXyDatasetBarItem,
99
VueUiXyDatasetLineItem,
@@ -638,78 +638,70 @@ export async function copyAltTextForCompareFacetBarChart({
638638
await config.copy(altText)
639639
}
640640

641-
type CompareQuadrantChartConfig = VueUiQuadrantConfig & {
641+
type CompareScatterChartConfig = VueUiScatterConfig & {
642642
copy: (text: string) => Promise<void>
643643
$t: TrendTranslateFunction
644+
x: {
645+
label: string
646+
formatter: (v: number) => string
647+
}
648+
y: {
649+
label: string
650+
formatter: (v: number) => string
651+
}
644652
}
645653

646-
// Used for FacetQuadrantChart.vue
647-
export function createAltTextForCompareQuadrantChart({
654+
// Used for FacetScatterChart.vue
655+
export function createAltTextForCompareScatterChart({
648656
dataset,
649657
config,
650-
}: AltCopyArgs<VueUiQuadrantDatapoint[], CompareQuadrantChartConfig>) {
658+
}: AltCopyArgs<VueUiScatterSeries[], CompareScatterChartConfig>) {
651659
if (!dataset) return ''
652660

653-
const packages = {
654-
topRight: dataset.filter(d => d.quadrant === 'TOP_RIGHT'),
655-
topLeft: dataset.filter(d => d.quadrant === 'TOP_LEFT'),
656-
bottomRight: dataset.filter(d => d.quadrant === 'BOTTOM_RIGHT'),
657-
bottomLeft: dataset.filter(d => d.quadrant === 'BOTTOM_LEFT'),
658-
}
661+
const { x, y } = config
662+
const { label: labelX, formatter: formatterX } = x
663+
const { label: labelY, formatter: formatterY } = y
659664

660-
const descriptions = {
661-
topRight: '',
662-
topLeft: '',
663-
bottomRight: '',
664-
bottomLeft: '',
665-
}
665+
const datapoints = dataset.map(d => {
666+
const rawX = d.values?.[0]?.x ?? 0
667+
const rawY = d.values?.[0]?.y ?? 0
668+
const name = d.fullName ?? ''
666669

667-
if (packages.topRight.length) {
668-
descriptions.topRight = config.$t('compare.quadrant_chart.copy_alt.side_analysis_top_right', {
669-
packages: packages.topRight.map(p => p.fullname).join(', '),
670-
})
671-
}
672-
673-
if (packages.topLeft.length) {
674-
descriptions.topLeft = config.$t('compare.quadrant_chart.copy_alt.side_analysis_top_left', {
675-
packages: packages.topLeft.map(p => p.fullname).join(', '),
676-
})
677-
}
678-
679-
if (packages.bottomRight.length) {
680-
descriptions.bottomRight = config.$t(
681-
'compare.quadrant_chart.copy_alt.side_analysis_bottom_right',
682-
{
683-
packages: packages.bottomRight.map(p => p.fullname).join(', '),
684-
},
685-
)
686-
}
670+
return {
671+
x: formatterX(rawX),
672+
y: formatterY(rawY),
673+
name,
674+
}
675+
})
687676

688-
if (packages.bottomLeft.length) {
689-
descriptions.bottomLeft = config.$t(
690-
'compare.quadrant_chart.copy_alt.side_analysis_bottom_left',
691-
{
692-
packages: packages.bottomLeft.map(p => p.fullname).join(', '),
693-
},
677+
const analysis = datapoints
678+
.map(d =>
679+
config.$t('compare.scatter_chart.copy_alt.analysis', {
680+
package: d.name,
681+
x_name: labelX,
682+
y_name: labelY,
683+
x_value: d.x,
684+
y_value: d.y,
685+
}),
694686
)
695-
}
696-
697-
const analysis = Object.values(descriptions).filter(Boolean).join('. ')
687+
.join(', ')
698688

699-
const altText = config.$t('compare.quadrant_chart.copy_alt.description', {
700-
packages: dataset.map(p => p.fullname).join(', '),
689+
const altText = config.$t('compare.scatter_chart.copy_alt.description', {
690+
x_name: labelX,
691+
y_name: labelY,
692+
packages: datapoints.map(d => d.name).join(', '),
701693
analysis,
702694
watermark: config.$t('package.trends.copy_alt.watermark'),
703695
})
704696

705697
return altText
706698
}
707699

708-
export async function copyAltTextForCompareQuadrantChart({
700+
export async function coopyAltTextForCompareScatterChart({
709701
dataset,
710702
config,
711-
}: AltCopyArgs<VueUiQuadrantDatapoint[], any>) {
712-
const altText = createAltTextForCompareQuadrantChart({ dataset, config })
703+
}: AltCopyArgs<VueUiScatterSeries[], CompareScatterChartConfig>) {
704+
const altText = createAltTextForCompareScatterChart({ dataset, config })
713705
await config.copy(altText)
714706
}
715707

0 commit comments

Comments
 (0)