{"spec_id":"timeline-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// timeline-basic: Event Timeline\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 85/100 | Created: 2026-09-09\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// Product launch roadmap: milestones grouped by project phase.\nconst categories = [\"Planning\", \"Development\", \"Testing\", \"Launch\"];\nconst events = [\n  { date: \"2025-01-15\", name: \"Market Research Complete\", category: \"Planning\" },\n  { date: \"2025-02-20\", name: \"Requirements Finalized\", category: \"Planning\" },\n  { date: \"2025-03-24\", name: \"Design Sprint Wraps\", category: \"Planning\" },\n  { date: \"2025-04-28\", name: \"Core API Built\", category: \"Development\" },\n  { date: \"2025-06-09\", name: \"Beta Feature Freeze\", category: \"Development\" },\n  { date: \"2025-07-14\", name: \"Internal Alpha Test\", category: \"Testing\" },\n  { date: \"2025-08-25\", name: \"Public Beta Launch\", category: \"Testing\" },\n  { date: \"2025-09-29\", name: \"Security Audit Passed\", category: \"Testing\" },\n  { date: \"2025-11-03\", name: \"Marketing Campaign Starts\", category: \"Launch\" },\n  { date: \"2025-12-10\", name: \"General Availability\", category: \"Launch\" },\n];\n\nconst DATE_LABEL = new Intl.DateTimeFormat(\"en-US\", { month: \"short\", day: \"numeric\", year: \"numeric\" });\nconst points = events.map((e, i) => ({\n  timestamp: new Date(`${e.date}T00:00:00Z`).getTime(),\n  name: e.name,\n  category: e.category,\n  dateLabel: DATE_LABEL.format(new Date(`${e.date}T00:00:00Z`)),\n  side: i % 2 === 0 ? 1 : -1,\n}));\n\nconst dayMs = 24 * 60 * 60 * 1000;\nconst minTime = Math.min(...points.map((p) => p.timestamp)) - 25 * dayMs;\nconst maxTime = Math.max(...points.map((p) => p.timestamp)) + 25 * dayMs;\n\n// Vertical stems from the baseline (y=0) to each event marker, broken between\n// events via a null-y point so ECharts starts a fresh segment per stem.\nconst stemData = [];\npoints.forEach((p) => {\n  stemData.push([p.timestamp, 0]);\n  stemData.push([p.timestamp, p.side]);\n  stemData.push([p.timestamp, null]);\n});\n\n// --- Init -------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option -----------------------------------------------------------------\nconst titleText = \"Product Launch Roadmap · timeline-basic · javascript · echarts · anyplot.ai\";\nconst titleFontSize = Math.round(22 * Math.min(1, 67 / titleText.length));\n\nconst labelRich = {\n  name: { color: t.ink, fontSize: 14, fontWeight: \"bold\", lineHeight: 20 },\n  date: { color: t.inkSoft, fontSize: 12, lineHeight: 16 },\n};\n\nconst categorySeries = categories.map((cat, i) => ({\n  name: cat,\n  type: \"scatter\",\n  data: points\n    .filter((p) => p.category === cat)\n    .map((p) => ({\n      value: [p.timestamp, p.side],\n      name: p.name,\n      dateLabel: p.dateLabel,\n      label: {\n        show: true,\n        position: p.side > 0 ? \"top\" : \"bottom\",\n        distance: 14,\n        formatter: `{name|${p.name}}\\n{date|${p.dateLabel}}`,\n        rich: labelRich,\n        align: \"center\",\n      },\n    })),\n  symbolSize: 18,\n  itemStyle: { color: t.palette[i], borderColor: t.pageBg, borderWidth: 2 },\n  z: 3,\n}));\n\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: titleText,\n    left: \"center\",\n    top: 40,\n    textStyle: { color: t.ink, fontSize: titleFontSize, fontWeight: 500 },\n  },\n  legend: {\n    data: categories,\n    top: 100,\n    left: \"center\",\n    itemGap: 32,\n    textStyle: { color: t.ink, fontSize: 16 },\n  },\n  tooltip: {\n    trigger: \"item\",\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    textStyle: { color: t.ink },\n    formatter: (params) =>\n      `<b>${params.data.name}</b><br/>${params.seriesName} &middot; ${params.data.dateLabel}`,\n  },\n  grid: { left: 80, right: 80, top: 190, bottom: 190 },\n  xAxis: {\n    type: \"time\",\n    min: minTime,\n    max: maxTime,\n    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: \"{MMM} {yyyy}\" },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    min: -1.6,\n    max: 1.6,\n    show: false,\n  },\n  series: [\n    {\n      name: \"baseline\",\n      type: \"line\",\n      data: [\n        [minTime, 0],\n        [maxTime, 0],\n      ],\n      showSymbol: false,\n      silent: true,\n      lineStyle: { color: t.inkSoft, width: 2 },\n      z: 1,\n    },\n    {\n      name: \"stems\",\n      type: \"line\",\n      data: stemData,\n      showSymbol: false,\n      silent: true,\n      connectNulls: false,\n      lineStyle: { color: t.inkSoft, width: 1.5 },\n      z: 2,\n    },\n    ...categorySeries,\n  ],\n});\n"}