{"spec_id":"upset-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// upset-basic: UpSet Plot for Multi-Set Intersection Analysis\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-09\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: customer membership across five marketing-engagement criteria ---\nfunction lcg(seed) {\n  let s = seed >>> 0;\n  return function () {\n    s = (1103515245 * s + 12345) >>> 0;\n    return (s % 2147483648) / 2147483648;\n  };\n}\nconst rand = lcg(42);\n\nconst setLabels = [\"Email\", \"App\", \"Loyalty\", \"Social\", \"Newsletter\"];\nconst probabilities = [0.34, 0.27, 0.21, 0.17, 0.13];\nconst elementCount = 3200;\n\nconst intersectionCounts = new Map();\nconst setTotals = new Array(setLabels.length).fill(0);\n\nfor (let i = 0; i < elementCount; i++) {\n  let mask = 0;\n  for (let s = 0; s < setLabels.length; s++) {\n    if (rand() < probabilities[s]) mask |= 1 << s;\n  }\n  if (mask === 0) continue;\n  intersectionCounts.set(mask, (intersectionCounts.get(mask) || 0) + 1);\n  for (let s = 0; s < setLabels.length; s++) {\n    if (mask & (1 << s)) setTotals[s]++;\n  }\n}\n\nconst intersections = Array.from(intersectionCounts.entries())\n  .map(([mask, size]) => ({\n    mask,\n    size,\n    degree: mask.toString(2).split(\"1\").length - 1,\n  }))\n  .sort((a, b) => b.size - a.size)\n  .slice(0, 12);\n\nconst maxDegree = setLabels.length;\nconst setSizeAxisMax = Math.ceil(Math.max(...setTotals) / 200) * 200;\n\n// interpolate between the two imprint_seq stops by intersection degree\nfunction seqColor(degree) {\n  const ratio = maxDegree > 1 ? (degree - 1) / (maxDegree - 1) : 0;\n  const hex = (c) => parseInt(c, 16);\n  const from = t.seq[0].replace(\"#\", \"\");\n  const to = t.seq[1].replace(\"#\", \"\");\n  const r = Math.round(hex(from.slice(0, 2)) + ratio * (hex(to.slice(0, 2)) - hex(from.slice(0, 2))));\n  const g = Math.round(hex(from.slice(2, 4)) + ratio * (hex(to.slice(2, 4)) - hex(from.slice(2, 4))));\n  const b = Math.round(hex(from.slice(4, 6)) + ratio * (hex(to.slice(4, 6)) - hex(from.slice(4, 6))));\n  return `rgb(${r}, ${g}, ${b})`;\n}\n\n// membership matrix dots — one entry per (column, row) cell\nconst dotData = [];\nfor (let col = 0; col < intersections.length; col++) {\n  const mask = intersections[col].mask;\n  for (let row = 0; row < setLabels.length; row++) {\n    const member = (mask & (1 << row)) !== 0 ? 1 : 0;\n    dotData.push({ value: [col, row, member] });\n  }\n}\n\n// connecting lines — one entry per column, spanning min..max member row\nconst lineData = intersections.map((it, col) => {\n  const rows = [];\n  for (let row = 0; row < setLabels.length; row++) {\n    if (it.mask & (1 << row)) rows.push(row);\n  }\n  return { value: [col, Math.min(...rows), Math.max(...rows)] };\n});\n\n// --- Init --------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\nconst columnLabels = intersections.map(() => \"\");\n\n// --- Option --------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"upset-basic · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 12,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  grid: [\n    { id: \"topBar\", left: 220, right: 60, top: 130, bottom: 460 },\n    { id: \"leftBar\", left: 40, right: 1380, top: 480, bottom: 90 },\n    { id: \"matrix\", left: 220, right: 60, top: 480, bottom: 90 },\n  ],\n  graphic: [\n    {\n      type: \"text\",\n      left: 1410,\n      top: 44,\n      style: { text: \"Intersection degree\", fill: t.inkSoft, fontSize: 12, fontWeight: 500 },\n    },\n    {\n      type: \"rect\",\n      left: 1410,\n      top: 64,\n      shape: { width: 130, height: 8 },\n      style: {\n        fill: new echarts.graphic.LinearGradient(0, 0, 1, 0, [\n          { offset: 0, color: t.seq[0] },\n          { offset: 1, color: t.seq[1] },\n        ]),\n      },\n    },\n    {\n      type: \"text\",\n      left: 1410,\n      top: 76,\n      style: { text: \"1 set\", fill: t.inkSoft, fontSize: 10 },\n    },\n    {\n      type: \"text\",\n      left: 1410 + 130,\n      top: 76,\n      style: { text: `${maxDegree} sets`, fill: t.inkSoft, fontSize: 10, align: \"right\" },\n    },\n  ],\n  xAxis: [\n    {\n      gridId: \"topBar\",\n      type: \"category\",\n      data: columnLabels,\n      axisLabel: { show: false },\n      axisTick: { show: false },\n      axisLine: { lineStyle: { color: t.inkSoft } },\n    },\n    {\n      gridId: \"leftBar\",\n      type: \"value\",\n      inverse: true,\n      position: \"bottom\",\n      min: 0,\n      max: setSizeAxisMax,\n      interval: setSizeAxisMax / 2,\n      name: \"Set size\",\n      nameLocation: \"middle\",\n      nameGap: 34,\n      nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n      axisLabel: { color: t.inkSoft, fontSize: 12 },\n      axisLine: { lineStyle: { color: t.inkSoft } },\n      splitLine: { show: false },\n    },\n    {\n      gridId: \"matrix\",\n      type: \"category\",\n      data: columnLabels,\n      axisLabel: { show: false },\n      axisTick: { show: false },\n      axisLine: { show: false },\n    },\n  ],\n  yAxis: [\n    {\n      gridId: \"topBar\",\n      type: \"value\",\n      name: \"Intersection size\",\n      nameLocation: \"middle\",\n      nameGap: 56,\n      nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n      axisLabel: { color: t.inkSoft, fontSize: 12 },\n      axisLine: { lineStyle: { color: t.inkSoft } },\n      splitLine: { lineStyle: { color: t.grid } },\n    },\n    {\n      gridId: \"leftBar\",\n      type: \"category\",\n      data: setLabels,\n      inverse: true,\n      axisLabel: { color: t.ink, fontSize: 15 },\n      axisTick: { show: false },\n      axisLine: { show: false },\n    },\n    {\n      gridId: \"matrix\",\n      type: \"category\",\n      data: setLabels,\n      inverse: true,\n      axisLabel: { show: false },\n      axisTick: { show: false },\n      axisLine: { show: false },\n      splitLine: {\n        show: true,\n        interval: 0,\n        lineStyle: { color: t.grid },\n      },\n    },\n  ],\n  series: [\n    {\n      name: \"Intersection size\",\n      type: \"bar\",\n      xAxisIndex: 0,\n      yAxisIndex: 0,\n      barWidth: \"62%\",\n      data: intersections.map((it, idx) => ({\n        value: it.size,\n        itemStyle:\n          idx === 0\n            ? { color: seqColor(it.degree), borderColor: t.ink, borderWidth: 2 }\n            : { color: seqColor(it.degree) },\n        label:\n          idx === 0\n            ? { show: true, position: \"top\", color: t.ink, fontSize: 13, fontWeight: 600 }\n            : undefined,\n      })),\n    },\n    {\n      name: \"Set size\",\n      type: \"bar\",\n      xAxisIndex: 1,\n      yAxisIndex: 1,\n      barWidth: \"55%\",\n      itemStyle: { color: t.palette[0] },\n      data: setTotals,\n    },\n    {\n      name: \"connections\",\n      type: \"custom\",\n      coordinateSystem: \"cartesian2d\",\n      xAxisIndex: 2,\n      yAxisIndex: 2,\n      data: lineData,\n      renderItem: (params, api) => {\n        const col = api.value(0);\n        const rowMin = api.value(1);\n        const rowMax = api.value(2);\n        if (rowMin === rowMax) return null;\n        const p1 = api.coord([col, rowMin]);\n        const p2 = api.coord([col, rowMax]);\n        return {\n          type: \"line\",\n          shape: { x1: p1[0], y1: p1[1], x2: p2[0], y2: p2[1] },\n          style: { stroke: t.ink, lineWidth: 4 },\n          silent: true,\n        };\n      },\n    },\n    {\n      name: \"membership\",\n      type: \"custom\",\n      coordinateSystem: \"cartesian2d\",\n      xAxisIndex: 2,\n      yAxisIndex: 2,\n      data: dotData,\n      renderItem: (params, api) => {\n        const col = api.value(0);\n        const row = api.value(1);\n        const member = api.value(2) === 1;\n        const pos = api.coord([col, row]);\n        return {\n          type: \"circle\",\n          shape: { cx: pos[0], cy: pos[1], r: member ? 15 : 10 },\n          style: member\n            ? { fill: t.ink }\n            : { fill: \"transparent\", stroke: t.grid, lineWidth: 2 },\n          silent: true,\n        };\n      },\n    },\n  ],\n});\n"}