Skip to main content
Data Visualization
CHAPTER 05 Beginner

Line Charts and Trend Visualization

Updated: May 18, 2026
5 min read

# CHAPTER 5

Line Charts and Trend Visualization

1. Chapter Introduction

Line charts are the most common business visualization — perfect for showing how metrics change over time. Revenue trends, website traffic, stock prices, and KPI trajectories all live in line charts.

2. Basic to Advanced Line Charts

python
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# Sample: Monthly website traffic
months = pd.date_range('2024-01', periods=12, freq='MS')
traffic = [45000, 48000, 52000, 51000, 58000, 63000,
           61000, 65000, 70000, 68000, 72000, 78000]

fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# Chart 1: Basic line
axes[0,0].plot(months, traffic, color='#2196F3', linewidth=2)
axes[0,0].set_title('Basic Line Chart')

# Chart 2: Styled with markers
axes[0,1].plot(months, traffic, 'o-', color='#E91E63', linewidth=2,
               markersize=8, markerfacecolor='white', markeredgewidth=2,
               markeredgecolor='#E91E63', label='Monthly Traffic')
axes[0,1].fill_between(months, traffic, alpha=0.1, color='#E91E63')
axes[0,1].set_title('Line with Markers and Fill')
axes[0,1].legend()

# Chart 3: With trend line
traffic_arr = np.array(traffic)
x_num = np.arange(len(traffic))
z = np.polyfit(x_num, traffic_arr, 1)
p = np.poly1d(z)
axes[1,0].plot(months, traffic, 'o-', color='#4CAF50', linewidth=2, label='Actual')
axes[1,0].plot(months, p(x_num), '--', color='#FF5722', linewidth=2, label=f'Trend (+{z[0]:.0f}/month)')
axes[1,0].set_title('With Linear Trend Line')
axes[1,0].legend()

# Chart 4: With annotations
axes[1,1].plot(months, traffic, 'o-', color='#9C27B0', linewidth=2)
peak_idx = traffic.index(max(traffic))
axes[1,1].annotate(f'Peak: {max(traffic):,}',
    xy=(months[peak_idx], max(traffic)),
    xytext=(months[peak_idx-3], max(traffic) + 3000),
    arrowprops=dict(arrowstyle='->', color='gray'))
axes[1,1].set_title('With Peak Annotation')

for ax in axes.flatten():
    ax.xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter('%b'))
    ax.grid(True, alpha=0.3)
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)

plt.suptitle('Line Chart Variations', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.savefig('line_variations.png', dpi=150)
plt.show()

3. Multi-Line Comparison Charts

python
123456789101112131415161718192021222324252627282930
# Multi-product revenue comparison
products = {
    'Product A': [42000, 45000, 51000, 48000, 53000, 62000, 58000, 61000, 67000, 72000, 89000, 95000],
    'Product B': [28000, 31000, 35000, 33000, 38000, 42000, 39000, 43000, 47000, 51000, 60000, 68000],
    'Product C': [15000, 16000, 18000, 17000, 19000, 22000, 21000, 24000, 26000, 28000, 32000, 36000]
}

colors = ['#1565C0', '#2E7D32', '#E65100']
months_str = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']

fig, ax = plt.subplots(figsize=(12, 6))

for (product, values), color in zip(products.items(), colors):
    ax.plot(months_str, values, 'o-', color=color, linewidth=2.5,
            markersize=6, label=f'{product} (Dec: ${values[-1]:,.0f})')

# Add a target line
ax.axhline(y=50000, color='red', linestyle=':', linewidth=1.5, label='Target: $50K')

ax.set_title('Product Revenue Comparison — 2024', fontsize=14, fontweight='bold')
ax.set_xlabel('Month')
ax.set_ylabel('Revenue ($)')
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'${x/1000:.0f}K'))
ax.legend(loc='upper left', framealpha=0.8)
ax.grid(True, alpha=0.3)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.tight_layout()
plt.savefig('multi_line.png', dpi=150)
plt.show()

4. Mini Project: Monthly Revenue Tracker

python
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

