Kesadaran aproksimasi

Downsampling untuk skala rendering (LTTB dan sejenisnya).

Daftar isi

Aproksimasi adalah kelas downsampling yang menyasar skala rendering, yaitu memilih titik yang paling menjaga bentuk visual deret, alih-alih menyimpan agregat statistik. Topik ini menjelaskan tiga strategi downsampling visualisasi, yaitu naif, avg-bucket, dan LTTB (Largest Triangle Three Buckets), mekanisme LTTB sebagai algoritma pilihan titik berbasis segitiga, serta kesadaran aproksimasi yang menuntut kode klien untuk menandai titik hasil pilihan alih-alih menyamarkannya dengan data mentah. Setelah membaca topik ini, sebuah deret dengan ribuan titik dapat dirender pada layar dengan ratusan piksel tanpa kehilangan puncak tajam yang penting secara visual.

Downsampling untuk skala rendering#

Layar dashboard memiliki jumlah piksel terbatas, sehingga deret dengan ribuan titik tidak dapat dirender tanpa tumpang tindih. Sebuah grafik dengan lebar 1.080 piksel tidak mampu menampilkan 10.000 titik per hari tanpa banyak titik berbagi piksel yang sama, sehingga visualisasi menjadi tidak terbaca. Downsampling untuk skala rendering memecahkan masalah ini dengan memilih subset titik yang dimuat pada layar, sehingga jumlah titik yang dirender turun drastis tanpa mengaburkan bentuk deret. Berbeda dengan downsampling statistik yang menyimpan agregat per bucket, downsampling rendering tidak menyimpan hasilnya pada basis data, melainkan dihitung pada lapisan aplikasi atau klien visualisasi.

Pilihan algoritma downsampling rendering bergantung pada sifat deret dan kelas bentuk yang ingin dipertahankan. Algoritma naif yang mengambil setiap N-th titik cepat, tetapi dapat melewatkan puncak tajam bila puncak jatuh di antara sampel. Algoritma avg-bucket yang menghitung rerata per bucket mulus, tetapi meratakan puncak dan lembah, sehingga deret terlihat lebih datar daripada aslinya. Algoritma LTTB memilih titik per bucket yang membentuk segitiga terbesar dengan titik terpilih sebelumnya dan rata-rata bucket berikutnya, sehingga puncak tajam tetap dipertahankan. LTTB pertama kali diusulkan oleh Sveinn Steinarsson pada tahun 2013 dan kini menjadi algoritma bawaan banyak library visualisasi deret waktu.

Tiga strategi dan kegagalannya#

Tiga strategi downsampling rendering yang paling sering dibahas adalah naif, avg-bucket, dan LTTB. Strategi naif mengambil setiap N-th titik, sehingga subset keluaran berukuran tetap dan pemilihannya deterministik. Kegagalan naif muncul ketika puncak lokal jatuh tepat di antara dua sampel yang diambil, sehingga puncak tidak terlihat sama sekali pada grafik. Strategi avg-bucket menghitung rerata per bucket, sehingga subset keluaran berukuran tetap dan nilai tiap titik adalah rerata titik mentah dalam bucket. Kegagalan avg-bucket muncul karena rerata selalu lebih kecil dari puncak lokal, sehingga puncak tajam terlihat lebih rendah daripada aslinya. Strategi LTTB memilih satu titik per bucket yang memaksimalkan luas segitiga yang dibentuk dengan titik terpilih sebelumnya dan rata-rata bucket berikutnya, sehingga titik yang dipilih condong ke titik-titik yang membawa informasi visual paling besar.

LTTB: pilihan titik berbasis segitiga#

Skrip berikut membangkitkan 1.000 titik telemetri dengan dua puncak lokal tajam, lalu menerapkan tiga strategi downsampling untuk menghasilkan 100 titik output. Strategi naif mengambil setiap 10-th titik, strategi avg-bucket menghitung rerata per 10 titik, sedangkan LTTB memilih satu titik per bucket berdasarkan luas segitiga. Skrip mencatat puncak yang terlihat pada masing-masing strategi, sehingga perbedaan antara ketiganya terukur.

