IlmHamroh
JavaScript Full-stack/21-qism. React: media, integratsiyalar, PWA5/32-dars26 daqiqa
Mundarija (34)

React grafik kutubxonalari: Chart.js, ECharts, Nivo, visx va moliya grafiklari

Qisqacha: Grafik ikki xil chiziladi. SVG — har ustun va yozuv alohida DOM elementi (Recharts, Nivo, visx). Canvas — bitta <canvas>, ichida piksellar (Chart.js, ECharts, lightweight-charts). O'nlab nuqtada SVG qulay, o'n minglab nuqtada canvas tez ishlaydi. Lekin canvas ichini ekran o'quvchi ko'rmaydi — matn muqobili o'zingizdan. Kanon 6 ta kutubxonani o'lchadi: visx +12.2 KB dan ECharts +167.0 KB gacha. ECharts va lightweight-charts vazifalar ning Trusted Types siyosatida yiqildi, Chart.js, Nivo va visx — 0 buzilish.

Bu darsda

  • SVG va canvas grafiklar farqini tushuntirasiz va vazifaga qarab tanlaysiz.
  • Chart.js'ni react-chartjs-2 bilan ulaysiz: ro'yxatdan o'tkazish, bosish, aria-label.
  • 10 000 nuqtali grafikni decimation bilan tez chizasiz va nega "har 20-nuqta" xavfli ekanini ko'rsatasiz.
  • ECharts, Nivo va visx qanday ishlashini bilasiz, sham (candlestick) grafigini o'qiysiz va chizasiz.
  • Kutubxonani hajm, CSP va a11y bo'yicha o'lchab tanlaysiz.

Oldin bilishingiz kerak: Grafiklar I: Recharts, Canvas 2D asoslari, Brauzer xavfsizlik modeli, XSS va DOM xavfsizligi, useRef II: DOM elementlari.

1. Nega bu kerak?

O'tgan darsda Recharts bilan haftalik sotuv grafigini chizdik — besh ustun. Bugun Jasur akada ikki yangi savol bor.

Birinchisi — tandir. «Bahor»ning yangi tandirida harorat sensori bor, u har daqiqada o'lchaydi. Bir hafta — 10 000 ga yaqin o'lchov. Jasur aka hammasini bitta chiziqda ko'rmoqchi: "Qachon pech o'chib qolgan?".

Ikkinchisi — xarid. Guruch narxi har kuni o'zgaradi. Hafta boshida qancha edi, oxirida qancha, eng arzon va eng qimmat payti qachon? Oddiy chiziq faqat bitta narxni ko'rsatadi. Bu savolga moliya grafigi — sham grafigi javob beradi.

Recharts ikkalasida ham noqulay. 10 000 nuqtali SVG — 10 000 ta DOM elementi. Sham grafigi esa unda tayyor yo'q. Shuning uchun boshqa kutubxonalarni ko'ramiz.

Hayotiy o'xshatish: bino rejasi. SVG — Lego'dan yig'ilgan maket: har g'isht alohida, birini olib, rangini o'zgartirish mumkin. Canvas — fotosurat: tez olinadi, minglab detal sig'adi, lekin undan bitta g'ishtni ajratib bo'lmaydi.

2. SVG va canvas: ikki xil chizish

2.1 SVG — har shakl alohida element

Recharts grafigi ichini eslang: <rect>, <text>, <path>. Har biri DOM'da, har biriga CSS va hodisa ulash mumkin. Ekran o'quvchi va qidiruvchi matnni o'qiydi.

