Skip to content

Biểu đồ cột so sánh ​

So sánh

Nó đã trở nên tốt hơn hay tệ hơn? Đặt trước và sau cạnh nhau trên một cột cho mỗi nhãn, và khoảng cách đã thu hẹp (hoặc mở rộng) là điều đầu tiên người đọc nhìn thấy.

Example
  • 2019 (trước, màu nhạt)
  • 2024 (sau, màu đậm)
canvas · responsive
Tìm hiểu thêm: Hướng dẫn Insights·Hướng dẫn DevTools

Biểu đồ ở trên là cùng một engine trong mọi framework - chỉ có mã tích hợp bên dưới là khác nhau.

Dữ liệu lớn trên WebGPU Thử nghiệm ​

ComparableHorizontalBarChart có tùy chọn renderer="webgpu" để vẽ hai cột con của mỗi hàng dưới dạng hình chữ nhật theo instance trên GPU trong khi trục, nhãn và tooltip vẫn ở lớp SVG. Tính năng này được kiểm soát theo khả năng: trên trình duyệt không có WebGPU, nó sẽ tự động hạ cấp xuống canvas, và getContext().renderer báo cáo bất kỳ renderer nào thực sự đã vẽ.

⚗️ Experimental - not yet stable. WebGPU rendering is an opt-in preview. It needs a WebGPU-capable browser (Chrome / Edge, or Safari 26+); everywhere else it falls back to canvas automatically. Axes, labels and tooltips stay on the SVG layer - only the data marks are painted on the GPU.
Heavy-data demo · ~120 rows… detecting

Xem dữ liệu chạy theo năm ​

Gắn date cho từng dòng rồi bật timeline: biểu đồ cột thành câu chuyện theo từng năm với nút play và thanh tua riêng, mỗi lần hiển thị khoảng cách trước/sau của một giai đoạn. Mặc định tắt - không bật thì biểu đồ giữ nguyên.

Bấm nút play dưới biểu đồ: dữ liệu chạy qua từng năm, mỗi lần một snapshot. Kéo thanh tua để nhảy đến năm bất kỳ.

tsx
const ref = useRef<ComparableHorizontalBarChartHandle>(null);

<ComparableHorizontalBarChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / seekIndex(i) / stepForward()
vue
<ComparableHorizontalBarChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" />
svelte
<div use:comparableHorizontalBarChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>
ts
applyComparableHorizontalBarChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });
html
<michi-vz-comparable-horizontal-bar-chart id="c"></michi-vz-comparable-horizontal-bar-chart>
<script>
  const el = document.getElementById("c");
  el.timeline = { speedMs: 1000, loop: true };
  // el.getTimeline() -> play() / pause() / seek(year) / seekIndex(i)
</script>
  • speedMs chỉnh nhịp chạy, loop quay vòng, autoplay: true tự chạy khi mount, showControl: false ẩn thanh điều khiển có sẵn.
  • Giá trị trượt mượt giữa các giai đoạn theo mặc định (interpolate); chỉnh chuyển động bằng tweenMs và easing, hoặc đặt interpolate: false để cắt thẳng. Khi bật reduced motion, biểu đồ luôn cắt thẳng.
  • Controller headless luôn sẵn sàng: chart.timeline() cho play() / pause() / toggle() / seek(period) / seekIndex(i) / stepForward() / stepBack(), kèm onStep và formatPeriod trong config khi cần tự dựng UI.
  • seek(period) tìm kỳ trước, so sánh dạng chữ, nên seek(2021) và seek("2021") đều tới 2021 dù ngày trong dữ liệu là số hay chuỗi. Số chỉ được hiểu là vị trí (0 = kỳ đầu) khi không có kỳ nào khớp, còn chuỗi không khớp kỳ nào thì không làm gì. seekIndex(i) luôn đi theo vị trí, giống thanh tua có sẵn.
  • filter (top-N, sắp xếp) vẫn áp dụng bên trong từng giai đoạn, nên "top 5 mỗi năm" chạy được ngay.
  • Dòng không có date hiển thị ở mọi giai đoạn.

Bán kính góc ​

Mặc định mỗi cột được vẽ với góc bo 5px. Trên màn hình nhỏ các cột trở nên mỏng - nửa dải của layout grouped có thể chỉ dày khoảng 10px - và bán kính 5px trên một cột dày 10px biến nó thành hình viên thuốc. barRadius đặt bán kính theo px: 0 cho góc vuông, 2-4 cho góc gần vuông.

barRadius: 5 (mặc định)
barRadius: 2
barRadius: 0

Cùng biểu đồ, cùng dữ liệu; chỉ barRadius thay đổi. Hãy đổi renderer: svg và canvas bo góc giống hệt nhau.

tsx
<ComparableHorizontalBarChart {...props} layout="grouped" barRadius={2} />
vue
<ComparableHorizontalBarChart :options="{ ...props, layout: 'grouped', barRadius: 2 }" />
svelte
<div use:comparableHorizontalBarChart={{ ...props, layout: "grouped", barRadius: 2 }}></div>
ts
applyComparableHorizontalBarChartProps(this.c.nativeElement, { ...props, layout: "grouped", barRadius: 2 });
html
<michi-vz-comparable-horizontal-bar-chart layout="grouped" bar-radius="2"></michi-vz-comparable-horizontal-bar-chart>
  • Bán kính được vẽ không bao giờ vượt quá một nửa bề rộng hoặc chiều cao của cột, ở cả svg lẫn canvas.
  • Không truyền barRadius thì giữ nguyên giao diện 5px như trước. Giá trị âm cho góc vuông.
  • Renderer thử nghiệm webgpu luôn vẽ góc vuông, bất kể giá trị.

