Tabel 121 titik data tidak dibaca siapa pun; kurva S dibaca dalam dua detik. Bagian ini mengubah struct hasil menjadi tiga gambar: kurva biomassa dengan anotasi kunci, kurva laju yang menampar titik belok, dan panel perbandingan tiga skenario laju. Semua digambar oleh satu fungsi buatVisualisasi yang aman dijalankan tanpa layar — figur dibuat tersembunyi, disimpan ke PNG, lalu ditutup.

Yang kita bangun pada bagian ini:

  • Gambar 1 — kurva P vs t dengan garis K, garis K/2, dan marker t50,
  • Gambar 2 — laju dP/dt vs t dengan puncak sebagai titik belok,
  • Gambar 3 — subplot tiga skenario r = 0.05, 0.08, 0.12,
  • kesalahan umum visualisasi simulasi dan cara menghindarinya,
  • persiapan data heatmap dari hasil sweep untuk Bagian 4.

Tiga Gambar dari Satu Fungsi #

buatVisualisasi(hasil, dirOutput) menerima struct hasil Bagian 2 dan folder keluaran, menyimpan tiga PNG, lalu mengonfirmasi tiap berkas lewat fprintf:

text
Gambar 1: kurva-pertumbuhan.png - kurva P, garis K dan K/2, t50 = 57.44 hari
Gambar 2: laju-pertumbuhan.png - laju dP/dt, puncak 1.9997 per hari di t = 57.50 hari
Gambar 3: skenario-r.png - perbandingan r = 0.05, 0.08, 0.12 per hari

Mengapa tiga gambar dan bukan satu? Karena tiap gambar menjawab satu pertanyaan: gambar 1 menjawab “berapa dan kapan”, gambar 2 menjawab “kapan paling cepat”, gambar 3 menjawab “bagaimana kalau lajunya berubah”.

Kurva Pertumbuhan dengan Anotasi #

Kurva utama adalah P versus t. Nilai yang membuatnya berguna justru bukan kurvanya, melainkan tiga acuan di sekelilingnya: kapasitas K, setengah kapasitas, dan waktu t50. Berikut bagian Gambar 1 dari src/buatVisualisasi.m:

matlab
% ---- Gambar 1: kurva pertumbuhan + anotasi ----
f1 = figure('visible', 'off');
plot(t, P, 'b-', 'LineWidth', 2);
hold on;
plot([t(1) t(end)], [K K], 'k--', 'LineWidth', 1);
plot([t(1) t(end)], [K/2 K/2], 'r--', 'LineWidth', 1);
plot(t50, K/2, 'ko', 'MarkerFaceColor', 'k');
hold off;
text(t50 + 4, K/2 + 3, sprintf('t50 = %.1f hari', t50));
legend('P(t)', 'K', 'K/2', 't50', 'Location', 'southeast');
xlabel('waktu t (hari)');
ylabel('biomassa P (satuan)');
title('Pertumbuhan logistik biomassa tanaman');
grid on;
berkas{1} = 'kurva-pertumbuhan.png';
saveas(f1, fullfile(dirOutput, berkas{1}));
delete(f1);

Mari kita bedah baris-baris kode di atas:

  1. Baris 2 (f1 = figure('visible', 'off');): figur dibuat tersembunyi sejak awal. Skrip tetap berjalan di mesin tanpa tampilan — server uji, container, CI — dan saveas tetap menghasilkan PNG.
  2. Baris 5-6 (plot([t(1) t(end)], [K K], 'k--', ...)): garis horizontal digambar manual lewat dua titik (t awal, K) dan (t akhir, K). MATLAB modern punya yline untuk ini, tetapi Octave belum menyediakannya; dua titik bekerja di keduanya.
  3. Baris 7 (plot(t50, K/2, 'ko', 'MarkerFaceColor', 'k');): marker bulat diletakkan tepat di perpotongan garis K/2 dan kurva — momen biomassa mencapai separuh kapasitas.
  4. Baris 9 (text(t50 + 4, K/2 + 3, sprintf(...))): anotasi teks dibangun dengan sprintf dari data, bukan diketik manual, sehingga angkanya ikut benar saat parameter berubah. Offset +4 hari dan +3 satuan menyimpannya dari garis putus-putus.
  5. Baris 10 (legend('P(t)', 'K', 'K/2', 't50', ...)): empat entri legenda cocok dengan empat panggilan plot sebelumnya — legenda menghitung panggilan plot, jadi urutan label harus mengikuti urutan penggambaran.
  6. Baris 15-17 (saveas ... delete(f1);): gambar disimpan dengan fullfile agar pemisah folder benar di sistem operasi mana pun, lalu figur ditutup supaya memori grafik tidak menumpuk saat fungsi dipanggil berulang.

