{"spec_id":"star-chart-constellation","library":"echarts","language":"javascript","code":"// anyplot.ai\n// star-chart-constellation: Star Chart with Constellations\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-17\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Projection -------------------------------------------------------------\n// Azimuthal-equidistant map centred on the north celestial pole: declination\n// maps linearly to radius (pole at the centre) and right ascension to angle, so\n// the sky boundary is a true circle. Showing the northern sky down to dec -35\n// keeps Orion and the winter stars in frame.\nconst DEC_LIMIT = -35;\nconst R_MAX = 90 - DEC_LIMIT; // 125 — radius of the visible boundary\n\nfunction project(raHours, decDeg) {\n  const r = 90 - decDeg; // 0 at the pole, R_MAX at the boundary\n  const theta = (raHours / 24) * 2 * Math.PI; // RA 0h points up, increases CW\n  return [r * Math.sin(theta), r * Math.cos(theta)];\n}\n\n// --- Constellation catalogue (real bright stars, approx. J2000) -------------\n// Each star is [name, RA hours, Dec degrees, apparent magnitude]; edges index\n// into the constellation's own star list to form the stick figure.\nconst CONSTELLATIONS = [\n  { abbr: \"UMa\", stars: [[\"Dubhe\", 11.06, 61.75, 1.79], [\"Merak\", 11.03, 56.38, 2.37], [\"Phecda\", 11.90, 53.69, 2.44], [\"Megrez\", 12.26, 57.03, 3.31], [\"Alioth\", 12.90, 55.96, 1.77], [\"Mizar\", 13.40, 54.93, 2.04], [\"Alkaid\", 13.79, 49.31, 1.86]],\n    edges: [[0, 1], [1, 2], [2, 3], [3, 0], [3, 4], [4, 5], [5, 6]] },\n  { abbr: \"UMi\", stars: [[\"Polaris\", 2.53, 89.26, 1.98], [\"Yildun\", 17.54, 86.59, 4.36], [\"Eps UMi\", 16.77, 82.04, 4.21], [\"Zeta UMi\", 15.73, 77.79, 4.32], [\"Eta UMi\", 16.29, 75.76, 4.95], [\"Pherkad\", 15.35, 71.83, 3.05], [\"Kochab\", 14.85, 74.16, 2.08]],\n    edges: [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 3]] },\n  { abbr: \"Cas\", stars: [[\"Caph\", 0.15, 59.15, 2.28], [\"Schedar\", 0.68, 56.54, 2.24], [\"Gamma Cas\", 0.95, 60.72, 2.47], [\"Ruchbah\", 1.43, 60.24, 2.68], [\"Segin\", 1.91, 63.67, 3.38]],\n    edges: [[0, 1], [1, 2], [2, 3], [3, 4]] },\n  { abbr: \"Cyg\", stars: [[\"Deneb\", 20.69, 45.28, 1.25], [\"Sadr\", 20.37, 40.26, 2.23], [\"Gienah\", 20.77, 33.97, 2.48], [\"Delta Cyg\", 19.75, 45.13, 2.87], [\"Albireo\", 19.51, 27.96, 3.18]],\n    edges: [[0, 1], [1, 4], [2, 1], [1, 3]] },\n  { abbr: \"Lyr\", stars: [[\"Vega\", 18.62, 38.78, 0.03], [\"Zeta Lyr\", 18.75, 37.60, 4.36], [\"Delta Lyr\", 18.90, 36.90, 4.30], [\"Sulafat\", 18.98, 32.69, 3.26], [\"Sheliak\", 18.83, 33.36, 3.52]],\n    edges: [[0, 1], [1, 2], [2, 3], [3, 4], [4, 1]] },\n  { abbr: \"Boo\", stars: [[\"Arcturus\", 14.26, 19.18, -0.05], [\"Muphrid\", 13.91, 18.40, 2.68], [\"Izar\", 14.75, 27.07, 2.35], [\"Delta Boo\", 15.26, 33.31, 3.46], [\"Nekkar\", 15.03, 40.39, 3.49], [\"Seginus\", 14.53, 38.31, 3.03]],\n    edges: [[0, 1], [0, 2], [2, 3], [3, 4], [4, 5], [5, 2]] },\n  { abbr: \"CrB\", stars: [[\"Theta CrB\", 15.55, 31.36, 4.14], [\"Nusakan\", 15.46, 29.11, 3.66], [\"Alphecca\", 15.58, 26.71, 2.22], [\"Gamma CrB\", 15.71, 26.30, 3.81], [\"Delta CrB\", 15.82, 26.07, 4.59], [\"Eps CrB\", 15.96, 26.88, 4.13]],\n    edges: [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]] },\n  { abbr: \"Leo\", stars: [[\"Regulus\", 10.14, 11.97, 1.36], [\"Eta Leo\", 10.12, 16.76, 3.48], [\"Algieba\", 10.33, 19.84, 2.08], [\"Adhafera\", 10.28, 23.42, 3.43], [\"Eps Leo\", 9.76, 23.77, 2.98], [\"Zosma\", 11.24, 20.52, 2.56], [\"Chort\", 11.24, 15.43, 3.33], [\"Denebola\", 11.82, 14.57, 2.14]],\n    edges: [[0, 1], [1, 2], [2, 3], [3, 4], [2, 5], [5, 6], [6, 0], [5, 7], [7, 6]] },\n  { abbr: \"Gem\", stars: [[\"Pollux\", 7.76, 28.03, 1.14], [\"Castor\", 7.58, 31.89, 1.58], [\"Mebsuta\", 6.73, 25.13, 3.06], [\"Propus\", 6.25, 22.51, 3.30], [\"Wasat\", 7.34, 21.98, 3.53], [\"Mekbuda\", 7.07, 20.57, 3.90], [\"Alhena\", 6.63, 16.40, 1.92], [\"Tejat\", 6.38, 22.51, 2.87]],\n    edges: [[1, 0], [1, 2], [2, 3], [0, 4], [4, 5], [5, 6], [2, 7]] },\n  { abbr: \"Aur\", stars: [[\"Capella\", 5.28, 45.99, 0.08], [\"Menkalinan\", 5.99, 44.95, 1.90], [\"Mahasim\", 5.99, 37.21, 2.62], [\"Elnath\", 5.44, 28.61, 1.65], [\"Hassaleh\", 4.95, 33.17, 2.69], [\"Almaaz\", 5.03, 43.82, 2.99]],\n    edges: [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0]] },\n  { abbr: \"Per\", stars: [[\"Mirfak\", 3.41, 49.86, 1.79], [\"Gamma Per\", 3.08, 53.51, 2.91], [\"Delta Per\", 3.72, 47.79, 3.01], [\"Eps Per\", 3.96, 40.01, 2.89], [\"Zeta Per\", 3.90, 31.88, 2.85], [\"Algol\", 3.14, 40.96, 2.12]],\n    edges: [[1, 0], [0, 2], [2, 3], [3, 4], [0, 5]] },\n  { abbr: \"And\", stars: [[\"Alpheratz\", 0.14, 29.09, 2.06], [\"Delta And\", 0.66, 30.86, 3.27], [\"Mirach\", 1.16, 35.62, 2.05], [\"Almach\", 2.06, 42.33, 2.10]],\n    edges: [[0, 1], [1, 2], [2, 3]] },\n  { abbr: \"Peg\", stars: [[\"Markab\", 23.08, 15.21, 2.49], [\"Scheat\", 23.06, 28.08, 2.42], [\"Alpheratz\", 0.14, 29.09, 2.06], [\"Algenib\", 0.22, 15.18, 2.83], [\"Enif\", 21.74, 9.88, 2.39]],\n    edges: [[0, 1], [1, 2], [2, 3], [3, 0], [0, 4]] },\n  { abbr: \"Dra\", stars: [[\"Eltanin\", 17.94, 51.49, 2.36], [\"Grumium\", 17.89, 56.87, 3.73], [\"Rastaban\", 17.51, 52.30, 2.79], [\"Altais\", 19.21, 67.66, 3.07], [\"Aldhibah\", 17.15, 65.71, 3.17], [\"Edasich\", 15.42, 58.97, 3.29], [\"Thuban\", 14.07, 64.38, 3.65]],\n    edges: [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]] },\n  { abbr: \"Cep\", stars: [[\"Alderamin\", 21.31, 62.59, 2.45], [\"Alfirk\", 21.48, 70.56, 3.23], [\"Errai\", 23.66, 77.63, 3.21], [\"Iota Cep\", 22.83, 66.20, 3.52], [\"Zeta Cep\", 22.18, 58.20, 3.39]],\n    edges: [[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]] },\n  { abbr: \"Ori\", stars: [[\"Betelgeuse\", 5.92, 7.41, 0.45], [\"Bellatrix\", 5.42, 6.35, 1.64], [\"Meissa\", 5.59, 9.93, 3.39], [\"Mintaka\", 5.53, -0.30, 2.23], [\"Alnilam\", 5.60, -1.20, 1.69], [\"Alnitak\", 5.68, -1.94, 1.74], [\"Saiph\", 5.80, -9.67, 2.07], [\"Rigel\", 5.24, -8.20, 0.18]],\n    edges: [[1, 0], [0, 2], [2, 1], [1, 3], [3, 4], [4, 5], [5, 0], [5, 6], [6, 7], [7, 3]] },\n  { abbr: \"Tau\", stars: [[\"Aldebaran\", 4.60, 16.51, 0.87], [\"Ain\", 4.48, 19.18, 3.53], [\"Elnath\", 5.44, 28.61, 1.65], [\"Hyadum\", 4.33, 15.63, 3.65], [\"Lambda Tau\", 4.01, 12.49, 3.41], [\"Zeta Tau\", 5.63, 21.14, 3.00]],\n    edges: [[4, 3], [3, 0], [0, 1], [1, 2], [0, 5]] },\n  { abbr: \"Ari\", stars: [[\"Hamal\", 2.12, 23.46, 2.00], [\"Sheratan\", 1.91, 20.81, 2.64], [\"Mesarthim\", 1.89, 19.29, 3.86], [\"41 Ari\", 2.83, 27.26, 3.61]],\n    edges: [[0, 1], [1, 2], [0, 3]] },\n  { abbr: \"Tri\", stars: [[\"Beta Tri\", 2.16, 34.99, 3.00], [\"Mothallah\", 1.88, 29.59, 3.42], [\"Gamma Tri\", 2.29, 33.85, 4.01]],\n    edges: [[1, 0], [0, 2], [2, 1]] },\n  { abbr: \"CMi\", stars: [[\"Procyon\", 7.66, 5.22, 0.34], [\"Gomeisa\", 7.45, 8.29, 2.89]],\n    edges: [[0, 1]] },\n  { abbr: \"CVn\", stars: [[\"Cor Caroli\", 12.93, 38.32, 2.89], [\"Chara\", 12.56, 41.36, 4.24]],\n    edges: [[0, 1]] },\n];\n\n// Flatten the catalogue into scatter points, line segments and centroid labels.\nconst namedStars = [];\nconst constLines = [];\nconst constLabels = [];\nfor (const c of CONSTELLATIONS) {\n  const pts = c.stars.map(([name, ra, dec, mag]) => {\n    const [x, y] = project(ra, dec);\n    namedStars.push([x, y, mag, name]);\n    return [x, y];\n  });\n  for (const [a, b] of c.edges) constLines.push({ coords: [pts[a], pts[b]] });\n  const cx = pts.reduce((s, p) => s + p[0], 0) / pts.length;\n  const cy = pts.reduce((s, p) => s + p[1], 0) / pts.length;\n  constLabels.push({ value: [cx, cy], name: c.abbr });\n}\n\n// --- Background field stars (deterministic, area-uniform in the sky disk) ----\nlet seed = 1234567;\nconst rng = () => ((seed = (seed * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff);\nconst fieldStars = [];\nfor (let i = 0; i < 280; i++) {\n  const theta = rng() * 2 * Math.PI;\n  const r = R_MAX * Math.sqrt(rng()); // sqrt → uniform density across the disk\n  const mag = 4.0 + rng() * 1.6; // faint background, all <= 5.6\n  const size = Math.max(1.3, 3.4 - (mag - 4.0) * 1.3);\n  fieldStars.push([r * Math.cos(theta), r * Math.sin(theta), size]);\n}\n\n// --- Coordinate grid (dec circles + RA radials + boundary) ------------------\nconst decCircle = (dec) => {\n  const pts = [];\n  for (let ra = 0; ra <= 24.0001; ra += 0.2) pts.push(project(ra, dec));\n  return { coords: pts };\n};\nconst gridLines = [60, 30, 0, -30].map(decCircle);\nfor (let ra = 0; ra < 24; ra += 3) gridLines.push({ coords: [project(ra, 86), project(ra, DEC_LIMIT)] });\n\n// --- Ecliptic (dashed) ------------------------------------------------------\nconst eps = (23.4393 * Math.PI) / 180;\nconst ecliptic = [];\nfor (let lam = 0; lam <= 360; lam += 2) {\n  const L = (lam * Math.PI) / 180;\n  let ra = Math.atan2(Math.cos(eps) * Math.sin(L), Math.cos(L));\n  if (ra < 0) ra += 2 * Math.PI;\n  const dec = (Math.asin(Math.sin(eps) * Math.sin(L)) * 180) / Math.PI;\n  ecliptic.push(project((ra / (2 * Math.PI)) * 24, dec));\n}\n\n// --- RA / Dec tick labels ---------------------------------------------------\nconst raTicks = [];\nfor (let ra = 0; ra < 24; ra += 3) {\n  const [x, y] = project(ra, 90 - (R_MAX + 8)); // r = R_MAX + 8, just outside the boundary\n  raTicks.push({ value: [x, y], name: `${ra}h` });\n}\nconst decTicks = [60, 30, 0, -30].map((dec) => ({ value: [7, 90 - dec], name: `${dec >= 0 ? \"+\" : \"\"}${dec}°` }));\n\n// --- Render -----------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\nconst glow = window.ANYPLOT_THEME === \"dark\" ? 7 : 0;\n\nconst series = [\n  { id: \"grid\", type: \"lines\", coordinateSystem: \"cartesian2d\", polyline: true, silent: true,\n    lineStyle: { color: t.grid, width: 1 }, data: gridLines },\n  { id: \"boundary\", type: \"lines\", coordinateSystem: \"cartesian2d\", polyline: true, silent: true,\n    lineStyle: { color: t.inkSoft, width: 1.6, opacity: 0.55 }, data: [decCircle(DEC_LIMIT)] },\n  { id: \"ecliptic\", type: \"lines\", coordinateSystem: \"cartesian2d\", polyline: true, silent: true,\n    lineStyle: { color: t.inkSoft, width: 1.6, opacity: 0.55, type: \"dashed\" }, data: [{ coords: ecliptic }] },\n  { id: \"constlines\", type: \"lines\", coordinateSystem: \"cartesian2d\", polyline: true, silent: true,\n    lineStyle: { color: t.inkSoft, width: 1.2, opacity: 0.40 }, data: constLines },\n  { id: \"field\", type: \"scatter\", coordinateSystem: \"cartesian2d\", silent: true,\n    symbolSize: (d) => d[2], itemStyle: { color: t.inkSoft, opacity: 0.5 }, data: fieldStars },\n  { id: \"named-stars\", type: \"scatter\", coordinateSystem: \"cartesian2d\",\n    emphasis: { disabled: true },\n    itemStyle: { borderColor: t.pageBg, borderWidth: 0.5, shadowBlur: glow, shadowColor: t.inkSoft },\n    data: namedStars },\n  { id: \"const-labels\", type: \"scatter\", coordinateSystem: \"cartesian2d\", silent: true,\n    symbolSize: 6, itemStyle: { color: \"transparent\" },\n    label: { show: true, formatter: \"{b}\", color: t.inkSoft, fontSize: 15, fontWeight: 500,\n             fontStyle: \"italic\", textShadowColor: t.pageBg, textShadowBlur: 6 }, data: constLabels },\n  { id: \"ra-ticks\", type: \"scatter\", coordinateSystem: \"cartesian2d\", silent: true,\n    symbolSize: 6, itemStyle: { color: \"transparent\" },\n    label: { show: true, formatter: \"{b}\", color: t.inkSoft, fontSize: 13,\n             textShadowColor: t.pageBg, textShadowBlur: 6 }, data: raTicks },\n  { id: \"dec-ticks\", type: \"scatter\", coordinateSystem: \"cartesian2d\", silent: true,\n    symbolSize: 6, itemStyle: { color: \"transparent\" },\n    label: { show: true, position: \"right\", formatter: \"{b}\", color: t.inkSoft, fontSize: 13,\n             textShadowColor: t.pageBg, textShadowBlur: 6 }, data: decTicks },\n];\n\nconst starsIndex = series.findIndex((s) => s.id === \"named-stars\");\n\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"star-chart-constellation · javascript · echarts · anyplot.ai\",\n    left: \"center\", top: 24, textStyle: { color: t.ink, fontSize: 22, fontWeight: 600 },\n  },\n  grid: { left: 60, right: 60, top: 100, bottom: 20 },\n  // Magnitude → point size and the Imprint sequential colormap (bright = brand\n  // green #009E73, faint = blue). Continuous data, so imprint_seq is used.\n  visualMap: {\n    type: \"continuous\", min: -0.5, max: 5.0, dimension: 2, seriesIndex: starsIndex,\n    calculable: false, orient: \"vertical\", left: 28, bottom: 36,\n    itemWidth: 18, itemHeight: 150,\n    text: [\"bright\\n−0.5\", \"5.0\\nfaint\"], textGap: 10,\n    textStyle: { color: t.inkSoft, fontSize: 13 },\n    inRange: { symbolSize: [26, 4], color: t.seq },\n  },\n  xAxis: { type: \"value\", min: -145, max: 145, show: false },\n  yAxis: { type: \"value\", min: -145, max: 145, show: false },\n  series,\n});\n\nchart.on(\"finished\", () => { window.__anyplotReady = true; });\n"}