python lttb_perbandingan.py
"""Implementasi LTTB (Largest Triangle Three Buckets) dan perbandingannya.

Skrip membangkitkan 1000 titik telemetri dengan dua puncak lokal tajam,
lalu menerapkan tiga strategi downsampling untuk menghasilkan 100 titik
output. Strategi naif mengambil setiap N-th titik, strategi avg-bucket
menghitung rerata per bucket, sedangkan LTTB memilih titik per bucket
yang membentuk segitiga terbesar dengan titik terpilih sebelumnya dan
rata-rata bucket berikutnya. Tujuannya memperlihatkan bahwa LTTB
mempertahankan puncak tajam yang hilang pada kedua strategi lainnya.
"""
from __future__ import annotations

import numpy as np
import pandas as pd


def lttb(data: np.ndarray, threshold: int) -> np.ndarray:
    """Implementasi LTTB (Sveinn Steinarsson, 2013).

    data: array (n, 2) berisi (x, y). threshold: jumlah titik output.
    Algoritma membagi data menjadi threshold-2 bucket, lalu memilih satu
    titik per bucket yang membentuk segitiga terbesar dengan titik terpilih
    sebelumnya dan rata-rata bucket berikutnya.
    """
    n = len(data)
    if threshold >= n or threshold < 3:
        return data
    sampled = np.empty((threshold, 2))
    sampled[0] = data[0]
    sampled[-1] = data[-1]

    bucket_size = (n - 2) / (threshold - 2)
    a = 0  # indeks titik terpilih sebelumnya
    for i in range(threshold - 2):
        start = int(np.floor(i * bucket_size)) + 1
        end = int(np.floor((i + 1) * bucket_size)) + 1
        end = min(end, n - 1)
        if start >= end:
            start = end - 1
        bucket = data[start:end]
        next_start = int(np.floor((i + 1) * bucket_size)) + 1
        next_end = int(np.floor((i + 2) * bucket_size)) + 1
        next_end = min(next_end, n)
        next_slice = data[next_start:next_end]
        if len(next_slice) == 0:
            avg_x, avg_y = float(data[-1, 0]), float(data[-1, 1])
        else:
            avg_x = float(np.mean(next_slice[:, 0]))
            avg_y = float(np.mean(next_slice[:, 1]))

        max_area = -1.0
        max_idx = 0
        ax, ay = data[a]
        for j, (bx, by) in enumerate(bucket):
            area = abs((ax - avg_x) * (by - ay) - (ax - bx) * (avg_y - ay)) * 0.5
            if area > max_area:
                max_area = area
                max_idx = j
        sampled[i + 1] = bucket[max_idx]
        a = start + max_idx
    return sampled


def bangun_telemetri(n: int = 1000) -> np.ndarray:
    rng = np.random.default_rng(7)
    x = np.arange(n)
    y = (
        10.0
        + np.sin(x / 80.0)
        + 5.0 * np.exp(-((x - 250) ** 2) / 50.0)
        + 4.0 * np.exp(-((x - 750) ** 2) / 30.0)
        + rng.normal(0, 0.2, n)
    )
    return np.column_stack([x, y])


