01ภาพรวมของกราฟนี้
Calendar heatmap วางแต่ละวันเป็นช่องในตารางที่คอลัมน์คือสัปดาห์และแถวคือวันในสัปดาห์ การจัดแบบนี้ทำให้รูปแบบสองชั้นปรากฏพร้อมกัน คือรูปแบบรายสัปดาห์ในแนวตั้งและรูปแบบรายฤดูกาลในแนวนอน
เปรียบเทียบให้เห็นภาพ
เหมือนปฏิทินตั้งโต๊ะที่เราระบายสีช่องตามปริมาณงานของแต่ละวัน พอมองทั้งแผ่นก็เห็นทันทีว่าช่วงไหนยุ่งและวันไหนของสัปดาห์ที่ว่างเสมอ
ใช้ที่ไหนบ้าง — จำนวนคำสั่งซื้อรายวัน กิจกรรมของผู้ใช้ ปริมาณการใช้ไฟฟ้า หรือการติดตามพฤติกรรมรายวันตลอดปี
สร้างอย่างไร
- ขั้นที่ 1จัดวันเป็นตารางสัปดาห์ × วันในสัปดาห์
- ขั้นที่ 2เลือกช่วงสีแบบเฉดเดียว
- ขั้นที่ 3ระบายสีตามค่า
- ขั้นที่ 4ใส่ legend และทำเครื่องหมายวันหยุด
ตัวอย่างคลาสสิก · GitHub contribution graph
ตารางสีเขียวที่แสดงกิจกรรมรายวันของนักพัฒนาตลอดปี เป็นตัวอย่างที่ทำให้ปฏิทินความร้อนกลายเป็นรูปแบบที่คนทั่วไปคุ้นเคย และแสดงจุดแข็งของมันชัดเจน คือเห็นทั้งวันที่ขยันและช่วงที่หายไปในภาพเดียว
02อธิบายภาษาคนเข้าใจง่าย ๆ
ข้อมูลรายวันตลอดปีมี 365 จุด ถ้าวาดเป็นกราฟเส้นจะได้เส้นหยักถี่ยิบที่บอกภาพรวมไม่ได้ และมองไม่เห็นเลยว่าวันเสาร์อาทิตย์ต่างจากวันธรรมดาไหม
ปฏิทินความร้อนแก้ด้วยการเปลี่ยนแกนเวลาหนึ่งมิติให้เป็นตารางสองมิติ คอลัมน์คือสัปดาห์ที่ไล่จากซ้ายไปขวา และแถวคือวันจันทร์ถึงอาทิตย์
ทันทีที่จัดแบบนี้ รูปแบบสองชั้นก็โผล่ออกมาเอง ถ้าแถวล่างสองแถวจางกว่าเสมอแปลว่าเสาร์อาทิตย์เงียบ และถ้าคอลัมน์ช่วงกลางเข้มกว่าแปลว่ามีฤดูกาล
สิ่งที่กราฟนี้เก่งเป็นพิเศษคือการหาช่องว่าง ถ้ามีแถบจางยาว ๆ กลางปี นั่นอาจเป็นช่วงที่ระบบเก็บข้อมูลล่ม ซึ่งเป็นสิ่งที่กราฟเส้นมักกลบไป
ข้อจำกัดคืออ่านค่าที่แม่นยำไม่ได้เพราะเข้ารหัสด้วยความเข้มของสี ควรใช้คู่กับ tooltip หรือมีกราฟเส้นข้าง ๆ สำหรับการอ่านค่า
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
date | วันที่ | เวลา | x | 90–730 วัน | ต้องเป็นรายวันและควรครอบคลุมทั้งช่วงโดยไม่ข้ามวัน |
value | ค่าของวันนั้น | ตัวเลขต่อเนื่อง | color | ต่อเนื่องหรือจำนวนนับ | เข้ารหัสด้วยความเข้มของสี จึงอ่านค่าได้หยาบ |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- เติมวันที่ขาดให้ครบทุกวันในช่วง โดยทำเครื่องหมายว่าไม่มีข้อมูล
- ตัดสินใจว่าจะใช้ช่วงสีแบบเชิงเส้นหรือแบบควอนไทล์
- เตรียมรายการวันหยุดไว้ทำเครื่องหมาย
ค่าที่หายไป — ★ วันที่ไม่มีข้อมูลต้องใช้สีที่ต่างจากศูนย์อย่างชัดเจน เช่น สีเทาลายทาง เพราะ "ไม่มีคำสั่งซื้อ" กับ "ไม่ได้เก็บข้อมูล" เป็นคนละเรื่อง
สเกลของแกน — เฉดเดียวอ่อนไปเข้ม ถ้าข้อมูลเบ้แรงให้ใช้การแบ่งแบบควอนไทล์แทนเชิงเส้น แต่ต้องเขียนบอก
กราฟจะเริ่มพังเมื่อไร
เกิน 2 ปีในภาพเดียวช่องจะเล็กจนกดไม่ได้ — แยกเป็นปีละแถว
หน้าตาไฟล์ที่ต้องเตรียม
date,value
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- รูปแบบรายสัปดาห์ เช่น เสาร์อาทิตย์ต่างจากวันธรรมดา
- รูปแบบตามฤดูกาลจากความเข้มที่เปลี่ยนไปตามคอลัมน์
- วันที่โดดเด่นผิดปกติ
- ช่วงที่ข้อมูลหายไป
สิ่งที่กราฟนี้ตอบไม่ได้
- อ่านค่าที่แม่นยำไม่ได้เพราะใช้ความเข้มของสี
- ไม่บอกแนวโน้มระยะยาวได้ชัดเท่ากราฟเส้น
อ่านตามลำดับนี้
- มองแนวตั้งก่อนเพื่อหารูปแบบรายสัปดาห์
- มองแนวนอนเพื่อหารูปแบบตามฤดูกาล
- หาช่องหรือแถบที่ผิดจากรอบข้าง
คำถามที่ตอบได้
- วันไหนของสัปดาห์ที่ยุ่งที่สุด
- มีฤดูกาลหรือไม่
- มีช่วงที่ข้อมูลขาดหายไปไหม
สัญญาณว่ามีอะไรผิด
- ใช้สีเดียวกันสำหรับศูนย์กับไม่มีข้อมูล
- ใช้ rainbow palette ซึ่งทำให้ลำดับของค่าอ่านไม่ออก
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ใส่ legend ไล่เฉดพร้อมค่าต่ำสุดและสูงสุด
- ทำเครื่องหมายวันหยุดสำคัญ
- ใช้สีที่ต่างชัดเจนสำหรับวันที่ไม่มีข้อมูล
รูปแบบชื่อกราฟที่ควรใช้
กิจกรรมสูงสุดใน <ช่วงเวลา> โดย <วันในสัปดาห์> เงียบที่สุดอย่างสม่ำเสมอ
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ calendar-heatmap-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด calendar-heatmap-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("calendar-heatmap-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "weekly" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
df["date"] = pd.to_datetime(df["date"])
d = df.sort_values("date")
d["week"] = ((d["date"] - d["date"].min()).dt.days + d["date"].min().weekday()) // 7
d["dow"] = d["date"].dt.weekday
grid = np.full((7, d["week"].max() + 1), np.nan) # nan = ไม่มีข้อมูล ต่างจากศูนย์
for _, r in d.iterrows():
grid[int(r["dow"]), int(r["week"])] = r["value"]
im = ax.imshow(grid, cmap="Blues", aspect="equal")
ax.set_yticks(range(7)); ax.set_yticklabels(["จ", "อ", "พ", "พฤ", "ศ", "ส", "อา"], fontsize=8)
ax.set_xticks([]); fig.colorbar(im, ax=ax, shrink=0.6, label="ค่า")
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("calendar-heatmap.png", dpi=160)
print("เขียน calendar-heatmap.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (26 แถว)
| วันที่ | ค่า |
|---|---|
| 2025-01-01 | 24.0 |
| 2025-01-08 | 25.0 |
| 2025-01-15 | 32.0 |
| 2025-01-22 | 23.0 |
| 2025-01-29 | 21.0 |
| 2025-02-05 | 23.0 |
| 2025-02-12 | 25.0 |
| 2025-02-19 | 24.0 |
| 2025-02-26 | 15.0 |
| 2025-03-05 | 19.0 |
| 2025-03-12 | 13.0 |
| 2025-03-19 | 33.0 |
| 2025-03-26 | 25.0 |
| 2025-04-02 | 17.0 |
| 2025-04-09 | 16.0 |
| 2025-04-16 | 18.0 |
| 2025-04-23 | 25.0 |
| 2025-04-30 | 21.0 |
| 2025-05-07 | 21.0 |
| 2025-05-14 | 31.0 |
| 2025-05-21 | 18.0 |
| 2025-05-28 | 28.0 |
| 2025-06-04 | 18.0 |
| 2025-06-11 | 18.0 |
| 2025-06-18 | 33.0 |
| 2025-06-25 | 16.0 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: มองแนวตั้งจะเห็นว่าสองแถวล่าง (เสาร์อาทิตย์) จางกว่าอย่างสม่ำเสมอ ซึ่งเป็นรูปแบบที่กราฟเส้นรายวันกลบไปหมด
ข้อควรระวัง
- ★ ใช้สีเดียวกันสำหรับ "ค่าเป็นศูนย์" กับ "ไม่มีข้อมูล" ซึ่งเป็นคนละความหมายโดยสิ้นเชิง
- ใช้ rainbow palette ทำให้ลำดับของค่าอ่านไม่ออก
- ใส่หลายปีในภาพเดียวจนช่องเล็กเกินกด
- ใช้ช่วงสีเชิงเส้นกับข้อมูลที่เบ้แรง ทำให้เกือบทุกช่องดูจางเหมือนกันหมด
เมื่อไหร่ไม่ควรใช้กราฟนี้
- ข้อมูลไม่ใช่รายวัน→ใช้แทน: heatmap-matrix หรือ line-chart
- ต้องอ่านค่าที่แม่นยำ→ใช้แทน: line-chart หรือ data-table-viz
- มีข้อมูลน้อยกว่า 2 เดือน→ใช้แทน: bar-chart รายวัน