Task 3 – Canvas Popularity Charts
Project Files
Your job is to fill in two functions at the top of index.js. Everything below the 🛑 line is infrastructure — do not touch it. The data object, checkbox setup, and drawAll() are all provided for you. No <script> tag setup needed for this task.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Task 3.</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<h1>3. Popularity</h1>
<div id="main">
<div>
<canvas id="column-chart" width="800" height="400"></canvas>
<canvas id="pie-chart" width="800" height="400"></canvas>
</div>
<div id="video-checkboxes"></div>
</div>
<script src="index.js"></script>
</body>
</html>// ✏️ Work only in the two functions below — above the 🛑 line
const canvasColumnChart = document.querySelector('#column-chart') // 800×400 px
const contextColumnChart = canvasColumnChart.getContext('2d')
function drawColumn(video, index, columnWidth) {
// ✏️ Your code here (subtasks a, b, c)
}
const canvasPieChart = document.querySelector('#pie-chart') // 800×400 px
const contextPieChart = canvasPieChart.getContext('2d')
let startAngle = 0 // accumulated across slice calls
function drawCircleSector(video, totalViews, centerX, centerY, radius) {
const sliceAngle = (video.views / totalViews) * 2 * Math.PI
// ✏️ Your code here (subtasks d, e)
startAngle += sliceAngle
}
// //////////////////////////////////////////////////////////
// 🛑 DO NOT MODIFY ANYTHING BELOW THIS LINE 🛑
// (data object, checkbox setup, drawAll — provided for you)
// //////////////////////////////////////////////////////////The infrastructure calls drawColumn(video, index, columnWidth) for every filtered video and drawCircleSector(video, totalViews, centerX, centerY, radius) for each pie slice — passing all values you need.
Canvas coordinate system
In the HTML Canvas, y = 0 is the top of the canvas and increases downward. The canvas is 400 px tall. A bar of height views whose bottom should be at y = 400 must therefore start at y = 400 - views.
y = 0 ─────────────────────────
▲ top of tall bar
y = 105 ───────────────────────── (400 - 295 for "Little Big - Uno")
│
│ bar height = views
│
y = 400 ───────────────────────── ← bottom of canvas (baseline)
In drawColumn, draw a column for the video using fillRect. Top-left: x = index × columnWidth, y = 400 - views. Width is columnWidth, height is views.
fillRect(x, y, width, height) draws a solid rectangle using the current fillStyle. It does not require beginPath() or fill().
The x positions tile left to right: column 0 starts at 0 * columnWidth = 0, column 1 at 1 * columnWidth, and so on. The columnWidth is pre-calculated as 800 / filteredData.length by the infrastructure so bars always span the full canvas width.
The bar’s bottom is always at y = 400 (the canvas bottom). Setting y = 400 - video.views and height = video.views ensures this regardless of the view count. More views → smaller y value → taller bar.
Note: color must be set with fillStyle before fillRect is called. See subtask b.
function drawColumn(video, index, columnWidth) {
contextColumnChart.fillRect(
index * columnWidth, // x: left edge of this column slot
400 - video.views, // y: top of the bar (canvas y grows downward)
columnWidth, // width: fills the slot exactly
video.views // height: proportional to view count
);
} The rectangle color should be the first 6 characters of the video id. Use substring. Add a # to the beginning.
video.id is a lowercase hex string like "6fb178a2". substring(0, 6) extracts the first 6 characters: "6fb178". Prepending # gives "#6fb178" — a valid CSS hex color.
substring(start, end) returns characters from index start up to (not including) end. substring(0, 6) → indices 0, 1, 2, 3, 4, 5.
Order matters: fillStyle must be assigned before calling fillRect. The canvas renders using whatever fillStyle is currently set when the drawing call is made.
// Set this BEFORE fillRect so the color applies to the rectangle
contextColumnChart.fillStyle = `#${video.id.substring(0, 6)}`; Display the view count above the column. Suggested styles: fillStyle: black, font: 12px Arial, textAlign: center. Position: x = index × columnWidth + columnWidth/2, y = 400 - views - 5.
fillText(text, x, y) draws text at the given position. Unlike fillRect, the y coordinate positions the baseline of the text, not its top — so - 5 gives a small gap above the bar without cutting off descenders.
textAlign = 'center' makes the x coordinate the horizontal center of the text. Combined with x = index * columnWidth + columnWidth / 2 (the middle of the column slot), this centers the label over the bar regardless of how wide the text is.
fillStyle = 'black' must be set again here because fillStyle was changed to the video’s color for the rectangle in subtask b. Canvas state persists between drawing calls.
contextColumnChart.fillStyle = 'black';
contextColumnChart.font = '12px Arial';
contextColumnChart.textAlign = 'center';
contextColumnChart.fillText(
video.views,
index * columnWidth + columnWidth / 2, // center of the column
400 - video.views - 5 // 5px above the bar top
); In drawCircleSector, draw the slice. Outline starts at the center. Then draw an arc from startAngle to startAngle + sliceAngle. Finally, close the path back to the center.
The canvas path API is declarative: you first describe the shape, then render it with fill() or stroke().
The sequence for a pie slice:
beginPath()— starts a fresh path (without this, the arc gets joined to whatever path was open before)moveTo(centerX, centerY)— moves the “pen” to the center without drawingarc(x, y, radius, start, end)— draws the curved outer edge fromstartAngletostartAngle + sliceAngle; the canvas implicitly draws a line frommoveToto the arc startclosePath()— draws a straight line from the arc end back tomoveTo(the center), completing the slice shapefill()— fills the enclosed area with the currentfillStyle
sliceAngle is computed by the scaffolding as a proportion of the full circle: (views / totalViews) * 2π. The module-level startAngle accumulates across calls so each slice starts where the previous ended.
Note: fillStyle must be set before fill() — see subtask e.
function drawCircleSector(video, totalViews, centerX, centerY, radius) {
const sliceAngle = (video.views / totalViews) * 2 * Math.PI
contextPieChart.beginPath();
contextPieChart.moveTo(centerX, centerY);
contextPieChart.arc(centerX, centerY, radius, startAngle, startAngle + sliceAngle);
contextPieChart.closePath();
contextPieChart.fill();
startAngle += sliceAngle;
} The pie slice color should also be the first 6 characters of id. Use substring. Add a # to the beginning.
Identical logic to the column chart (subtask b). The same video.id hex string gives a consistent color for each video across both charts — a bar and its pie slice share the same color.
fillStyle must be set before fill() is called. The natural place is after closePath() and before fill().
// Set BEFORE fill() so the color applies to this slice
contextPieChart.fillStyle = `#${video.id.substring(0, 6)}`; Complete Solution
function drawColumn(video, index, columnWidth) {
// b: color from id
contextColumnChart.fillStyle = `#${video.id.substring(0, 6)}`;
// a: draw bar
contextColumnChart.fillRect(
index * columnWidth,
400 - video.views,
columnWidth,
video.views
);
// c: label above bar
contextColumnChart.fillStyle = 'black';
contextColumnChart.font = '12px Arial';
contextColumnChart.textAlign = 'center';
contextColumnChart.fillText(
video.views,
index * columnWidth + columnWidth / 2,
400 - video.views - 5
);
}
function drawCircleSector(video, totalViews, centerX, centerY, radius) {
const sliceAngle = (video.views / totalViews) * 2 * Math.PI
// e: color from id
contextPieChart.fillStyle = `#${video.id.substring(0, 6)}`;
// d: draw slice
contextPieChart.beginPath();
contextPieChart.moveTo(centerX, centerY);
contextPieChart.arc(centerX, centerY, radius, startAngle, startAngle + sliceAngle);
contextPieChart.closePath();
contextPieChart.fill();
startAngle += sliceAngle;
}