np.random.seed(42)
months = pd.date_range('2024-01', periods=12, freq='MS')
revenue = np.array([95000, 88000, 102000, 97000, 115000, 128000,
                    120000, 132000, 140000, 135000, 155000, 175000])
target = np.full(12, 120000)
ma3 = pd.Series(revenue).rolling(3).mean().values  # 3-month moving average

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), sharex=True)

# Top: Revenue vs Target
ax1.fill_between(months, revenue, target,
                  where=(revenue >= target), alpha=0.2, color='green', label='Above target')
ax1.fill_between(months, revenue, target,
                  where=(revenue < target), alpha=0.2, color=&#039;red', label='Below target')
ax1.plot(months, revenue, &#039;o-', color='#1565C0', linewidth=2.5, markersize=8, label='Revenue')
ax1.plot(months, target, &#039;--', color='gray', linewidth=1.5, label='Target ($120K)')
ax1.plot(months, ma3, &#039;-', color='#FF9800', linewidth=2, alpha=0.8, label='3-Month MA')
ax1.set_title(&#039;Revenue vs Target with Moving Average', fontsize=13, fontweight='bold')
ax1.set_ylabel(&#039;Revenue ($)')
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f&#039;${x/1000:.0f}K'))
ax1.legend(loc=&#039;upper left')
ax1.grid(True, alpha=0.3)
ax1.spines[&#039;top'].set_visible(False)
ax1.spines[&#039;right'].set_visible(False)

# Bottom: Month-over-month change
mom_change = np.diff(revenue, prepend=revenue[0])
colors = [&#039;#4CAF50' if c >= 0 else '#F44336' for c in mom_change]
ax2.bar(months, mom_change, color=colors, alpha=0.8, width=20)
ax2.axhline(y=0, color=&#039;black', linewidth=0.8)
ax2.set_title(&#039;Month-over-Month Change', fontsize=13, fontweight='bold')
ax2.set_ylabel(&#039;Change ($)')
ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f&#039;${x:+,.0f}'))
ax2.xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter(&#039;%b'))
ax2.grid(True, alpha=0.3)
ax2.spines[&#039;top'].set_visible(False)
ax2.spines[&#039;right'].set_visible(False)

total = revenue.sum()
avg = revenue.mean()
fig.suptitle(f&#039;Revenue Tracker 2024 | Total: ${total/1e6:.2f}M | Avg: ${avg:,.0f}/month',
              fontsize=14, fontweight=&#039;bold')
plt.tight_layout()
plt.savefig(&#039;revenue_tracker.png', dpi=150, bbox_inches='tight')
plt.show()

5. Common Mistakes

  • Too many lines: More than 5-6 lines in one chart creates spaghetti plots. Use small multiples or highlight only key lines.
  • Not starting Y-axis at zero for percentage data: Truncated Y-axes can make small changes look dramatic — a 2% change can look like 100% with a truncated axis.

6. MCQs

Question 1

Line chart is best for?

Question 2

np.polyfit(x, y, 1) calculates?

Question 3

fillbetween(x, y1, y2, where=cond) fills?

Question 4

3-month moving average is calculated with?

Question 5

sharex=True in subplots?

Question 6

Too many lines on one chart causes?

Question 7

ax.xaxis.setmajorformatter(DateFormatter('%b')) formats dates as?

Question 8

markerfacecolor='white' creates?

Question 9

Month-over-month (MoM) change is calculated with?

Question 10

Reference line for target/threshold uses?

7. Interview Questions

  • Q: When would you use a line chart vs a bar chart for monthly data?
  • Q: How do you add a trend line to a line chart in Matplotlib?

8. Summary

Line charts communicate time-based trends instantly. Key enhancements: trend lines (np.polyfit), moving averages (.rolling()), fill_between for above/below target areas, and annotations for key points. Limit to 5-6 lines per chart. Always label the Y-axis clearly with units.

9. Next Chapter Recommendation

In Chapter 6: Bar Charts and Categorical Data, we master vertical, horizontal, grouped, and stacked bar charts for categorical comparison.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·