Skip to content

Commit 0785a92

Browse files
committed
Downloads chart: Add tooltip with exact counts
1 parent f2db1c5 commit 0785a92

File tree

3 files changed

+185
-10
lines changed

3 files changed

+185
-10
lines changed

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

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const colors = [
2424

2525
String strokeColorClass(int i) => 'downloads-chart-stroke-${colors[i]}';
2626
String fillColorClass(int i) => 'downloads-chart-fill-${colors[i]}';
27+
String squareColorClass(int i) => 'downloads-chart-square-${colors[i]}';
2728

2829
void create(HTMLElement element, Map<String, String> options) {
2930
final dataPoints = options['points'];
@@ -35,12 +36,19 @@ void create(HTMLElement element, Map<String, String> options) {
3536
if (versionsRadio == null) {
3637
throw UnsupportedError('data-downloads-chart-versions-radio required');
3738
}
39+
Element createNewSvg() {
40+
return document.createElementNS('http://www.w3.org/2000/svg', 'svg')
41+
..setAttribute('height', '100%')
42+
..setAttribute('width', '100%');
43+
}
3844

39-
final svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
40-
svg.setAttribute('height', '100%');
41-
svg.setAttribute('width', '100%');
45+
var svg = createNewSvg();
4246
element.append(svg);
4347

48+
final toolTip = HTMLDivElement()
49+
..setAttribute('class', 'downloads-chart-tooltip');
50+
document.body!.appendChild(toolTip);
51+
4452
final data = WeeklyVersionDownloadCounts.fromJson((utf8.decoder
4553
.fuse(json.decoder)
4654
.convert(base64Decode(dataPoints)) as Map<String, dynamic>));
@@ -80,15 +88,19 @@ void create(HTMLElement element, Map<String, String> options) {
8088
throw UnsupportedError('Unsupported versions-radio value: "$value"');
8189
}
8290
radioButton.onClick.listen((e) {
83-
drawChart(svg, displayList, data.newestDate);
91+
element.removeChild(svg);
92+
svg = createNewSvg();
93+
element.append(svg);
94+
drawChart(svg, toolTip, displayList, data.newestDate);
8495
});
8596
});
8697

87-
drawChart(svg, majorDisplayLists, data.newestDate);
98+
drawChart(svg, toolTip, majorDisplayLists, data.newestDate);
8899
}
89100

