@@ -26,6 +26,11 @@ String strokeColorClass(int i) => 'downloads-chart-stroke-${colors[i]}';
2626String fillColorClass (int i) => 'downloads-chart-fill-${colors [i ]}' ;
2727String squareColorClass (int i) => 'downloads-chart-square-${colors [i ]}' ;
2828
29+ enum DisplayMode {
30+ stacked,
31+ unstacked,
32+ }
33+
2934void create (HTMLElement element, Map <String , String > options) {
3035 final dataPoints = options['points' ];
3136 if (dataPoints == null ) {
@@ -36,6 +41,12 @@ void create(HTMLElement element, Map<String, String> options) {
3641 if (versionsRadio == null ) {
3742 throw UnsupportedError ('data-downloads-chart-versions-radio required' );
3843 }
44+
45+ final displayRadio = options['display-radio' ];
46+ if (displayRadio == null ) {
47+ throw UnsupportedError ('data-downloads-chart-display-radio required' );
48+ }
49+
3950 Element createNewSvg () {
4051 return document.createElementNS ('http://www.w3.org/2000/svg' , 'svg' )
4152 ..setAttribute ('height' , '100%' )
@@ -72,6 +83,9 @@ void create(HTMLElement element, Map<String, String> options) {
7283 weeksToDisplay,
7384 );
7485
86+ var currentDisplayList = majorDisplayLists;
87+ var currentDisplayMode = DisplayMode .unstacked;
88+
7589 final versionModesLists = {
7690 'major' : majorDisplayLists,
7791 'minor' : minorDisplayLists,
@@ -91,7 +105,34 @@ void create(HTMLElement element, Map<String, String> options) {
91105 element.removeChild (svg);
92106 svg = createNewSvg ();
93107 element.append (svg);
94- drawChart (svg, toolTip, displayList, data.newestDate);
108+ currentDisplayList = displayList;
109+ drawChart (svg, toolTip, displayList, data.newestDate,
110+ displayMode: currentDisplayMode);
111+ });
112+ });
113+
114+ final displayModesMap = < String , DisplayMode > {
115+ 'stacked' : DisplayMode .stacked,
116+ 'unstacked' : DisplayMode .unstacked
117+ };
118+
119+ final displayModes = document.getElementsByName (displayRadio).toList ();
120+ displayModes.forEach ((i) {
121+ final radioButton = i as HTMLInputElement ;
122+ final value = radioButton.value;
123+ final displayMode = displayModesMap[value];
124+
125+ if (displayMode == null ) {
126+ throw UnsupportedError ('Unsupported display-radio value: "$value "' );
127+ }
128+
129+ radioButton.onClick.listen ((e) {
130+ element.removeChild (svg);
131+ svg = createNewSvg ();
132+ element.append (svg);
133+ currentDisplayMode = displayMode;
134+ drawChart (svg, toolTip, currentDisplayList, data.newestDate,
135+ displayMode: displayMode);
95136 });
96137 });
97138
@@ -103,7 +144,7 @@ void drawChart(
103144 HTMLDivElement toolTip,
104145 ({List <String > ranges, List <List <int >> weekLists}) displayLists,
105146 DateTime newestDate,
106- {bool stacked = false }) {
147+ {DisplayMode displayMode = DisplayMode .unstacked }) {
107148 final ranges = displayLists.ranges;
108149 final values = displayLists.weekLists;
109150
@@ -128,8 +169,11 @@ void drawChart(
128169 /// Computes max value on y-axis such that we get a nice division for the
129170 /// interval length between the numbers shown by the ticks on the y axis.
130171 (int maxY, int interval) computeMaxYAndInterval (List <List <int >> values) {
131- final maxDownloads =
132- values.fold <int >(1 , (a, b) => math.max <int >(a, b.reduce (math.max)));
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)));
176+
133177 final digits = maxDownloads.toString ().length;
134178 final buffer = StringBuffer ()..write ('1' );
135179 if (digits > 2 ) {
@@ -244,18 +288,49 @@ void drawChart(
244288
245289 // Chart lines and legends
246290
247- final lines = < StringBuffer > [];
291+ final lastestDownloads = List .filled (values.length, 0 );
292+ final lines = < List <(double , double )>> [];
248293 for (int versionRange = 0 ; versionRange < values[0 ].length; versionRange++ ) {
249- final line = StringBuffer ();
250- var c = 'M' ;
294+ final List <(double , double )> lineCoordinates = < (double , double )> [];
251295 for (int week = 0 ; week < values.length; week++ ) {
296+ if (displayMode == DisplayMode .stacked) {
297+ lastestDownloads[week] += values[week][versionRange];
298+ } else {
299+ lastestDownloads[week] = values[week][versionRange];
300+ }
252301 final (x, y) = computeCoordinates (
253302 computeDateForWeekNumber (newestDate, values.length, week),
254- values[week][versionRange]);
255- line.write (' $c $x $y ' );
256- c = 'L' ;
303+ lastestDownloads[week]);
304+ lineCoordinates.add ((x, y));
257305 }
258- lines.add (line);
306+ lines.add (lineCoordinates);
307+ }
308+
309+ StringBuffer computeLinePath (List <(double , double )> coordinates) {
310+ final path = StringBuffer ();
311+ var command = 'M' ;
312+ coordinates.forEach ((c) {
313+ path.write (' $command ${c .$1 } ${c .$2 }' );
314+ command = 'L' ;
315+ });
316+ return path;
317+ }
318+
319+ StringBuffer computeAreaPath (List <(double , double )> topCoordinates,
320+ List <(double , double )> bottomCoordinates) {
321+ final path = StringBuffer ();
322+ var command = 'M' ;
323+ topCoordinates.forEach ((c) {
324+ path.write (' $command ${c .$1 } ${c .$2 }' );
325+ command = 'L' ;
326+ });
327+
328+ bottomCoordinates.reversed.forEach ((c) {
329+ path.write (' $command ${c .$1 } ${c .$2 }' );
330+ command = 'L' ;
331+ });
332+ path.write ('Z' );
333+ return path;
259334 }
260335
261336 double legendX = xZero;
@@ -265,14 +340,27 @@ void drawChart(
265340 final legendHeight = 8 ;
266341
267342 for (int i = 0 ; i < lines.length; i++ ) {
343+ // We add the lines in reverse order so that the newest versions get the
344+ // main colors.
345+ final line = computeLinePath (lines[lines.length - 1 - i]);
268346 final path = SVGPathElement ();
269347 path.setAttribute ('class' , '${strokeColorClass (i )} downloads-chart-line ' );
270- // We assign colors in reverse order so that main colors are chosen first for
271- // the newest versions.
272- path.setAttribute ('d' , '${lines [lines .length - 1 - i ]}' );
348+ path.setAttribute ('d' , '$line ' );
273349 path.setAttribute ('clip-path' , 'url(#clipRect)' );
274350 chart.append (path);
275351
352+ if (displayMode == DisplayMode .stacked) {
353+ final prevLine = i == lines.length - 1
354+ ? [(xZero, yZero), (xMax, yZero)]
355+ : lines[lines.length - 1 - i - 1 ];
356+ final areaPath = computeAreaPath (lines[lines.length - 1 - i], prevLine);
357+ final area = SVGPathElement ();
358+ area.setAttribute ('class' , '${fillColorClass (i )} downloads-chart-area ' );
359+ area.setAttribute ('d' , '$areaPath ' );
360+ area.setAttribute ('clip-path' , 'url(#clipRect)' );
361+ chart.append (area);
362+ }
363+
276364 final legend = SVGRectElement ();
277365 chart.append (legend);
278366 legend.setAttribute ('class' ,
0 commit comments