Laju Pertumbuhan dan Titik Belok #

Turunan numerik diff(P)./diff(t) dari grid harian memberi laju rata-rata per hari — dan puncaknya adalah informasi penting kurva S. Berikut bagian Gambar 2:

matlab
% ---- Gambar 2: laju pertumbuhan + titik belok ----
laju = diff(P) ./ diff(t);
tTengah = t(1:end-1) + diff(t) / 2;
[lajuMaks, iMaks] = max(laju);

f2 = figure('visible', 'off');
plot(tTengah, laju, 'b-', 'LineWidth', 2);
hold on;
plot(tTengah(iMaks), lajuMaks, 'ro', 'MarkerFaceColor', 'r');
hold off;
text(tTengah(iMaks) + 5, 0.9 * lajuMaks, ...
    sprintf('puncak %.2f per hari di t = %.1f', lajuMaks, ...
    tTengah(iMaks)));
legend('dP/dt', 'puncak', 'Location', 'southeast');
xlabel('waktu t (hari)');
ylabel('laju dP/dt (satuan per hari)');
title('Laju pertumbuhan: puncak di titik belok');
grid on;
berkas{2} = 'laju-pertumbuhan.png';
saveas(f2, fullfile(dirOutput, berkas{2}));
delete(f2);

Mari kita bedah baris-baris kode di atas:

  1. Baris 2-3 (laju = ... tTengah = ...): diff memendekan vektor satu elemen, jadi laju antar hari diberi waktu khusus di tengah interval — (t ke-k + t ke-k+1)/2. Tanpa ini, kurva laju bergeser setengah hari ke kiri.
  2. Baris 4 ([lajuMaks, iMaks] = max(laju);): satu panggilan memberi nilai puncak dan posisinya — pola yang sama dipakai analisisFase.
  3. Baris 9-13 (marker dan anotasi puncak): lingkaran merah menandai puncak; teksnya dihitung dari lajuMaks dan tTengah(iMaks). Pada hasil nyata: 1.9997 per hari di t = 57.5 — menimpa t50 = 57.44. Bukan kebetulan: puncak laju logistik memang jatuh tepat di P = K/2, itulah titik belok.

Bentuk kurvanya sendiri sudah bercerita: naik, memuncak di tengah musim, lalu turun mendekati nol — fase eksponensial, transisi, dan jenuh yang dihitung analisisFase kini terlihat mata.

Membandingkan Skenario dengan subplot #

Pertanyaan “bagaimana kalau lajunya berubah?” dijawab dengan mengulang simulasi untuk beberapa r lalu menumpuknya sebagai subplot. Berikut bagian Gambar 3:

matlab
% ---- Gambar 3: perbandingan skenario laju r ----
daftarR = [0.05 0.08 0.12];
paramsSkenario = hasil.params;
f3 = figure('visible', 'off');
for k = 1:numel(daftarR)
    paramsSkenario.r = daftarR(k);
    hasilK = jalankanSimulasi(paramsSkenario);
    subplot(3, 1, k);
    plot(hasilK.t, hasilK.P, 'b-', 'LineWidth', 2);
    hold on;
    plot([hasilK.t(1) hasilK.t(end)], [K K], 'k--', 'LineWidth', 1);
    hold off;
    title(sprintf('r = %.2f per hari (K = %g)', daftarR(k), K));
    xlabel('waktu t (hari)');
    ylabel('P (satuan)');
    grid on;
end
berkas{3} = 'skenario-r.png';
saveas(f3, fullfile(dirOutput, berkas{3}));
delete(f3);

