{"spec_id":"heatmap-periodic-table","library":"echarts","language":"javascript","code":"// anyplot.ai\n// heatmap-periodic-table: Periodic Table Property Heatmap\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 92/100 | Created: 2026-06-15\n//# anyplot-orientation: square\n// anyplot.ai\n// heatmap-periodic-table: Periodic Table Property Heatmap\n// Library: echarts 5.5.1 | JavaScript 22\n// Quality: pending | Created: 2026-06-15\n\nconst t = window.ANYPLOT_TOKENS;\nconst W = window.ANYPLOT_SIZE.width;\nconst H = window.ANYPLOT_SIZE.height;\n\n// --- Data: First Ionization Energy (kJ/mol) --------------------------------\n// col 0-17 = groups 1-18; row 0-6 = periods 1-7; lanthanides=7.5; actinides=8.5\nconst ELEMENTS = [\n  // Period 1\n  {s:\"H\",  z:1,   col:0,  row:0,   v:1312.0},\n  {s:\"He\", z:2,   col:17, row:0,   v:2372.3},\n  // Period 2\n  {s:\"Li\", z:3,   col:0,  row:1,   v:520.2},\n  {s:\"Be\", z:4,   col:1,  row:1,   v:899.5},\n  {s:\"B\",  z:5,   col:12, row:1,   v:800.6},\n  {s:\"C\",  z:6,   col:13, row:1,   v:1086.5},\n  {s:\"N\",  z:7,   col:14, row:1,   v:1402.3},\n  {s:\"O\",  z:8,   col:15, row:1,   v:1313.9},\n  {s:\"F\",  z:9,   col:16, row:1,   v:1681.0},\n  {s:\"Ne\", z:10,  col:17, row:1,   v:2080.7},\n  // Period 3\n  {s:\"Na\", z:11,  col:0,  row:2,   v:495.8},\n  {s:\"Mg\", z:12,  col:1,  row:2,   v:737.7},\n  {s:\"Al\", z:13,  col:12, row:2,   v:577.5},\n  {s:\"Si\", z:14,  col:13, row:2,   v:786.5},\n  {s:\"P\",  z:15,  col:14, row:2,   v:1011.8},\n  {s:\"S\",  z:16,  col:15, row:2,   v:999.6},\n  {s:\"Cl\", z:17,  col:16, row:2,   v:1251.2},\n  {s:\"Ar\", z:18,  col:17, row:2,   v:1520.6},\n  // Period 4\n  {s:\"K\",  z:19,  col:0,  row:3,   v:418.8},\n  {s:\"Ca\", z:20,  col:1,  row:3,   v:589.8},\n  {s:\"Sc\", z:21,  col:2,  row:3,   v:633.1},\n  {s:\"Ti\", z:22,  col:3,  row:3,   v:658.8},\n  {s:\"V\",  z:23,  col:4,  row:3,   v:650.9},\n  {s:\"Cr\", z:24,  col:5,  row:3,   v:652.9},\n  {s:\"Mn\", z:25,  col:6,  row:3,   v:717.3},\n  {s:\"Fe\", z:26,  col:7,  row:3,   v:762.5},\n  {s:\"Co\", z:27,  col:8,  row:3,   v:760.4},\n  {s:\"Ni\", z:28,  col:9,  row:3,   v:737.1},\n  {s:\"Cu\", z:29,  col:10, row:3,   v:745.5},\n  {s:\"Zn\", z:30,  col:11, row:3,   v:906.4},\n  {s:\"Ga\", z:31,  col:12, row:3,   v:578.8},\n  {s:\"Ge\", z:32,  col:13, row:3,   v:762.0},\n  {s:\"As\", z:33,  col:14, row:3,   v:947.0},\n  {s:\"Se\", z:34,  col:15, row:3,   v:941.0},\n  {s:\"Br\", z:35,  col:16, row:3,   v:1139.9},\n  {s:\"Kr\", z:36,  col:17, row:3,   v:1350.8},\n  // Period 5\n  {s:\"Rb\", z:37,  col:0,  row:4,   v:403.0},\n  {s:\"Sr\", z:38,  col:1,  row:4,   v:549.5},\n  {s:\"Y\",  z:39,  col:2,  row:4,   v:600.0},\n  {s:\"Zr\", z:40,  col:3,  row:4,   v:640.1},\n  {s:\"Nb\", z:41,  col:4,  row:4,   v:652.1},\n  {s:\"Mo\", z:42,  col:5,  row:4,   v:684.3},\n  {s:\"Tc\", z:43,  col:6,  row:4,   v:702.0},\n  {s:\"Ru\", z:44,  col:7,  row:4,   v:710.2},\n  {s:\"Rh\", z:45,  col:8,  row:4,   v:719.7},\n  {s:\"Pd\", z:46,  col:9,  row:4,   v:804.4},\n  {s:\"Ag\", z:47,  col:10, row:4,   v:731.0},\n  {s:\"Cd\", z:48,  col:11, row:4,   v:867.8},\n  {s:\"In\", z:49,  col:12, row:4,   v:558.3},\n  {s:\"Sn\", z:50,  col:13, row:4,   v:708.6},\n  {s:\"Sb\", z:51,  col:14, row:4,   v:834.0},\n  {s:\"Te\", z:52,  col:15, row:4,   v:869.3},\n  {s:\"I\",  z:53,  col:16, row:4,   v:1008.4},\n  {s:\"Xe\", z:54,  col:17, row:4,   v:1170.4},\n  // Period 6 (col 2 gap = lanthanide reference tile)\n  {s:\"Cs\", z:55,  col:0,  row:5,   v:375.7},\n  {s:\"Ba\", z:56,  col:1,  row:5,   v:502.9},\n  {s:\"Hf\", z:72,  col:3,  row:5,   v:658.5},\n  {s:\"Ta\", z:73,  col:4,  row:5,   v:761.0},\n  {s:\"W\",  z:74,  col:5,  row:5,   v:770.0},\n  {s:\"Re\", z:75,  col:6,  row:5,   v:760.0},\n  {s:\"Os\", z:76,  col:7,  row:5,   v:840.0},\n  {s:\"Ir\", z:77,  col:8,  row:5,   v:880.0},\n  {s:\"Pt\", z:78,  col:9,  row:5,   v:870.0},\n  {s:\"Au\", z:79,  col:10, row:5,   v:890.1},\n  {s:\"Hg\", z:80,  col:11, row:5,   v:1007.1},\n  {s:\"Tl\", z:81,  col:12, row:5,   v:589.4},\n  {s:\"Pb\", z:82,  col:13, row:5,   v:715.6},\n  {s:\"Bi\", z:83,  col:14, row:5,   v:703.0},\n  {s:\"Po\", z:84,  col:15, row:5,   v:812.1},\n  {s:\"At\", z:85,  col:16, row:5,   v:920.0},\n  {s:\"Rn\", z:86,  col:17, row:5,   v:1037.0},\n  // Period 7 (col 2 gap = actinide reference tile)\n  {s:\"Fr\", z:87,  col:0,  row:6,   v:380.0},\n  {s:\"Ra\", z:88,  col:1,  row:6,   v:509.3},\n  {s:\"Rf\", z:104, col:3,  row:6,   v:null},\n  {s:\"Db\", z:105, col:4,  row:6,   v:null},\n  {s:\"Sg\", z:106, col:5,  row:6,   v:null},\n  {s:\"Bh\", z:107, col:6,  row:6,   v:null},\n  {s:\"Hs\", z:108, col:7,  row:6,   v:null},\n  {s:\"Mt\", z:109, col:8,  row:6,   v:null},\n  {s:\"Ds\", z:110, col:9,  row:6,   v:null},\n  {s:\"Rg\", z:111, col:10, row:6,   v:null},\n  {s:\"Cn\", z:112, col:11, row:6,   v:null},\n  {s:\"Nh\", z:113, col:12, row:6,   v:null},\n  {s:\"Fl\", z:114, col:13, row:6,   v:null},\n  {s:\"Mc\", z:115, col:14, row:6,   v:null},\n  {s:\"Lv\", z:116, col:15, row:6,   v:null},\n  {s:\"Ts\", z:117, col:16, row:6,   v:null},\n  {s:\"Og\", z:118, col:17, row:6,   v:null},\n  // Lanthanides (row 7.5)\n  {s:\"La\", z:57,  col:2,  row:7.5, v:538.1},\n  {s:\"Ce\", z:58,  col:3,  row:7.5, v:534.4},\n  {s:\"Pr\", z:59,  col:4,  row:7.5, v:527.0},\n  {s:\"Nd\", z:60,  col:5,  row:7.5, v:533.1},\n  {s:\"Pm\", z:61,  col:6,  row:7.5, v:540.0},\n  {s:\"Sm\", z:62,  col:7,  row:7.5, v:544.5},\n  {s:\"Eu\", z:63,  col:8,  row:7.5, v:547.1},\n  {s:\"Gd\", z:64,  col:9,  row:7.5, v:593.4},\n  {s:\"Tb\", z:65,  col:10, row:7.5, v:565.8},\n  {s:\"Dy\", z:66,  col:11, row:7.5, v:573.0},\n  {s:\"Ho\", z:67,  col:12, row:7.5, v:581.0},\n  {s:\"Er\", z:68,  col:13, row:7.5, v:589.3},\n  {s:\"Tm\", z:69,  col:14, row:7.5, v:596.7},\n  {s:\"Yb\", z:70,  col:15, row:7.5, v:603.4},\n  {s:\"Lu\", z:71,  col:16, row:7.5, v:523.5},\n  // Actinides (row 8.5)\n  {s:\"Ac\", z:89,  col:2,  row:8.5, v:499.0},\n  {s:\"Th\", z:90,  col:3,  row:8.5, v:587.0},\n  {s:\"Pa\", z:91,  col:4,  row:8.5, v:568.0},\n  {s:\"U\",  z:92,  col:5,  row:8.5, v:597.6},\n  {s:\"Np\", z:93,  col:6,  row:8.5, v:604.5},\n  {s:\"Pu\", z:94,  col:7,  row:8.5, v:584.7},\n  {s:\"Am\", z:95,  col:8,  row:8.5, v:578.0},\n  {s:\"Cm\", z:96,  col:9,  row:8.5, v:581.0},\n  {s:\"Bk\", z:97,  col:10, row:8.5, v:601.0},\n  {s:\"Cf\", z:98,  col:11, row:8.5, v:608.0},\n  {s:\"Es\", z:99,  col:12, row:8.5, v:619.0},\n  {s:\"Fm\", z:100, col:13, row:8.5, v:627.0},\n  {s:\"Md\", z:101, col:14, row:8.5, v:635.0},\n  {s:\"No\", z:102, col:15, row:8.5, v:642.0},\n  {s:\"Lr\", z:103, col:16, row:8.5, v:470.0},\n];\n\n// --- Color utilities (4 functions, all essential) --------------------------\nfunction lerpColor(h1, h2, f) {\n  const r1=parseInt(h1.slice(1,3),16), g1=parseInt(h1.slice(3,5),16), b1=parseInt(h1.slice(5,7),16);\n  const r2=parseInt(h2.slice(1,3),16), g2=parseInt(h2.slice(3,5),16), b2=parseInt(h2.slice(5,7),16);\n  return `rgb(${Math.round(r1+(r2-r1)*f)},${Math.round(g1+(g2-g1)*f)},${Math.round(b1+(b2-b1)*f)})`;\n}\n\nfunction getTextColor(bg) {\n  const m = bg.startsWith('#')\n    ? [parseInt(bg.slice(1,3),16), parseInt(bg.slice(3,5),16), parseInt(bg.slice(5,7),16)]\n    : bg.match(/\\d+/g).map(Number);\n  const lin = c => { const s=c/255; return s<=0.03928 ? s/12.92 : Math.pow((s+0.055)/1.055, 2.4); };\n  return 0.2126*lin(m[0])+0.7152*lin(m[1])+0.0722*lin(m[2]) > 0.30 ? '#1A1A17' : '#F0EFE8';\n}\n\n// --- Value range -----------------------------------------------------------\nconst vals = ELEMENTS.filter(e => e.v != null).map(e => e.v);\nconst vMin = Math.min(...vals);\nconst vMax = Math.max(...vals);\nconst GREY_TILE = window.ANYPLOT_THEME === 'light' ? '#C4C3BC' : '#3C3C38';\n\nfunction tileColor(v) {\n  return v == null ? GREY_TILE : lerpColor(t.seq[0], t.seq[1], (v-vMin)/(vMax-vMin));\n}\n\n// --- Layout: rectangular tiles fill the full square canvas ----------------\n// TW from width, TH from height — separating axes avoids the 30% bottom gap\nconst NCOLS = 18;\nconst NROWS = 9.5; // 7 main periods + 0.5 gap + 2 f-block rows\nconst ML = 46, MR = 16, MT = 86, MB = 10;\nconst CB_H = 22, CB_LABEL = 20, CB_GAP = 16; // colorbar section height\nconst gridAvailH = H - MT - MB - CB_GAP - CB_H - CB_LABEL;\nconst gridAvailW = W - ML - MR;\nconst TW = Math.floor(gridAvailW / NCOLS);\nconst TH = Math.floor(gridAvailH / NROWS);\nconst GAP = Math.max(2, Math.round(Math.min(TW, TH) * 0.04));\nconst ox = ML + Math.floor((gridAvailW - TW * NCOLS) / 2);\nconst oy = MT;\n\nfunction tilePx(col, row) {\n  return {\n    x:  ox + col*TW + GAP,\n    y:  oy + row*TH + GAP,\n    w:  TW - 2*GAP,\n    h:  TH - 2*GAP,\n    cx: ox + col*TW + TW*0.5,\n    cy: oy + row*TH + TH*0.5,\n  };\n}\n\nconst symFont = `bold ${Math.floor(TH * 0.30)}px sans-serif`;\nconst znFont  = `${Math.floor(TH * 0.17)}px sans-serif`;\nconst valFont = `${Math.max(9, Math.floor(TH * 0.17))}px sans-serif`;\n\n// --- Build graphic elements ------------------------------------------------\nconst graphic = [];\n\ngraphic.push({type:'rect', left:0, top:0, right:0, bottom:0, style:{fill:t.pageBg}});\n\n// Title & subtitle\ngraphic.push({type:'text', left:'center', top:18,\n  style:{text:'heatmap-periodic-table · javascript · echarts · anyplot.ai',\n         font:'bold 20px sans-serif', fill:t.ink, textAlign:'center', textBaseline:'top'}});\ngraphic.push({type:'text', left:'center', top:46,\n  style:{text:'First Ionization Energy (kJ/mol)',\n         font:'14px sans-serif', fill:t.inkSoft, textAlign:'center', textBaseline:'top'}});\n\n// Period labels (1–7) left of main grid — show periodic trend direction\nconst pLabelFont = `${Math.max(10, Math.floor(TH * 0.20))}px sans-serif`;\nfor (let p = 0; p < 7; p++) {\n  graphic.push({type:'text', style:{\n    x: ox - 6,\n    y: oy + p*TH + TH*0.5,\n    text: String(p+1),\n    font: pLabelFont, fill: t.inkSoft, textAlign:'right', textBaseline:'middle',\n  }});\n}\n\n// Lanthanide / actinide row labels on left margin\nconst fLabelFont = `${Math.max(8, Math.floor(TH * 0.15))}px sans-serif`;\n[{label:'Ln', row:7.5}, {label:'Ac', row:8.5}].forEach(({label, row}) => {\n  graphic.push({type:'text', style:{\n    x: ox - 6,\n    y: oy + row*TH + TH*0.5,\n    text: label, font: fLabelFont, fill: t.inkSoft, textAlign:'right', textBaseline:'middle', opacity:0.75,\n  }});\n});\n\n// Group category annotations in the 0.5-TH gap between period 7 and lanthanides\n// These name the three major blocks to explain the IE trend pattern\nconst gapY = oy + 7.25 * TH; // center of 0.5-row gap\nconst gAnnotFont = `${Math.max(8, Math.floor(TH * 0.14))}px sans-serif`;\n[\n  {col0:0,  col1:0,  label:'Alkali'},\n  {col0:1,  col1:1,  label:'Alk. Earth'},\n  {col0:2,  col1:11, label:'Transition Metals'},\n  {col0:12, col1:16, label:'p-block'},\n  {col0:17, col1:17, label:'Noble Gases'},\n].forEach(({col0, col1, label}) => {\n  const cx = ox + col0*TW + (col1-col0+1)*TW*0.5;\n  graphic.push({type:'text', style:{\n    x: cx, y: gapY, text: label,\n    font: gAnnotFont, fill: t.inkSoft, textAlign:'center', textBaseline:'middle', opacity:0.70,\n  }});\n});\n\n// Element tiles\nfor (const el of ELEMENTS) {\n  const {x, y, w, h, cx, cy} = tilePx(el.col, el.row);\n  const bgColor = tileColor(el.v);\n  const textCol = getTextColor(bgColor);\n\n  graphic.push({type:'rect', shape:{x, y, width:w, height:h}, style:{fill:bgColor, lineWidth:0}});\n\n  graphic.push({type:'text', style:{\n    x: x+3, y: y+2, text: String(el.z),\n    font: znFont, fill: textCol, opacity:0.85, textAlign:'left', textBaseline:'top',\n  }});\n\n  graphic.push({type:'text', style:{\n    x: cx, y: cy, text: el.s,\n    font: symFont, fill: textCol, textAlign:'center', textBaseline:'middle',\n  }});\n\n  if (el.v != null) {\n    graphic.push({type:'text', style:{\n      x: cx, y: y+h-2, text: el.v.toFixed(0),\n      font: valFont, fill: textCol, opacity:0.80, textAlign:'center', textBaseline:'bottom',\n    }});\n  }\n}\n\n// f-block reference tiles (dashed border, atomic-number range)\nconst refFont = `${Math.max(8, Math.floor(TH * 0.17))}px sans-serif`;\n[{col:2, row:5, label:'57–71'}, {col:2, row:6, label:'89–103'}].forEach(({col, row, label}) => {\n  const {x, y, w, h, cx, cy} = tilePx(col, row);\n  graphic.push({type:'rect', shape:{x, y, width:w, height:h},\n    style:{fill:'none', stroke:t.inkSoft, lineWidth:1, lineDash:[3,2]}});\n  graphic.push({type:'text', style:{\n    x: cx, y: cy, text: label, font: refFont, fill: t.inkSoft, textAlign:'center', textBaseline:'middle',\n  }});\n});\n\n// --- Colorbar -------------------------------------------------------------\nconst cbBarY = oy + NROWS * TH + CB_GAP;\nconst cbBarW = Math.floor(TW * NCOLS * 0.65);\nconst cbBarX = ox + Math.floor((TW * NCOLS - cbBarW) / 2);\nconst cbSteps = 160;\n\nfor (let i = 0; i < cbSteps; i++) {\n  const f = i / cbSteps;\n  graphic.push({type:'rect',\n    shape:{x: cbBarX + f*cbBarW, y: cbBarY, width: Math.ceil(cbBarW/cbSteps)+1, height: CB_H},\n    style:{fill: lerpColor(t.seq[0], t.seq[1], f), lineWidth:0}});\n}\ngraphic.push({type:'rect', shape:{x:cbBarX, y:cbBarY, width:cbBarW, height:CB_H},\n  style:{fill:'none', stroke:t.inkSoft, lineWidth:1}});\n\nconst cbFont = `${Math.max(10, Math.floor(TH * 0.18))}px sans-serif`;\nconst cbLabelY = cbBarY + CB_H + 5;\ngraphic.push({type:'text', style:{x:cbBarX,           y:cbLabelY, text:vMin.toFixed(0), font:cbFont, fill:t.inkSoft, textAlign:'left',   textBaseline:'top'}});\ngraphic.push({type:'text', style:{x:cbBarX+cbBarW,    y:cbLabelY, text:vMax.toFixed(0), font:cbFont, fill:t.inkSoft, textAlign:'right',  textBaseline:'top'}});\ngraphic.push({type:'text', style:{x:cbBarX+cbBarW*0.5,y:cbLabelY, text:'kJ/mol',         font:cbFont, fill:t.inkSoft, textAlign:'center', textBaseline:'top'}});\n\nconst cbAnnotFont = `${Math.max(9, Math.floor(TH * 0.15))}px sans-serif`;\ngraphic.push({type:'text', style:{x:cbBarX,        y:cbBarY-5, text:'low IE',  font:cbAnnotFont, fill:t.inkSoft, textAlign:'left',  textBaseline:'bottom', opacity:0.70}});\ngraphic.push({type:'text', style:{x:cbBarX+cbBarW, y:cbBarY-5, text:'high IE', font:cbAnnotFont, fill:t.inkSoft, textAlign:'right', textBaseline:'bottom', opacity:0.70}});\n\n// --- Render ---------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\nchart.setOption({animation:false, backgroundColor:t.pageBg, graphic});\n"}