From 9e7f4b958d98195229c2302ac906e32915bd2bf4 Mon Sep 17 00:00:00 2001 From: Sarah Zakarias Date: Tue, 4 Feb 2025 11:10:40 +0000 Subject: [PATCH 1/2] Downloads chart: show several version modes --- app/lib/frontend/dom/material.dart | 28 ++++++++++++++ .../templates/views/pkg/score_tab.dart | 20 +++++++++- .../src/widget/downloads_chart/widget.dart | 38 +++++++++++++++++-- pkg/web_css/lib/src/_pkg.scss | 8 ++++ 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/app/lib/frontend/dom/material.dart b/app/lib/frontend/dom/material.dart index d0958e050b..62d53a94a7 100644 --- a/app/lib/frontend/dom/material.dart +++ b/app/lib/frontend/dom/material.dart @@ -436,3 +436,31 @@ d.Node option({ ], ); } + +d.Node radioButtons({ + required String name, + required List<({String id, String value})> idsAndValues, + String? checkedId, + Iterable? classes, + String? leadingText, +}) { + final nodes = []; + if (leadingText != null) { + nodes.add(d.strong(text: leadingText)); + } + idsAndValues.forEach((e) { + nodes.add(d.input( + id: e.id, + type: 'radio', + name: name, + value: e.value, + classes: [ + ...?classes, + ], + attributes: {if (e.id == checkedId) 'checked': ''}, + )); + nodes.add(d.label(attributes: {'for': e.id}, child: d.text(e.value))); + }); + + return d.div(classes: ['mdc-form-field'], children: nodes); +} diff --git a/app/lib/frontend/templates/views/pkg/score_tab.dart b/app/lib/frontend/templates/views/pkg/score_tab.dart index 90d32d941f..a4cf66c803 100644 --- a/app/lib/frontend/templates/views/pkg/score_tab.dart +++ b/app/lib/frontend/templates/views/pkg/score_tab.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'package:_pub_shared/data/download_counts_data.dart'; import 'package:_pub_shared/format/number_format.dart'; import 'package:pana/models.dart'; +import 'package:pub_dev/frontend/dom/material.dart'; import 'package:pub_dev/service/download_counts/backend.dart'; import 'package:pub_dev/shared/utils.dart'; @@ -179,18 +180,35 @@ d.Node _section(ReportSection section) { } d.Node _downloadsChart(WeeklyVersionDownloadCounts weeklyVersionDownloads) { + final versionModes = d.div( + classes: ['downloads-chart-version-modes'], + children: [ + radioButtons( + leadingText: 'By versions: ', + name: 'version-modes', + idsAndValues: [ + (id: 'major', value: 'Major'), + (id: 'minor', value: 'Minor'), + (id: 'patch', value: 'Patch') + ], + classes: ['downloads-chart-radio-button'], + checkedId: 'major') + ], + ); final container = d.div( classes: ['downloads-chart'], id: '-downloads-chart', attributes: { 'data-widget': 'downloads-chart', 'data-downloads-chart-points': - base64Encode(jsonUtf8Encoder.convert(weeklyVersionDownloads)) + base64Encode(jsonUtf8Encoder.convert(weeklyVersionDownloads)), + 'data-downloads-chart-versions-radio': 'version-modes', }, ); return d.fragment([ d.h1(text: 'Weekly Downloads over the last 40 weeks'), + versionModes, container, ]); } diff --git a/pkg/web_app/lib/src/widget/downloads_chart/widget.dart b/pkg/web_app/lib/src/widget/downloads_chart/widget.dart index ea7223bf29..1e23801cd6 100644 --- a/pkg/web_app/lib/src/widget/downloads_chart/widget.dart +++ b/pkg/web_app/lib/src/widget/downloads_chart/widget.dart @@ -30,15 +30,19 @@ void create(HTMLElement element, Map options) { throw UnsupportedError('data-downloads-chart-points required'); } + final versionsRadio = options['versions-radio']; + if (versionsRadio == null) { + throw UnsupportedError('data-downloads-chart-versions-radio required'); + } + final svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('height', '100%'); svg.setAttribute('width', '100%'); - element.append(svg); + final data = WeeklyVersionDownloadCounts.fromJson((utf8.decoder .fuse(json.decoder) .convert(base64Decode(dataPoints)) as Map)); - final weeksToDisplay = math.min(40, data.totalWeeklyDownloads.length); final majorDisplayLists = prepareWeekLists( @@ -47,6 +51,34 @@ void create(HTMLElement element, Map options) { weeksToDisplay, ); + final minorDisplayLists = prepareWeekLists( + data.totalWeeklyDownloads, + data.minorRangeWeeklyDownloads, + weeksToDisplay, + ); + + final patchDisplayLists = prepareWeekLists( + data.totalWeeklyDownloads, + data.patchRangeWeeklyDownloads, + weeksToDisplay, + ); + + final versionModesLists = { + 'Major': majorDisplayLists, + 'Minor': minorDisplayLists, + 'Patch': patchDisplayLists + }; + + final versionModes = document.getElementsByName(versionsRadio); + for (int i = 0; i < versionModes.length; i++) { + final radioButton = versionModes.item(i) as HTMLInputElement; + if (versionModesLists[radioButton.value] != null) { + radioButton.onClick.listen((e) { + drawChart(svg, versionModesLists[radioButton.value]!, data.newestDate); + }); + } + } + drawChart(svg, majorDisplayLists, data.newestDate); } @@ -111,7 +143,7 @@ void drawChart( } final chart = SVGGElement(); - svg.append(chart); + svg.replaceChildren(chart); // Axis and ticks diff --git a/pkg/web_css/lib/src/_pkg.scss b/pkg/web_css/lib/src/_pkg.scss index 3e43828e07..a92798a28f 100644 --- a/pkg/web_css/lib/src/_pkg.scss +++ b/pkg/web_css/lib/src/_pkg.scss @@ -289,6 +289,14 @@ padding-top: 16px; } + .downloads-chart-version-modes { + float: right; + } + + .downloads-chart-radio-button { + margin-left: 10px; + } + .downloads-chart-frame { fill:none; stroke-width: 1; From b42de8ace10ca80e3bf29020359c3770b20e79ae Mon Sep 17 00:00:00 2001 From: Sarah Zakarias Date: Tue, 4 Feb 2025 12:19:59 +0000 Subject: [PATCH 2/2] comments --- app/lib/frontend/dom/material.dart | 10 +++---- .../templates/views/pkg/score_tab.dart | 10 +++---- .../src/widget/downloads_chart/widget.dart | 27 +++++++++++-------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/app/lib/frontend/dom/material.dart b/app/lib/frontend/dom/material.dart index 62d53a94a7..c7c1464435 100644 --- a/app/lib/frontend/dom/material.dart +++ b/app/lib/frontend/dom/material.dart @@ -439,8 +439,8 @@ d.Node option({ d.Node radioButtons({ required String name, - required List<({String id, String value})> idsAndValues, - String? checkedId, + required List<({String label, String value, String id})> radios, + String? initialValue, Iterable? classes, String? leadingText, }) { @@ -448,7 +448,7 @@ d.Node radioButtons({ if (leadingText != null) { nodes.add(d.strong(text: leadingText)); } - idsAndValues.forEach((e) { + radios.forEach((e) { nodes.add(d.input( id: e.id, type: 'radio', @@ -457,9 +457,9 @@ d.Node radioButtons({ classes: [ ...?classes, ], - attributes: {if (e.id == checkedId) 'checked': ''}, + attributes: {if (e.value == initialValue) 'checked': ''}, )); - nodes.add(d.label(attributes: {'for': e.id}, child: d.text(e.value))); + nodes.add(d.label(attributes: {'for': e.id}, child: d.text(e.label))); }); return d.div(classes: ['mdc-form-field'], children: nodes); diff --git a/app/lib/frontend/templates/views/pkg/score_tab.dart b/app/lib/frontend/templates/views/pkg/score_tab.dart index a4cf66c803..c061924308 100644 --- a/app/lib/frontend/templates/views/pkg/score_tab.dart +++ b/app/lib/frontend/templates/views/pkg/score_tab.dart @@ -186,13 +186,13 @@ d.Node _downloadsChart(WeeklyVersionDownloadCounts weeklyVersionDownloads) { radioButtons( leadingText: 'By versions: ', name: 'version-modes', - idsAndValues: [ - (id: 'major', value: 'Major'), - (id: 'minor', value: 'Minor'), - (id: 'patch', value: 'Patch') + radios: [ + (id: 'version-modes-major', value: 'major', label: 'Major'), + (id: 'version-modes-minor', value: 'minor', label: 'Minor'), + (id: 'version-modes-patch', value: 'patch', label: 'Patch') ], classes: ['downloads-chart-radio-button'], - checkedId: 'major') + initialValue: 'major') ], ); final container = d.div( diff --git a/pkg/web_app/lib/src/widget/downloads_chart/widget.dart b/pkg/web_app/lib/src/widget/downloads_chart/widget.dart index 1e23801cd6..7d1191aa72 100644 --- a/pkg/web_app/lib/src/widget/downloads_chart/widget.dart +++ b/pkg/web_app/lib/src/widget/downloads_chart/widget.dart @@ -9,6 +9,7 @@ import 'package:_pub_shared/data/download_counts_data.dart'; import 'package:_pub_shared/format/date_format.dart'; import 'package:_pub_shared/format/number_format.dart'; import 'package:web/web.dart'; +import 'package:web_app/src/web_util.dart'; import 'computations.dart'; @@ -64,20 +65,24 @@ void create(HTMLElement element, Map options) { ); final versionModesLists = { - 'Major': majorDisplayLists, - 'Minor': minorDisplayLists, - 'Patch': patchDisplayLists + 'major': majorDisplayLists, + 'minor': minorDisplayLists, + 'patch': patchDisplayLists }; - final versionModes = document.getElementsByName(versionsRadio); - for (int i = 0; i < versionModes.length; i++) { - final radioButton = versionModes.item(i) as HTMLInputElement; - if (versionModesLists[radioButton.value] != null) { - radioButton.onClick.listen((e) { - drawChart(svg, versionModesLists[radioButton.value]!, data.newestDate); - }); + final versionModes = document.getElementsByName(versionsRadio).toList(); + versionModes.forEach((i) { + final radioButton = i as HTMLInputElement; + final value = radioButton.value; + final displayList = versionModesLists[value]; + + if (displayList == null) { + throw UnsupportedError('Unsupported versions-radio value: "$value"'); } - } + radioButton.onClick.listen((e) { + drawChart(svg, displayList, data.newestDate); + }); + }); drawChart(svg, majorDisplayLists, data.newestDate); }