90101
void drawChart(
91102
Element svg,
103+
HTMLDivElement toolTip,
92104
({List<String> ranges, List<List<int>> weekLists}) displayLists,
93105
DateTime newestDate,
94106
{bool stacked = false}) {
@@ -103,12 +115,14 @@ void drawChart(
103115
final leftPadding = 30;
104116
final rightPadding = 70; // Make extra room for labels on y-axis
105117
final chartWidth = frameWidth - leftPadding - rightPadding;
106-
final chartheight = 420;
118+
final chartHeight = 420;
119+
120+
final toolTipOffsetFromMouse = 15;
107121

108122
DateTime computeDateForWeekNumber(
109-
DateTime newestDate, int totalWeeks, int weekNumber) {
123+
DateTime newestDate, int totalWeeks, int weekIndex) {
110124
return newestDate.copyWith(
111-
day: newestDate.day - 7 * (totalWeeks - weekNumber - 1));
125+
day: newestDate.day - 7 * (totalWeeks - weekIndex - 1));
112126
}
113127

114128
/// Computes max value on y-axis such that we get a nice division for the
@@ -143,7 +157,7 @@ void drawChart(
143157
final x = leftPadding +
144158
chartWidth * duration.inMilliseconds / xAxisSpan.inMilliseconds;
145159

146-
final y = topPadding + (chartheight - chartheight * (downloads / maxY));
160+
final y = topPadding + (chartHeight - chartHeight * (downloads / maxY));
147161
return (x, y);
148162
}
149163

@@ -222,7 +236,7 @@ void drawChart(
222236
clipPath.setAttribute('id', 'clipRect');
223237
final clipRect = SVGRectElement();
224238
clipRect.setAttribute('y', '$yMax');
225-
clipRect.setAttribute('height', '${chartheight - (lineThickness / 2)}');
239+
clipRect.setAttribute('height', '${chartHeight - (lineThickness / 2)}');
226240
clipRect.setAttribute('x', '$xZero');
227241
clipRect.setAttribute('width', '$chartWidth');
228242
clipPath.append(clipRect);
@@ -291,4 +305,86 @@ void drawChart(
291305
legendLabel.getBBox().width +
292306
labelPadding;
293307
}
308+
309+
final cursor = SVGLineElement()
310+
..setAttribute('class', 'downloads-chart-cursor')
311+
..setAttribute('stroke-dasharray', '15,3')
312+
..setAttribute('x1', '0')
313+
..setAttribute('x2', '0')
314+
..setAttribute('y1', '$yZero')
315+
..setAttribute('y2', '$yMax');
316+
chart.append(cursor);
317+
318+
// Setup mouse handling
319+
320+
DateTime? lastSelectedDay;
321+
void hideCursor(_) {
322+
cursor.setAttribute('style', 'opacity:0');
323+
toolTip.setAttribute('style', 'opacity:0;position:absolute;');
324+
lastSelectedDay = null;
325+
}
326+
327+
hideCursor(1);
328+
329+
svg.onMouseMove.listen((e) {
330+
final boundingRect = chart.getBoundingClientRect();
331+
if (e.x < boundingRect.x + xZero ||
332+
e.x > boundingRect.x + xMax ||
333+
e.y < boundingRect.y + yMax ||
334+
e.y > boundingRect.y + yZero) {
335+
// We are outside the actual chart area
336+
hideCursor(1);
337+
return;
338+
}
339+
340+
cursor.setAttribute('style', 'opacity:1');
341+
toolTip.setAttribute(
342+
'style',
343+
'top:${e.y + toolTipOffsetFromMouse + document.scrollingElement!.scrollTop}px;'
344+
'left:${e.x}px;');
345+
346+
final pointPercentage =
347+
(e.x - chart.getBoundingClientRect().x - xZero) / chartWidth;
348+
final nearestIndex = ((values.length - 1) * pointPercentage).round();
349+
350+
final selectedDay =
351+
computeDateForWeekNumber(newestDate, values.length, nearestIndex);
352+
if (selectedDay == lastSelectedDay) return;
353+
354+
final coords = computeCoordinates(selectedDay, 0);
355+
cursor.setAttribute('transform', 'translate(${coords.$1}, 0)');
356+
357+
final startDay = selectedDay.subtract(Duration(days: 7));
358+
toolTip.replaceChildren(HTMLDivElement()
359+
..setAttribute('class', 'downloads-chart-tooltip-date')
360+
..text =
361+
'${formatAbbrMonthDay(startDay)} - ${formatAbbrMonthDay(selectedDay)}');
362+
363+
final downloads = values[nearestIndex];
364+
for (int i = 0; i < downloads.length; i++) {
365+
final index = ranges.length - 1 - i;
366+
if (downloads[index] > 0) {
367+
// We only show the exact download count in the tooltip if it's non-zero.
368+
final square = HTMLDivElement()
369+
..setAttribute(
370+
'class', 'downloads-chart-tooltip-square ${squareColorClass(i)}');
371+
final rangeText = HTMLSpanElement()..text = '${ranges[index]}: ';
372+
final tooltipRange = HTMLDivElement()
373+
..setAttribute('class', 'downloads-chart-tooltip-row')
374+
..append(square)
375+
..append(rangeText);
376+
final downloadsText = HTMLSpanElement()
377+
..setAttribute('class', 'downloads-chart-tooltip-downloads')
378+
..text = '${formatWithThousandSeperators(downloads[index])}';
379+
final tooltipRow = HTMLDivElement()
380+
..setAttribute('class', 'downloads-chart-tooltip-row')
381+
..append(tooltipRange)
382+
..append(downloadsText);
383+
toolTip.append(tooltipRow);
384+
}
385+
}
386+
lastSelectedDay = selectedDay;
387+
});
388+
389+
svg.onMouseLeave.listen(hideCursor);
294390
}

pkg/web_css/lib/src/_pkg.scss

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,49 @@
299299
stroke: var(--pub-downloads-chart-frame-color);
300300
}
301301

