{"spec_id":"genome-track-multi","library":"echarts","language":"javascript","code":"// anyplot.ai\n// genome-track-multi: Genome Track Viewer\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-08-25\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Deterministic PRNG (LCG, no Math.random in the browser) ---------------\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\n// --- Genomic region ----------------------------------------------------\nconst CHROM = \"chr7\";\nconst REGION_START = 140700000;\nconst REGION_END = 140720000;\n\n// --- Gene models (exon/intron structure, strand +/-) ------------------------\nconst genes = [\n  {\n    name: \"GeneA\",\n    strand: \"+\",\n    color: t.palette[0],\n    exons: [\n      [140701000, 140701500],\n      [140702800, 140703400],\n      [140704600, 140705100],\n      [140706400, 140707000],\n      [140707600, 140708000],\n    ],\n  },\n  {\n    name: \"GeneB\",\n    strand: \"-\",\n    color: t.palette[1],\n    exons: [\n      [140710500, 140711100],\n      [140712300, 140712900],\n      [140714000, 140714600],\n      [140715800, 140716400],\n      [140717800, 140718500],\n    ],\n  },\n];\n\n// --- Coverage track (read depth sampled every 200 bp) -----------------------\nfunction insideExon(pos) {\n  return genes.some((gene) => gene.exons.some(([s, e]) => pos >= s && pos <= e));\n}\nconst coveragePositions = [];\nfor (let x = REGION_START; x <= REGION_END; x += 200) coveragePositions.push(x);\nconst coverageValues = coveragePositions.map((x) => {\n  const baseline = 18 + 6 * Math.sin((x - REGION_START) / 2200);\n  const exonBump = insideExon(x) ? 20 : 0;\n  const noise = (rand() - 0.5) * 6;\n  return Math.max(2, Math.round((baseline + exonBump + noise) * 10) / 10);\n});\n\n// --- Variant calls (feature_type SNP / Indel, value = quality score) --------\nconst variants = [\n  { pos: 140701200, quality: 42, type: \"SNP\" },\n  { pos: 140702900, quality: 55, type: \"SNP\" },\n  { pos: 140703600, quality: 28, type: \"Indel\" },\n  { pos: 140704800, quality: 60, type: \"SNP\" },\n  { pos: 140706100, quality: 33, type: \"SNP\" },\n  { pos: 140707800, quality: 47, type: \"Indel\" },\n  { pos: 140709200, quality: 38, type: \"SNP\" },\n  { pos: 140710700, quality: 52, type: \"SNP\" },\n  { pos: 140711900, quality: 25, type: \"Indel\" },\n  { pos: 140713100, quality: 58, type: \"SNP\" },\n  { pos: 140714300, quality: 30, type: \"SNP\" },\n  { pos: 140715600, quality: 44, type: \"Indel\" },\n  { pos: 140716900, quality: 36, type: \"SNP\" },\n  { pos: 140718100, quality: 50, type: \"SNP\" },\n  { pos: 140719300, quality: 40, type: \"Indel\" },\n];\nconst variantColor = { SNP: t.palette[3], Indel: t.palette[4] };\n\n// --- Regulatory elements (promoters / enhancers) ----------------------------\nconst regulatory = [\n  { name: \"Promoter\", start: 140700550, end: 140701000, color: t.palette[5] },\n  { name: \"Enhancer\", start: 140708800, end: 140709400, color: t.palette[6] },\n  { name: \"Promoter\", start: 140718500, end: 140718950, color: t.palette[5] },\n  { name: \"Enhancer\", start: 140719300, end: 140719900, color: t.palette[6] },\n];\n\n// --- Shared pixel layout — one x-mapping reused by every track -------------\nconst GRID_LEFT = 130;\nconst GRID_RIGHT = 70;\nconst GRID_WIDTH = window.ANYPLOT_SIZE.width - GRID_LEFT - GRID_RIGHT;\nconst xPix = (bp) => GRID_LEFT + ((bp - REGION_START) / (REGION_END - REGION_START)) * GRID_WIDTH;\n\nconst GENE_TOP = 76,\n  GENE_H = 190,\n  GENE_PAD = 15;\nconst yPixGene = (v) => GENE_TOP + GENE_H - GENE_PAD - (v / 10) * (GENE_H - 2 * GENE_PAD);\n\nconst COV_TOP = 280,\n  COV_H = 170;\nconst VAR_TOP = 464,\n  VAR_H = 190;\n\nconst REG_TOP = 668,\n  REG_H = 150,\n  REG_PAD = 15;\nconst yPixReg = (v) => REG_TOP + REG_H - REG_PAD - (v / 10) * (REG_H - 2 * REG_PAD);\n\nconst AXIS_Y = 818;\n\n// --- Graphic overlay: track shading, gene models, regulatory rects, axis ---\nconst graphicEls = [];\n\n// Alternating background bands behind the axis-free tracks\ngraphicEls.push(\n  { type: \"rect\", z: -10, shape: { x: GRID_LEFT, y: GENE_TOP, width: GRID_WIDTH, height: GENE_H }, style: { fill: t.elevatedBg } },\n  { type: \"rect\", z: -10, shape: { x: GRID_LEFT, y: REG_TOP, width: GRID_WIDTH, height: REG_H }, style: { fill: t.elevatedBg } },\n);\n\n// Track name labels (left column, shared by every track)\n[\n  [\"Genes\", GENE_TOP + GENE_H / 2],\n  [\"Coverage\", COV_TOP + COV_H / 2],\n  [\"Variants\", VAR_TOP + VAR_H / 2],\n  [\"Regulatory\", REG_TOP + REG_H / 2],\n].forEach(([label, y]) => {\n  graphicEls.push({\n    type: \"text\",\n    x: 16,\n    y,\n    style: { text: label, fill: t.ink, fontSize: 15, fontWeight: \"bold\", textVerticalAlign: \"middle\" },\n  });\n});\n\n// Gene models: intron backbone, exon blocks, strand chevrons, name labels\ngenes.forEach((gene) => {\n  const gStart = gene.exons[0][0];\n  const gEnd = gene.exons[gene.exons.length - 1][1];\n  const yMid = yPixGene(5);\n\n  graphicEls.push({\n    type: \"line\",\n    shape: { x1: xPix(gStart), y1: yMid, x2: xPix(gEnd), y2: yMid },\n    style: { stroke: gene.color, lineWidth: 2.5 },\n  });\n\n  gene.exons.forEach(([s, e]) => {\n    graphicEls.push({\n      type: \"rect\",\n      shape: { x: xPix(s), y: yPixGene(7), width: xPix(e) - xPix(s), height: yPixGene(3) - yPixGene(7) },\n      style: { fill: gene.color },\n    });\n  });\n\n  for (let i = 0; i < gene.exons.length - 1; i++) {\n    const midX = xPix((gene.exons[i][1] + gene.exons[i + 1][0]) / 2);\n    const dir = gene.strand === \"+\" ? 1 : -1;\n    const half = 6;\n    graphicEls.push({\n      type: \"polygon\",\n      shape: {\n        points: [\n          [midX - dir * half, yMid - half],\n          [midX + dir * half, yMid],\n          [midX - dir * half, yMid + half],\n        ],\n      },\n      style: { fill: gene.color },\n    });\n  }\n\n  graphicEls.push({\n    type: \"text\",\n    x: xPix(gStart),\n    y: yPixGene(9.5),\n    style: { text: `${gene.name} (${gene.strand})`, fill: gene.color, fontSize: 13, fontWeight: \"bold\" },\n  });\n});\n\n// Regulatory rectangles + name labels\nregulatory.forEach((r) => {\n  graphicEls.push({\n    type: \"rect\",\n    shape: { x: xPix(r.start), y: yPixReg(7), width: xPix(r.end) - xPix(r.start), height: yPixReg(3) - yPixReg(7) },\n    style: { fill: r.color },\n  });\n  graphicEls.push({\n    type: \"text\",\n    x: xPix(r.start),\n    y: yPixReg(9.2),\n    style: { text: r.name, fill: r.color, fontSize: 12, fontWeight: \"bold\" },\n  });\n});\n\n// Shared genomic-position axis + vertical alignment guides across all tracks\nconst ticks = [];\nfor (let x = REGION_START; x <= REGION_END; x += 5000) ticks.push(x);\n\ngraphicEls.push({\n  type: \"line\",\n  shape: { x1: GRID_LEFT, y1: AXIS_Y, x2: GRID_LEFT + GRID_WIDTH, y2: AXIS_Y },\n  style: { stroke: t.inkSoft, lineWidth: 1 },\n});\n\nticks.forEach((x) => {\n  const px = xPix(x);\n  graphicEls.push(\n    { type: \"line\", z: -10, shape: { x1: px, y1: GENE_TOP, x2: px, y2: AXIS_Y }, style: { stroke: t.grid, lineWidth: 1 } },\n    { type: \"line\", shape: { x1: px, y1: AXIS_Y, x2: px, y2: AXIS_Y + 6 }, style: { stroke: t.inkSoft, lineWidth: 1 } },\n    { type: \"text\", x: px, y: AXIS_Y + 26, style: { text: x.toLocaleString(\"en-US\"), fill: t.inkSoft, fontSize: 13, textAlign: \"center\" } },\n  );\n});\n\ngraphicEls.push({\n  type: \"text\",\n  x: GRID_LEFT + GRID_WIDTH / 2,\n  y: AXIS_Y + 52,\n  style: { text: `${CHROM} position (bp)`, fill: t.inkSoft, fontSize: 14, textAlign: \"center\" },\n});\n\n// Value-axis unit labels, tucked inside the top-right corner of their track\ngraphicEls.push(\n  { type: \"text\", x: GRID_LEFT + GRID_WIDTH - 6, y: COV_TOP + 10, style: { text: \"Depth\", fill: t.inkSoft, fontSize: 12, textAlign: \"right\" } },\n  { type: \"text\", x: GRID_LEFT + GRID_WIDTH - 6, y: VAR_TOP + 10, style: { text: \"Qual\", fill: t.inkSoft, fontSize: 12, textAlign: \"right\" } },\n);\n\n// --- Chart option ------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"genome-track-multi · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 20,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: \"normal\" },\n  },\n  legend: {\n    data: [\"SNP\", \"Indel\"],\n    top: 14,\n    right: GRID_RIGHT,\n    itemWidth: 14,\n    itemHeight: 14,\n    itemGap: 16,\n    textStyle: { color: t.inkSoft, fontSize: 13 },\n  },\n  grid: [\n    { left: GRID_LEFT, right: GRID_RIGHT, top: COV_TOP, height: COV_H },\n    { left: GRID_LEFT, right: GRID_RIGHT, top: VAR_TOP, height: VAR_H },\n  ],\n  xAxis: [\n    { gridIndex: 0, type: \"value\", min: REGION_START, max: REGION_END, show: false },\n    { gridIndex: 1, type: \"value\", min: REGION_START, max: REGION_END, show: false },\n  ],\n  yAxis: [\n    {\n      gridIndex: 0,\n      type: \"value\",\n      min: 0,\n      max: 50,\n      interval: 25,\n      position: \"right\",\n      axisLine: { show: false },\n      axisTick: { show: false },\n      axisLabel: { color: t.inkSoft, fontSize: 12 },\n      splitLine: { show: false },\n    },\n    {\n      gridIndex: 1,\n      type: \"value\",\n      min: 0,\n      max: 60,\n      interval: 30,\n      position: \"right\",\n      axisLine: { show: false },\n      axisTick: { show: false },\n      axisLabel: { color: t.inkSoft, fontSize: 12 },\n      splitLine: { show: false },\n    },\n  ],\n  series: [\n    {\n      name: \"Coverage\",\n      type: \"line\",\n      xAxisIndex: 0,\n      yAxisIndex: 0,\n      data: coveragePositions.map((x, i) => [x, coverageValues[i]]),\n      showSymbol: false,\n      smooth: 0.3,\n      lineStyle: { color: t.palette[2], width: 2 },\n      areaStyle: { color: t.palette[2], opacity: 0.35 },\n    },\n    {\n      name: \"SNP\",\n      type: \"bar\",\n      xAxisIndex: 1,\n      yAxisIndex: 1,\n      barWidth: 3,\n      itemStyle: { color: variantColor.SNP },\n      data: variants\n        .filter((v) => v.type === \"SNP\")\n        .map((v) => ({ value: [v.pos, v.quality], itemStyle: { color: variantColor.SNP } })),\n    },\n    {\n      name: \"Indel\",\n      type: \"bar\",\n      xAxisIndex: 1,\n      yAxisIndex: 1,\n      barWidth: 3,\n      itemStyle: { color: variantColor.Indel },\n      data: variants\n        .filter((v) => v.type === \"Indel\")\n        .map((v) => ({ value: [v.pos, v.quality], itemStyle: { color: variantColor.Indel } })),\n    },\n    {\n      name: \"SNP\",\n      type: \"scatter\",\n      xAxisIndex: 1,\n      yAxisIndex: 1,\n      symbolSize: (val) => 6 + val[1] * 0.25,\n      legendHoverLink: false,\n      itemStyle: { color: variantColor.SNP },\n      data: variants\n        .filter((v) => v.type === \"SNP\")\n        .map((v) => ({ value: [v.pos, v.quality], itemStyle: { color: variantColor.SNP } })),\n    },\n    {\n      name: \"Indel\",\n      type: \"scatter\",\n      xAxisIndex: 1,\n      yAxisIndex: 1,\n      symbolSize: (val) => 6 + val[1] * 0.25,\n      legendHoverLink: false,\n      itemStyle: { color: variantColor.Indel },\n      data: variants\n        .filter((v) => v.type === \"Indel\")\n        .map((v) => ({ value: [v.pos, v.quality], itemStyle: { color: variantColor.Indel } })),\n    },\n  ],\n  graphic: { elements: graphicEls },\n});\n"}