@@ -10,6 +10,7 @@ import 'package:_pub_shared/format/date_format.dart';
1010import 'package:_pub_shared/format/number_format.dart' ;
1111import 'package:web/web.dart' ;
1212import 'package:web_app/src/web_util.dart' ;
13+ import 'package:web_app/src/widget/widget.dart' ;
1314
1415import 'computations.dart' ;
1516
@@ -29,6 +30,7 @@ String squareColorClass(int i) => 'downloads-chart-square-${colors[i]}';
2930enum DisplayMode {
3031 stacked,
3132 unstacked,
33+ percentage,
3234}
3335
3436void create (HTMLElement element, Map <String , String > options) {
@@ -64,6 +66,8 @@ void create(HTMLElement element, Map<String, String> options) {
6466 .fuse (json.decoder)
6567 .convert (base64Decode (dataPoints)) as Map <String , dynamic >));
6668 final weeksToDisplay = math.min (40 , data.totalWeeklyDownloads.length);
69+ final totals =
70+ data.totalWeeklyDownloads.sublist (0 , weeksToDisplay).reversed.toList ();
6771
6872 final majorDisplayLists = prepareWeekLists (
6973 data.totalWeeklyDownloads,
@@ -106,14 +110,21 @@ void create(HTMLElement element, Map<String, String> options) {
106110 svg = createNewSvg ();
107111 element.append (svg);
108112 currentDisplayList = displayList;
109- drawChart (svg, toolTip, displayList, data.newestDate,
110- displayMode: currentDisplayMode);
113+ drawChart (
114+ svg,
115+ toolTip,
116+ displayList,
117+ data.newestDate,
118+ totals,
119+ displayMode: currentDisplayMode,
120+ );
111121 });
112122 });
113123
114124 final displayModesMap = < String , DisplayMode > {
115125 'stacked' : DisplayMode .stacked,
116- 'unstacked' : DisplayMode .unstacked
126+ 'unstacked' : DisplayMode .unstacked,
127+ 'percentage' : DisplayMode .percentage,
117128 };
118129
119130 final displayModes = document.getElementsByName (displayRadio).toList ();
@@ -131,19 +142,32 @@ void create(HTMLElement element, Map<String, String> options) {
131142 svg = createNewSvg ();
132143 element.append (svg);
133144 currentDisplayMode = displayMode;
134- drawChart (svg, toolTip, currentDisplayList, data.newestDate,
135- displayMode: displayMode);
145+ drawChart (
146+ svg,
147+ toolTip,
148+ currentDisplayList,
149+ data.newestDate,
150+ totals,
151+ displayMode: displayMode,
152+ );
136153 });
137154 });
138155
139- drawChart (svg, toolTip, majorDisplayLists, data.newestDate);
156+ drawChart (
157+ svg,
158+ toolTip,
159+ majorDisplayLists,
160+ data.newestDate,
161+ totals,
162+ );
140163}
141164
142165void drawChart (
143166 Element svg,
144167 HTMLDivElement toolTip,
145168 ({List <String > ranges, List <List <int >> weekLists}) displayLists,
146169 DateTime newestDate,
170+ List <int > totals,
147171 {DisplayMode displayMode = DisplayMode .unstacked}) {
148172 final ranges = displayLists.ranges;
149173 final values = displayLists.weekLists;
@@ -169,10 +193,13 @@ void drawChart(
169193 /// Computes max value on y-axis such that we get a nice division for the
170194 /// interval length between the numbers shown by the ticks on the y axis.
171195 (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)));
196+ final maxDownloads = switch (displayMode) {
197+ DisplayMode .unstacked =>
198+ values.fold <int >(1 , (a, b) => math.max <int >(a, b.reduce (math.max))),
199+ DisplayMode .stacked => values.fold <int >(
200+ 1 , (a, b) => math.max <int >(a, b.reduce ((x, y) => x + y))),
201+ _ => 100 // percentage
202+ };
176203
177204 final digits = maxDownloads.toString ().length;
178205 final buffer = StringBuffer ()..write ('1' );
@@ -195,7 +222,7 @@ void drawChart(
195222 final firstDate = computeDateForWeekNumber (newestDate, values.length, 0 );
196223 final xAxisSpan = newestDate.difference (firstDate);
197224
198- (double , double ) computeCoordinates (DateTime date, int downloads) {
225+ (double , double ) computeCoordinates (DateTime date, num downloads) {
199226 final duration = date.difference (firstDate);
200227 // We don't risk division by 0 here, since `xAxisSpan` is a non-zero duration.
201228 final x = leftPadding +
@@ -257,8 +284,10 @@ void drawChart(
257284 final tickLabel = SVGTextElement ();
258285 tickLabel.setAttribute (
259286 'class' , 'downloads-chart-tick-label downloads-chart-tick-label-y' );
260- tickLabel.text =
261- '${compactFormat (i * interval ).value }${compactFormat (i * interval ).suffix }' ;
287+ final suffix = displayMode == DisplayMode .percentage
288+ ? '%'
289+ : compactFormat (i * interval).suffix;
290+ tickLabel.text = '${compactFormat (i * interval ).value }$suffix ' ;
262291 tickLabel.setAttribute ('x' , '${xMax + marginPadding }' );
263292 tickLabel.setAttribute ('y' , '$y ' );
264293 chart.append (tickLabel);
@@ -288,19 +317,24 @@ void drawChart(
288317
289318 // Chart lines and legends
290319
291- final lastestDownloads = List .filled (values.length, 0 );
320+ final latestDownloads = List < num > .filled (values.length, 0 );
292321 final lines = < List <(double , double )>> [];
293322 for (int versionRange = 0 ; versionRange < values[0 ].length; versionRange++ ) {
294323 final List <(double , double )> lineCoordinates = < (double , double )> [];
295324 for (int week = 0 ; week < values.length; week++ ) {
296- if (displayMode == DisplayMode .stacked) {
297- lastestDownloads[week] += values[week][versionRange];
325+ final value = displayMode == DisplayMode .percentage
326+ ? values[week][versionRange] * 100 / totals[week]
327+ : values[week][versionRange];
328+
329+ if (displayMode == DisplayMode .unstacked) {
330+ latestDownloads[week] = value;
298331 } else {
299- lastestDownloads [week] = values[week][versionRange] ;
332+ latestDownloads [week] += value ;
300333 }
334+
301335 final (x, y) = computeCoordinates (
302336 computeDateForWeekNumber (newestDate, values.length, week),
303- lastestDownloads [week]);
337+ latestDownloads [week]);
304338 lineCoordinates.add ((x, y));
305339 }
306340 lines.add (lineCoordinates);
@@ -349,7 +383,8 @@ void drawChart(
349383 path.setAttribute ('clip-path' , 'url(#clipRect)' );
350384 chart.append (path);
351385
352- if (displayMode == DisplayMode .stacked) {
386+ if (displayMode == DisplayMode .stacked ||
387+ displayMode == DisplayMode .percentage) {
353388 final prevLine = i == lines.length - 1
354389 ? [(xZero, yZero), (xMax, yZero)]
355390 : lines[lines.length - 1 - i - 1 ];
@@ -450,20 +485,26 @@ void drawChart(
450485
451486 final downloads = values[nearestIndex];
452487 for (int i = 0 ; i < downloads.length; i++ ) {
453- final index = ranges.length - 1 - i;
454- if (downloads[index ] > 0 ) {
488+ final rangeIndex = ranges.length - 1 - i;
489+ if (downloads[rangeIndex ] > 0 ) {
455490 // We only show the exact download count in the tooltip if it is non-zero.
456491 final square = HTMLDivElement ()
457492 ..setAttribute (
458493 'class' , 'downloads-chart-tooltip-square ${squareColorClass (i )}' );
459- final rangeText = HTMLSpanElement ()..text = '${ranges [index ]}: ' ;
494+ final rangeText = HTMLSpanElement ()..text = '${ranges [rangeIndex ]}: ' ;
460495 final tooltipRange = HTMLDivElement ()
461496 ..setAttribute ('class' , 'downloads-chart-tooltip-row' )
462497 ..append (square)
463498 ..append (rangeText);
499+
500+ final suffix = (displayMode == DisplayMode .percentage)
501+ ? '(${(downloads [rangeIndex ] * 100 / totals [nearestIndex ]).toStringAsPrecision (2 )}%)'
502+ : '' ;
503+ final text =
504+ '${formatWithThousandSeperators (downloads [rangeIndex ])}$suffix ' ;
464505 final downloadsText = HTMLSpanElement ()
465506 ..setAttribute ('class' , 'downloads-chart-tooltip-downloads' )
466- ..text = '${ formatWithThousandSeperators ( downloads [ index ])}' ;
507+ ..text = text ;
467508 final tooltipRow = HTMLDivElement ()
468509 ..setAttribute ('class' , 'downloads-chart-tooltip-row' )
469510 ..append (tooltipRange)
0 commit comments