{"spec_id":"heatmap-calendar","library":"echarts","language":"javascript","code":"// anyplot.ai\n// heatmap-calendar: Basic Calendar Heatmap\n// Library: echarts 6.1.0 | JavaScript 22.23.1\n// Quality: 90/100 | Created: 2026-07-23\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Daily open-source commit counts across two calendar years, generated with a\n// small fixed-seed LCG (weekday-biased, with a handful of streak/rest cycles).\nlet seed = 42;\nfunction nextRand() {\n  seed = (seed * 1103515245 + 12345) % 2147483648;\n  return seed / 2147483648;\n}\n\nfunction datesInYear(year) {\n  const dates = [];\n  const start = new Date(Date.UTC(year, 0, 1));\n  const end = new Date(Date.UTC(year, 11, 31));\n  for (let d = start; d <= end; d.setUTCDate(d.getUTCDate() + 1)) {\n    dates.push(d.toISOString().slice(0, 10));\n  }\n  return dates;\n}\n\nfunction commitSeries(year) {\n  return datesInYear(year).map((dateStr) => {\n    const dow = new Date(dateStr + \"T00:00:00Z\").getUTCDay(); // 0=Sun..6=Sat\n    const weekdayBias = dow === 0 || dow === 6 ? 1.5 : 6;\n    const streak = Math.sin(new Date(dateStr).getTime() / 6.5e8) > 0.3 ? 3 : 0;\n    const value = Math.round(weekdayBias * nextRand() + streak * nextRand());\n    return [dateStr, value];\n  });\n}\n\nconst MONTH_ABBR = [\n  \"Jan\",\n  \"Feb\",\n  \"Mar\",\n  \"Apr\",\n  \"May\",\n  \"Jun\",\n  \"Jul\",\n  \"Aug\",\n  \"Sep\",\n  \"Oct\",\n  \"Nov\",\n  \"Dec\",\n];\n\nfunction busiestWeek(series) {\n  let bestStart = 0;\n  let bestSum = -1;\n  for (let start = 0; start <= series.length - 7; start++) {\n    const sum = series\n      .slice(start, start + 7)\n      .reduce((acc, [, value]) => acc + value, 0);\n    if (sum > bestSum) {\n      bestSum = sum;\n      bestStart = start;\n    }\n  }\n  const week = series.slice(bestStart, bestStart + 7);\n  const dates = new Set(week.map(([dateStr]) => dateStr));\n  const fmt = (dateStr) => {\n    const d = new Date(dateStr + \"T00:00:00Z\");\n    return `${MONTH_ABBR[d.getUTCMonth()]} ${d.getUTCDate()}`;\n  };\n  return { dates, label: `${fmt(week[0][0])} – ${fmt(week[6][0])}` };\n}\n\nconst years = [2023, 2024];\nconst seriesData = years.map((year) => commitSeries(year));\nconst maxValue = Math.max(...seriesData.flat().map((d) => d[1]));\nconst busiestWeeks = seriesData.map((series) => busiestWeek(series));\nconst highlightedData = seriesData.map((series, i) =>\n  series.map(([dateStr, value]) =>\n    busiestWeeks[i].dates.has(dateStr)\n      ? { value: [dateStr, value], itemStyle: { borderColor: t.amber, borderWidth: 2 } }\n      : [dateStr, value],\n  ),\n);\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------------\nconst calendarTop = 130;\nconst calendarHeight = 260;\nconst calendarGap = 70;\n\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: [\n    {\n      text: \"heatmap-calendar · javascript · echarts · anyplot.ai\",\n      left: \"center\",\n      top: 20,\n      textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n    },\n    ...years.map((year, i) => ({\n      text: String(year),\n      left: 90,\n      top: calendarTop + i * (calendarHeight + calendarGap) - 42,\n      textStyle: { color: t.ink, fontSize: 18, fontWeight: 600 },\n    })),\n    ...years.map((year, i) => ({\n      text: `Busiest week: ${busiestWeeks[i].label}`,\n      right: 60,\n      top: calendarTop + i * (calendarHeight + calendarGap) - 42,\n      textAlign: \"right\",\n      textStyle: { color: t.amber, fontSize: 13, fontWeight: 500 },\n    })),\n    {\n      text: \"Commits per day\",\n      left: \"center\",\n      bottom: 34,\n      textStyle: { color: t.inkSoft, fontSize: 12, fontWeight: 400 },\n    },\n  ],\n  visualMap: {\n    min: 0,\n    max: maxValue,\n    calculable: true,\n    orient: \"horizontal\",\n    left: \"center\",\n    bottom: 10,\n    inRange: { color: t.seq },\n    textStyle: { color: t.inkSoft, fontSize: 13 },\n  },\n  calendar: years.map((year, i) => ({\n    top: calendarTop + i * (calendarHeight + calendarGap),\n    left: 90,\n    right: 60,\n    cellSize: [24, calendarHeight / 7],\n    range: String(year),\n    splitLine: { lineStyle: { color: t.grid, width: 1 } },\n    itemStyle: { borderColor: t.pageBg, borderWidth: 3 },\n    dayLabel: {\n      firstDay: 1,\n      nameMap: [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"],\n      color: t.inkSoft,\n      fontSize: 13,\n    },\n    monthLabel: { color: t.inkSoft, fontSize: 14 },\n    yearLabel: { show: false },\n  })),\n  series: years.map((year, i) => ({\n    type: \"heatmap\",\n    coordinateSystem: \"calendar\",\n    calendarIndex: i,\n    data: highlightedData[i],\n  })),\n});\n"}