@@ -12,23 +12,17 @@ import 'package:web/web.dart';
1212
1313import 'computations.dart' ;
1414
15- const lineColorClasses = [
16- 'downloads-chart-line-color- blue' ,
17- 'downloads-chart-line-color- red' ,
18- 'downloads-chart-line-color- green' ,
19- 'downloads-chart-line-color- purple' ,
20- 'downloads-chart-line-color- orange' ,
21- 'downloads-chart-line-color- turquoise' ,
15+ const colors = [
16+ 'blue' ,
17+ 'red' ,
18+ 'green' ,
19+ 'purple' ,
20+ 'orange' ,
21+ 'turquoise' ,
2222];
2323
24- const legendColorClasses = [
25- 'downloads-chart-legend-color-blue' ,
26- 'downloads-chart-legend-color-red' ,
27- 'downloads-chart-legend-color-green' ,
28- 'downloads-chart-legend-color-purple' ,
29- 'downloads-chart-legend-color-orange' ,
30- 'downloads-chart-legend-color-turquoise' ,
31- ];
24+ String strokeColorClass (int i) => 'downloads-chart-stroke-${colors [i ]}' ;
25+ String fillColorClass (int i) => 'downloads-chart-fill-${colors [i ]}' ;
3226
3327void create (HTMLElement element, Map <String , String > options) {
3428 final dataPoints = options['points' ];
@@ -61,11 +55,13 @@ void create(HTMLElement element, Map<String, String> options) {
6155void drawChart (Element svg, List <String > ranges, List <List <int >> values,
6256 DateTime newestDate,
6357 {bool stacked = false }) {
64- final width = 775 ; // TODO(zarah): make this width dynamic
58+ if (values.isEmpty) return ;
59+ final frameWidth =
60+ 775 ; // TODO(zarah): Investigate if this width can be dynamic
6561 final topPadding = 30 ;
6662 final leftPadding = 30 ;
6763 final rightPadding = 70 ; // make extra room for labels on y-axis
68- final drawingWidth = width - leftPadding - rightPadding;
64+ final chartWidth = frameWidth - leftPadding - rightPadding;
6965 final chartheight = 420 ;
7066
7167 DateTime computeDateForWeekNumber (
@@ -75,7 +71,7 @@ void drawChart(Element svg, List<String> ranges, List<List<int>> values,
7571 }
7672
7773 // Computes max value on y-axis such that we get a nice division for the
78- // interval length between the numbers shown by the tics on the y axis.
74+ // interval length between the numbers shown by the ticks on the y axis.
7975 (int maxY, int interval) computeMaxYAndInterval (List <List <int >> values) {
8076 final maxDownloads =
8177 values.fold <int >(1 , (a, b) => math.max <int >(a, b.reduce (math.max)));
@@ -98,81 +94,85 @@ void drawChart(Element svg, List<String> ranges, List<List<int>> values,
9894
9995 final (maxY, interval) = computeMaxYAndInterval (values);
10096 final firstDate = computeDateForWeekNumber (newestDate, values.length, 0 );
97+ final xAxisSpan = newestDate.difference (firstDate);
10198
10299 (double , double ) computeCoordinates (DateTime date, int downloads) {
103- final xAxisSpan = newestDate.difference (firstDate);
104100 final duration = date.difference (firstDate);
101+ // We don't risk division by 0 here, since `xAxisSpan` is a non-zero duration.
105102 final x = leftPadding +
106- drawingWidth * duration.inMilliseconds / xAxisSpan.inMilliseconds;
103+ chartWidth * duration.inMilliseconds / xAxisSpan.inMilliseconds;
104+
107105 final y = topPadding + (chartheight - chartheight * (downloads / maxY));
108106 return (x, y);
109107 }
110108
111109 final chart = SVGGElement ();
112110 svg.append (chart);
113111
114- // Axis and tics
112+ // Axis and ticks
115113
116114 final (xZero, yZero) = computeCoordinates (firstDate, 0 );
117115 final (xMax, yMax) = computeCoordinates (newestDate, maxY);
118116 final lineThickness = 1 ;
119117 final padding = 8 ;
120- final ticLength = 10 ;
121- final ticLabelYCoor = yZero + ticLength + 2 * padding;
118+ final labelPadding = 16 ;
119+ final tickLength = 10 ;
120+ final tickLabelYCoordinate = yZero + tickLength + labelPadding;
122121
123122 final xaxis = SVGPathElement ();
124123 xaxis.setAttribute ('class' , 'downloads-chart-x-axis' );
125124 // We add half of the line thickness at both ends of the x-axis so that it
126- // covers the vertical tics at the end.
125+ // covers the vertical ticks at the end.
127126 xaxis.setAttribute ('d' ,
128127 'M${xZero - (lineThickness / 2 )} $yZero L${xMax + (lineThickness / 2 )} $yZero ' );
129128 chart.append (xaxis);
130129
131- var firstTicLabel = SVGTextElement ();
130+ late SVGTextElement firstTickLabel;
131+ // place a tick every 4 weeks
132132 for (int week = 0 ; week < values.length; week += 4 ) {
133133 final date = computeDateForWeekNumber (newestDate, values.length, week);
134134 final (x, y) = computeCoordinates (date, 0 );
135135
136- final tic = SVGPathElement ();
137- tic .setAttribute ('class' , 'downloads-chart-x-axis' );
138- tic .setAttribute ('d' , 'M$x $y l0 $ticLength ' );
139- chart.append (tic );
136+ final tick = SVGPathElement ();
137+ tick .setAttribute ('class' , 'downloads-chart-x-axis' );
138+ tick .setAttribute ('d' , 'M$x $y l0 $tickLength ' );
139+ chart.append (tick );
140140
141- final ticLabel = SVGTextElement ();
142- chart.append (ticLabel );
143- ticLabel .setAttribute (
144- 'class' , 'downloads-chart-tic -label downloads-chart-tic -label-x' );
145- ticLabel .text = formatAbbrMonthDay (date);
146- ticLabel .setAttribute ('y' , '$ticLabelYCoor ' );
147- ticLabel .setAttribute ('x' , '$x ' );
141+ final tickLabel = SVGTextElement ();
142+ chart.append (tickLabel );
143+ tickLabel .setAttribute (
144+ 'class' , 'downloads-chart-tick -label downloads-chart-tick -label-x' );
145+ tickLabel .text = formatAbbrMonthDay (date);
146+ tickLabel .setAttribute ('y' , '$tickLabelYCoordinate ' );
147+ tickLabel .setAttribute ('x' , '$x ' );
148148
149149 if (week == 0 ) {
150- firstTicLabel = ticLabel ;
150+ firstTickLabel = tickLabel ;
151151 }
152152 }
153153
154154 for (int i = 0 ; i <= maxY / interval; i++ ) {
155155 final (x, y) = computeCoordinates (firstDate, i * interval);
156156
157- final ticLabel = SVGTextElement ();
158- ticLabel .setAttribute (
159- 'class' , 'downloads-chart-tic -label downloads-chart-tic -label-y' );
160- ticLabel .text =
157+ final tickLabel = SVGTextElement ();
158+ tickLabel .setAttribute (
159+ 'class' , 'downloads-chart-tick -label downloads-chart-tick -label-y' );
160+ tickLabel .text =
161161 '${compactFormat (i * interval ).value }${compactFormat (i * interval ).suffix }' ;
162- ticLabel .setAttribute ('x' , '${xMax + padding }' );
163- ticLabel .setAttribute ('y' , '$y ' );
164- chart.append (ticLabel );
162+ tickLabel .setAttribute ('x' , '${xMax + padding }' );
163+ tickLabel .setAttribute ('y' , '$y ' );
164+ chart.append (tickLabel );
165165
166166 if (i == 0 ) {
167- // No long tic in the bottom, we have the x-axis here.
167+ // No long tick in the bottom, we have the x-axis here.
168168 continue ;
169169 }
170170
171- final longTic = SVGPathElement ();
172- longTic .setAttribute ('class' , 'downloads-chart-frame' );
173- longTic .setAttribute ('d' ,
171+ final longTick = SVGPathElement ();
172+ longTick .setAttribute ('class' , 'downloads-chart-frame' );
173+ longTick .setAttribute ('d' ,
174174 'M${xZero - (lineThickness / 2 )} $y L${xMax - (lineThickness / 2 )} $y ' );
175- chart.append (longTic );
175+ chart.append (longTick );
176176 }
177177
178178 // We use the clipPath to cut the ends of the chart lines so that we don't
@@ -182,8 +182,8 @@ void drawChart(Element svg, List<String> ranges, List<List<int>> values,
182182 final clipRect = SVGRectElement ();
183183 clipRect.setAttribute ('y' , '$yMax ' );
184184 clipRect.setAttribute ('height' , '${chartheight - (lineThickness / 2 )}' );
185- clipRect.setAttribute ('x' , '${ xZero - ( lineThickness / 2 )} ' );
186- clipRect.setAttribute ('width' , '${ drawingWidth + lineThickness } ' );
185+ clipRect.setAttribute ('x' , '$xZero ' );
186+ clipRect.setAttribute ('width' , '$chartWidth ' );
187187 clipPath.append (clipRect);
188188 chart.append (clipPath);
189189
@@ -203,59 +203,63 @@ void drawChart(Element svg, List<String> ranges, List<List<int>> values,
203203 lines.add (line);
204204 }
205205
206- double legendXCoor = xZero - firstTicLabel. getBBox ().width / 2 ;
206+ double legendXCoor = xZero;
207207 double legendYCoor =
208- ticLabelYCoor + firstTicLabel .getBBox ().height + 2 * padding ;
208+ tickLabelYCoordinate + firstTickLabel .getBBox ().height + labelPadding ;
209209 final legendWidth = 20 ;
210210 final legendHeight = 8 ;
211211
212- for (int j = 0 ; j < lines.length; j ++ ) {
212+ for (int i = 0 ; i < lines.length; i ++ ) {
213213 final path = SVGPathElement ();
214- path.setAttribute ('class' , '${lineColorClasses [ j ] } downloads-chart-line ' );
215- // We assign colors in revers order so that main colors are chosen first for
214+ path.setAttribute ('class' , '${strokeColorClass ( i ) } downloads-chart-line ' );
215+ // We assign colors in reverse order so that main colors are chosen first for
216216 // the newest versions.
217- path.setAttribute ('d' , '${lines [lines .length - 1 - j ]}' );
217+ path.setAttribute ('d' , '${lines [lines .length - 1 - i ]}' );
218218 path.setAttribute ('clip-path' , 'url(#clipRect)' );
219219 chart.append (path);
220220
221221 final legend = SVGRectElement ();
222222 chart.append (legend);
223- legend.setAttribute (
224- 'class' , ' downloads-chart-legend ${legendColorClasses [ j ] }' );
223+ legend.setAttribute ('class' ,
224+ 'downloads-chart-legend ${fillColorClass ( i )} ${ strokeColorClass ( i ) }' );
225225 legend.setAttribute ('height' , '$legendHeight ' );
226226 legend.setAttribute ('width' , '$legendWidth ' );
227227
228228 final legendLabel = SVGTextElement ();
229229 chart.append (legendLabel);
230- legendLabel.setAttribute (
231- 'class' , 'downloads-chart-tic-label downloads-chart-tic-label-y' );
232- legendLabel.text = ranges[j];
230+ legendLabel.setAttribute ('class' , 'downloads-chart-tick-label' );
231+ if (i == 5 ) {
232+ // We have an 'other' line
233+ legendLabel.text = 'Other' ;
234+ } else {
235+ legendLabel.text = ranges[ranges.length - 1 - i];
236+ }
233237
234238 if (legendXCoor + padding + legendWidth + legendLabel.getBBox ().width >
235239 xMax) {
236240 // There is no room for the legend and label.
237241 // Make a new line and update legendXCoor and legendYCoor accordingly.
238242
239- legendXCoor = xZero - firstTicLabel. getBBox ().width / 2 ;
243+ legendXCoor = xZero;
240244 legendYCoor += 2 * padding + legendHeight;
241245 }
242246
243247 legend.setAttribute ('x' , '$legendXCoor ' );
244248 legend.setAttribute ('y' , '$legendYCoor ' );
245- legendLabel.setAttribute ('y' , '${legendYCoor + legendHeight / 2 }' );
249+ legendLabel.setAttribute ('y' , '${legendYCoor + legendHeight }' );
246250 legendLabel.setAttribute ('x' , '${legendXCoor + padding + legendWidth }' );
247251
248252 // Update x coordinate for next legend
249253 legendXCoor +=
250- legendWidth + padding + legendLabel.getBBox ().width + 2 * padding ;
254+ legendWidth + padding + legendLabel.getBBox ().width + labelPadding ;
251255 }
252256
253- final height = legendYCoor + 3 * padding;
254- final frame = SVGRectElement ();
257+ final frameHeight = legendYCoor + padding + labelPadding;
258+ final frame = SVGRectElement ()
259+ ..setAttribute ('class' , 'downloads-chart-frame' )
260+ ..setAttribute ('height' , '$frameHeight ' )
261+ ..setAttribute ('width' , '$frameWidth ' )
262+ ..setAttribute ('rx' , '15' )
263+ ..setAttribute ('ry' , '15' );
255264 chart.append (frame);
256- frame.setAttribute ('height' , '$height ' );
257- frame.setAttribute ('width' , '$width ' );
258- frame.setAttribute ('rx' , '15' );
259- frame.setAttribute ('ry' , '15' );
260- frame.setAttribute ('class' , 'downloads-chart-frame' );
261265}
0 commit comments