Narxi — element soni. Kanon tajribasida bir xil grafik (ikki ustun, «#bozor» 3, «#uy» 1) har kutubxonada chizildi va DOM elementlari sanaldi (kutubxonalar-21.json):

Kutubxona Chizish Elementlar
Recharts SVG 77
Nivo SVG 86
visx SVG 5
Chart.js canvas 3

Recharts va Nivo o'qlar, chiziqlar va yozuvlarni ham chizadi, visx esa faqat biz so'ragan ikki <rect> ni. Chart.js'da esa sarlavha, o'ram va bitta <canvas> — qolgani piksel.

Endi nuqtalar ko'paysa nima bo'lishini o'zingiz ko'ring. Har nuqta — bitta <circle>:

jsx
import { useEffect, useState } from "react";

function makePoints(n) {
  return Array.from({ length: n }, (_, i) => ({
    x: (i / n) * 300,
    y: 60 + 40 * Math.sin(i / 20),
  }));
}

export default function App() {
  const [count, setCount] = useState(100);
  const points = makePoints(count);
  useEffect(() => {
    const n = document.querySelectorAll("circle").length;
    console.log(`${count} nuqta → ${n} ta <circle>`);
  }, [count]);
  return (
    <figure>
      <svg viewBox="0 0 300 120" width="300" aria-hidden="true">
        {points.map((p, i) => (
          <circle key={i} cx={p.x} cy={p.y} r="1.5" fill="#1f6f4a" />
        ))}
      </svg>
      <button type="button" onClick={() => setCount(1000)}>
        1 000 nuqta
      </button>
    </figure>
  );
}

Konsolda:

text
100 nuqta → 100 ta <circle>
1000 nuqta → 1000 ta <circle>

Har nuqta — DOM'da bitta element. React har birini yaratadi, brauzer har birining joyini hisoblaydi. 1 000 tagacha bu sezilmaydi. 10 000 da esa sahifa og'irlashadi, sichqoncha bilan yurish qotadi. Har bir yangi ma'lumotda React o'n ming elementni solishtiradi (rekonsilyatsiya).

2.2 Canvas — bitta element, ichida piksellar

Canvas ni Canvas 2D asoslari darsida ko'rgansiz: getContext("2d"), keyin lineTo, fillRect. Natija — rasm. Brauzer uchun 10 000 nuqta ham, 10 nuqta ham bitta element. Shuning uchun katta ma'lumotda canvas kutubxonalar tezroq.

Lekin canvas bilan uchta narsa yo'qoladi:

  1. Matn DOM'da yo'q. Ekran o'quvchi grafikni "rasm" deb ham tanimaydi — <canvas> ga o'zingiz nom berasiz.
  2. CSS ishlamaydi. Rang, shrift — JS sozlamasi orqali. Qorong'i temaga o'tish ham kod bilan.
  3. Hodisa butun canvas'da. Qaysi ustun bosilganini kutubxona koordinatadan hisoblaydi.

Qaysi biri qachon — qisqa yo'l:

flowchart TB
  S{"Nuqtalar soni?"} -->|"yuzlab"| A{"Grafik turi?"}
  S -->|"minglab va ko'p"| C["Canvas: Chart.js, ECharts"]
  A -->|"oddiy: ustun, chiziq"| R["SVG: Recharts, Nivo"]
  A -->|"noodatiy, o'z dizayn"| V["SVG: visx yoki o'z SVG"]
  A -->|"moliya: sham, birja"| L["lightweight-charts"]
  C --> T["+ matn muqobili (jadval)"]

Diagrammaning oxirgi tuguniga qarang: canvas tanlasangiz, matn muqobili majburiy qadamga aylanadi.

Tekshirib ko'ring: Grafikdagi har ustunga CSS bilan :hover effekt bermoqchisiz. Chart.js'da canvas rect:hover { … } yozdingiz — ishlamadi. Nega?

Javob

Canvas ichida rect elementi yo'q — u yerda faqat piksellar. CSS selektori hech narsa topmaydi. Chart.js'da hover rangi sozlama bilan beriladi (hoverBackgroundColor), kutubxona uni o'zi chizadi. SVG grafikda esa rect:hover ishlaydi, chunki ustun — haqiqiy DOM elementi.

3. Chart.js va react-chartjs-2

3.1 Ro'yxatdan o'tkazish

Chart.js — eng mashhur canvas grafik kutubxonasi, React'siz ham ishlaydi. React uchun ingichka o'ram — react-chartjs-2: <Bar>, <Line>, <Pie> komponentlari. Kanon o'lchagan versiyalar: chart.js 4.5.1, react-chartjs-2 5.3.1.

bash
npm i -E chart.js@4.5.1 react-chartjs-2@5.3.1

Chart.js 4 da har qism — shkala, element, plagin — alohida. Kerakligini o'zingiz ro'yxatdan o'tkazasiz (register). Shunda bundler keraksizini tashlab yuboradi (tree shaking).

Bu blok natija oynasida ishlaydi, lekin tekshiruvchimiz uni chiza olmaydi: jsdom'da canvas yo'q. «Ishga tushir» ni bosing va juma ustunini bosing:

jsx
import { useRef } from "react";
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  BarElement,
  Tooltip,
  Legend,
} from "chart.js";
import { Bar, getElementAtEvent } from "react-chartjs-2";

ChartJS.register(
  CategoryScale,
  LinearScale,
  BarElement,
  Tooltip,
  Legend,
);

const data = {
  labels: ["Du", "Se", "Ch", "Pa", "Ju"],
  datasets: [
    {
      label: "Osh (porsiya)",
      data: [32, 41, 28, 45, 60],
      backgroundColor: "#1f6f4a",
    },
  ],
};

export default function App() {
  const chartRef = useRef(null);
  function handleClick(event) {
    const [hit] = getElementAtEvent(chartRef.current, event);
    if (hit) {
      const day = data.labels[hit.index];
      console.log(day, data.datasets[0].data[hit.index]);
    }
  }
  return (
    <div style={{ maxWidth: 480 }}>
      <Bar
        ref={chartRef}
        data={data}
        options={{ plugins: { legend: { position: "bottom" } } }}
        role="img"
        aria-label="Haftalik osh sotuvi: eng ko'pi juma, 60 porsiya"
        onClick={handleClick}
      />
    </div>
  );
}

Biz bu blokni Chrome 155 da (headless) ochib, juma ustunini dasturiy bosdik. Konsolga Ju 60 chiqdi, CSP buzilishi — 0.

Kodda to'rt narsa:

  1. ChartJS.register(...) — modul darajasida, bir marta. Komponent ichida emas.
  2. ref — react-chartjs-2 Chart.js nusxasini ref'ga beradi. getElementAtEvent(chart, event) bosilgan nuqtani topadi: index — nechanchi ustun (useRef II).
  3. role="img" va aria-label — <canvas> ga o'tadi. Malika "Haftalik osh sotuvi: eng ko'pi juma…" ni eshitadi. Ichidagi raqamlarni esa eshitmaydi — ular uchun jadval kerak (Recharts darsidagi qoida).
  4. responsive sukut bo'yicha yoqilgan: grafik ota-element eniga moslashadi, balandligi aspectRatio (sukut 2) bilan.

Ichki tomoni ham muhim. react-chartjs-2 data o'zgarganda grafikni o'chirib qayta yaratmaydi — Chart.js'ning update() ini chaqiradi. Shunda yangi ustun silliq o'sib chiqadi.

3.2 Eng ko'p uchraydigan xato: ro'yxatdan o'tmagan qism

CategoryScale ni register ga yozish esdan chiqdi, deylik. Konsolda:

text
Error: "category" is not a registered scale.

Tarjimasi: "category ro'yxatdan o'tgan shkala emas". Biz bu xatoni Node 24 da chart.js 4.5.1 bilan oldik. Matn nima yetishmayotganini aniq aytadi: category → CategoryScale, linear → LinearScale, "point" element → PointElement.

