A Table Is Data. A Picture Is Understanding.
Suppose your school's cricket team posted this over-by-over total in a T20 innings: after over 1 the score was 8 runs, after over 2 it was 15, after over 3 it was 22, after over 4 it was 40, after over 5 it was 45, and after over 6 it was 58. Look at those six numbers and ask yourself: was the team scoring at a steady pace, or did something dramatic happen? To answer that from the numbers alone, you have to subtract each pair — 15 minus 8 is 7, 22 minus 15 is 7, but 40 minus 22 is 18 — and only after doing that arithmetic in your head do you notice that over 4 was an explosion of boundaries and sixes while the other overs were quiet singles and twos.
Now imagine the same six numbers drawn as points on a graph, joined by a line that rises from left to right. The moment you look at it, the sudden steep jump between over 3 and over 4 jumps out at you — no subtraction required. That gap between "numbers you must calculate on" and "shapes your eyes read instantly" is the entire reason data visualization exists as a subject. It is also why the CBSE Computer Science and Informatics Practices syllabus, and every real data-driven job from a sports analyst to an ISRO mission-control engineer, expects you to know how to turn a list of numbers into a chart — not just how to print a list.
The tool you will use to do this in Python is called Matplotlib. It is a plotting library — a collection of ready-made Python functions that take your lists of numbers and turn them into figures with lines, bars, dots, or wedges. Its most commonly used part is a module called pyplot, which by strong convention every Python programmer imports under the short name plt:
import matplotlib.pyplot as plt
From this point on, every plotting instruction in this chapter starts with plt. followed by the name of a chart type or a setting you want to change.
Two Things Every Chart Has: a Figure and Axes
Before drawing anything, it helps to know the two objects Matplotlib always creates behind the scenes. The Figure is the entire canvas — the whole window or the whole image file, including any margins around the edges. The Axes is the actual rectangular plotting region inside that canvas, the part that has an x-coordinate system and a y-coordinate system and where your data actually gets drawn. This is a common early confusion: "axes" here does not mean the two lines (the x-axis and the y-axis) — it means the whole plotting box that contains those lines, the gridlines, the data, and the tick labels. When you call a plotting function like plt.plot(...) without first creating anything, Matplotlib quietly creates one Figure containing one Axes for you, and every subsequent plt. command (labels, title, legend) acts on that same Axes until you call plt.show().
The diagram below labels every one of these parts on the run-rate chart we are about to build, so you can see exactly what each piece of vocabulary refers to.
Building the Line Chart, Step by Step
Here is the complete code that produces the chart shown above:
import matplotlib.pyplot as plt
overs = [1, 2, 3, 4, 5, 6]
runs = [8, 15, 22, 40, 45, 58]
plt.plot(overs, runs)
plt.xlabel("Overs")
plt.ylabel("Runs")
plt.title("Run Rate in a T20 Innings")
plt.show()
Trace through it line by line. The first line imports the library. The next two lines create two ordinary Python lists of equal length — overs holds the x-values, runs holds the corresponding y-values, and the pairing is by position: the first entry of overs (1) goes with the first entry of runs (8), the second entry (2) goes with the second entry (15), and so on. This is the single most important rule in Matplotlib: the two lists you hand to a plotting function must be the same length, because Matplotlib zips them together into (x, y) points.
plt.plot(overs, runs) is the actual drawing instruction. Since no Figure or Axes exists yet, Matplotlib creates one automatically, plots the six points (1, 8), (2, 15), (3, 22), (4, 40), (5, 45), (6, 58), and joins consecutive points with straight line segments. It does this using its default color, which is a shade of blue with the exact code #1f77b4 — you did not ask for blue anywhere in this code, Matplotlib simply always starts with that color unless told otherwise. The three lines after that attach a label to the x-axis, a label to the y-axis, and a title to the Axes — none of these lines draw anything new, they only add text around the existing plot. Finally, plt.show() renders everything and displays it.
Skipping xlabel, ylabel, and title is one of the most heavily penalized mistakes in CBSE practical exams, and for good reason outside the exam too: a chart with unlabeled axes is not real data visualization, because a viewer cannot tell what the numbers mean. A rising line could be runs, rainfall in millimetres, or your pocket money in rupees — the labels are what turn "a line going up" into "runs rising over overs."
How a Number Becomes a Pixel: Linear Scaling
It's worth understanding what plt.plot() is actually computing when it turns your data into a picture, because it is a genuinely simple piece of algebra you already know: linear scaling. The Axes box on screen has a fixed pixel height — say the plotting area runs from pixel row 390 (representing the value 0, at the bottom) up to pixel row 90 (representing the value 60, at the top), so the box is 300 pixels tall and covers a data range of 60.
To place a data value of 40 runs, Matplotlib works out what fraction of the way from 0 to 60 the value 40 sits: 40 ÷ 60 = 0.667, or two-thirds of the way up. Then it converts that fraction into pixels by multiplying by the box height (0.667 × 300 ≈ 200 pixels), and subtracts that from the bottom pixel row, because pixel rows count downward while data values count upward: 390 − 200 = 190. That is exactly the y-pixel used for the "40" point in the diagram above. The general formula, for a value v inside a data range from min to max, plotted in a box whose bottom pixel is bottomPx and whose height is heightPx, is:
pixel_y = bottomPx - ((v - min) / (max - min)) * heightPx
Every single point on every chart in this chapter — lines, bars, scatter dots, pie wedges — is placed using some version of this same idea: take a data value, work out where it falls proportionally between the smallest and largest values in that dimension, and convert that proportion into a position on the canvas. Once you see that a chart is just repeated proportional scaling, log scales, "auto-zooming" axes, and even bar-chart heights all stop looking like separate tricks and start looking like the same formula applied slightly differently.
Comparing Two Series: Multiple Lines and the Color Cycle
Real comparisons usually need more than one line on the same Axes. Here is the run-rate of Team A against Team B in the same match:
overs = [1, 2, 3, 4, 5, 6]
team_a = [8, 15, 22, 40, 45, 58]
team_b = [5, 12, 18, 25, 30, 33]
plt.plot(overs, team_a, label="Team A")
plt.plot(overs, team_b, label="Team B")
plt.xlabel("Overs")
plt.ylabel("Runs")
plt.title("Run Comparison")
plt.legend()
plt.show()
Both calls draw into the same Axes because no new figure was started between them. The first plt.plot() call takes the first color in Matplotlib's built-in color cycle, blue (#1f77b4). The second call automatically advances to the next color in that same cycle, orange (#ff7f0e) — you never typed a color name, yet the two lines come out visibly different. This isn't random: Matplotlib's default color cycle is a fixed, ordered list of ten colors, and the first five are blue, orange, green, red, and purple, in that exact order. Each new Axes starts its own cycle counter fresh at position zero (blue); every additional plotting call on that same Axes advances the counter by one, wrapping back to the start only after all ten are used. The label="Team A" and label="Team B" arguments don't affect the drawing at all — they only supply the text that plt.legend() later reads to build the small color-coded key box on the chart.
Common misconception, worth correcting explicitly: many students assume Matplotlib picks colors "randomly," or that you must always specify a color manually or the chart will look broken. Neither is true. Colors are chosen deterministically from that fixed ten-color list, in that fixed order, based purely on how many plotting calls have already been made on the current Axes — nothing about it depends on your data values or on chance.
Scatter Plots: Showing a Relationship, Not a Sequence
A line chart implicitly claims that consecutive points are connected in a meaningful sequence — over 1 leads into over 2, which leads into over 3. That is true for overs in a match, or for months in a year, because there is a natural order. But sometimes you want to compare two measurements per person or per item where the order of the items themselves means nothing — for example, hours a student studied that week against the marks they scored:
import matplotlib.pyplot as plt
hours_studied = [1, 2, 3, 4, 5, 6, 7]
marks_scored = [35, 40, 50, 55, 62, 78, 85]
plt.scatter(hours_studied, marks_scored)
plt.xlabel("Hours Studied")
plt.ylabel("Marks Scored (out of 100)")
plt.title("Does Studying More Help?")
plt.show()
plt.scatter() plots the same seven (x, y) pairs as isolated dots — it deliberately does not draw any connecting line between them, because a scatter plot's entire purpose is to let your eye judge whether the dots trend upward, downward, or scatter randomly, without the chart pre-suggesting a path. This is a second common misconception worth naming directly: plt.scatter() does not automatically fit or draw a trend line through the dots — if the points happen to rise from bottom-left to top-right, that upward pattern is something you visually notice yourself; producing an actual best-fit line would require separate regression code that is not part of a basic scatter call. In this snippet, since it is the very first and only plotting instruction in a fresh script, Matplotlib's color cycle is back at its starting position, so the seven dots are drawn in the same default blue, #1f77b4, as the very first line chart in this chapter — the cycle resets for every new Axes, it does not remember colors used in a previous, separate script.
Bar Charts: Comparing Discrete Categories
Overs and study-hours are numeric and have a natural order — they belong on a line or scatter plot. Subject names do not have a numeric order at all, so comparing marks across subjects calls for a different chart:
subjects = ["Maths", "Science", "English", "Hindi", "Social Science"]
marks = [88, 76, 91, 84, 79]
plt.bar(subjects, marks)
plt.ylabel("Marks (out of 100)")
plt.title("Term 1 Report Card")
plt.show()
plt.bar() places one rectangle per category, spaced evenly along the x-axis regardless of the text length of each label, with each bar's height scaled by the same proportional-scaling idea from earlier — the tallest bar here, English at 91, reaches nearly the top of the axis, while the shortest, Science at 76, is noticeably lower. All five bars come out in the same default blue, because they were all produced by a single plt.bar() call — the color cycle only advances between separate plotting calls on the same Axes, not between the individual bars drawn inside one call. If you wanted each subject in a different color, or wanted the bars sorted horizontally instead — useful when category names are long — you would reach for plt.barh(), which draws the same idea with the categories running down the y-axis and values extending rightward along the x-axis.
Pie Charts: Parts of a Whole
A pie chart answers a narrower question than the charts above: not "how does this compare to that," but "what share of one single total does each part make up." It only makes sense when your numbers are genuinely parts of one whole that sum to something meaningful. Here is a hypothetical way a student's 24-hour day might split up:
activities = ["School", "Sleep", "Study at Home", "Play/Screen Time", "Other"]
hours = [6, 8, 4, 2, 4]
plt.pie(hours, labels=activities, autopct="%1.0f%%")
plt.title("A Sample Student's Day (24 Hours)")
plt.show()
Notice the five hour values add up to 24, matching the 24 hours in a day — that constraint is exactly what makes a pie chart appropriate here; if the numbers didn't represent parts of one fixed total, a pie chart would be misleading. Matplotlib computes each wedge's angle as that category's share of the total, multiplied by 360°: School gets 6/24 × 360° = 90°, a clean quarter of the circle. The autopct="%1.0f%%" argument tells Matplotlib to print each wedge's percentage, rounded to a whole number, directly on the slice: School comes out around 25%, Sleep around 33%, Study at Home around 17%, and Play/Screen Time around 8%. A frequently repeated, and correct, rule of good data visualization is that pie charts stop being readable once you have more than five or six slices, or once several slices are close enough in size that the eye can't rank them by angle alone — in those situations a bar chart, which lets you compare bar heights along a straight shared baseline, is almost always the clearer choice.
Histograms: How Continuous Data Spreads Out
A histogram looks like a bar chart but answers a completely different question, and confusing the two is another common mistake. A bar chart has one bar per named category you chose (Maths, Science, ...). A histogram has one bar per numeric range — called a bin — that Matplotlib chooses by dividing the full span of your continuous data into equal-width slices, and then counts how many data values fall inside each slice.
marks = [45, 67, 78, 52, 88, 91, 34, 72, 65, 58,
81, 76, 49, 63, 70, 85, 92, 55, 68, 74]
plt.hist(marks, bins=5)
plt.xlabel("Marks")
plt.ylabel("Number of Students")
plt.title("Marks Distribution")
plt.show()
There are 20 marks in the list, ranging from a minimum of 34 to a maximum of 92 — a span of 58. Asking for bins=5 means Matplotlib divides that 58-mark span into 5 equal pieces, each 58 ÷ 5 = 11.6 marks wide: [34, 45.6), [45.6, 57.2), [57.2, 68.8), [68.8, 80.4), and [80.4, 92]. Walking through the 20 values and dropping each into its bin gives 2 students in the lowest bin, 3 in the second, and 5 each in the third, fourth, and fifth — so the bar heights drawn are 2, 3, 5, 5, 5. Reading that shape tells you something no single average could: marks are not clustered in the middle, they are fairly evenly spread across the upper four-fifths of the range, with comparatively few students scoring below about 46. That is the kind of pattern a histogram is built to reveal — the distribution or spread of one variable — while a bar chart compares separate named things and a line chart tracks change across an ordered sequence.
Saving a Chart as an Image File
To keep a chart as a file instead of only viewing it, use plt.savefig("filename.png") in place of, or before, plt.show(). Ordering matters: on several Matplotlib setups, once plt.show() finishes displaying a figure it clears that figure from memory, so a savefig() call placed after it can end up saving a blank image. Modern notebook environments with an inline backend often don't have this problem because they redraw the figure fresh for display without clearing the underlying object — but since behavior differs across setups and Matplotlib versions, the dependable habit that works everywhere is to call plt.savefig() before plt.show(), or to call savefig() on its own when you don't need an on-screen preview in that run at all.
Practice: Predict Before You Run
Question 1. In a brand-new script, this is the only plotting code that runs:
x = [1, 2, 3, 4]
y = [10, 40, 20, 30]
plt.scatter(x, y)
plt.show()
What will the four dots look like, and what color will they be? Work it out before reading on.
Answer notes: four separate blue dots (matplotlib's default color cycle starts with blue, #1f77b4 — the same blue used earlier in this chapter's line chart), plotted at (1, 10), (2, 40), (3, 20), (4, 30). No line connects them, because scatter() never draws connecting segments the way plot() does. They are blue and not any other color because this is the only plotting call in a fresh script, so the color cycle is at its very first position — the same reasoning that made the earlier hours-vs-marks scatter plot blue.
Question 2. If a script calls plt.plot() three separate times on the same Axes before calling plt.show() once, what colors will the three lines be, in order?
Answer notes: blue (#1f77b4), then orange (#ff7f0e), then green (#2ca02c) — the first three entries of Matplotlib's fixed default color cycle, assigned in call order, regardless of what the data values are.
Question 3. A student wants to show how a class's average marks changed across all 12 months of a school year, and picks a pie chart with 12 slices, one per month. Is this a good choice? Why or why not?
Answer notes: no — a pie chart shows how parts make up one fixed whole at a single moment, and 12 slices is already past the point where the eye can compare wedge sizes reliably. Monthly averages changing over time is a sequence with a natural order, which is exactly what a line chart is built to show clearly; a line chart would make the trend across months immediately visible in a way 12 pie wedges cannot.
Summary
- Data visualization turns lists of numbers into shapes your eyes can read instantly, catching patterns like sudden jumps that raw numbers hide behind arithmetic.
- Matplotlib's
pyplotmodule, imported asplt, is the standard Python plotting toolkit; every chart lives inside a Figure (the whole canvas) and an Axes (the actual plotting box with its own coordinate system). - Every point on a chart is placed using linear scaling: a data value's proportional position between the data's minimum and maximum is converted into a proportional position between the Axes' pixel boundaries.
- Always label your axes and title your chart — CBSE practicals and real-world clarity both depend on it.
- Matplotlib's default color cycle is a fixed, ordered ten-color list starting blue, orange, green, red, purple; it is not random, and it resets to blue at the start of every new Axes/script.
- Use
plot()for a numeric, ordered sequence where connecting lines are meaningful;scatter()for comparing two variables per item where order doesn't matter and no line should be implied;bar()for comparing discrete named categories;pie()for showing parts of one fixed whole; andhist()for showing how one continuous variable is distributed across value ranges (bins), not named categories. - Call
plt.savefig()beforeplt.show()to reliably save a chart to a file, since some setups clear the figure once it has been shown.