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 grafikKeluaran
Bagaimana tren tiap produk — naik, turun, atau berulang?line plot tiga seritren_bulanan.png
Bagaimana sebaran tiap produk — stabil, lebar, atau berpuncak musiman?histogram per produkdistribusi_penjualan.png
Bagaimana perbandingan total tahunan antar produk beserta variabilitasnya?grouped bar + errorbarperbandingan_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:

matlab
% 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 : biru

Tiap 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:

matlab
% ---- 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):

  1. 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.
  2. Baris 23-29 (hold on; ... end): hold on membuat tiga panggilan plot menumpuk pada sumbu yang sama; loop menggambar satu seri per produk, dan handle tiap garis dikumpulkan ke garis untuk keperluan legend.
  3. Baris 26-27 (h = plot(...); set(h, ...)): plot menggambar garis solid, lalu set menimpa warnanya dengan warna(k, :) dan menebalkan garis ke 1.6 poin agar tiga seri mudah dibedakan.
  4. Baris 31 (set(gca, 'XTick', 1:3:36);): tanda sumbu-x diletakkan tiap tiga bulan (per kuartal) — tanpa ini, 36 label bulan saling bertabrakan.
  5. Baris 33-35 (xlabel sampai title): label-x menyebut pemetaan 1 = Jan 2022, 36 = Des 2024 sehingga periode data jelas tanpa perlu kolom tanggal di dataset.
  6. Baris 36 (legend(garis, namaProduk, ...)): legend dibangun dari handle yang dikumpulkan loop, diletakkan di northwest — pojok yang kosong karena ketiga seri menanjak dari kiri-bawah.
  7. Baris 37-38 (print(f1, ...); delete(f1);): figur ditulis sebagai PNG 150 dpi ke folderHasil, 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:

matlab
% ---- 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
end

Blok ditutup print(f2, ...) dan delete(f2) — pola headless yang sama dengan figur 1. Beberapa hal penting dari kode di atas:

  1. 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.
  2. [n, pusat] = hist(penjualan(:, k), 10); — hist yang dipanggil dengan argumen keluaran hanya menghitung sepuluh bin (frekuensi n, pusat bin pusat) tanpa menggambar; bar(pusat, n, 1.0) yang menggambar. Pola dua langkah ini memberi kontrol penuh atas warna dan lebar batang.
  3. plot([m m], [0, yAtas(2)], 'k--', ...) menarik garis putus vertikal di rata-rata produk; tinggi garis diambil dari get(gca, 'YLim') sehingga selalu memotong batang tertinggi, berapa pun nilainya.
  4. if k == nProduk memberi 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:

matlab
% ---- 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):

  1. 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.
  2. 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.
  3. Baris 71 (hb = bar(posisi, st.totalTahunan(:, k)', lebar);): kolom ke-k dari tabel total tahunan ditranspose menjadi vektor baris — bar menuntut data y sejajar dengan posisi. Warna batang tetap warna produk.
  4. 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.
  5. Baris 79 (set(gca, 'XTick', x, 'XTickLabel', ...)): posisi 1-3 diberi label tahun 2022-2024, bukan angka mentah.
  6. 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:

matlab
% 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, :));
end

Output yang Dihasilkan:

text
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     7454

Tiga 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.