Skip to content

Biểu đồ bong bóng

Cấu thành

"Mỗi cái lớn cỡ nào, và bao nhiêu phần trong đó đã được hiện thực hóa?" Một đám mây bong bóng trả lời về độ lớn chỉ trong nháy mắt: mỗi vòng tròn có kích thước theo giá trị (diện tích, không phải bán kính), và một mô phỏng trọng lực kéo chúng vào một cụm gọn gàng để những cái lớn rõ ràng nổi bật. Giống như treemap, mỗi bong bóng có thể mang một phần tách hai phần - một lõi đã hiện thực hóa đặc bên trong một vành chưa khai thác nhạt hơn - để bạn đọc được cả quy mô lẫn tiến độ cùng lúc.

Example
canvas · responsive
Tìm hiểu thêm: Hướng dẫn Insights·Hướng dẫn DevTools

Không cần tách? Bỏ partial để có một đám mây tỷ lệ gọn gàng, mỗi danh mục một màu:

Example
canvas · responsive
Tìm hiểu thêm: Hướng dẫn Insights·Hướng dẫn DevTools

Cụm được bố trí bằng d3-force: các bong bóng rơi về phía trung tâm (gravity) và đẩy nhau ra để không bao giờ chồng lấn (va chạm). Mô phỏng được ổn định đồng bộ, nên SVG và canvas dựng ra cùng một bố cục giống hệt nhau, có thể tái tạo được.

Khi nào nên dùng

  • Độ lớn trong nháy mắt. Sản phẩm, thị trường, từ khóa hiện thành một đám mây bong bóng có kích thước: khi "cái nào lớn?" quan trọng hơn thứ hạng chính xác, cụm bong bóng trả lời tức thì.
  • Bản đồ cơ hội. Với phần chia, một bong bóng to mà lõi đã hiện thực hóa còn mỏng nghĩa là tiền vẫn đang nằm trên bàn: góc nhìn quét tìm dư địa cho các buổi rà soát danh mục.
  • Nếu vị trí cần mang ý nghĩa (hai trục số, mối tương quan), đó là việc của biểu đồ phân tán; đám mây bong bóng đánh đổi vị trí lấy sự gọn gàng.

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

BubbleChart có tùy chọn renderer="webgpu" để vẽ đám mây bong bóng dưới dạng các vòng tròn theo instance trên GPU trong khi 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ẽ.

Giống trang biểu đồ phân tán, demo bên dưới cũng mượn cảm hứng từ vật lý hạt: ~1.500 cụm năng lượng tái dựng từ một sự kiện va chạm mô phỏng, mỗi cụm một bong bóng, tô màu theo hệ đo. Vài cú va đập mạnh vượt hẳn hàng nghìn cụm mềm, và cách xếp trọng lực biến toàn bộ ngân sách năng lượng của sự kiện thành một đám mây dễ đọc.

⚗️ 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 · ~1.500 cụm năng lượng mô phỏng… detecting

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

Gắn date cho từng bong bóng rồi bật timeline: đám mây bong bóng 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ị kích thước 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<BubbleChartHandle>(null);

<BubbleChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / stepForward()
vue
<BubbleChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" />
svelte
<div use:bubbleChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>
ts
applyBubbleChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });
html
<michi-vz-bubble-chart id="c"></michi-vz-bubble-chart>
<script>
  const el = document.getElementById("c");
  el.timeline = { speedMs: 1000, loop: true };
  // el.getTimeline() -> play() / pause() / seek(year)
</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 tweenMseasing, 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) / stepForward() / stepBack(), kèm onStepformatPeriod trong config khi cần tự dựng UI.
  • filter (top-N, sắp xếp) vẫn áp dụng bên trong từng giai đoạn, nên mỗi năm chỉ giữ lại top 5 bong bóng.
  • Bong bóng không có date hiển thị ở mọi giai đoạn.

Cách dùng

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

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

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

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

// component (uses CUSTOM_ELEMENTS_SCHEMA)
// template: <michi-vz-bubble-chart #c></michi-vz-bubble-chart>
applyBubbleChartProps(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-bubble-chart id="c"></michi-vz-bubble-chart>
<script>
  Object.assign(document.getElementById("c"), props); // dataSet, splitLabels, …
</script>
ts
import { mountBubbleChart } from "@michi-vz/core";

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

Cấu trúc dữ liệu

Mỗi phần tử dataSet là một bong bóng: một label, một value (diện tích), một partial tùy chọn (phần đã hiện thực hóa), và một color tùy chọn.

ts
const props = {
  splitLabels: ["Realized", "Untapped"],
  showLegend: true,
  gravity: 0.09, // higher = tighter cluster
  dataSet: [
    { label: "Germany", value: 120, partial: 64 }, // 53% realized
    { label: "United States", value: 152, partial: 88 },
    { label: "China", value: 168, partial: 51 },
  ],
};

Trọng lực & phần tách

gravity quy định các bong bóng bị kéo về trung tâm mạnh đến đâu (càng cao = càng chặt), padding là khoảng cách giữa chúng, và fillRatio là đám mây lấp bao nhiêu phần của khu vực vẽ. Phần tách phản chiếu treemap: partial khắc ra một lõi đã hiện thực hóa chuẩn theo diện tích (bán kính r·√(partial/value)), và phần còn lại hiện lên như một sắc thái nhạt hơn của cùng tông màu - một màu đặc dưới một lớp phủ trắng, để nó hoạt động tốt trên cả nền sáng nền tối. Đặt tên cho các phần bằng splitLabels.

API

Các prop được định kiểu là BubbleChartProps 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. Tài liệu tham khảo đầy đủ: Bubble API.