302+
.downloads-chart-tooltip {
303+
border-radius: 5px;
304+
margin: 0px;
305+
padding: 2px 8px;
306+
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
307+
background-color:var(--pub-neutral-bgColor);
308+
opacity: 0.9;
309+
z-index: 100000;
310+
position: absolute;
311+
border: 0.5px solid var(--pub-downloads-chart-frame-color);
312+
}
313+
314+
.downloads-chart-tooltip-square {
315+
display: flex;
316+
height: 12px;
317+
width: 12px;
318+
border: 1px solid;
319+
margin-right: 5px;
320+
margin-left: 5px;
321+
}
322+
.downloads-chart-tooltip-date {
323+
font-size: small;
324+
color: var(--pub-score_label-text-color);;
325+
}
326+
.downloads-chart-tooltip-downloads {
327+
padding-right: 4px;
328+
padding-left: 10px;
329+
}
330+
331+
.downloads-chart-tooltip-row {
332+
display: flex;
333+
flex-direction: row;
334+
align-items: center;
335+
justify-content: space-between;
336+
font-size: small;
337+
color: var(--pub-score_label-text-color);
338+
}
339+
340+
.downloads-chart-cursor {
341+
stroke: var(--pub-score_label-text-color);
342+
stroke-width: 1;
343+
}
344+
302345
.downloads-chart-x-axis {
303346
fill: none;
304347
stroke-width: 1;
@@ -347,6 +390,36 @@
347390
fill:var(--pub-downloads-chart-color-5);
348391
}
349392

393+
.downloads-chart-square-blue {
394+
background-color:var(--pub-downloads-chart-color-0-bg);
395+
color:var(--pub-downloads-chart-color-0);
396+
}
397+
398+
.downloads-chart-square-red {
399+
background-color:var(--pub-downloads-chart-color-1-bg);
400+
color: var(--pub-downloads-chart-color-1);
401+
}
402+
403+
.downloads-chart-square-green {
404+
background-color:var(--pub-downloads-chart-color-2-bg);
405+
color: var(--pub-downloads-chart-color-2)
406+
}
407+
408+
.downloads-chart-square-purple {
409+
background-color:var(--pub-downloads-chart-color-3-bg);
410+
color:var(--pub-downloads-chart-color-3);
411+
}
412+
413+
.downloads-chart-square-orange {
414+
background-color:var(--pub-downloads-chart-color-4-bg);
415+
color:var(--pub-downloads-chart-color-4);
416+
}
417+
418+
.downloads-chart-square-turquoise {
419+
background-color:var(--pub-downloads-chart-color-5-bg);
420+
color:var(--pub-downloads-chart-color-5);
421+
}
422+
350423
.downloads-chart-line {
351424
fill: none;
352425
stroke-width: 2;

pkg/web_css/lib/src/_variables.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,17 @@
8181
--mdc-typography-font-family: var(--pub-default-text-font_family);
8282

8383
--pub-downloads-chart-color-0: var(--pub-markdown-alert-note);
84+
--pub-downloads-chart-color-0-bg: rgb(9, 105, 218, 0.3);
8485
--pub-downloads-chart-color-1: var(--pub-markdown-alert-caution);
86+
--pub-downloads-chart-color-1-bg:rgb(207, 34, 46, 0.3);
8587
--pub-downloads-chart-color-2: var(--pub-markdown-alert-tip);
88+
--pub-downloads-chart-color-2-bg: rgb(26, 127, 55, 0.3);
8689
--pub-downloads-chart-color-3: var(--pub-markdown-alert-important);
90+
--pub-downloads-chart-color-3-bg: rgb(130, 80, 223, 0.3);
8791
--pub-downloads-chart-color-4: var(--pub-markdown-alert-warning);
92+
--pub-downloads-chart-color-4-bg:rgb(154, 103, 0, 0.3);
8893
--pub-downloads-chart-color-5: #12a4af;
94+
--pub-downloads-chart-color-5-bg: rgb(18, 164, 175, 0.3);
8995
}
9096

9197
/// Variables that are specific to the light theme.

0 commit comments

Comments
 (0)