{"spec_id":"waterfall-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// waterfall-basic: Basic Waterfall Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.1\n// Quality: 89/100 | Created: 2026-08-04\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Quarterly bridge from starting revenue to net profit, in $ thousands.\nconst categories = [\n  \"Starting Revenue\",\n  \"Product Sales\",\n  \"Service Revenue\",\n  \"Returns & Discounts\",\n  \"Operating Costs\",\n  \"Taxes\",\n  \"Net Profit\",\n];\nconst stepType = [\"total\", \"increase\", \"increase\", \"decrease\", \"decrease\", \"decrease\", \"total\"];\nconst rawValues = [850, 320, 180, -95, -410, -145, 700];\n\nconst fmt = (n) => n.toLocaleString(\"en-US\");\n\nconst base = [];\nconst totalSeries = [];\nconst increaseSeries = [];\nconst decreaseSeries = [];\nconst connectorTop = [];\nconst labelText = [];\nconst runningTotalText = [];\n\nlet cumulative = 0;\nconst lastIndex = categories.length - 1;\nfor (let i = 0; i < categories.length; i++) {\n  const kind = stepType[i];\n  const value = rawValues[i];\n\n  totalSeries.push(0);\n  increaseSeries.push(0);\n  decreaseSeries.push(0);\n\n  if (kind === \"total\") {\n    base.push(0);\n    cumulative = value;\n    labelText.push(`$${fmt(value)}k`);\n    runningTotalText.push(\"\");\n    // Emphasize the ending Net Profit total: dark outline + bold, larger label.\n    totalSeries[i] =\n      i === lastIndex\n        ? {\n            value,\n            itemStyle: { color: t.palette[2], borderColor: t.ink, borderWidth: 2.5 },\n            label: {\n              formatter: () => `$${fmt(value)}k`,\n              fontWeight: 700,\n              fontSize: 17,\n              color: t.ink,\n            },\n          }\n        : value;\n  } else if (kind === \"increase\") {\n    base.push(cumulative);\n    increaseSeries[i] = value;\n    cumulative += value;\n    labelText.push(`+$${fmt(value)}k`);\n    runningTotalText.push(`Total: $${fmt(cumulative)}k`);\n  } else {\n    cumulative += value;\n    base.push(cumulative);\n    decreaseSeries[i] = -value;\n    labelText.push(`-$${fmt(Math.abs(value))}k`);\n    runningTotalText.push(`Total: $${fmt(cumulative)}k`);\n  }\n  connectorTop.push(cumulative);\n}\n\nconst maxTop = Math.max(...connectorTop, ...totalSeries.map((v) => (typeof v === \"object\" ? v.value : v)));\nconst yAxisMax = Math.ceil((maxTop * 1.3) / 100) * 100;\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------------\nconst topLabel = {\n  show: true,\n  position: \"top\",\n  formatter: (params) => {\n    if (params.value === 0) return \"\";\n    const idx = params.dataIndex;\n    const sub = runningTotalText[idx];\n    return sub ? `{main|${labelText[idx]}}\\n{sub|${sub}}` : `{main|${labelText[idx]}}`;\n  },\n  rich: {\n    main: { color: t.inkSoft, fontSize: 14, lineHeight: 16 },\n    sub: { color: t.inkSoft, fontSize: 11, lineHeight: 14 },\n  },\n};\n\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"waterfall-basic · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 26, fontWeight: 500 },\n  },\n  legend: {\n    data: [\"Total\", \"Increase\", \"Decrease\"],\n    top: 60,\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    itemWidth: 16,\n    itemHeight: 12,\n  },\n  grid: { left: 110, right: 60, top: 130, bottom: 90 },\n  xAxis: {\n    type: \"category\",\n    data: categories,\n    axisLabel: { color: t.inkSoft, fontSize: 14, interval: 0 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Amount ($ thousands)\",\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    max: yAxisMax,\n    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: \"${value}k\" },\n    axisLine: { show: false },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      name: \"placeholder\",\n      type: \"bar\",\n      stack: \"total\",\n      silent: true,\n      itemStyle: { color: \"transparent\" },\n      data: base,\n    },\n    {\n      name: \"Total\",\n      type: \"bar\",\n      stack: \"total\",\n      barWidth: \"55%\",\n      itemStyle: { color: t.palette[2] },\n      label: topLabel,\n      data: totalSeries,\n    },\n    {\n      name: \"Increase\",\n      type: \"bar\",\n      stack: \"total\",\n      barWidth: \"55%\",\n      itemStyle: { color: t.palette[0] },\n      label: topLabel,\n      data: increaseSeries,\n    },\n    {\n      name: \"Decrease\",\n      type: \"bar\",\n      stack: \"total\",\n      barWidth: \"55%\",\n      itemStyle: { color: t.palette[4] },\n      label: topLabel,\n      data: decreaseSeries,\n    },\n    {\n      name: \"connector\",\n      type: \"line\",\n      step: \"end\",\n      symbol: \"none\",\n      silent: true,\n      legendHoverLink: false,\n      lineStyle: { color: t.inkSoft, type: \"dashed\", width: 1.5, opacity: 0.6 },\n      data: connectorTop,\n    },\n  ],\n});\n"}