{"spec_id":"stock-event-flags","library":"echarts","language":"javascript","code":"// anyplot.ai\n// stock-event-flags: Stock Chart with Event Flags\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-08-26\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Small LCG PRNG — the browser has no seeded Math.random.\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\nconst TRADING_DAYS = 180;\nconst tradingDates = [];\nconst axisLabels = [];\nconst candles = []; // [open, close, low, high] per index — echarts candlestick order\nconst closes = [];\n\nconst MONTHS = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\nlet cursor = new Date(2024, 0, 2);\nlet price = 145;\nfor (let i = 0; i < TRADING_DAYS; i++) {\n  while (cursor.getDay() === 0 || cursor.getDay() === 6) {\n    cursor.setDate(cursor.getDate() + 1);\n  }\n  const open = price;\n  const drift = (rand() - 0.48) * 4.5;\n  const close = Math.max(20, open + drift);\n  const high = Math.max(open, close) + rand() * 2.2;\n  const low = Math.min(open, close) - rand() * 2.2;\n\n  tradingDates.push(new Date(cursor));\n  axisLabels.push(`${MONTHS[cursor.getMonth()]} ${cursor.getDate()}`);\n  candles.push([Number(open.toFixed(2)), Number(close.toFixed(2)), Number(low.toFixed(2)), Number(high.toFixed(2))]);\n  closes.push(Number(close.toFixed(2)));\n\n  price = close;\n  cursor.setDate(cursor.getDate() + 1);\n}\n\nconst priceMax = Math.max(...candles.map((c) => c[3]));\nconst priceMin = Math.min(...candles.map((c) => c[2]));\nconst pricePad = (priceMax - priceMin) * 0.05;\nconst upperLane = priceMax + pricePad * 2.5;\nconst lowerLane = priceMin - pricePad * 2.5;\n\n// Corporate events — index into tradingDates, alternating lanes so flags never\n// obscure the price series. Colors come from the Imprint palette (positions 2,\n// 3, 4, 6 — 1 and 5 stay reserved for the candlestick's bullish/bearish green/red).\nconst EVENT_STYLES = {\n  earnings: { color: t.palette[1], glyph: \"E\", label: \"Earnings\" },\n  dividend: { color: t.palette[2], glyph: \"D\", label: \"Dividend\" },\n  split: { color: t.palette[3], glyph: \"S\", label: \"Split\" },\n  news: { color: t.palette[5], glyph: \"N\", label: \"News\" },\n};\n\nconst events = [\n  { index: 12, type: \"earnings\", lane: \"upper\", note: \"Q4 earnings beat\" },\n  { index: 33, type: \"dividend\", lane: \"lower\", note: \"$0.24 dividend\" },\n  { index: 58, type: \"earnings\", lane: \"upper\", note: \"Q1 earnings miss\" },\n  { index: 75, type: \"news\", lane: \"lower\", note: \"AI chip launch\" },\n  { index: 96, type: \"dividend\", lane: \"upper\", note: \"$0.24 dividend\" },\n  { index: 105, type: \"earnings\", lane: \"lower\", note: \"Q2 earnings beat\" },\n  { index: 128, type: \"split\", lane: \"upper\", note: \"2-for-1 split\" },\n  { index: 150, type: \"earnings\", lane: \"lower\", note: \"Q3 earnings beat\" },\n  { index: 168, type: \"news\", lane: \"upper\", note: \"Analyst upgrade\" },\n];\n\n// One scatter series per event type — gives each type its own legend entry\n// and connector-line color, while sharing the upper/lower lane placement.\nconst eventSeries = Object.keys(EVENT_STYLES).map((type) => {\n  const style = EVENT_STYLES[type];\n  const typeEvents = events.filter((e) => e.type === type);\n  return {\n    name: style.label,\n    type: \"scatter\",\n    z: 5,\n    data: typeEvents.map((e) => ({\n      value: [e.index, e.lane === \"upper\" ? upperLane : lowerLane],\n      symbol: \"pin\",\n      symbolSize: 44,\n      symbolRotate: e.lane === \"upper\" ? 0 : 180,\n      label: { show: true, formatter: style.glyph, color: \"#FFFDF6\", fontSize: 14, fontWeight: \"bold\" },\n      note: e.note,\n    })),\n    itemStyle: { color: style.color },\n    markLine: {\n      silent: true,\n      symbol: [\"none\", \"none\"],\n      lineStyle: { type: \"dashed\", color: style.color, width: 1.5, opacity: 0.6 },\n      data: typeEvents.map((e) => [\n        { coord: [e.index, e.lane === \"upper\" ? upperLane : lowerLane] },\n        { coord: [e.index, closes[e.index]] },\n      ]),\n    },\n    tooltip: {\n      formatter: (p) => `${style.label}<br/>${axisLabels[p.data.value[0]]} · ${p.data.note}`,\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: \"stock-event-flags · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22 },\n  },\n  legend: {\n    top: 46,\n    data: [\"Price\", \"Earnings\", \"Dividend\", \"Split\", \"News\"],\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    itemWidth: 16,\n    itemHeight: 10,\n  },\n  grid: { left: 90, right: 60, top: 130, bottom: 80 },\n  xAxis: {\n    type: \"category\",\n    data: axisLabels,\n    boundaryGap: true,\n    axisLabel: { color: t.inkSoft, fontSize: 14, interval: \"auto\" },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    min: Math.floor(lowerLane - pricePad * 2),\n    max: Math.ceil(upperLane + pricePad * 2),\n    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: \"${value}\" },\n    axisLine: { show: false },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  tooltip: { trigger: \"item\" },\n  series: [\n    {\n      name: \"Price\",\n      type: \"candlestick\",\n      data: candles,\n      itemStyle: {\n        color: t.palette[0], // bullish (close > open) — brand green, finance semantic\n        color0: t.palette[4], // bearish (close < open) — matte red, finance semantic\n        borderColor: t.palette[0],\n        borderColor0: t.palette[4],\n      },\n    },\n    ...eventSeries,\n  ],\n});\n"}