Magyar zászlóMagyar

Task 3 – Canvas Popularity Charts

JavaScript Exam 2024-25-2 11 points total

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)

Draw the column rectangle
fillRect()canvas coordinate system
3 pts
Requirement

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
    );
}
Column color from video ID
fillStyleString.substring()hex color
1 pt
Requirement

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 view count above the column
fillText()textAlignfont
2 pts
Requirement

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
);
Draw the pie slice
beginPath()moveTo()arc()closePath()fill()
3 pts
Requirement

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:

  1. beginPath() — starts a fresh path (without this, the arc gets joined to whatever path was open before)
  2. moveTo(centerX, centerY) — moves the “pen” to the center without drawing
  3. arc(x, y, radius, start, end) — draws the curved outer edge from startAngle to startAngle + sliceAngle; the canvas implicitly draws a line from moveTo to the arc start
  4. closePath() — draws a straight line from the arc end back to moveTo (the center), completing the slice shape
  5. fill() — fills the enclosed area with the current fillStyle

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;
}
Pie slice color from video ID
fillStylesubstring()
1 pt
Requirement

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;
}