#HTML

<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <title>UpSetJS Bundle Example</title> <link rel="stylesheet" href="style.css" /> <!-- UpsetJS Bundle (Preact 内蔵 / UMD) --> <script src="https://unpkg.com/@upsetjs/bundle/umd/upsetjs-bundle.min.js"></script> </head> <body> <div id="root"></div> <!-- 外部 JavaScript --> <script src="app.js"></script> </body> </html>


#JS

// UpsetJS bundle のエイリアス const { h, render } = upsetjsBundle.preact; const { UpSetJS } = upsetjsBundle.react; const { extractCombinations } = upsetjsBundle.model; // JSON 読み込み async function loadJSON() { const res = await fetch("data.json"); return await res.json(); } // Filter Panel Component function FilterPanel(props) { const { sets, combinations, activeSets, setActiveSets, activeGroups, setActiveGroups, search, setSearch, sort, setSort } = props; return h("div", { class: "panel" }, [ h("h3", {}, "Search"), h("input", { class: "search-box", type: "text", value: search, placeholder: "Search sets/groups", onInput: (e) => setSearch(e.target.value) }), h("h3", {}, "Sort"), h( "select", { class: "sort-select", value: sort, onInput: (e) => setSort(e.target.value) }, [ h("option", { value: "name" }, "Name"), h("option", { value: "size" }, "Size") ] ), h("h3", {}, "Sets"), ...sets .filter((s) => s.name.includes(search)) .map((s) => h("label", {}, [ h("input", { type: "checkbox", checked: activeSets[s.name], onInput: () => setActiveSets({ ...activeSets, [s.name]: !activeSets[s.name] }) }), s.name ]) ), h("h3", {}, "Groups"), ...combinations .filter((c) => c.name.includes(search)) .map((c) => h("label", {}, [ h("input", { type: "checkbox", checked: activeGroups[c.name] ?? false, onInput: () => setActiveGroups({ ...activeGroups, [c.name]: !activeGroups[c.name] }) }), `${c.name} (${c.size})` ]) ) ]); } // Main App Component function App({ data }) { const [activeSets, setActiveSets] = upsetjsBundle.preactHooks.useState(() => { const all = new Set(data.elements.flatMap((e) => e.sets)); return Object.fromEntries([...all].map((s) => [s, true])); }); const [activeGroups, setActiveGroups] = upsetjsBundle.preactHooks.useState({}); const [search, setSearch] = upsetjsBundle.preactHooks.useState(""); const [sort, setSort] = upsetjsBundle.preactHooks.useState("name"); const filteredBySet = data.elements.filter((elem) => elem.sets.some((s) => activeSets[s]) ); const { sets, combinations } = extractCombinations(filteredBySet); const sortedCombinations = sort === "size" ? [...combinations].sort((a, b) => b.size - a.size) : [...combinations].sort((a, b) => a.name.localeCompare(b.name)); const filteredCombinations = Object.keys(activeGroups).some( (k) => activeGroups[k] ) ? sortedCombinations.filter((c) => activeGroups[c.name]) : sortedCombinations; const attributes = [ { type: "boxplot", label: "Boxplot", accessor: (elem, combination) => elem.values?.[combination.name] ?? null }, { type: "scatter", label: "Scatter", accessor: (elem, combination) => elem.values?.[combination.name] ?? null } ]; return h("div", { id: "layout", style: "display:flex; gap:20px;" }, [ h(FilterPanel, { sets, combinations, activeSets, setActiveSets, activeGroups, setActiveGroups, search, setSearch, sort, setSort }), h(UpSetJS, { sets, combinations: filteredCombinations, attributes, width: 800, height: 600 }) ]); } // JSON を読み込んで描画 loadJSON().then((data) => { render(h(App, { data }), document.getElementById("root")); });


#CSS

body { font-family: sans-serif; margin: 20px; display: flex; gap: 20px; } #root { display: flex; gap: 20px; } .panel { min-width: 240px; border-right: 1px solid #ccc; padding-right: 10px; } .panel h3 { margin-top: 20px; margin-bottom: 8px; } label { display: block; margin-bottom: 4px; } .search-box { margin-bottom: 10px; } .sort-select { margin-bottom: 10px; }


#data

{ "elements": [ { "name": "A", "sets": ["S1", "S2"], "values": { "S1": 5, "S2": 8 } }, { "name": "B", "sets": ["S1"], "values": { "S1": 3 } }, { "name": "C", "sets": ["S2"], "values": { "S2": 10 } } ] }