Cách dùng ​

tsx
import { ComparableHorizontalBarChart } from "@michi-vz/react";

export default () => <ComparableHorizontalBarChart {...props} />; // props = the chart options
vue
<script setup>
import { ComparableHorizontalBarChart } from "@michi-vz/vue";
</script>

<template>
  <ComparableHorizontalBarChart :options="props" />
</template>
svelte
<script>
  import { comparableHorizontalBarChart } from "@michi-vz/svelte";
</script>

<div use:comparableHorizontalBarChart={props}></div>
ts
// main.ts - register the elements once
import "@michi-vz/angular";
import { applyComparableHorizontalBarChartProps } from "@michi-vz/angular";

// component (uses CUSTOM_ELEMENTS_SCHEMA)
// template: <michi-vz-comparable-horizontal-bar-chart #c></michi-vz-comparable-horizontal-bar-chart>
applyComparableHorizontalBarChartProps(this.c.nativeElement, props);
html
<script type="module" src="https://cdn.jsdelivr.net/npm/@michi-vz/wc/dist/michi-vz-wc.bundle.js"></script>

<michi-vz-comparable-horizontal-bar-chart id="c"></michi-vz-comparable-horizontal-bar-chart>
<script>
  Object.assign(document.getElementById("c"), props); // dataSet/series, title, …
</script>
ts
import { mountComparableHorizontalBarChart } from "@michi-vz/core";

const chart = mountComparableHorizontalBarChart(el, props);
chart.update(next);
chart.getContext(); // renderer-agnostic, LLM-ready
chart.destroy();

API ​

Các prop được định kiểu là ComparableHorizontalBarChartProps trong @michi-vz/core. Dùng chung cho mọi biểu đồ: width, height, margin, colors / colorsMapping, renderer ("svg", "canvas", hoặc "webgpu" thử nghiệm), highlightItems, disabledItems, và các callback on*. onChartDataProcessed / getContext() trả về ChartContext độc lập với renderer.

Ghi chú về hành vi ​

Hai cột con cho mỗi hàng ​

Mỗi hàng vẽ valueBased (màu nhạt) và valueCompared (màu đậm), phân kỳ từ x=0; cột con ngắn hơn luôn được vẽ đè lên trên nên cả hai giá trị đều nhìn thấy được, dù tăng hay giảm. colorsBasedMapping cho cột con valueBased một màu riêng theo từng nhãn: kết hợp một tông màu sáng không trong suốt với valueBasedOpacity: 1 (như demo ở trên) để tương phản trước/sau rõ nhất trên cả hai theme. valueBasedOpacity / valueComparedOpacity đặt độ mờ tô của chúng. Một cột con có màu tô đã phân giải là transparent sẽ bị bỏ qua (consumer ẩn một nửa qua CSS). minBarWidth (mặc định 5) đặt sàn cho một cột khác không để các giá trị gần bằng không vẫn hiển thị được.

patternsMapping - tô bằng hoa văn / ảnh ​

patternsMapping: Record<label, imageSrc> tô cột con value-based bằng một ảnh lặp lại thay vì một màu đặc. createHatchPattern({ color, angle?, spacing?, strokeWidth? }) (xuất từ @michi-vz/core và @michi-vz/react) trả về một data-URI SVG hoa văn gạch chéo cho trường hợp phổ biến. Bộ dựng canvas lặp nó qua ctx.createPattern và dựng lại một khi ảnh tải xong.

Trục giá trị (x) ​

xAxisPredefinedDomain: [min, max] cố định phạm vi trục giá trị (bí danh của xAxisDomain). showZeroLineForXAxis vẽ một đường đặc tại x=0 (cho biểu đồ phân kỳ); showGrid bật/tắt các đường lưới dọc (mặc định tắt). xAxisFormat định dạng nhãn của các mốc.

Cột nhãn (y) ​

Các nhãn danh mục trên trục y nằm trong một cột bên trái rộng tickHtmlWidth px (mặc định 100, có dấu chấm lửng). padding.left thụt vào khu vực vẽ (cột + trục giá trị) sang phải MÀ KHÔNG di chuyển các nhãn - mở ra không gian cho một cột nhãn rộng. horizontalTickPosition: { x, y } xê dịch các nhãn để căn chỉnh với một chú giải bên ngoài. hideTickLabels ẩn chúng hoàn toàn (khi tên danh mục đã nằm trong một chú giải).

Tooltip ​

tooltipFormatter(datum, dataSet, type) nhận hàng đang được trỏ chuột vào, tất cả các hàng, và type của cột con đang được trỏ vào ("based" | "compared"). Nó trả về một chuỗi HTML; wrapper React còn nhận thêm một React node (được chuyển đổi thành HTML tĩnh). Tooltip có sẵn nhận biết cạnh (lật khi gần cạnh phải/trên).

Đang tải / không có dữ liệu + tương tác ​

isLoading và isNodata điều khiển lớp phủ (React: isLoadingComponent / isNodataComponent). Di chuột làm nổi bật một hàng (các hàng khác mờ đi) và mouseleave xóa trạng thái đó; các cột được bo tròn (mặc định bán kính 5, chỉnh bằng barRadius) với viền 1px.

Cơ quan quyết định màu sắc của consumer: ngữ cảnh mang theo legendData ({ label, color, dataLabelSafe }) để một hệ thống màu tiêm CSS có thể dùng làm khóa cho các quy tắc theo từng nhãn; onChartDataProcessed chỉ được phát ra khi ngữ cảnh thay đổi (phát lại một ngữ cảnh không đổi ở mỗi lần render có thể tạo vòng lặp cho một consumer dispatch ở mỗi lần gọi).