Skip to content

Commit 08c0c05

Browse files
committed
Downloads chart: Add option to display as percentage
1 parent e21442b commit 08c0c05

File tree

2 files changed

+68
-23
lines changed

2 files changed

+68
-23
lines changed

app/lib/frontend/templates/views/pkg/score_tab.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ d.Node _downloadsChart(WeeklyVersionDownloadCounts weeklyVersionDownloads) {
219219
value: 'stacked',
220220
label: 'Stacked',
221221
),
222+
(
223+
id: 'version-modes-percentage',
224+
value: 'percentage',
225+
label: 'Percentage',
226+
),
222227
],
223228
classes: ['downloads-chart-radio-button'],
224229
initialValue: 'unstacked')

pkg/web_app/lib/src/widget/downloads_chart/widget.dart

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ String squareColorClass(int i) => 'downloads-chart-square-${colors[i]}';
2929
enum DisplayMode {
3030
stacked,
3131
unstacked,
32+
percentage,
3233
}
3334

3435
void create(HTMLElement element, Map<String, String> options) {
@@ -64,6 +65,8 @@ void create(HTMLElement element, Map<String, String> options) {
6465
.fuse(json.decoder)
6566
.convert(base64Decode(dataPoints)) as Map<String, dynamic>));
6667
final weeksToDisplay = math.min(40, data.totalWeeklyDownloads.length);
68+
final totals =
69+
data.totalWeeklyDownloads.sublist(0, weeksToDisplay).reversed.toList();
6770

6871
final majorDisplayLists = prepareWeekLists(
6972
data.totalWeeklyDownloads,
@@ -106,14 +109,21 @@ void create(HTMLElement element, Map<String, String> options) {
106109
svg = createNewSvg();
107110
element.append(svg);
108111
currentDisplayList = displayList;
109-
drawChart(svg, toolTip, displayList, data.newestDate,
110-
displayMode: currentDisplayMode);
112+
drawChart(
113+
svg,
114+
toolTip,
115+
displayList,
116+
data.newestDate,
117+
totals,
118+
displayMode: currentDisplayMode,
119+
);
111120
});
112121
});
113122

114123
final displayModesMap = <String, DisplayMode>{
115124
'stacked': DisplayMode.stacked,
116-
'unstacked': DisplayMode.unstacked
125+
'unstacked': DisplayMode.unstacked,
126+
'percentage': DisplayMode.percentage,
117127
};
118128

119129
final displayModes = document.getElementsByName(displayRadio).toList();
@@ -131,19 +141,32 @@ void create(HTMLElement element, Map<String, String> options) {
131141
svg = createNewSvg();
132142
element.append(svg);
133143
currentDisplayMode = displayMode;
134-
drawChart(svg, toolTip, currentDisplayList, data.newestDate,
135-
displayMode: displayMode);
144+
drawChart(
145+
svg,
146+
toolTip,
147+
currentDisplayList,
148+
data.newestDate,
149+
totals,
150+
displayMode: displayMode,
151+
);
136152
});
137153
});
138154

139-
drawChart(svg, toolTip, majorDisplayLists, data.newestDate);
155+
drawChart(
156+
svg,
157+
toolTip,
158+
majorDisplayLists,
159+
data.newestDate,
160+
totals,
161+
);
140162
}
141163