Ikki yo'l bor. Kerakli qismlarni bittalab qo'shish — kichik bundle. Yoki hammasi birdan:

js
import "chart.js/auto"; // hamma qism ro'yxatdan o'tadi — kattaroq

chart.js/auto — prototip uchun qulay. Production'da hajmni o'lchab, kerakli qismlarga o'ting.

3.3 Katta ma'lumot: decimation

Endi tandir. 10 000 ta o'lchovni 800 piksellik grafikka chizsak, har piksel ustuniga 12 tadan ortiq nuqta tushadi. Ko'z ularni baribir ajrata olmaydi. Demak, chizishdan oldin nuqtalarni kamaytirish mumkin. Bu — decimation (nuqtalarni siyraklashtirish).

Eng sodda yo'l — har 20-nuqtani olish. Lekin u xavfli. Avval tushunchani kutubxonasiz sinab ko'raylik:

js
// 10 000 ta o'lchov: harorat sensori, har daqiqada
const points = Array.from({ length: 10_000 }, (_, i) => ({
  x: i,
  y: 20 + (i % 60) / 10,
}));
points[7777].y = 95; // bitta keskin sakrash (avariya!)

// 1-usul: har 20-nuqtani olish
const every20 = points.filter((p) => p.x % 20 === 0);

// 2-usul: min-max — har bo'lakdan eng kichik va eng katta
function minMax(data, buckets) {
  const size = Math.ceil(data.length / buckets);
  const result = [];
  for (let i = 0; i < data.length; i += size) {
    const part = data.slice(i, i + size);
    const min = part.reduce((a, b) => (b.y < a.y ? b : a));
    const max = part.reduce((a, b) => (b.y > a.y ? b : a));
    result.push(...(min.x < max.x ? [min, max] : [max, min]));
  }
  return result;
}
const decimated = minMax(points, 250);

const peak = (data) => Math.max(...data.map((p) => p.y));
const show = (name, data) =>
  console.log(`${name}: ${data.length} nuqta, cho'qqi ${peak(data)}`);
show("asl", points);
show("har 20-chi", every20);
show("min-max", decimated);

Konsolda:

text
asl: 10000 nuqta, cho'qqi 95
har 20-chi: 500 nuqta, cho'qqi 24
min-max: 500 nuqta, cho'qqi 95

Ikkalasi ham 500 nuqta qoldirdi. Lekin "har 20-chi" usul avariyani yo'qotdi: 7777-nuqta 20 ga bo'linmaydi va tashlab yuborildi. Grafik "hammasi joyida" deb ko'rsatadi. Min-max esa har bo'lakdan eng kichik va eng katta qiymatni saqlaydi — cho'qqi qoladi. Bu — o'quv modeli. Chart.js'da xuddi shu g'oya tayyor plagin:

jsx
import {
  Chart as ChartJS,
  LinearScale,
  LineElement,
  PointElement,
  Decimation,
  Tooltip,
} from "chart.js";
import { Line } from "react-chartjs-2";

ChartJS.register(
  LinearScale,
  LineElement,
  PointElement,
  Decimation,
  Tooltip,
);

// 10 000 ta o'lchov (oldingi blokdagi kabi), cho'qqi — 7777 da
const points = Array.from({ length: 10_000 }, (_, i) => ({
  x: i,
  y: i === 7777 ? 95 : 20 + (i % 60) / 10,
}));

const options = {
  parsing: false, // ma'lumot tayyor {x, y} — qayta o'qilmaydi
  animation: false,
  scales: { x: { type: "linear" } },
  plugins: {
    decimation: { enabled: true, algorithm: "min-max" },
  },
  elements: { point: { radius: 0 } },
};

export default function App() {
  return (
    <Line
      data={{ datasets: [{ label: "Harorat", data: points }] }}
      options={options}
      role="img"
      aria-label="Sensor harorati: 10 000 o'lchov, bitta cho'qqi 95"
    />
  );
}

Biz Chrome 155 da o'lchadik: 784 piksel enli grafikda Chart.js 10 000 nuqtadan 1 851 tasini chizdi, cho'qqi 95 saqlandi.

Decimation faqat shartlar bajarilsa ishlaydi. Bu shartlarni Chart.js 4.5.1 manba kodidan tekshirdik (plugin_decimation):

Shart Nega
grafik turi — line ustunni siyraklashtirib bo'lmaydi
x o'qi linear yoki time toifalar ("Du") orasida "oraliq" yo'q
parsing: false ma'lumot {x, y} ko'rinishida tayyor bo'lsin
nuqtalar > 4 × grafik eni kam bo'lsa plagin hech narsa qilmaydi

Shartlardan biri bajarilmasa — xato chiqmaydi, plagin jim o'tib ketadi. Grafik sekin bo'lsa, birinchi shu jadvalni tekshiring. Ikkinchi algoritm — "lttb" (Largest Triangle Three Buckets). U chiziq shaklini yaxshiroq saqlaydi, samples bilan nuqtalar sonini berasiz.

Tekshirib ko'ring: Kunlik sotuv grafigida x o'qi "Du", "Se", … toifalar. Decimation yoqildi, lekin grafik o'zgarmadi. Nega?

Javob

Toifali o'q (category) — shartlardan biri buzilgan. Plagin faqat linear yoki time o'qda ishlaydi, chunki "bu ikki nuqta orasida yana nima bor" degan savol faqat son yoki vaqt o'qida ma'noli. Yana: bir haftada yetti nuqta — kamaytirishning hojati ham yo'q.

4. ECharts: bitta sozlama obyekti

4.1 option — grafikning to'liq tavsifi

