Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/lib/frontend/templates/views/pkg/score_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
2 changes: 2 additions & 0 deletions app/test/frontend/golden/pkg_score_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ <h1>Weekly downloads</h1>
<label for="display-modes-unstacked">Unstacked</label>
<input id="version-modes-stacked" class="downloads-chart-radio-button" type="radio" name="display-modes" value="stacked"/>
<label for="version-modes-stacked">Stacked</label>
<input id="version-modes-percentage" class="downloads-chart-radio-button" type="radio" name="display-modes" value="percentage"/>
<label for="version-modes-percentage">Percentage</label>
</div>
</div>
<div class="downloads-chart-version-modes">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ <h1>Weekly downloads</h1>
<label for="display-modes-unstacked">Unstacked</label>
<input id="version-modes-stacked" class="downloads-chart-radio-button" type="radio" name="display-modes" value="stacked"/>
<label for="version-modes-stacked">Stacked</label>
<input id="version-modes-percentage" class="downloads-chart-radio-button" type="radio" name="display-modes" value="percentage"/>
<label for="version-modes-percentage">Percentage</label>
</div>
</div>
<div class="downloads-chart-version-modes">
Expand Down
86 changes: 63 additions & 23 deletions pkg/web_app/lib/src/widget/downloads_chart/widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ String squareColorClass(int i) => 'downloads-chart-square-${colors[i]}';
enum DisplayMode {
stacked,
unstacked,
percentage,
}

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

final majorDisplayLists = prepareWeekLists(
data.totalWeeklyDownloads,
Expand Down Expand Up @@ -106,14 +109,21 @@ void create(HTMLElement element, Map<String, String> 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 = <String, DisplayMode>{
'stacked': DisplayMode.stacked,
'unstacked': DisplayMode.unstacked
'unstacked': DisplayMode.unstacked,
'percentage': DisplayMode.percentage,
};

final displayModes = document.getElementsByName(displayRadio).toList();
Expand All @@ -131,19 +141,32 @@ void create(HTMLElement element, Map<String, String> 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(
Element svg,
HTMLDivElement toolTip,
({List<String> ranges, List<List<int>> weekLists}) displayLists,
DateTime newestDate,
List<int> totals,
{DisplayMode displayMode = DisplayMode.unstacked}) {
final ranges = displayLists.ranges;
final values = displayLists.weekLists;
Expand All @@ -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<List<int>> values) {
final maxDownloads = displayMode == DisplayMode.unstacked
? values.fold<int>(1, (a, b) => math.max<int>(a, b.reduce(math.max)))
: values.fold<int>(
1, (a, b) => math.max<int>(a, b.reduce((x, y) => x + y)));
final maxDownloads = switch (displayMode) {
DisplayMode.unstacked =>
values.fold<int>(1, (a, b) => math.max<int>(a, b.reduce(math.max))),
DisplayMode.stacked => values.fold<int>(
1, (a, b) => math.max<int>(a, b.reduce((x, y) => x + y))),
_ => 100 // percentage
};

final digits = maxDownloads.toString().length;
final buffer = StringBuffer()..write('1');
Expand All @@ -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 +
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -288,19 +316,24 @@ void drawChart(

// Chart lines and legends

final lastestDownloads = List.filled(values.length, 0);
final latestDownloads = List<num>.filled(values.length, 0);
final lines = <List<(double, double)>>[];
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);
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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)
Expand Down