142164
void drawChart(
143165
Element svg,
144166
HTMLDivElement toolTip,
145167
({List<String> ranges, List<List<int>> weekLists}) displayLists,
146168
DateTime newestDate,
169+
List<int> totals,
147170
{DisplayMode displayMode = DisplayMode.unstacked}) {
148171
final ranges = displayLists.ranges;
149172
final values = displayLists.weekLists;
@@ -169,10 +192,13 @@ void drawChart(
169192
/// Computes max value on y-axis such that we get a nice division for the
170193
/// interval length between the numbers shown by the ticks on the y axis.
171194
(int maxY, int interval) computeMaxYAndInterval(List<List<int>> values) {
172-
final maxDownloads = displayMode == DisplayMode.unstacked
173-
? values.fold<int>(1, (a, b) => math.max<int>(a, b.reduce(math.max)))
174-
: values.fold<int>(
175-
1, (a, b) => math.max<int>(a, b.reduce((x, y) => x + y)));
195+
final maxDownloads = switch (displayMode) {
196+
DisplayMode.unstacked =>
197+
values.fold<int>(1, (a, b) => math.max<int>(a, b.reduce(math.max))),
198+
DisplayMode.stacked => values.fold<int>(
199+
1, (a, b) => math.max<int>(a, b.reduce((x, y) => x + y))),
200+
_ => 100 // percentage
201+
};
176202

177203
final digits = maxDownloads.toString().length;
178204
final buffer = StringBuffer()..write('1');
@@ -195,7 +221,7 @@ void drawChart(
195221
final firstDate = computeDateForWeekNumber(newestDate, values.length, 0);
196222
final xAxisSpan = newestDate.difference(firstDate);
197223

198-
(double, double) computeCoordinates(DateTime date, int downloads) {
224+
(double, double) computeCoordinates(DateTime date, num downloads) {
199225
final duration = date.difference(firstDate);
200226
// We don't risk division by 0 here, since `xAxisSpan` is a non-zero duration.
201227
final x = leftPadding +
@@ -257,8 +283,10 @@ void drawChart(
257283
final tickLabel = SVGTextElement();
258284
tickLabel.setAttribute(
259285
'class', 'downloads-chart-tick-label downloads-chart-tick-label-y');
260-
tickLabel.text =
261-
'${compactFormat(i * interval).value}${compactFormat(i * interval).suffix}';
286+
final suffix = displayMode == DisplayMode.percentage
287+
? '%'
288+
: compactFormat(i * interval).suffix;
289+
tickLabel.text = '${compactFormat(i * interval).value}$suffix';
262290
tickLabel.setAttribute('x', '${xMax + marginPadding}');
263291
tickLabel.setAttribute('y', '$y');
264292
chart.append(tickLabel);
@@ -288,19 +316,24 @@ void drawChart(
288316

289317
// Chart lines and legends
290318

291-
final lastestDownloads = List.filled(values.length, 0);
319+
final latestDownloads = List<num>.filled(values.length, 0);
292320
final lines = <List<(double, double)>>[];
293321
for (int versionRange = 0; versionRange < values[0].length; versionRange++) {
294322
final List<(double, double)> lineCoordinates = <(double, double)>[];
295323
for (int week = 0; week < values.length; week++) {
296-
if (displayMode == DisplayMode.stacked) {
297-
lastestDownloads[week] += values[week][versionRange];
324+
final value = displayMode == DisplayMode.percentage
325+
? values[week][versionRange] * 100 / totals[week]
326+
: values[week][versionRange];
327+
328+
if (displayMode == DisplayMode.unstacked) {
329+
latestDownloads[week] = value;
298330
} else {
299-
lastestDownloads[week] = values[week][versionRange];
331+
latestDownloads[week] += value;
300332
}
333+
301334
final (x, y) = computeCoordinates(
302335
computeDateForWeekNumber(newestDate, values.length, week),
303-
lastestDownloads[week]);
336+
latestDownloads[week]);
304337
lineCoordinates.add((x, y));
305338
}
306339
lines.add(lineCoordinates);
@@ -349,7 +382,8 @@ void drawChart(
349382
path.setAttribute('clip-path', 'url(#clipRect)');
350383
chart.append(path);
351384

352-
if (displayMode == DisplayMode.stacked) {
385+
if (displayMode == DisplayMode.stacked ||
386+
displayMode == DisplayMode.percentage) {
353387
final prevLine = i == lines.length - 1
354388
? [(xZero, yZero), (xMax, yZero)]
355389
: lines[lines.length - 1 - i - 1];
@@ -450,20 +484,26 @@ void drawChart(
450484

451485
final downloads = values[nearestIndex];
452486
for (int i = 0; i < downloads.length; i++) {
453-
final index = ranges.length - 1 - i;
454-
if (downloads[index] > 0) {
487+
final rangeIndex = ranges.length - 1 - i;
488+
if (downloads[rangeIndex] > 0) {
455489
// We only show the exact download count in the tooltip if it is non-zero.
456490
final square = HTMLDivElement()
457491
..setAttribute(
458492
'class', 'downloads-chart-tooltip-square ${squareColorClass(i)}');
459-
final rangeText = HTMLSpanElement()..text = '${ranges[index]}: ';
493+
final rangeText = HTMLSpanElement()..text = '${ranges[rangeIndex]}: ';
460494
final tooltipRange = HTMLDivElement()
461495
..setAttribute('class', 'downloads-chart-tooltip-row')
462496
..append(square)
463497
..append(rangeText);
498+
499+
final suffix = (displayMode == DisplayMode.percentage)
500+
? '(${(downloads[rangeIndex] * 100 / totals[nearestIndex]).toStringAsPrecision(2)}%)'
501+
: '';
502+
final text =
503+
'${formatWithThousandSeperators(downloads[rangeIndex])}$suffix';
464504
final downloadsText = HTMLSpanElement()
465505
..setAttribute('class', 'downloads-chart-tooltip-downloads')
466-
..text = '${formatWithThousandSeperators(downloads[index])}';
506+
..text = text;
467507
final tooltipRow = HTMLDivElement()
468508
..setAttribute('class', 'downloads-chart-tooltip-row')
469509
..append(tooltipRange)

0 commit comments

Comments
 (0)