{"spec_id":"icicle-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// icicle-basic: Basic Icicle Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic): a small project file tree -----------\n// Folders carry no explicit value; their value is the sum of their children.\nconst rawNodes = [\n  { name: \"Project\", parent: null },\n  { name: \"Src\", parent: \"Project\" },\n  { name: \"Docs\", parent: \"Project\" },\n  { name: \"Tests\", parent: \"Project\" },\n  { name: \"Assets\", parent: \"Project\" },\n  { name: \"Components\", parent: \"Src\" },\n  { name: \"Utils\", parent: \"Src\" },\n  { name: \"Api\", parent: \"Src\" },\n  { name: \"Styles\", parent: \"Src\" },\n  { name: \"Unit\", parent: \"Tests\" },\n  { name: \"Integration\", parent: \"Tests\" },\n  { name: \"Fonts\", parent: \"Assets\" },\n  { name: \"Header.js\", parent: \"Components\", value: 4.2 },\n  { name: \"Footer.js\", parent: \"Components\", value: 3.1 },\n  { name: \"Sidebar.js\", parent: \"Components\", value: 5.6 },\n  { name: \"Modal.js\", parent: \"Components\", value: 2.8 },\n  { name: \"format.js\", parent: \"Utils\", value: 1.9 },\n  { name: \"validate.js\", parent: \"Utils\", value: 2.4 },\n  { name: \"client.js\", parent: \"Api\", value: 6.7 },\n  { name: \"endpoints.js\", parent: \"Api\", value: 3.3 },\n  { name: \"theme.css\", parent: \"Styles\", value: 2.1 },\n  { name: \"layout.css\", parent: \"Styles\", value: 1.6 },\n  { name: \"guide.md\", parent: \"Docs\", value: 8.4 },\n  { name: \"api.md\", parent: \"Docs\", value: 5.9 },\n  { name: \"changelog.md\", parent: \"Docs\", value: 3.2 },\n  { name: \"format.test.js\", parent: \"Unit\", value: 2.0 },\n  { name: \"validate.test.js\", parent: \"Unit\", value: 2.2 },\n  { name: \"api.test.js\", parent: \"Integration\", value: 4.5 },\n  { name: \"logo.svg\", parent: \"Assets\", value: 1.2 },\n  { name: \"icons.svg\", parent: \"Assets\", value: 3.8 },\n  { name: \"Inter.woff2\", parent: \"Fonts\", value: 45.0 },\n  { name: \"Mono.woff2\", parent: \"Fonts\", value: 38.0 },\n];\n\n// --- Build tree + aggregate values bottom-up -------------------------------\nconst nodeMap = new Map();\nrawNodes.forEach((n) => nodeMap.set(n.name, { ...n, children: [] }));\nnodeMap.forEach((n) => {\n  if (n.parent) nodeMap.get(n.parent).children.push(n);\n});\nconst root = nodeMap.get(\"Project\");\n\nconst aggregate = (node) => {\n  if (node.children.length === 0) return node.value;\n  node.value = node.children.reduce((sum, child) => sum + aggregate(child), 0);\n  return node.value;\n};\naggregate(root);\n\n// --- Focal point: the single largest leaf and its ancestor chain ----------\n// (Inter.woff2 -> Fonts -> Assets -> Project) get a thin outline so the\n// dominant contributor to the hierarchy is visually called out.\nlet largestLeaf = null;\nnodeMap.forEach((n) => {\n  if (n.children.length === 0 && (!largestLeaf || n.value > largestLeaf.value)) {\n    largestLeaf = n;\n  }\n});\nconst highlightNames = new Set();\nfor (let n = largestLeaf; n; n = n.parent ? nodeMap.get(n.parent) : null) {\n  highlightNames.add(n.name);\n}\n\n// --- Partition layout: x = cumulative value, y = depth (rows) -------------\nconst rows = [];\nlet maxDepth = 0;\nconst layout = (node, depth, x0, x1) => {\n  maxDepth = Math.max(maxDepth, depth);\n  rows.push([x0, x1, depth, node.value, node.name, highlightNames.has(node.name) ? 1 : 0]);\n  let x = x0;\n  node.children.forEach((child) => {\n    const width = (child.value / node.value) * (x1 - x0);\n    layout(child, depth + 1, x, x + width);\n    x += width;\n  });\n};\nlayout(root, 0, 0, root.value);\n\n// --- Color by hierarchy level (Imprint palette) ----------------------------\nconst LEVEL_COLORS = [t.palette[0], t.palette[1], t.palette[2], t.palette[3]];\nconst textColorFor = (hex) => {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n  const brightness = (r * 299 + g * 587 + b * 114) / 1000;\n  return brightness > 140 ? \"#1A1A17\" : \"#FFFDF6\";\n};\n\nconst GAP = 4;\n\nconst renderItem = (params, api) => {\n  const x0 = api.value(0);\n  const x1 = api.value(1);\n  const depth = api.value(2);\n  const name = api.value(4);\n  const highlight = api.value(5) === 1;\n\n  const corner = api.coord([x0, depth]);\n  const size = api.size([x1 - x0, 1]);\n  const width = Math.max(Math.abs(size[0]) - GAP, 0);\n  const height = Math.max(Math.abs(size[1]) - GAP, 0);\n  const fill = LEVEL_COLORS[depth % LEVEL_COLORS.length];\n  const showLabel = width > 48 && height > 18;\n\n  return {\n    type: \"rect\",\n    shape: { x: corner[0] + GAP / 2, y: corner[1] + GAP / 2, width, height },\n    style: {\n      fill,\n      stroke: highlight ? t.ink : undefined,\n      lineWidth: highlight ? 2 : 0,\n      text: showLabel ? name : undefined,\n      textFill: textColorFor(fill),\n      textPosition: \"insideLeft\",\n      textDistance: 8,\n      textVerticalAlign: \"middle\",\n      fontSize: 13,\n      fontWeight: 500,\n      width: Math.max(width - 14, 0),\n      height,\n      overflow: \"truncate\",\n      ellipsis: \"…\",\n    },\n  };\n};\n\n// --- Init --------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"icicle-basic · javascript · echarts · anyplot.ai\",\n    subtext: \"Rectangle width = file size in KB · color = hierarchy depth · outline = largest contributor\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22 },\n    subtextStyle: { color: t.inkSoft, fontSize: 14 },\n  },\n  tooltip: {\n    formatter: (params) =>\n      `<strong>${params.value[4]}</strong><br/>${params.value[3].toFixed(1)} KB`,\n  },\n  grid: { left: 30, right: 30, top: 130, bottom: 30 },\n  xAxis: { type: \"value\", min: 0, max: root.value, show: false },\n  yAxis: { type: \"value\", min: 0, max: maxDepth + 1, inverse: true, show: false },\n  series: [\n    {\n      type: \"custom\",\n      coordinateSystem: \"cartesian2d\",\n      renderItem,\n      encode: { x: [0, 1], y: 2 },\n      data: rows,\n    },\n  ],\n});\n"}