viewof museumComparison = {
const container = html`
<div style="font-family: Arial, sans-serif; display: flex; gap: 30px; max-width: 1200px;">
<!-- Main Content Column -->
<div style="flex: 2; display: flex; flex-direction: column;">
<style>
h1 {
text-align: center;
margin-top: 20px;
font-size: 24px;
margin-left: 20%;
}
.control-container {
display: flex;
flex-direction: column;
align-items: center;
margin: 15px 0;
}
.dropdown-group {
display: flex;
flex-direction: column;
align-items: center;
margin: 5px;
}
label {
font-weight: bold;
margin-bottom: 5px;
text-align: center;
}
select, input[type='number'], button {
padding: 5px;
font-size: 14px;
border: 1px solid #ccc;
min-width: 200px;
}
#dynamicDropdownsContainer {
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 20px;
}
.bar {
fill-opacity: 0.8;
}
.bar:hover {
fill-opacity: 1;
}
.tooltip {
position: absolute;
background-color: white;
border: 1px solid black;
padding: 8px;
font-size: 14px;
display: none;
pointer-events: none;
border-radius: 5px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
}
.axis {
font-size: 14px;
font-weight: bold;
}
.grid line {
stroke: lightgray;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
stroke-dasharray: 4,4;
}
.grid path {
stroke-width: 0;
}
.hidden { display: none; }
.visible { display: block; }
</style>
<h1>Museum Social Reach Comparison</h1>
<!-- Comparison Mode -->
<div class="control-container">
<label for="comparisonModeSelect">Comparison Mode:</label>
<select id="comparisonModeSelect">
<option value="single">Single vs Single</option>
<option value="group" selected>Group vs Single</option>
</select>
</div>
<!-- Single vs Single -->
<div id="singleMuseumsContainer" class="control-container hidden">
<div class="dropdown-group">
<label for="museumSelectA">Museum A:</label>
<select id="museumSelectA"><option value="">Select a Museum</option></select>
</div>
<div class="dropdown-group">
<label for="museumSelectB">Museum B:</label>
<select id="museumSelectB"><option value="">Select a Museum</option></select>
</div>
</div>
<!-- Group vs Single -->
<div id="groupMuseumsContainer" class="control-container visible">
<div class="dropdown-group">
<label for="numMuseumsInput">How many museums in the group?</label>
<input type="number" id="numMuseumsInput" min="2" max="208" value="2"/>
<button id="generateButton">Generate Dropdowns</button>
</div>
<div id="dynamicDropdownsContainer"></div>
<div class="dropdown-group">
<label for="singleMuseumSelect">Museum to be Compared:</label>
<select id="singleMuseumSelect"><option value="">Select a Museum</option></select>
</div>
</div>
<!-- Metric Dropdown -->
<div class="dropdown-group">
<label for="metricSelect">Select Metric:</label>
<select id="metricSelect"><option value="">Select a Metric</option></select>
</div>
<!-- Chart SVG -->
<svg width="800" height="500"></svg>
</div>
`;
// A) Grab references
const comparisonModeSelect = container.querySelector("#comparisonModeSelect");
const singleMuseumsContainer = container.querySelector("#singleMuseumsContainer");
const groupMuseumsContainer = container.querySelector("#groupMuseumsContainer");
const museumSelectA = container.querySelector("#museumSelectA");
const museumSelectB = container.querySelector("#museumSelectB");
const singleMuseumSelect = container.querySelector("#singleMuseumSelect");
const numMuseumsInput = container.querySelector("#numMuseumsInput");
const generateButton = container.querySelector("#generateButton");
const dynamicContainer = container.querySelector("#dynamicDropdownsContainer");
const metricSelect = container.querySelector("#metricSelect");
// B) Chart setup & Data
const svg = d3.select(container).select("svg");
const margin = { top: 50, right: 30, bottom: 100, left: 100 };
const width = +svg.attr("width") - margin.left - margin.right;
const height = +svg.attr("height") - margin.top - margin.bottom;
const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);
const x = d3.scaleBand().range([0, width]).padding(0.4);
const y = d3.scaleLinear().range([height, 0]);
const xAxis = g.append("g").attr("transform", `translate(0,${height})`);
const yAxis = g.append("g");
// Add grid group
const yGrid = g.append("g").attr("class", "grid");
const tooltip = d3.select(container).append("div").attr("class", "tooltip");
const data = await FileAttachment("museum.json").json();
data.forEach(d => {
d.instagram = +d["Instagram Followers"] || 0;
d.twitter = +d["Twitter Followers"] || 0;
d.youtube = +d["YouTube Subscribers"] || 0;
d.reviews = +d["Number of Google Reviews"] || 0;
});
const metrics = [
{ label: "Instagram Followers", value: "instagram" },
{ label: "Twitter Followers", value: "twitter" },
{ label: "YouTube Subscribers", value: "youtube" },
{ label: "Google Reviews", value: "reviews" }
];
metrics.forEach(m => {
const opt = document.createElement("option"); opt.value = m.value; opt.text = m.label; metricSelect.appendChild(opt);
});
const museumNames = data.map(d => d.museum_name).sort();
const fillSelect = sel => {
sel.innerHTML = "";
const ph = document.createElement("option"); ph.value = ""; ph.text = "Select a Museum"; ph.selected = true; sel.appendChild(ph);
museumNames.forEach(name => { const o = document.createElement("option"); o.value = name; o.text = name; sel.appendChild(o); });
};
fillSelect(museumSelectA); fillSelect(museumSelectB); fillSelect(singleMuseumSelect);
let groupDropdowns = [];
comparisonModeSelect.onchange = () => {
// Clear existing bars
g.selectAll('rect').remove();
const mode = comparisonModeSelect.value;
singleMuseumsContainer.style.display = mode === 'single' ? 'flex' : 'none';
groupMuseumsContainer.style.display = mode === 'group' ? 'flex' : 'none';
updateChart();
};
[museumSelectA, museumSelectB, singleMuseumSelect, metricSelect].forEach(el => el.onchange = updateChart);
generateButton.onclick = () => {
dynamicContainer.innerHTML = '';
groupDropdowns = [];
const count = +numMuseumsInput.value;
for (let i = 0; i < count; i++) {
const wrapper = document.createElement('div'); wrapper.className = 'dropdown-group';
const lbl = document.createElement('label'); lbl.textContent = `Museum #${i+1}:`; wrapper.appendChild(lbl);
const sel = document.createElement('select'); fillSelect(sel); wrapper.appendChild(sel);
dynamicContainer.appendChild(wrapper); groupDropdowns.push(sel); sel.onchange = updateChart;
}
updateChart();
};
function updateChart() {
const mode = comparisonModeSelect.value;
const metric = metricSelect.value;
if (!metric) return;
let bars = [];
if (mode === 'single') {
const A = data.find(d => d.museum_name === museumSelectA.value);
const B = data.find(d => d.museum_name === museumSelectB.value);
if (!A || !B) return;
bars = [
{ name: A.museum_name, val: A[metric], color: '#E74C3C' },
{ name: B.museum_name, val: B[metric], color: '#3498DB' }
];
} else {
const S = data.find(d => d.museum_name === singleMuseumSelect.value);
const names = groupDropdowns.map(s => s.value).filter(v => v);
const vals = data.filter(d => names.includes(d.museum_name)).map(d => d[metric]);
if (!S || !vals.length) return;
const avg = vals.reduce((a, b) => a + b, 0) / vals.length;
bars = [
{ name: S.museum_name, val: S[metric], color: '#E74C3C' },
{ name: `Avg of ${vals.length}`, val: avg, color: '#3498DB' }
];
}
x.domain(bars.map(b => b.name));
y.domain([0, d3.max(bars, b => b.val)]).nice();
xAxis.call(d3.axisBottom(x));
yAxis.call(d3.axisLeft(y));
// Draw grid
yGrid.call(d3.axisLeft(y).tickSize(-width).tickFormat(''));
const sel = g.selectAll('rect').data(bars, d => d.name);
sel.join(
enter => enter.append('rect')
.attr('x', d => x(d.name)).attr('width', x.bandwidth())
.attr('y', y(0)).attr('height', 0).attr('fill', d => d.color)
.on('mouseover', (e, d) => {
tooltip.style('display', 'block')
.html(`<strong>${d.name}</strong><br/>${d.val.toLocaleString()}`)
.style('left', e.pageX + 10 + 'px')
.style('top', e.pageY - 20 + 'px');
})
.on('mouseout', () => tooltip.style('display', 'none'))
.transition().duration(800).attr('y', d => y(d.val)).attr('height', d => height - y(d.val)),
update => update.transition().duration(800)
.attr('x', d => x(d.name)).attr('width', x.bandwidth())
.attr('y', d => y(d.val)).attr('height', d => height - y(d.val)).attr('fill', d => d.color),
exit => exit.transition().duration(500)
.attr('y', y(0)).attr('height', 0).remove()
);
}
updateChart();
return container;
}