Mari kita bedah baris-baris kode di atas:

  1. Baris 3 (paramsSkenario = hasil.params;): struct disalin per nilai. Mengubah paramsSkenario.r di dalam loop tidak menyentuh parameter simulasi dasar yang tersimpan di hasil.params.
  2. Baris 5-7 (for k = ... hasilK = jalankanSimulasi(...)): tiap skenario mensimulasikan ulang dari awal — simulasi 121 titik hanya butuh sekejap, jadi tidak ada alasan mendaur ulang data lama.
  3. Baris 8 (subplot(3, 1, k);): tiga panel bertumpuk vertikal dengan sumbu waktu yang sejajar — pembandingan kurva waktu paling mudah dibaca saat matanya bergerak lurus ke bawah. tiledlayout adalah alternatif modern di MATLAB, tetapi subplot bekerja di kedua mesin.
  4. Baris 11 (garis K di tiap panel): garis acuan yang sama di tiap panel membuat perbandingan adil — ketiga kurva menuju K yang sama; yang berbeda hanyalah kecepatannya. Panel r = 0.05 bahkan belum menyentuh 90% K pada hari ke-120.

Kesalahan Umum Visualisasi Simulasi #

KesalahanAkibatPerbaikan
Sumbu y dipotong (misalnya mulai dari 80)kurva terlihat “sudah jenuh” padahal belumbiarkan rentang alami; bila perlu, mulai dari 0
Label tanpa satuan“P = 55” tidak berarti apa punxlabel/ylabel selalu menyebut satuan
Banyak kurva berbeda parameter dalam satu axeslegenda membludak, pola tak terbacapisahkan dengan subplot
Angka anotasi diketik manualsalah saat parameter berubahbangun dari data lewat sprintf
Tanpa garis acuan K“99.3340” tidak jelas dekat atau jauh dari kapasitasgambar garis K dan K/2

Menyimpan Gambar dan Resolusi #

saveas(f, berkas) menyimpan resolusi layar — cukup untuk web dan laporan layar. Untuk cetak, print(f, 'kurva.png', '-dpng', '-r300') menaikkan resolusi ke 300 dpi; keduanya tersedia di MATLAB dan Octave. MATLAB R2020b+ juga menawarkan exportgraphics(f, 'kurva.png', 'Resolution', 300) yang hasilnya lebih rapi — tulis sebagai pilihan modern bila target Anda MATLAB murni.

Menyiapkan Data Heatmap #

Bagian 4 akan menyapu grid parameter r x K, dan hasilnya paling enak dibaca sebagai heatmap. Data-datanya sudah berbentuk matriks Pakhir (baris = r, kolom = K); yang tersisa hanya memetakan ke sumbu. Potongan berikut dari src/jalankanStudi.m menggambar heatmap dari data sweep:

matlab
% ---- 5. heatmap dari data sweep ----
[R, KK] = meshgrid(sweep.r, sweep.K);
f4 = figure('visible', 'off');
contourf(R, KK, sweep.Pakhir.');
colorbar();
xlabel('r (per hari)');
ylabel('K (satuan biomassa)');
title('P akhir hari ke-120 pada grid r x K');
namaHeatmap = 'heatmap-sweep.png';
saveas(f4, fullfile(dirOutput, namaHeatmap));
delete(f4);
fprintf('Heatmap: %s - P akhir pada grid r x K\n', namaHeatmap);

Mari kita bedah baris-baris kode di atas:

  1. Baris 2 ([R, KK] = meshgrid(sweep.r, sweep.K);): meshgrid membangun grid koordinat dengan baris mengikuti argumen kedua (K) dan kolom mengikuti argumen pertama (r) — ukuran 4x5.
  2. Baris 4 (contourf(R, KK, sweep.Pakhir.')): Pakhir berukuran 5x4 (baris r), jadi ia harus di-transpose agar sejalan dengan grid 4x5 hasil meshgrid. Transpose yang hilang adalah bug heatmap paling umum: gambar tetap tergambar, tapi terbalik diameternya.
  3. Baris 5 (colorbar();): kontur terisi tanpa skala warna hanya jadi dekorasi; colorbar memberi angkanya.

Tiga gambar dan mesin penghasilnya sudah berdiri. Bagian 4 mengisi panel datanya: menyapu 20 kombinasi parameter, membaca matriksnya, dan melipat semuanya menjadi laporan otomatis.