Apache ECharts — Xitoyda Baidu boshlagan, hozir Apache fondidagi katta kutubxona. Yuzlab grafik turi bor: issiqlik xaritasi, daraxt, grafik-tarmoq, sham. React'da u komponentlar bilan emas, bitta sozlama obyekti bilan ishlaydi:

tsx
import * as echarts from "echarts/core";
import { BarChart } from "echarts/charts";
import { GridComponent, TooltipComponent } from "echarts/components";
import { SVGRenderer } from "echarts/renderers";
import ReactECharts from "echarts-for-react/esm/core";

echarts.use([BarChart, GridComponent, TooltipComponent, SVGRenderer]);

const option = {
  tooltip: { trigger: "axis", renderMode: "richText" },
  xAxis: { type: "category", data: ["Du", "Se", "Ch"] },
  yAxis: { type: "value" },
  series: [{ type: "bar", name: "Osh", data: [32, 41, 28] }],
};

<ReactECharts
  echarts={echarts}
  option={option}
  opts={{ renderer: "svg" }}
  style={{ height: 240 }}
/>;

Recharts'dagi <XAxis> bu yerda xAxis: {…} maydoni. Hamma narsa bitta JSON'ga o'xshash obyektda — uni serverdan ham olsa bo'ladi.

echarts/core + echarts.use([...]) — Chart.js'dagi register ning o'zi: faqat kerakli qism bundle'ga tushadi. Shunda ham kanon o'lchovida hajm eng katta — +167.0 KB (kanon CanvasRenderer bilan o'lchagan; SVG chizuvchili variant o'lchanmagan). import * as echarts from "echarts" (to'liq) yozsangiz, bundan ham ko'p bo'ladi.

echarts-for-react — uchinchi tomon o'rami (kanon tajribasida 3.0.6). U option o'zgarganda setOption ni chaqiradi, komponent o'chganda dispose qiladi.

4.2 Canvas yoki SVG — ECharts'da tanlov sizda

ECharts ikkala usulda ham chiza oladi: renderer: "canvas" (sukut) yoki "svg". Kanon tajribasida ECharts vazifalar ning Trusted Types siyosatida ochilmadi. Kanon qaydida sabab qisqa — "innerHTML, tooltip". Biz dars uchun aniqroq qidirdik: to'rt variantni Chrome 155 da, kanon CSP'si ostida ochdik (bu bizning sinovimiz, kanon emas).

Variant TT buzilishi Natija
canvas 1 sahifa chizilmadi
svg 0 9 ta path, o'q yozuvlari DOM'da
svg + HTML tooltip, sichqoncha ustida 1 grafik bor, tooltip yiqildi
svg + renderMode: "richText" 0 grafik bor

Birinchi qator kanon qaydini to'ldiradi: grafik tooltip'gacha yetmaydi. Canvas chizuvchisi (zrender kutubxonasi) ishga tushganda konteynerni tozalaydi: root.innerHTML = ''. Bo'sh satr ham Trusted Types'da taqiqlangan — siyosat satrning ichiga qaramaydi, faqat turini tekshiradi (XSS va DOM xavfsizligi).

Uchinchi qator — tooltip. Sukut bo'yicha u HTML: el.innerHTML = content. Sichqoncha ustunga kelishi bilan buzilish chiqdi. renderMode: "richText" tooltip'ni grafikning o'zida chizadi — HTML'siz.

Demak, ECharts'ni qattiq CSP'li ilovada ishlatish mumkin: SVG chizuvchi + richText tooltip. Bu — bitta sinov, hamma grafik turi uchun emas. O'z grafigingizni CSP bilan o'zingiz sinang.

4.3 ECharts va accessibility

ECharts'da aria: { enabled: true } bor: kutubxona grafik uchun avtomatik tavsif yasaydi. SVG variantida biz uni o'qidik:

text
This is a chart with type Bar chart named Osh.The data is as follows: the data for Du is 0, 32, the data for Se is 1, 41.

Ikki muammo ko'rinadi. Matn inglizcha, va "Du is 0, 32" — ustun tartib raqami (0) ham qiymat bilan qo'shilib ketgan. Malika uchun bu chalkash. Tavsifni o'zingiz bering (aria.label.description) yoki grafik yoniga jadval qo'ying.

5. Nivo va visx: SVG'ning ikki qutbi

5.1 Nivo — tayyor, chiroyli

Nivo — d3 hisoblari ustiga qurilgan tayyor React grafiklari. Bitta komponent, ko'p prop:

tsx
import { ResponsiveBar } from "@nivo/bar";

<div style={{ height: 240 }}>
  <ResponsiveBar
    data={[
      { kun: "Du", osh: 32 },
      { kun: "Se", osh: 41 },
    ]}
    keys={["osh"]}
    indexBy="kun"
    margin={{ top: 10, right: 10, bottom: 30, left: 40 }}
    role="img"
    ariaLabel="Haftalik osh sotuvi"
  />
</div>;

keys — qaysi maydonlar ustun bo'ladi, indexBy — qaysi maydon o'qda. ResponsiveBar ota-element o'lchamini oladi — otaga balandlik bering (Recharts'dagi tuzoq). Nivo ko'p turlarda canvas variantini ham beradi (BarCanvas). Kanon: @nivo/bar 0.99.0 — +81.3 KB, CSP 0.

5.2 visx — g'isht va loy

visx (Airbnb) — grafik emas, grafik yasash uchun qismlar: shkalalar (scaleBand, scaleLinear), shakllar (Bar, LinePath), guruhlar. Kanon tajribasidagi kod:

tsx
import { Group } from "@visx/group";
import { scaleBand, scaleLinear } from "@visx/scale";
import { Bar } from "@visx/shape";

