diff --git a/app/lib/frontend/templates/views/pkg/score_tab.dart b/app/lib/frontend/templates/views/pkg/score_tab.dart
index be621b7915..aa2a3ce60e 100644
--- a/app/lib/frontend/templates/views/pkg/score_tab.dart
+++ b/app/lib/frontend/templates/views/pkg/score_tab.dart
@@ -219,6 +219,11 @@ d.Node _downloadsChart(WeeklyVersionDownloadCounts weeklyVersionDownloads) {
value: 'stacked',
label: 'Stacked',
),
+ (
+ id: 'version-modes-percentage',
+ value: 'percentage',
+ label: 'Percentage',
+ ),
],
classes: ['downloads-chart-radio-button'],
initialValue: 'unstacked')
diff --git a/app/test/frontend/golden/pkg_score_page.html b/app/test/frontend/golden/pkg_score_page.html
index 8f60450a61..ec19dbb5ae 100644
--- a/app/test/frontend/golden/pkg_score_page.html
+++ b/app/test/frontend/golden/pkg_score_page.html
@@ -314,6 +314,8 @@
Weekly downloads
+
+
diff --git a/app/test/frontend/golden/pkg_score_page_with_downloads_chart.html b/app/test/frontend/golden/pkg_score_page_with_downloads_chart.html
index 8f60450a61..ec19dbb5ae 100644
--- a/app/test/frontend/golden/pkg_score_page_with_downloads_chart.html
+++ b/app/test/frontend/golden/pkg_score_page_with_downloads_chart.html
@@ -314,6 +314,8 @@
Weekly downloads
+
+
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 c226ec5a12..1db34f3586 100644
--- a/pkg/web_app/lib/src/widget/downloads_chart/widget.dart
+++ b/pkg/web_app/lib/src/widget/downloads_chart/widget.dart
@@ -29,6 +29,7 @@ String squareColorClass(int i) => 'downloads-chart-square-${colors[i]}';
enum DisplayMode {
stacked,
unstacked,
+ percentage,
}
void create(HTMLElement element, Map options) {
@@ -64,6 +65,8 @@ void create(HTMLElement element, Map options) {
.fuse(json.decoder)
.convert(base64Decode(dataPoints)) as Map));
final weeksToDisplay = math.min(40, data.totalWeeklyDownloads.length);
+ final totals =
+ data.totalWeeklyDownloads.sublist(0, weeksToDisplay).reversed.toList();
final majorDisplayLists = prepareWeekLists(
data.totalWeeklyDownloads,
@@ -106,14 +109,21 @@ void create(HTMLElement element, Map options) {
svg = createNewSvg();
element.append(svg);
currentDisplayList = displayList;
- drawChart(svg, toolTip, displayList, data.newestDate,
- displayMode: currentDisplayMode);
+ drawChart(
+ svg,
+ toolTip,
+ displayList,
+ data.newestDate,
+ totals,
+ displayMode: currentDisplayMode,
+ );
});
});
final displayModesMap = {
'stacked': DisplayMode.stacked,
- 'unstacked': DisplayMode.unstacked
+ 'unstacked': DisplayMode.unstacked,
+ 'percentage': DisplayMode.percentage,
};
final displayModes = document.getElementsByName(displayRadio).toList();
@@ -131,12 +141,24 @@ void create(HTMLElement element, Map options) {
svg = createNewSvg();
element.append(svg);
currentDisplayMode = displayMode;
- drawChart(svg, toolTip, currentDisplayList, data.newestDate,
- displayMode: displayMode);
+ drawChart(
+ svg,
+ toolTip,
+ currentDisplayList,
+ data.newestDate,
+ totals,
+ displayMode: displayMode,
+ );
});
});
- drawChart(svg, toolTip, majorDisplayLists, data.newestDate);
+ drawChart(
+ svg,
+ toolTip,
+ majorDisplayLists,
+ data.newestDate,
+ totals,
+ );
}
void drawChart(
@@ -144,6 +166,7 @@ void drawChart(
HTMLDivElement toolTip,
({List ranges, List> weekLists}) displayLists,
DateTime newestDate,
+ List totals,
{DisplayMode displayMode = DisplayMode.unstacked}) {
final ranges = displayLists.ranges;
final values = displayLists.weekLists;
@@ -169,10 +192,13 @@ void drawChart(
/// Computes max value on y-axis such that we get a nice division for the
/// interval length between the numbers shown by the ticks on the y axis.
(int maxY, int interval) computeMaxYAndInterval(List> values) {
- final maxDownloads = displayMode == DisplayMode.unstacked
- ? values.fold(1, (a, b) => math.max(a, b.reduce(math.max)))
- : values.fold(
- 1, (a, b) => math.max(a, b.reduce((x, y) => x + y)));
+ final maxDownloads = switch (displayMode) {
+ DisplayMode.unstacked =>
+ values.fold(1, (a, b) => math.max(a, b.reduce(math.max))),
+ DisplayMode.stacked => values.fold(
+ 1, (a, b) => math.max(a, b.reduce((x, y) => x + y))),
+ _ => 100 // percentage
+ };
final digits = maxDownloads.toString().length;
final buffer = StringBuffer()..write('1');
@@ -195,7 +221,7 @@ void drawChart(
final firstDate = computeDateForWeekNumber(newestDate, values.length, 0);
final xAxisSpan = newestDate.difference(firstDate);
- (double, double) computeCoordinates(DateTime date, int downloads) {
+ (double, double) computeCoordinates(DateTime date, num downloads) {
final duration = date.difference(firstDate);
// We don't risk division by 0 here, since `xAxisSpan` is a non-zero duration.
final x = leftPadding +
@@ -257,8 +283,10 @@ void drawChart(
final tickLabel = SVGTextElement();
tickLabel.setAttribute(
'class', 'downloads-chart-tick-label downloads-chart-tick-label-y');
- tickLabel.text =
- '${compactFormat(i * interval).value}${compactFormat(i * interval).suffix}';
+ final suffix = displayMode == DisplayMode.percentage
+ ? '%'
+ : compactFormat(i * interval).suffix;
+ tickLabel.text = '${compactFormat(i * interval).value}$suffix';
tickLabel.setAttribute('x', '${xMax + marginPadding}');
tickLabel.setAttribute('y', '$y');
chart.append(tickLabel);
@@ -288,19 +316,24 @@ void drawChart(
// Chart lines and legends
- final lastestDownloads = List.filled(values.length, 0);
+ final latestDownloads = List.filled(values.length, 0);
final lines = >[];
for (int versionRange = 0; versionRange < values[0].length; versionRange++) {
final List<(double, double)> lineCoordinates = <(double, double)>[];
for (int week = 0; week < values.length; week++) {
- if (displayMode == DisplayMode.stacked) {
- lastestDownloads[week] += values[week][versionRange];
+ final value = displayMode == DisplayMode.percentage
+ ? values[week][versionRange] * 100 / totals[week]
+ : values[week][versionRange];
+
+ if (displayMode == DisplayMode.unstacked) {
+ latestDownloads[week] = value;
} else {
- lastestDownloads[week] = values[week][versionRange];
+ latestDownloads[week] += value;
}
+
final (x, y) = computeCoordinates(
computeDateForWeekNumber(newestDate, values.length, week),
- lastestDownloads[week]);
+ latestDownloads[week]);
lineCoordinates.add((x, y));
}
lines.add(lineCoordinates);
@@ -349,7 +382,8 @@ void drawChart(
path.setAttribute('clip-path', 'url(#clipRect)');
chart.append(path);
- if (displayMode == DisplayMode.stacked) {
+ if (displayMode == DisplayMode.stacked ||
+ displayMode == DisplayMode.percentage) {
final prevLine = i == lines.length - 1
? [(xZero, yZero), (xMax, yZero)]
: lines[lines.length - 1 - i - 1];
@@ -450,20 +484,26 @@ void drawChart(
final downloads = values[nearestIndex];
for (int i = 0; i < downloads.length; i++) {
- final index = ranges.length - 1 - i;
- if (downloads[index] > 0) {
+ final rangeIndex = ranges.length - 1 - i;
+ if (downloads[rangeIndex] > 0) {
// We only show the exact download count in the tooltip if it is non-zero.
final square = HTMLDivElement()
..setAttribute(
'class', 'downloads-chart-tooltip-square ${squareColorClass(i)}');
- final rangeText = HTMLSpanElement()..text = '${ranges[index]}: ';
+ final rangeText = HTMLSpanElement()..text = '${ranges[rangeIndex]}: ';
final tooltipRange = HTMLDivElement()
..setAttribute('class', 'downloads-chart-tooltip-row')
..append(square)
..append(rangeText);
+
+ final suffix = (displayMode == DisplayMode.percentage)
+ ? '(${(downloads[rangeIndex] * 100 / totals[nearestIndex]).toStringAsPrecision(2)}%)'
+ : '';
+ final text =
+ '${formatWithThousandSeperators(downloads[rangeIndex])}$suffix';
final downloadsText = HTMLSpanElement()
..setAttribute('class', 'downloads-chart-tooltip-downloads')
- ..text = '${formatWithThousandSeperators(downloads[index])}';
+ ..text = text;
final tooltipRow = HTMLDivElement()
..setAttribute('class', 'downloads-chart-tooltip-row')
..append(tooltipRange)