#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 }
}
]
}