const d = [{ teg: "#bozor", n: 3 }, { teg: "#uy", n: 1 }];
const x = scaleBand({
  domain: d.map((q) => q.teg),
  range: [0, 360],
  padding: 0.2,
});
const y = scaleLinear({ domain: [0, 3], range: [200, 0] });

<svg width={360} height={200}>
  <Group>
    {d.map((q) => (
      <Bar
        key={q.teg}
        x={x(q.teg)}
        y={y(q.n)}
        width={x.bandwidth()}
        height={200 - y(q.n)}
        fill="#1f6f4a"
      />
    ))}
  </Group>
</svg>;

Shkala (scale) — qiymatni pikselga aylantiruvchi funksiya. y(3) → 0 (tepa), y(0) → 200 (past): SVG'da y o'qi pastga qarab o'sadi. O'qlar, tooltip, legend — alohida paketlar yoki o'zingiz. Natija — eng yengil: +12.2 KB, CSP 0, 5 ta element.

O'tgan darsdagi o'z SVG grafigimiz ham shu yo'l edi — faqat shkalani masshtab deb o'zimiz hisobladik. visx noodatiy dizayn kerak bo'lganda yaxshi: siz chizasiz, u hisoblaydi.

Tekshirib ko'ring: Dizayner Jasur akaga noodatiy grafik chizib berdi: ustunlar o'rniga osh qozonlari, balandligi sotuvga mutanosib. Nivo yoki visx?

Javob

visx. Nivo tayyor ko'rinishlarni beradi, ularni moslashtirish chegaralangan. visx esa faqat hisob beradi (scaleBand, scaleLinear), chizishni sizga qoldiradi: har ustun o'rniga <image> yoki <path> qo'yasiz. Narxi — o'qlar va tooltip'ni ham o'zingiz yozasiz.

6. Moliya grafiklari: sham

6.1 Sham nimani ko'rsatadi

Guruch narxi kun davomida o'zgaradi. Bir davr (kun, hafta) uchun to'rtta son muhim: ochilish (open), eng yuqori (high), eng past (low), yopilish (close). Qisqasi — OHLC. Sham grafigi (candlestick) shu to'rt sonni bitta "sham" bilan chizadi:

  • tana — ochilishdan yopilishgacha to'rtburchak;
  • pilik — eng past va eng yuqori orasidagi ingichka chiziq;
  • rang — narx o'sgan bo'lsa yashil, tushgan bo'lsa qizil.

Kutubxonasiz o'quv modeli — oddiy SVG. Narxlar o'ylab topilgan (o'quv ma'lumoti):

jsx
import { useEffect } from "react";

// Guruch, so'm/kg (o'quv ma'lumoti)
const weeks = [
  { name: "1-h", open: 14000, high: 14600, low: 13800, close: 14400 },
  { name: "2-h", open: 14400, high: 14500, low: 13900, close: 14000 },
  { name: "3-h", open: 14000, high: 15200, low: 13900, close: 15000 },
  { name: "4-h", open: 15000, high: 15300, low: 14700, close: 14800 },
];
const W = 320;
const H = 180;
const PAD = 20;
const low = Math.min(...weeks.map((w) => w.low));
const high = Math.max(...weeks.map((w) => w.high));
// shkala: narx → piksel (qimmat — tepada)
const y = (price) =>
  PAD + ((high - price) / (high - low)) * (H - 2 * PAD);
const step = W / weeks.length;

function Candle({ w, i }) {
  const up = w.close >= w.open;
  const x = i * step + step / 2;
  const top = y(Math.max(w.open, w.close));
  const bottom = y(Math.min(w.open, w.close));
  return (
    <g>
      <line
        x1={x}
        x2={x}
        y1={y(w.high)}
        y2={y(w.low)}
        stroke="#374151"
      />
      <rect
        x={x - 12}
        y={top}
        width={24}
        height={Math.max(1, bottom - top)}
        fill={up ? "#1f6f4a" : "#b91c1c"}
      />
      <text x={x} y={H - 2} textAnchor="middle" fontSize="12">
        {w.name}
      </text>
    </g>
  );
}

export default function App() {
  useEffect(() => {
    for (const w of weeks) {
      const diff = w.close - w.open;
      console.log(`${w.name}: ${diff > 0 ? "+" : ""}${diff}`);
    }
  }, []);
  return (
    <figure>
      <svg viewBox={`0 0 ${W} ${H}`} width={W} aria-hidden="true">
        {weeks.map((w, i) => (
          <Candle key={w.name} w={w} i={i} />
        ))}
      </svg>
      <figcaption>Guruch narxi, 4 hafta (so'm/kg)</figcaption>
    </figure>
  );
}

Konsolda:

text
1-h: +400
2-h: -400
3-h: +1000
4-h: -200

Ekranda:

text
1-h
2-h
3-h
4-h
Guruch narxi, 4 hafta (so'm/kg)

«Ishga tushir» — to'rtta sham. 1- va 3-hafta yashil (narx o'sdi), 2- va 4- qizil. 3-hafta shamiga qarang: pilik tepaga uzun cho'zilgan — hafta ichida narx 15 200 gacha chiqqan, yopilishda 15 000. Bitta shamda to'rt son — chiziqli grafik buni ko'rsata olmaydi.

y funksiyasi — visx'dagi scaleLinear ning qo'lda yozilgani. Ekranda: dagi har hafta nomi alohida qatorda — tekshiruvchi SVG <text> ni shunday o'qiydi.

6.2 lightweight-charts (TradingView)

Birjalardagi grafiklar odatda lightweight-charts bilan chiziladi — TradingView kompaniyasining ochiq kodli kutubxonasi. Canvas, juda tez, vaqt o'qi va masshtablash tayyor. React o'rami yo'q — ref va effekt bilan o'zimiz ulaymiz:

tsx
import { useEffect, useRef } from "react";
import { createChart, CandlestickSeries } from "lightweight-charts";
import type { CandlestickData } from "lightweight-charts";

export function PriceChart({ data }: { data: CandlestickData[] }) {
  const boxRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const chart = createChart(boxRef.current!, {
      height: 240,
      layout: { attributionLogo: false }, // pastda — nega
    });
    chart.addSeries(CandlestickSeries).setData(data);
    chart.timeScale().fitContent();
    return () => chart.remove(); // cleanup: canvas'lar o'chadi
  }, [data]);
  return <div ref={boxRef} role="img" aria-label="Guruch narxi" />;
}

