Angka yang bersih baru separuh pekerjaan — keputusan diambil dari gambar yang terbaca sekali pandang. Bagian ini membangun buatVisualisasi.m, fungsi yang mengubah data bersih dari Bagian 2 dan statistik dari hitungStatistik menjadi tiga figur PNG: tren bulanan tiga produk, distribusi penjualan per produk, dan perbandingan total tahunan. Ketiganya digambar headless — tanpa jendela — lalu disimpan sebagai PNG 150 dpi, sehingga pipeline kita aman dijalankan di server maupun runner tanpa layar.
Yang kita bangun pada bagian ini:
- tabel pilihan grafik yang memetakan pertanyaan analisis ke jenis grafik,
- konsistensi format — satu warna per produk, dipakai semua figur,
- figur 1 (line plot tren), figur 2 (histogram subplot), figur 3 (grouped bar + errorbar),
- prinsip satu grafik satu pesan, lalu demo nyata yang menghasilkan tiga berkas PNG.
Memilih Grafik dari Pertanyaan #
Jenis grafik ditentukan oleh pertanyaan analisis, bukan selera. Tiga pertanyaan yang kita tetapkan di Bagian 1 memetakan langsung ke tiga jenis grafik:
| Pertanyaan analisis (Bagian 1) | Jenis grafik | Keluaran |
|---|---|---|
| Bagaimana tren tiap produk — naik, turun, atau berulang? | line plot tiga seri | tren_bulanan.png |
| Bagaimana sebaran tiap produk — stabil, lebar, atau berpuncak musiman? | histogram per produk | distribusi_penjualan.png |
| Bagaimana perbandingan total tahunan antar produk beserta variabilitasnya? | grouped bar + errorbar | perbandingan_tahunan.png |
Tiap baris punya alasan: perjalanan waktu kontinu paling jujur dibaca dari garis; bentuk sebaran hanya tampak pada distribusi frekuensi; perbandingan kategori paling tepat batang, dengan errorbar menambahkan informasi variabilitas.
Prinsip yang kita pegang: satu grafik, satu pesan. Mencampur ketiganya dalam satu kanvas berarti kekacauan — 36 bulan x 3 produk sebagai bar chart menghasilkan 108 batang, dan skala bulanan bertabrakan dengan skala tahunan. Tiga figur terpisah lebih mudah dibaca, dibuat, dan diuji satu per satu.
Konsistensi Format Lintas Figur #
Pembaca laporan akan melihat ketiga figur berdampingan, jadi warna produk dikunci di satu matriks dan dipakai semua figur:
% satu warna per produk, sama di semua figur
warna = [0.45 0.30 0.20; % kopi : coklat
0.20 0.60 0.35; % teh : hijau
0.25 0.50 0.80]; % susu : biruTiap baris adalah RGB triplet bernilai 0-1; warna(k, :) mengambil warna produk ke-k. Kopi coklat, teh hijau, susu biru — asosiasi alami yang membuat mata tidak perlu belajar ulang di tiap figur. Ketiga figur juga lahir dari pola yang sama: figure('visible', 'off'), menggambar, print(f, '-dpng', '-r150', ...), lalu delete(f). Figur tidak pernah muncul di layar, PNG ditulis pada 150 dpi, dan memori figur dibebaskan begitu selesai.
Figur 1: Tren Bulanan Tiga Produk #
Pertanyaan pertama menanyakan arah perjalanan penjualan selama 36 bulan — line plot menjawabnya langsung:
% ---- figur 1: tren bulanan tiga produk ----
f1 = figure('visible', 'off');
hold on;
garis = [];
for k = 1:nProduk
h = plot(bulan, penjualan(:, k), '-');
set(h, 'Color', warna(k, :), 'LineWidth', 1.6);
garis = [garis, h];
end
hold off;
set(gca, 'XTick', 1:3:36);
grid on;
xlabel('Bulan ke- (1 = Jan 2022, 36 = Des 2024)');
ylabel('Unit terjual');
title('Tren penjualan bulanan tiga produk (36 bulan)');
legend(garis, namaProduk, 'Location', 'northwest');
print(f1, '-dpng', '-r150', fullfile(folderHasil, 'tren_bulanan.png'));
delete(f1);Mari kita bedah baris-baris kode di atas (nomor baris mengikuti berkas src/buatVisualisasi.m):
- Baris 22 (
f1 = figure('visible', 'off');): figur dibuat tanpa jendela — menggambar ke figur invisible sama saja dengan figur biasa, hanya tidak ditampilkan. Inilah kunci mode headless. - Baris 23-29 (
hold on; ... end):hold onmembuat tiga panggilanplotmenumpuk pada sumbu yang sama; loop menggambar satu seri per produk, dan handle tiap garis dikumpulkan kegarisuntuk keperluan legend. - Baris 26-27 (
h = plot(...); set(h, ...)):plotmenggambar garis solid, lalusetmenimpa warnanya denganwarna(k, :)dan menebalkan garis ke 1.6 poin agar tiga seri mudah dibedakan. - Baris 31 (
set(gca, 'XTick', 1:3:36);): tanda sumbu-x diletakkan tiap tiga bulan (per kuartal) — tanpa ini, 36 label bulan saling bertabrakan. - Baris 33-35 (
xlabelsampaititle): label-x menyebut pemetaan1 = Jan 2022, 36 = Des 2024sehingga periode data jelas tanpa perlu kolom tanggal di dataset. - Baris 36 (
legend(garis, namaProduk, ...)): legend dibangun dari handle yang dikumpulkan loop, diletakkan dinorthwest— pojok yang kosong karena ketiga seri menanjak dari kiri-bawah. - Baris 37-38 (
print(f1, ...); delete(f1);): figur ditulis sebagai PNG 150 dpi kefolderHasil, lalu figur dihapus dari memori.
Membaca hasilnya: kopi menanjak dari 581 unit (Jan 2022) ke 780 (Des 2024) dengan lonjakan Desember yang nyata tiap tahun; teh naik paling landai (246 ke 325) dengan puncak Juni; susu naik paling curam (448 ke 686) dengan musiman yang tipis. Ketiga karakter itu persis parameter formula yang kita tanam di Bagian 1 — figur hanya menyingkapnya.
Figur 2: Distribusi dengan Subplot #
Pertanyaan kedua menanyakan bentuk sebaran. Histogram membalasnya, satu panel per produk lewat subplot:
% ---- figur 2: distribusi penjualan per produk ----
f2 = figure('visible', 'off');
for k = 1:nProduk
subplot(3, 1, k);
% hist dengan output hanya menghitung; bar yang menggambar
[n, pusat] = hist(penjualan(:, k), 10);
hb = bar(pusat, n, 1.0);
set(hb, 'FaceColor', warna(k, :), 'EdgeColor', 'none');
m = st.rata2(k);
yAtas = get(gca, 'YLim');
hold on;
plot([m m], [0, yAtas(2)], 'k--', 'LineWidth', 1);
hold off;
grid on;
title(sprintf('Distribusi %s (garis putus = rata-rata)', namaProduk{k}));
ylabel('Frekuensi bulan');
if k == nProduk
xlabel('Unit terjual per bulan');
end
endBlok ditutup print(f2, ...) dan delete(f2) — pola headless yang sama dengan figur 1. Beberapa hal penting dari kode di atas:
subplot(3, 1, k)membagi kanvas menjadi tiga baris — tiap produk mendapat panel sendiri dengan sumbu-y independen, tetapi sejajar sehingga lebar sebaran bisa dibandingkan.[n, pusat] = hist(penjualan(:, k), 10);—histyang dipanggil dengan argumen keluaran hanya menghitung sepuluh bin (frekuensin, pusat binpusat) tanpa menggambar;bar(pusat, n, 1.0)yang menggambar. Pola dua langkah ini memberi kontrol penuh atas warna dan lebar batang.plot([m m], [0, yAtas(2)], 'k--', ...)menarik garis putus vertikal di rata-rata produk; tinggi garis diambil dariget(gca, 'YLim')sehingga selalu memotong batang tertinggi, berapa pun nilainya.if k == nProdukmemberi label-x hanya pada panel terbawah — label yang sama di tiga panel hanya akan berulang percuma.
Membaca hasilnya: histogram kopi paling lebar (std 105.8) dengan ekor kanan — jejak lonjakan Desember; bin terpadatnya menampung 8 bulan di sekitar 570 unit. Sebaran teh paling sempit (std 59.8), susu di tengah (74.3). Garis putus rata-rata kopi (594.5) jatuh di kanan pusat kepadatan — gejala sebaran miring kanan akibat musiman.
Figur 3: Perbandingan Tahunan dengan Errorbar #
Pertanyaan ketiga membandingkan total tahunan. Grouped bar membariskan tiga produk per tahun, dan errorbar menambahkan std bulanan sebagai sungkupan variabilitas:
% ---- figur 3: perbandingan tahunan + galat std ----
f3 = figure('visible', 'off');
x = 1:3; % posisi 2022, 2023, 2024
lebar = 0.22; % lebar batang tiap produk
batang = [];
hold on;
for k = 1:nProduk
posisi = x + (k - 2) * 0.25; % geser kiri / tengah / kanan
hb = bar(posisi, st.totalTahunan(:, k)', lebar);
set(hb, 'FaceColor', warna(k, :));
batang = [batang, hb];
he = errorbar(posisi, st.totalTahunan(:, k)', ...
st.devstdTahunan(:, k)', 'k.');
set(he, 'LineWidth', 1.2);
end
hold off;
set(gca, 'XTick', x, 'XTickLabel', {'2022', '2023', '2024'});
grid on;
xlabel('Tahun');
ylabel('Total unit terjual');
title('Total penjualan tahunan per produk (sungkupan = std bulanan)');
legend(batang, namaProduk, 'Location', 'northwest');
print(f3, '-dpng', '-r150', fullfile(folderHasil, 'perbandingan_tahunan.png'));
delete(f3);Mari kita bedah bagian intinya (nomor baris mengikuti berkas):
- Baris 65-66 (
x = 1:3; lebar = 0.22;): tiga posisi tahun dan lebar batang 0.22 unit — sisanya menjadi ruang di antara kelompok tahun. - Baris 70 (
posisi = x + (k - 2) * 0.25;): produk pertama digeser -0.25, kedua tetap, ketiga +0.25 — tiga batang berkelompok rapat tanpa bertumpuk; ekspresi(k - 2)menghasilkan -1, 0, +1 untuk k = 1..3. - Baris 71 (
hb = bar(posisi, st.totalTahunan(:, k)', lebar);): kolom ke-k dari tabel total tahunan ditranspose menjadi vektor baris —barmenuntut dataysejajar denganposisi. Warna batang tetap warna produk. - Baris 74-76 (
he = errorbar(...)): tiap batang diberi sungkupan ± std bulanan pada tahun itu; penanda'k.'menggambar titik data kecil di puncak batang sehingga perhatian jatuh ke garis galat vertikalnya. - Baris 79 (
set(gca, 'XTick', x, 'XTickLabel', ...)): posisi 1-3 diberi label tahun2022-2024, bukan angka mentah. - Baris 84 (
legend(batang, ...)): legend hanya menerima handle batang, bukan handle errorbar — tiga entri bersih tanpa duplikat.
Angka yang digambar figur ini tercetak pada demo di bawah: kopi selalu tertinggi, 6713 lalu 6961 lalu 7729 unit; susu mengejar paling cepat (+17.6% lalu +13.7%) hingga 7454. Satu detail yang layak dibaca jeli: sungkupan kopi 2022 paling lebar (std 127.3, versus 88.1 dan 86.1 pada tahun berikutnya) — jejak dua koreksi data Bagian 2 yang sama-sama jatuh di tahun 2022: spike 2304 yang dipotong ke pagar atas 867 dan NaN bulan ke-9 yang diimputasi median 589. Visualisasi yang jujur memperlihatkan efek pembersihan, bukan menyembunyikannya.
Demo: Tiga Figur Sekali Jalan #
Driver berikut menyiapkan data bersih dan statistik, memanggil buatVisualisasi, lalu mencetak daftar figur beserta data yang dikonsumsi figur 3:
% driver: gambar tiga figur pada folder kerja latihan
addpath('src');
folderHasil = fullfile('latihan', 'hasil');
if exist(folderHasil, 'dir') ~= 7
mkdir(folderHasil);
end
data = bersihkanData(muatData(buatDataset('latihan')));
st = hitungStatistik(data);
daftarFigur = buatVisualisasi(data, st, folderHasil);
fprintf('figur tersimpan:\n');
for k = 1:numel(daftarFigur)
fprintf(' %s\n', daftarFigur{k});
end
fprintf('\ndata figur 3 - total tahunan (unit):\n');
fprintf('%6s %8s %8s %8s\n', 'Tahun', 'kopi', 'teh', 'susu');
for t = 1:3
fprintf('%6d %8d %8d %8d\n', st.tahun(t), st.totalTahunan(t, :));
endOutput yang Dihasilkan:
figur tersimpan:
hasil/tren_bulanan.png
hasil/distribusi_penjualan.png
hasil/perbandingan_tahunan.png
data figur 3 - total tahunan (unit):
Tahun kopi teh susu
2022 6713 3805 5573
2023 6961 4200 6553
2024 7729 4353 7454Tiga PNG muncul di latihan/hasil/ dalam hitungan detik, masing-masing belasan hingga puluhan kilobyte. Nilai balik daftarFigur sengaja berupa nama relatif (hasil/...) — format yang sama dipakai laporan pada Bagian 4 untuk mendaftar figur, dan dipakai uji otomatis untuk memeriksa bahwa tiap berkas benar-benar ada dan tidak blank.
Tiga figur selesai dan menjadi bahan laporan. Bagian 4 — penutup proyek — merangkai statistik dan figur menjadi laporan_ringkasan.txt: struktur empat bagian, temuan yang dihitung bukan diketik, jaminan idempoten, dan uji end-to-end yang membuktikan seluruh pipeline layak dipercaya.