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="option" 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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more