// data: [{ time: "2026-10-19", open: 14000, high: 14600,
//          low: 13800, close: 14400 }, …]

5-versiyada seriya chart.addSeries(CandlestickSeries) bilan qo'shiladi. Eski maqolalarda chart.addCandlestickSeries() ni ko'rasiz — bu 4-versiya API'si.

Kanon tajribasida lightweight-charts 5.2.1 Trusted Types'da yiqildi (+51.4 KB). Biz sababini manba kodidan topdik: kutubxona TradingView logotipini chizadi, SVG'ni esa innerHTML bilan qo'yadi. Logotipni o'chirib (attributionLogo: false) qayta sinadik — CSP buzilishi 0, grafik chizildi (7 ta canvas).

Lekin logotip shunchaki bezak emas. Kutubxona Apache 2.0 litsenziyasida, tur faylidagi izoh shunday deydi: NOTICE faylidagi mualliflik yozuvi va tradingview.com'ga havola sahifangizda bo'lishi shart. Logotip shu talabni bajaradi. O'chirsangiz, havolani o'zingiz qo'ying (masalan, grafik ostida "Grafik: TradingView").

6.3 Ma'lumot tartibi har joyda boshqacha

OHLC to'rt son, lekin kutubxonalar ularni turli tartibda kutadi:

Kutubxona Bitta sham
lightweight-charts { time, open, high, low, close }
ECharts (type: "candlestick") [open, close, low, high]

ECharts'da massiv tartibi — ochilish, yopilish, eng past, eng yuqori. OHLC tartibida bersangiz, xato chiqmaydi — shamlar shunchaki noto'g'ri chiziladi. Obyektdan massivga o'tkazishni bitta funksiyaga yig'ing va unga test yozing.

7. Grafik va accessibility: kutubxonalar bo'yicha

O'tgan darsdagi qoidalar (matn muqobili, faqat rang emas, kontrast 3:1, harakatni kamaytirish) hamma kutubxonaga tegishli. Farq — kutubxona nima beradi:

Kutubxona Nima bor Sizdan
Chart.js <canvas> ga prop o'tadi role="img", aria-label, jadval
ECharts aria — avtomatik tavsif (inglizcha) o'zbekcha tavsif yoki jadval
Nivo role, ariaLabel proplari jadval
visx hech narsa — siz chizasiz hammasi
lightweight-charts hech narsa (canvas) aria-label, jadval

Sham grafigida rang (yashil/qizil) yolg'iz qolmasin: rang ajrata olmaydigan foydalanuvchi uchun tana ichi bo'sh/to'la yoki jadvalda "+400" kabi belgi qo'shing.

8. Biz sinab ko'rdik: hajm, CSP va narx

8.1 Hajm

Kanon har kutubxonaning eng kichik ishlaydigan misolini Vite 8.3.3 bilan yig'di. Hajm — faqat React'dan ortig'i, gzip:

