Public
Edited
Apr 26, 2024
Importers
Insert cell
Insert cell
viewof fileInput = inputFile()
Insert cell
function inputFile () {
const input = document.createElement("input");
input.type = "file";
input.accept = ".csv";
return input;
}
Insert cell
async function parsedData(fileInput) {
if (!fileInput.files || fileInput.files.length === 0) return null;
const file = fileInput.files[0];
if (!file) return null;

const reader = new FileReader();
try {
const text = await new Promise((resolve, reject) => {
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsText(file);
});
return d3.csvParse(text);
} catch (error) {
console.error("Error parsing CSV: ", error);
return null;
}
}
Insert cell
function createSelectionForm(options, {searchable = true, multiple = true}) {

const searchBar = searchable ? html`<input type="text" id="searchBox" placeholder="Search..." style="margin-bottom: 10px; width: 100%;" autoComplete="off">` : "";
const selectAllBtn = multiple ? htl.html`<button type="button" style="margin-right: 5px;">Select All</button>` : "";
const deSelectAllBtn = multiple ? htl.html`<button type="button" style="margin-right: 5px;">Deselect All</button>` : "";

if (searchable) {
searchBar.addEventListener("input", (e) => {
e.preventDefault();
searchOptions();
});
}

if (multiple) {
selectAllBtn.addEventListener("click", (e) => {
e.preventDefault();
selectAll(true);
});
deSelectAllBtn.addEventListener("click", (e) => {
e.preventDefault();
selectAll(false);
});
}

const form = htl.html`
<form onchange="this.dispatchEvent(new CustomEvent('input', {bubbles: true}))" style="max-width: 300px;">
${searchBar}
${selectAllBtn}
${deSelectAllBtn}
<div style="height: 150px; overflow-y: scroll; border: 1px solid #ccc;" id="optionsList">
${options.map(option => html`
<div class="option-entry">
<input type="checkbox" id="${option}" name="" value="${option}" checked>
<label for="${option}">${option}</label>
</div>`)}
</div>
</form>
`;

const searchOptions = () => {
const searchValue = form.querySelector('#searchBox').value.toLowerCase();
const optionEntries = form.querySelectorAll('.option-entry');
optionEntries.forEach(entry => {
const label = entry.querySelector('label').textContent.toLowerCase();
if (label.includes(searchValue)) {
entry.style.display = '';
} else {
entry.style.display = 'none';
}
});
};

const selectAll = (flag) => {
const checkboxes = form.querySelectorAll('#optionsList input[type="checkbox"]');
checkboxes.forEach(checkbox => {
checkbox.checked = flag;
});
form.dispatchEvent(new Event('input', { bubbles: true }));
};

return form;
}
Insert cell
function observeFilteredData(fileInput, columnsForm) {
return Generators.observe(notify => {
const updateData = async () => {
const rawData = await parsedData(fileInput);
if (!rawData || !columnsForm) {
notify(null);
return;
}
const selectedColumnCheckboxes = columnsForm.querySelectorAll('input[type="checkbox"]:checked');
const selectedColumns = Array.from(selectedColumnCheckboxes).map(cb => cb.value);
const filteredData = rawData.map(row => {
const filteredRow = {};
selectedColumns.forEach(column => {
filteredRow[column] = row[column];
});
return filteredRow;
});
notify(filteredData);
};

columnsForm.addEventListener('input', updateData);

fileInput.addEventListener('change', async () => {
const newForm = await createColumnSelectionForm(fileInput);
columnsForm.replaceWith(newForm);
columnsForm = newForm; // 更新columnsForm引用
updateData();
});

return () => {
columnsForm.removeEventListener('input', updateData);
fileInput.removeEventListener('change', updateData);
};
});
}
Insert cell
async function createColumnSelectionForm(fileInputElement) {
const data = await parsedData(fileInputElement);
if (!data) {
return html`<div>No data loaded or parsing error.</div>`;
}
const columns = data.columns;
return createSelectionForm(columns, { searchable: true, multiple: true });
}
Insert cell
viewof filteredData = observeFilteredData(viewof fileInput, viewof columnsForm);
Insert cell
viewof columnsForm = Generators.observe(notify => {
const updateColumnsForm = async () => {
const form = await createColumnSelectionForm(viewof fileInput);
notify(form);
};
viewof fileInput.addEventListener('change', updateColumnsForm);

updateColumnsForm();

return () => {
viewof fileInput.removeEventListener('change', updateColumnsForm);
};
});
Insert cell
tableOutput = Inputs.table(viewof filteredData);
Insert cell
d3 = require('d3')
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more