def main() -> None:
    data = bangun_telemetri(1000)
    print(f"data mentah: {len(data)} titik")
    print(f"  rentang y: {data[:, 1].min():.2f} - {data[:, 1].max():.2f}")
    print()

    threshold = 100

    step = max(1, len(data) // threshold)
    naive = data[::step][:threshold]
    print(f"naif (setiap {step}-th titik), {len(naive)} titik:")
    print(f"  puncak terlihat: {naive[:, 1].max():.2f}")
    print(f"  error terhadap puncak mentah: {abs(data[:, 1].max() - naive[:, 1].max()):.2f}")
    print()

    bucket_size = max(1, len(data) // threshold)
    n_buckets = (len(data) + bucket_size - 1) // bucket_size
    avg = np.empty((n_buckets, 2))
    for i in range(n_buckets):
        s = i * bucket_size
        e = min(s + bucket_size, len(data))
        avg[i, 0] = float(np.mean(data[s:e, 0]))
        avg[i, 1] = float(np.mean(data[s:e, 1]))
    print(f"avg-bucket (mean per bucket), {len(avg)} titik:")
    print(f"  puncak terlihat: {avg[:, 1].max():.2f}")
    print(f"  error terhadap puncak mentah: {abs(data[:, 1].max() - avg[:, 1].max()):.2f}")
    print()

    lttb_out = lttb(data, threshold)
    print(f"LTTB, {len(lttb_out)} titik:")
    print(f"  puncak terlihat: {lttb_out[:, 1].max():.2f}")
    print(f"  error terhadap puncak mentah: {abs(data[:, 1].max() - lttb_out[:, 1].max()):.2f}")
    print()

    ringkas = pd.DataFrame([
        {"metode": "mentah", "titik": len(data), "puncak": round(float(data[:, 1].max()), 2), "error_puncak": 0.0},
        {"metode": "naif", "titik": len(naive), "puncak": round(float(naive[:, 1].max()), 2), "error_puncak": round(abs(float(data[:, 1].max()) - float(naive[:, 1].max())), 2)},
        {"metode": "avg-bucket", "titik": len(avg), "puncak": round(float(avg[:, 1].max()), 2), "error_puncak": round(abs(float(data[:, 1].max()) - float(avg[:, 1].max())), 2)},
        {"metode": "LTTB", "titik": len(lttb_out), "puncak": round(float(lttb_out[:, 1].max()), 2), "error_puncak": round(abs(float(data[:, 1].max()) - float(lttb_out[:, 1].max())), 2)},
    ])
    print("perbandingan metode downsampling (target 100 titik dari 1000):")
    print(ringkas.to_string(index=False))
    print()

    print("kesimpulan:")
    print("  - Naif bisa melewati puncak bila puncak jatuh di antara sampel.")
    print("  - Avg-bucket meratakan puncak, sehingga tinggi puncak turun.")
    print("  - LTTB memilih titik yang menjaga bentuk visual, termasuk puncak tajam.")


if __name__ == "__main__":
    main()
text Keluaran
data mentah: 1000 titik
  rentang y: 8.62 - 14.87

naif (setiap 10-th titik), 100 titik:
  puncak terlihat: 14.37
  error terhadap puncak mentah: 0.51

avg-bucket (mean per bucket), 100 titik:
  puncak terlihat: 13.10
  error terhadap puncak mentah: 1.77

LTTB, 100 titik:
  puncak terlihat: 14.87
  error terhadap puncak mentah: 0.00

perbandingan metode downsampling (target 100 titik dari 1000):
    metode  titik  puncak  error_puncak
    mentah   1000   14.87          0.00
      naif    100   14.37          0.51
avg-bucket    100   13.10          1.77
      LTTB    100   14.87          0.00

kesimpulan:
  - Naif bisa melewati puncak bila puncak jatuh di antara sampel.
  - Avg-bucket meratakan puncak, sehingga tinggi puncak turun.
  - LTTB memilih titik yang menjaga bentuk visual, termasuk puncak tajam.

Keluaran di atas memperlihatkan perbedaan dramatis antara ketiga strategi pada puncak tajam. Puncak mentah bernilai 14.87, sedangkan naif hanya melihat 14.37 (error 0.51), avg-bucket hanya melihat 13.10 (error 1.77), dan LTTB melihat 14.87 (error 0). Selisih 1.77 pada avg-bucket bukan sekadar kesalahan kecil, sebab puncak yang seharusnya terlihat menonjol menjadi hampir rata dengan puncak sekitarnya, sehingga anomali yang penting secara visual hilang. Naif lebih baik dari avg-bucket pada contoh ini, tetapi hasilnya bergantung pada kebetulan posisi sampel, sehingga pada data yang berbeda naif dapat kehilangan puncak sepenuhnya bila puncak jatuh tepat di antara dua sampel.

Mekanisme LTTB#

LTTB membagi data menjadi threshold - 2 bucket, lalu memilih satu titik per bucket yang membentuk segitiga terbesar dengan dua titik referensi, yaitu titik terpilih dari bucket sebelumnya dan rata-rata bucket berikutnya. Titik pertama dan titik terakhir selalu disertakan pada keluaran, sehingga batas deret tetap utuh. Untuk setiap bucket, algoritma menghitung luas segitiga yang dibentuk oleh titik A (terpilih sebelumnya), titik B (kandidat dalam bucket saat ini), dan titik C (rata-rata bucket berikutnya), lalu memilih kandidat dengan luas terbesar. Luas segitiga yang besar menandakan titik B yang membawa informasi visual paling signifikan, sebab titik itu paling jauh dari garis lurus yang menghubungkan A dan C.

Kompleksitas LTTB adalah O(n)O(n), sebab setiap titik dipertimbangkan sekali sebagai kandidat. Implementasi naif pada skrip di atas menggunakan perulangan Python murni, sehingga pada data 1.000 titik berjalan dalam milidetik, tetapi pada data satu juta titik implementasi vektorisasi numpy atau C lebih tepat. Library lttb pada Python dan downsample pada JavaScript menyediakan implementasi yang sudah dioptimalkan, sehingga LTTB dapat digunakan pada produksi tanpa khawatir tentang kinerja. Beberapa library visualisasi seperti Apache ECharts juga menyematkan LTTB sebagai algoritma bawaan untuk downsampling deret panjang.

Diagram algoritma LTTB#

Diagram berikut mengilustrasikan mekanisme pemilihan titik pada LTTB untuk satu bucket. Titik A di kiri adalah titik terpilih dari bucket sebelumnya. Titik C di kanan adalah rata-rata bucket berikutnya. Lima kandidat B1-B5 berada di tengah, masing-masing membentuk segitiga dengan A dan C. Kandidat yang dipilih, yaitu B*, adalah kandidat dengan luas segitiga terbesar, yang pada diagram ini berada di posisi atas bucket.

Pemilihan titik pada LTTB: segitiga terbesar A-B-CAtitik terpilihsebelumnyaB*luas segitiga terbesarCrata-ratabucket berikutnyabucket i-1bucket i (kandidat)bucket i+1
Mekanisme LTTB: kandidat B* dipilih karena membentuk segitiga dengan luas terbesar bersama titik A dan rata-rata bucket berikutnya C

Demo interaktif perbandingan metode#

Demo berikut membandingkan empat metode downsampling pada 200 titik mentah dengan dua puncak tajam. Tombol di atas grafik mengganti metode antara “Raw”, “Naif”, “Avg-bucket”, dan “LTTB”, dengan target 40 titik output untuk tiga metode downsampling. Garis merah tipus di latar belakang selalu menampilkan deret mentah sebagai referensi, sedangkan garis biru tebal menampilkan hasil metode aktif. Statistik di bawah grafik mencatat puncak mentah, puncak yang terlihat, dan error puncak untuk setiap metode.

Kode sumber demo
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Perbandingan downsampling visualisasi: raw, naif, avg-bucket, LTTB</title>
<style>
  body { font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; padding: 12px; color: #1f2937; }
  .baris-tombol { display: flex; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
  button { padding: 6px 12px; border: 1px solid #2563eb; background: #fff; color: #1e3a8a; border-radius: 4px; cursor: pointer; font-size: 13px; }
  button.aktif { background: #2563eb; color: #fff; }
  .legenda { font-size: 12px; color: #4b5563; margin-top: 6px; }
  .legenda span { display: inline-block; margin-right: 12px; }
  .swatch { display: inline-block; width: 10px; height: 10px; vertical-align: middle; margin-right: 4px; }
  .statistik { font-size: 12px; color: #1f2937; margin-top: 8px; padding: 8px; background: #f9fafb; border-radius: 4px; }
  svg { display: block; width: 100%; height: 260px; }
</style>
</head>
<body>
<div class="baris-tombol">
  <button id="btn-raw" class="aktif" type="button">Raw (200 titik)</button>
  <button id="btn-naif" type="button">Naif (40 titik)</button>
  <button id="btn-avg" type="button">Avg-bucket (40 titik)</button>
  <button id="btn-lttb" type="button">LTTB (40 titik)</button>
</div>
<svg id="grafik" viewBox="0 0 640 260" preserveAspectRatio="none">
  <line x1="40" y1="220" x2="620" y2="220" stroke="#374151" stroke-width="1"/>
  <line x1="40" y1="20" x2="40" y2="220" stroke="#374151" stroke-width="1"/>
  <text x="20" y="30" text-anchor="end" font-size="10">15</text>
  <text x="20" y="125" text-anchor="end" font-size="10">11</text>
  <text x="20" y="220" text-anchor="end" font-size="10">8</text>
  <line x1="40" y1="30" x2="620" y2="30" stroke="#fecaca" stroke-width="0.5" stroke-dasharray="3,3"/>
  <polyline id="garis-raw" fill="none" stroke="#dc2626" stroke-width="0.8" stroke-opacity="0.4"/>
  <polyline id="garis-sampel" fill="none" stroke="#2563eb" stroke-width="1.8"/>
  <g id="titik-sampel"></g>
</svg>
<div class="legenda">
  <span><span class="swatch" style="background:#dc2626;opacity:0.4"></span>raw (referensi)</span>
  <span><span class="swatch" style="background:#2563eb"></span>metode aktif</span>
</div>
<div class="statistik" id="statistik">pilih metode downsampling untuk membandingkan visualisasi</div>
<script>
// Bangkitkan data mentah 200 titik dengan dua puncak tajam
function bangunData() {
  const data = [];
  for (let i = 0; i < 200; i++) {
    const y = 10 + Math.sin(i / 30) + 5 * Math.exp(-Math.pow(i - 50, 2) / 8) + 4 * Math.exp(-Math.pow(i - 150, 2) / 6);
    data.push({x: i, y: y});
  }
  return data;
}

const dataRaw = bangunData();
const threshold = 40;

// Strategi naif: setiap N-th titik
function naif(data, t) {
  const step = Math.max(1, Math.floor(data.length / t));
  const out = [];
  for (let i = 0; i < data.length; i += step) out.push(data[i]);
  if (out[out.length - 1] !== data[data.length - 1]) out.push(data[data.length - 1]);
  return out;
}

// Strategi avg-bucket: rerata per bucket
function avgBucket(data, t) {
  const size = Math.max(1, Math.floor(data.length / t));
  const n = Math.ceil(data.length / size);
  const out = [];
  for (let i = 0; i < n; i++) {
    const s = i * size;
    const e = Math.min(s + size, data.length);
    let sx = 0, sy = 0;
    for (let j = s; j < e; j++) { sx += data[j].x; sy += data[j].y; }
    out.push({x: sx / (e - s), y: sy / (e - s)});
  }
  return out;
}

// Strategi LTTB
function lttb(data, t) {
  const n = data.length;
  if (t >= n || t < 3) return data.slice();
  const sampled = [];
  sampled.push(data[0]);
  const bucketSize = (n - 2) / (t - 2);
  let a = 0;
  for (let i = 0; i < t - 2; i++) {
    const start = Math.floor(i * bucketSize) + 1;
    let end = Math.floor((i + 1) * bucketSize) + 1;
    end = Math.min(end, n - 1);
    if (start >= end) continue;
    const nextStart = Math.floor((i + 1) * bucketSize) + 1;
    let nextEnd = Math.floor((i + 2) * bucketSize) + 1;
    nextEnd = Math.min(nextEnd, n);
    let avgX = 0, avgY = 0, cnt = 0;
    for (let j = nextStart; j < nextEnd; j++) { avgX += data[j].x; avgY += data[j].y; cnt++; }
    if (cnt === 0) { avgX = data[n-1].x; avgY = data[n-1].y; }
    else { avgX /= cnt; avgY /= cnt; }
    const ax = data[a].x, ay = data[a].y;
    let maxArea = -1, maxIdx = start;
    for (let j = start; j < end; j++) {
      const bx = data[j].x, by = data[j].y;
      const area = Math.abs((ax - avgX) * (by - ay) - (ax - bx) * (avgY - ay)) * 0.5;
      if (area > maxArea) { maxArea = area; maxIdx = j; }
    }
    sampled.push(data[maxIdx]);
    a = maxIdx;
  }
  sampled.push(data[n - 1]);
  return sampled;
}

const lebar = 640, tinggi = 260;
const xMin = 0, xMax = 199, yMin = 8, yMax = 15;
const marginKiri = 40, marginBawah = 40, marginAtas = 20;
const plotW = lebar - marginKiri - 20;
const plotH = tinggi - marginBawah - marginAtas;

function xScale(x) { return marginKiri + (x - xMin) / (xMax - xMin) * plotW; }
function yScale(y) { return marginAtas + (yMax - y) / (yMax - yMin) * plotH; }

function polyline(d) { return d.map(p => xScale(p.x) + "," + yScale(p.y)).join(" "); }

function tampilkan(metode) {
  const rawPoin = polyline(dataRaw);
  document.getElementById("garis-raw").setAttribute("points", rawPoin);

  let sampel = [];
  let label = "";
  if (metode === "raw") { sampel = dataRaw; label = "Raw"; }
  else if (metode === "naif") { sampel = naif(dataRaw, threshold); label = "Naif (setiap N-th titik)"; }
  else if (metode === "avg") { sampel = avgBucket(dataRaw, threshold); label = "Avg-bucket"; }
  else if (metode === "lttb") { sampel = lttb(dataRaw, threshold); label = "LTTB"; }

  document.getElementById("garis-sampel").setAttribute("points", polyline(sampel));
  const g = document.getElementById("titik-sampel");
  g.innerHTML = "";
  if (metode !== "raw") {
    sampel.forEach(p => {
      const c = document.createElementNS("http://www.w3.org/2000/svg", "circle");
      c.setAttribute("cx", xScale(p.x));
      c.setAttribute("cy", yScale(p.y));
      c.setAttribute("r", "2.5");
      c.setAttribute("fill", "#2563eb");
      g.appendChild(c);
    });
  }

  const puncakRaw = Math.max(...dataRaw.map(p => p.y));
  const puncakSampel = Math.max(...sampel.map(p => p.y));
  const error = puncakRaw - puncakSampel;
  document.getElementById("statistik").textContent =
    `${label}: ${sampel.length} titik, puncak mentah = ${puncakRaw.toFixed(2)}, ` +
    `puncak terlihat = ${puncakSampel.toFixed(2)}, error = ${error.toFixed(2)}`;
}

function aktifkan(id) {
  document.querySelectorAll("button").forEach(b => b.classList.remove("aktif"));
  document.getElementById(id).classList.add("aktif");
}

document.getElementById("btn-raw").addEventListener("click", () => { aktifkan("btn-raw"); tampilkan("raw"); });
document.getElementById("btn-naif").addEventListener("click", () => { aktifkan("btn-naif"); tampilkan("naif"); });
document.getElementById("btn-avg").addEventListener("click", () => { aktifkan("btn-avg"); tampilkan("avg"); });
document.getElementById("btn-lttb").addEventListener("click", () => { aktifkan("btn-lttb"); tampilkan("lttb"); });

tampilkan("raw");
</script>
</body>
</html>

Demo di atas memperlihatkan mengapa LTTB menjadi pilihan utama untuk visualisasi. Pada metode naif, puncak tajam dapat terlihat penuh bila posisi sampel kebetulan tepat, tetapi dapat pula hilang sepenuhnya bila tidak. Pada metode avg-bucket, puncak selalu terlihat lebih rendah daripada aslinya, sehingga grafik terlihat lebih datar. Pada metode LTTB, puncak selalu dipertahankan, sebab algoritma secara eksplisit memilih titik yang membentuk segitiga terbesar dengan tetangga, sehingga titik-titik yang membawa informasi visual paling besar selalu masuk ke dalam keluaran.

Kesadaran aproksimasi#

Kesadaran aproksimasi menuntut kode klien untuk membedakan titik mentah dari titik hasil downsampling rendering. Pada visualisasi, perbedaan ini tidak terlihat, sebab LTTB menghasilkan titik yang merupakan subset dari titik mentah, sehingga grafik terlihat seperti deret penuh. Pada analisis lanjutan, perbedaan ini penting, sebab statistik yang dihitung dari hasil LTTB dapat berbeda dari statistik yang dihitung dari data mentah, terutama untuk puncak dan ekstrem lainnya. Padanan praktis adalah menyimpan flag is_rendered pada titik hasil downsampling, sehingga analisis lanjutan dapat memilih sumber data yang sesuai, yaitu data mentah untuk perhitungan presisi dan hasil LTTB untuk visualisasi.

Kesalahan umum#

  • Menggunakan avg-bucket untuk downsampling visualisasi. Rerata per bucket meratakan puncak tajam, sehingga grafik terlihat lebih datar daripada aslinya dan anomali penting hilang.
  • Menyimpan hasil LTTB sebagai pengganti data mentah. Hasil LTTB cocok untuk rendering, tetapi tidak untuk analisis presisi, sebab titik yang tidak terpilih tidak hilang dari data mentah.
  • Mengharapkan LTTB menangkap semua puncak. LTTB menjaga bentuk visual secara umum, tetapi pada threshold yang sangat rendah, beberapa puncak kecil tetap dapat terlewat.

Ringkasan#

  • Downsampling untuk skala rendering memilih subset titik yang dimuat pada layar, sehingga jumlah titik yang dirender turun drastis tanpa mengaburkan bentuk deret.
  • Tiga strategi utama adalah naif, avg-bucket, dan LTTB. Naif bisa melewatkan puncak, avg-bucket meratakan puncak, sedangkan LTTB mempertahankan puncak tajam dengan memilih titik berbasis luas segitiga.
  • Kesadaran aproksimasi menuntut kode klien menandai titik hasil downsampling, sebab analisis presisi tetap memerlukan data mentah, bukan hasil aproksimasi.

Referensi#