Grafik kutubxonalari: JS (gzip, React'dan tashqari)
  • visx 4.0.012,2 KB
  • Chart.js 4.5.150,6 KB
  • lightweight-charts 5.2.151,4 KB
  • Nivo 0.99.081,3 KB
  • Recharts 3.10.196,6 KB
  • ECharts 6.1.0 (core)167 KB

Manba: O'lchandi: kanon tajribasi, Vite 8.3.3, React 19.3.0, gzip -9, Chrome 155, natijalar/tajriba/kutubxonalar-21.txt, 2026-10-17

Taqqoslash uchun: React va react-dom — 66.1 KB. Recharts bu yerda 96.6 KB — vazifalar ichidagi 103.4 KB dan biroz kam, chunki misol kichikroq. ECharts — React'ning ikki yarim barobari.

8.2 CSP va Trusted Types

Xom natija (kutubxonalar-21.txt), xato matnlari qisqartirilgan:

text
grafik-chartjs      +  50.6 KB | CSP 0
grafik-echarts      + 167.0 KB | CSP require-trusted-types-for×1 | xato: … TrustedHTML …
grafik-lightweight  +  51.4 KB | CSP require-trusted-types-for×2 | xato: … TrustedHTML …
grafik-nivo         +  81.3 KB | CSP 0
grafik-recharts     +  96.6 KB | CSP 0
grafik-visx         +  12.2 KB | CSP 0

Hech biri tashqi serverga bormadi. Canvas o'zi CSP muammosi emas — Chart.js canvas va 0 buzilish. Muammo — kutubxona ichidagi bitta innerHTML: ECharts'da konteynerni tozalash, lightweight-charts'da logotip. Ikkalasini ham sozlama bilan aylanib o'tdik («ECharts» va «lightweight-charts» bo'limlari) — bu bizning qo'shimcha sinovimiz, kanon emas.

Yana bir halol eslatma: ECharts kanonda birinchi yig'ishda umuman chizilmadi — konsolda Minified React error #130 (React element turi noto'g'ri). Peer bog'liqliklar qayta o'rnatilgach, o'lchov takrorlandi va yuqoridagi raqam olindi. Birinchi xatoning aniq sababi tajribada yozilmagan.

8.3 Nega vazifalar o'zgarmaydi

Bu darsda kanon kodi o'zgarmaydi. Ilovadagi yagona grafik — teglar grafigi, u o'tgan darsda o'z SVG bilan yozilgan: +0.3 KB, CSP 0. 8 ta ustun uchun canvas ham, ECharts ham kerak emas.

Ammo jadval saqlanib qoladi. vazifalar ga keyin "kunlik bajarilgan vazifalar, bir yil" (365 nuqta) qo'shilsa — Chart.js (50.6 KB, CSP 0) yoki o'z SVG. 10 000 nuqtali jonli ma'lumot bo'lsa — Chart.js + decimation.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Error: "category" is not a registered scale. CategoryScale ro'yxatdan o'tmagan ChartJS.register(CategoryScale, …)
decimation yoqildi, grafik baribir sekin parsing, o'q turi yoki grafik turi mos emas shartlar jadvalini tekshiring
keskin cho'qqi grafikda yo'q "har N-nuqta" bilan siyraklashtirilgan min-max yoki LTTB
ECharts qattiq CSP'da chizilmaydi canvas chizuvchi innerHTML = '' renderer: "svg" + renderMode: "richText"
lightweight-charts TT'da yiqiladi logotip innerHTML bilan attributionLogo: false + o'z havolangiz
ECharts shamlari teskari OHLC tartibida berilgan [open, close, low, high]
chart.addCandlestickSeries is not a function 4-versiya kodi 5-versiyada addSeries(CandlestickSeries)
Nivo grafigi ko'rinmaydi Responsive… otasida balandlik yo'q otaga height
ekran o'quvchi grafikni "o'tkazib yuboradi" <canvas> nomsiz role="img" + aria-label, jadval

10. Mashqlar

Mashqlar kurs/mashqlar/21/05-grafiklar-ii/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): bo'sh joylarni to'ldiring

  • 50 000 nuqtali chiziq uchun chizish usuli:
  • Chart.js'da linear shkalasi uchun ro'yxatdan o'tadigan klass:
  • Decimation ishlashi uchun parsing qiymati:
  • Sham grafigidagi to'rt son (qisqartma):
  • Kanon o'lchovida eng yengil grafik kutubxonasi:
Yechim
  • canvas — 50 000 ta SVG elementi sahifani qotiradi, canvas uchun esa bu bitta element.
  • LinearScale — type: "linear" o'q shu klass bilan ishlaydi.
  • false — parsing: false bo'lsa, ma'lumot tayyor {x, y} deb olinadi va plagin uni qisqartira oladi.
  • OHLC — open, high, low, close.
  • visx — +12.2 KB, chunki u faqat shkala va shakl beradi.

2-mashq (o'rta): eng katta o'zgarish

«Sham nimani ko'rsatadi» bo'limidagi weeks massividan foydalanib, komponent yozing:

  1. Har hafta uchun <li>: 1-h: 14 000 → 14 400 (o'sdi) ko'rinishida. Tushgan bo'lsa — (tushdi).
  2. Ro'yxat ostida <p>: Eng katta o'zgarish: 3-h — close - open ning mutlaq qiymati eng katta hafta.

Ishora: sonni guruhlash — Recharts darsidagi som() g'oyasi (muntazam ifoda), mutlaq qiymat — Math.abs.

Yechim
jsx
const weeks = [
  { name: "1-h", open: 14000, high: 14600, low: 13800, close: 14400 },
  { name: "2-h", open: 14400, high: 14500, low: 13900, close: 14000 },
  { name: "3-h", open: 14000, high: 15200, low: 13900, close: 15000 },
  { name: "4-h", open: 15000, high: 15300, low: 14700, close: 14800 },
];

const group = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, " ");

export default function App() {
  const biggest = weeks.reduce((a, b) =>
    Math.abs(b.close - b.open) > Math.abs(a.close - a.open) ? b : a,
  );
  return (
    <section>
      <ul>
        {weeks.map((w) => (
          <li key={w.name}>
            {`${w.name}: ${group(w.open)} → ${group(w.close)} `}
            {w.close >= w.open ? "(o'sdi)" : "(tushdi)"}
          </li>
        ))}
      </ul>
      <p>Eng katta o'zgarish: {biggest.name}</p>
    </section>
  );
}

Ekranda:

text
1-h: 14 000 → 14 400 (o'sdi)
2-h: 14 400 → 14 000 (tushdi)
3-h: 14 000 → 15 000 (o'sdi)
4-h: 15 000 → 14 800 (tushdi)
Eng katta o'zgarish: 3-h

reduce har qadamda ikkitadan kattasini qoldiradi. Bunday ro'yxat — sham grafigining matn muqobili ham: Malika uchun rang emas, "(o'sdi)" so'zi.

3-mashq (qiyin): savdolardan sham yasash

Birjada narx emas, savdolar keladi: vaqt va narx. Kunlik shamni ulardan o'zingiz yasaysiz. toCandles(trades) funksiyasini yozing: savdolarni kun bo'yicha guruhlab, har kun uchun { day, open, high, low, close } qaytarsin. Ochilish — kunning birinchi savdosi, yopilish — oxirgisi (savdolar vaqt bo'yicha tartiblangan).

js
const trades = [
  { time: "2026-10-19T09:05", price: 14000 },
  { time: "2026-10-19T12:40", price: 14600 },
  { time: "2026-10-19T17:55", price: 14400 },
  { time: "2026-10-20T09:10", price: 14350 },
  { time: "2026-10-20T15:00", price: 13900 },
];

Ishora: kun — time.slice(0, 10); guruhlash — Map.groupBy yoki oddiy obyekt.

Yechim
js
const trades = [
  { time: "2026-10-19T09:05", price: 14000 },
  { time: "2026-10-19T12:40", price: 14600 },
  { time: "2026-10-19T17:55", price: 14400 },
  { time: "2026-10-20T09:10", price: 14350 },
  { time: "2026-10-20T15:00", price: 13900 },
];

function toCandles(list) {
  const byDay = Map.groupBy(list, (t) => t.time.slice(0, 10));
  return [...byDay].map(([day, items]) => {
    const prices = items.map((t) => t.price);
    return {
      day,
      open: prices[0],
      high: Math.max(...prices),
      low: Math.min(...prices),
      close: prices.at(-1),
    };
  });
}

for (const c of toCandles(trades)) {
  console.log(c.day, c.open, c.high, c.low, c.close);
}

Konsolda:

text
2026-10-19 14000 14600 14000 14400
2026-10-20 14350 14350 13900 13900

20-oktabrda savdo ikki marta — eng yuqori ochilishga, eng past yopilishga teng. Bunday shamda yuqori pilik yo'q. Map.groupBy kalitlarni birinchi uchragan tartibda saqlaydi, shuning uchun kunlar tartibi buzilmaydi. Haqiqiy birjada bu hisob serverda bo'ladi — brauzer tayyor shamlarni oladi.

4-mashq: tajriba qadami (ixtiyoriy)

vazifalar ga tegmang. Alohida Vite loyihada (kurs/tajriba/grafik/) Chart.js bilan 10 000 nuqtali chiziq chizing (decimation bilan). Keyin index.html ga vazifalar ning CSP <meta> ini qo'shing va konsolni tekshiring. Xuddi shuni ECharts bilan — avval canvas, keyin SVG chizuvchi bilan — takrorlang.

Yechim

Tekshiruv ro'yxati:

  • npm i -E chart.js@4.5.1 react-chartjs-2@5.3.1 — build'da gzip hajmi React'dan tashqari ~50 KB atrofida (kanon: 50.6).
  • Decimation: DevTools konsolida Chart.getChart(document.querySelector("canvas")).data.datasets[0].data.length — 10 000 dan ancha kam.
  • Chart.js + CSP — konsol toza.
  • ECharts canvas + CSP — This document requires 'TrustedHTML' assignment; renderer: "svg" bilan — toza. Tooltip'ga sichqoncha olib boring: HTML tooltip yana buzadi, renderMode: "richText" — yo'q.

11. Real ishda

  • Admin panel va dashboard'lar — Chart.js va ECharts eng ko'p uchraydi. Bir sahifada o'nlab grafik bo'lsa, ECharts'ning og'irligi o'zini oqlaydi.
  • Monitoring (server yuklamasi, IoT sensorlar) — minglab nuqta, canvas va decimation. Grafana kabi tizimlar ham shu g'oyada.
  • Fintech va birjalar — sham grafiklari, lightweight-charts. O'zbekistonda kripto va fond bozori ilovalari, valyuta kursi sahifalari.
  • Media va jurnalistika — noodatiy, bir martalik grafiklar: visx yoki to'g'ridan-to'g'ri d3.
  • Intervyu savollari: "SVG va canvas farqi?", "100 000 nuqtani qanday chizasiz?", "canvas grafikni ekran o'quvchiga qanday tushuntirasiz?", "kutubxona CSP bilan ishlaydimi — qanday tekshirasiz?".

Xulosa

  • SVG — har shakl DOM elementi: CSS, hodisa, a11y qulay, lekin minglab nuqtada sekin. Canvas — bitta element, tez, lekin matn muqobili sizdan.
  • Chart.js + react-chartjs-2: qismlar register bilan, ref + getElementAtEvent bosish uchun, <canvas> ga role="img" va aria-label.
  • Katta ma'lumot — decimation (min-max, LTTB); "har N-nuqta" cho'qqilarni yo'qotadi. Chart.js plagini faqat line, linear/time o'q va parsing: false da ishlaydi.
  • ECharts — bitta option obyekti, eng katta (+167.0 KB); qattiq CSP'da — SVG chizuvchi va richText tooltip.
  • Nivo — tayyor SVG grafiklar (+81.3), visx — shkala va shakllar (+12.2).
  • Sham grafigi — OHLC; lightweight-charts (+51.4): addSeries(CandlestickSeries), logotip TT'ni buzadi — o'chirsangiz, havolani o'zingiz bering.

Keyingi dars: Xaritalar I: Leaflet va OpenStreetMap — «Bahor» filiallari xaritada: plitkalar qanday yuklanadi, marker, popup, foydalanuvchi joylashuvi va nega Leaflet vazifalar CSP'sida import paytidayoq yiqiladi.

Manbalar

  • Chart.js 4: "Integration" (tree-shaking, register), "Data Decimation", "Accessibility" — chartjs.org/docs; manba kodi plugin_decimation (4.5.1)
  • react-chartjs-2: "Getting started", "getElementAtEvent" — react-chartjs-2.js.org
  • Apache ECharts: "Import ECharts" (echarts/core), "Canvas vs. SVG", tooltip.renderMode, aria, series-candlestick — echarts.apache.org
  • Nivo: "Bar" — nivo.rocks; visx: "Scale", "Shape" — airbnb.io/visx
  • lightweight-charts 5: "Series types", "Migrating from v4 to v5", attributionLogo — tradingview.github.io/lightweight-charts
  • WCAG 2.2: 1.1.1 Non-text Content, 1.4.1 Use of Color
  • Kanon: natijalar/tajriba/kutubxonalar-21.txt, kutubxonalar-21.json, kutubxonalar-21-qisman.txt, tajriba/kutubxonalar/src/k/grafik-*.tsx (2026-10-17)
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!