{"spec_id":"bubble-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// bubble-basic: Basic Bubble Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 86/100 | Updated: 2026-09-30\n\nconst t = window.ANYPLOT_TOKENS;\nconst size = window.ANYPLOT_SIZE;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Tiny fixed-seed LCG — the browser has no seeded RNG.\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\n// Market analysis: R&D investment vs. revenue growth, bubble size = a relative\n// market-strength index (0-100 scale, scored independently per company — not a\n// literal share of one shared 100% pie).\nconst companyCount = 65;\nconst bubbles = [];\nfor (let i = 0; i < companyCount; i++) {\n  const rdSpend = 5 + rand() * 95; // R&D investment, $M\n  const noise = (rand() - 0.5) * 30; // wide noise band so the trend isn't a tight line\n  const rawGrowth = 3 + rdSpend * 0.18 + noise;\n  // Soft floor: companies whose trend + noise would go non-positive settle at\n  // a small randomized value instead of a hard clamp, so the low-R&D cluster\n  // spreads naturally near the origin rather than piling up as concentric\n  // bubbles at a single y.\n  const growthRate = rawGrowth > 1 ? rawGrowth : 0.3 + rand() * 1.7; // revenue growth, %\n  const marketIndex = 10 + rand() * 90; // relative market-strength index\n  bubbles.push([rdSpend, growthRate, marketIndex]);\n}\n\n// A handful of deliberate outliers break the x/y trend, showing what the\n// bubble encoding reveals that a plain 2D scatter would blur: heavy R&D spend\n// doesn't guarantee growth, and a small agile spender can still break out.\nbubbles[3] = [88, 5, 71]; // legacy incumbent: heavy spend, weak growth\nbubbles[17] = [9, 46, 24]; // agile startup: tiny spend, breakout growth\nbubbles[41] = [61, 3, 85]; // large but stagnant market leader\n\nconst indexValues = bubbles.map((b) => b[2]);\nconst indexMin = Math.min(...indexValues);\nconst indexMax = Math.max(...indexValues);\n\n// Scale bubble diameter by sqrt(value) so on-screen AREA (not radius) is\n// proportional to the market index.\nconst minDiameter = 14;\nconst maxDiameter = 92;\nconst sizeScale = maxDiameter / Math.sqrt(indexMax);\nfunction diameterFor(value) {\n  return Math.max(minDiameter, sizeScale * Math.sqrt(value));\n}\n\n// Painter's-order fix: draw the largest bubbles first and the smallest last,\n// so small bubbles in the dense low-spend cluster render on top of large ones\n// instead of disappearing underneath them.\nconst orderedBubbles = bubbles.slice().sort((a, b) => b[2] - a[2]);\n\n// --- Init ---------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Size legend (three reference bubbles drawn as graphic elements) -------\nconst legendCx = size.width - 105;\nconst legendSamples = [\n  { value: indexMin, cy: size.height * 0.26 },\n  { value: (indexMin + indexMax) / 2, cy: size.height * 0.48 },\n  { value: indexMax, cy: size.height * 0.72 },\n];\n\nconst legendGraphics = [\n  {\n    type: \"text\",\n    left: legendCx - 100,\n    top: size.height * 0.14,\n    style: {\n      text: \"Relative Market Index\",\n      fill: t.inkSoft,\n      fontSize: 16,\n      fontWeight: \"bold\",\n    },\n  },\n  ...legendSamples.map((sample) => {\n    const r = diameterFor(sample.value) / 2;\n    return {\n      type: \"circle\",\n      shape: { cx: legendCx, cy: sample.cy, r },\n      style: { fill: t.palette[0], opacity: 0.68, stroke: t.pageBg, lineWidth: 1.5 },\n    };\n  }),\n  ...legendSamples.map((sample) => ({\n    type: \"text\",\n    left: legendCx + maxDiameter / 2 + 14,\n    top: sample.cy - 9,\n    style: {\n      text: `${Math.round(sample.value)}`,\n      fill: t.inkSoft,\n      fontSize: 16,\n    },\n  })),\n];\n\n// --- Option ------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"bubble-basic · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22 },\n  },\n  grid: { left: 100, right: 210, top: 110, bottom: 100 },\n  xAxis: {\n    type: \"value\",\n    name: \"R&D Investment ($M)\",\n    nameLocation: \"middle\",\n    nameGap: 40,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Revenue Growth Rate (%)\",\n    nameLocation: \"middle\",\n    nameGap: 55,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      type: \"scatter\",\n      data: orderedBubbles,\n      symbolSize: (value) => diameterFor(value[2]),\n      itemStyle: {\n        color: t.palette[0],\n        opacity: 0.68,\n        borderColor: t.pageBg,\n        borderWidth: 1.5,\n      },\n    },\n  ],\n  graphic: legendGraphics,\n});\n"}