Published
Edited
Jan 14, 2022
2 forks
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof mytable = Inputs.table(
pages[0],
{
columns: theseCols,
rows: page_settings.nrows,
sort: pages[1],
reverse: pages[2],
format: {
"Salary paid": d3.format("($,.2f"),
"Taxable benefits": d3.format("($,.2f"),
"Calendar year": d3.format("d") // format as "2020" rather than "2,020"
}
})
Insert cell
pages
Insert cell
// https://observablehq.com/@observablehq/introduction-to-views
viewof pages = {
// Find which column has been clicked for sorting, and the direction of the sort
// "▾" : descending sort (currentReverse = true)
// "▴" : ascending sort (currentReverse = false)
function clickedCol() {
let currentReverse = false;
let thisCol;
d3.selectAll("th span")._groups[0].forEach((d, i) => {
if (d.innerHTML) {
thisCol = d.nextSibling.data; // name of clicked col
currentReverse = d.innerHTML === "▴" ? false : true; // direction of sort
}
});
return [thisCol, currentReverse];
}
const rowsPerPage = page_settings.rowsPerPage + 1;
const numPagesGeneral = Math.ceil(search.length / rowsPerPage);
const numPages = (search.length <= (numPagesGeneral - 1) * rowsPerPage) ? numPagesGeneral - 1 : numPagesGeneral;

// Draw the buttons in a div
const element = html`<div id="pgDiv" style="display: inline-block;">
${Array.from({ length: numPages }).map(
(_, i) => {
let thisClass = i === 0 ? "btnactive" : "pgbtn"
return html`<button action="page" id="pg${i}" class=${thisClass}>${i + 1}</button>`;
}
)}
</div>`;

// Initial return values for pg0
const initSortCol = "Salary paid"
const initSortReverse = false;
element.value = [search, // no limits needed since pg0 cutoff by "rows" option of Inputs.table
initSortCol,
initSortReverse, // initially sort ascending
];

// Define button click event
element.onclick = (d) => {
// Store id of previous and currently clicked button
const prevClicked = d3.select(".btnactive").attr("id");
const nowClicked = d.target.id;
// Get action label from button div
// For later, to distiguish between page buttons and "Previous", "Next" and "Last" buttons
// when they get set up
let thisAction = d3.select(`#${d.target.id}`).attr("action");

// Handle Previous and Next buttons separately
if (thisAction === "page") {
if (nowClicked === prevClicked) {
return; // do nothing if button now clicked is already clicked
} else {
// Get name of column that table is sorted by (initally "Salary paid")
let thisCol = clickedCol()[0];
// Get sort direction
let currentReverse = clickedCol()[1];
// Page clicked
let thisPg = parseInt(d.target.id.split("pg")[1]);

// Deactivate previously clicked button & activate current button
d3.select(`#${prevClicked}`).attr("class", "pgbtn");
d3.select(`#${nowClicked}`).attr("class", "btnactive");
// Define starting row of data array for selected page
let lim0 = thisPg * (rowsPerPage);
// Define end row of data array for selected page. If end row
// is > length of array, stop at length of array.
let lim1 = lim0 + rowsPerPage - 1; //(lim0 + rowsPerPage) > search.length ? search.length : lim0 + rowsPerPage;
// Assign row limits to returned array
let sortedSearch = {};
if (typeof search[0][thisCol] == "string") {
sortedSearch = currentReverse ?
search.sort((a, b) => (a["First name"] < b["First name"] ? 1 : -1)) :
search.sort((a, b) => (a["First name"] > b["First name"] ? 1 : -1))
} else if (typeof search[0][thisCol] == "number") {
sortedSearch = currentReverse ?
search.sort((a, b) => b[thisCol] - a[thisCol]) :
search.sort((a, b) => a[thisCol] - b[thisCol]);
}

// Set up return values in array
element.value = [d.target.id === "pg0" ? sortedSearch : sortedSearch.slice(lim0, lim1), // returned, sorted page data
thisCol, // col to sort by
currentReverse, // sort direction
];
}
}
element.dispatchEvent(new Event("input", {bubbles: true}));
};

return element;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// for current table display styles, setting rows=30 in table option displays 13 rows on the page
page_settings = ({
nrows: 32,
rowsPerPage: 32/2 - 2
})
Insert cell
data_settings = ({
salaryMin: Math.min.apply(Math, data.map(function(o) {
return o["Salary paid"];
})),
salaryMax: Math.max.apply(Math, data.map(function(o) {
return o["Salary paid"];
})),
taxMin: Math.min.apply(Math, data.map(function(o) {
return o["Taxable benefits"];
})),
taxMax: Math.max.apply(Math, data.map(function(o) {
return o["Taxable benefits"];
}))
})
Insert cell
Insert cell
{
selCol;

// Toggle active class of column menu on and off
let els = d3.select(".col-tog")
els.classed("active", !els.classed("active"));
}
Insert cell
// Handles Filter button click
{
filterBtn; // registers click

// Toggle active class of filter elements on and off
let els = d3.selectAll(".tog")
els.classed("active", !els.classed("active"));

}
Insert cell
Insert cell
// https://observablehq.com/@triptych/google-fonts-in-observable
fontName = "Open Sans";
Insert cell
<style>
@import url('https://fonts.googleapis.com/css2?family=${fontName}&display=swap');

/* Toggle class for Filter button elements */
.tog, .col-tog, #el-label {
display: none;
}
.tog.active, .col-tog.active, #el-label.active {
display: flex;
}

.pgbtn button {
background-color: #e8e9ea;
}
.pgbtn {
cursor: pointer;
}
.btnactive {
background-color: pink !important;
}
.btndisabled {
opacity: 0.65;
}
.btndisabled:hover {
background-color: #e8e9ea;
cursor: not-allowed;
}
.spacer {
cursor: text;
border: none;
background-color: transparent;
}

.pgbtn {
border: 1px solid #fff !important;
}
.pgbtn.active {
border: 2px solid !important;
background-color: pink !important;
}

.el-label {
margin-top: 20px;
font-weight: bold;
}

input[type=range] {
margin-left: 25px !important;
}

table, form, button {
font-family: '${fontName}', sans-serif;
}

form, button {
font-size: 0.9rem !important;
}

label, .mylabel {
font-weight: bold;
width: 154px !important;
}

input {
height: 2.3125rem;
}

/* Filter button, Column toggle */
button {
height: 45px;
padding: 10px;
color: #06c !important;
}

.selector-title {
font-family: '${fontName}', sans-serif;
font-weight: bold;
font-size: 0.9rem !important;
}

/* Filter menu */
#filterColName label {
position: relative;
left: 126px;
top: 0px;
}
/* Table */
form {
overflow-y: hidden !important; /* hide vertical scroll bar */
}
table { padding-top: 10px; }
.oi-ec050e-table thead tr td, .oi-ec050e-table thead tr th {
border-bottom: 2px solid black;
}
tr {
background-color: none;
font-size: .9rem;
line-height: 1.5;
}

th {
/* padding-top: 22px !important;
padding-bottom: 22px !important; */
border-top: 2px solid black;
border-right: 1px solid lightgray;
text-align: center;
vertical-align: inherit;
}

.table-header td {
font-size: .9rem;
font-weight: 400;
}
</style>
Insert cell
Insert cell
// https://getbootstrap.com/docs/4.1/getting-started/introduction/#css
html`<code>css</code> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">`
Insert cell
Insert cell
Insert cell
// https://observablehq.com/@observablehq/introduction-to-views
viewof testlims = {
const pages = Math.ceil(data.length / page_settings.rowsPerPage);
const element = html`<div id="pgDiv" style="display: inline-block;">
<button action="previous" id="pgPrev" class="btndisabled">Previous</button>
${Array.from({ length: pages }).map(
(_, i) => {
let thisClass = i === 0 ? "btnactive" : "pgbtn"
return html`<button action="page" id="pg${i}" class=${thisClass}>${i + 1}</button>`;
}
)}
<button action="none" id="pgSpace" class="spacer">...</button>
<button action="next" id="pgNext">Next <span aria-hidden="true">></span></button>
<button action="last" id="pgLast">Last</button>
</div>`;

element.value = [0, 13];

element.onclick = (d) => {
const prevClicked = d3.select(".btnactive").attr("id");
const nowClicked = d.target.id;
// Get action label from button div
let thisAction = d3.select(`#${d.target.id}`).attr("action");

// Handle Previous and Next buttons separately
if (thisAction === "page") {
if (nowClicked === prevClicked) {
return;
} else {
console.log("display new page")
// Page clicked
let thisPg = parseInt(d.target.id.split("pg")[1]);
// Define starting row of data array for selected page
let lim0 = thisPg * (page_settings.rowsPerPage + 1);
// Define end row of data array for selected page. If end row
// is > length of array, stop at length of array.
let lim1 = (lim0 + page_settings.rowsPerPage) > data.length ? data.length : lim0 + page_settings.rowsPerPage;
// Assign row limits to returned array
element.value = [lim0, lim1];
}
} else if (thisAction === "last") {
const lastPg = Math.ceil(data.length / page_settings.rowsPerPage) - 1;

const lim0 = lastPg * (page_settings.rowsPerPage + 1);
const lim1 = (lim0 + page_settings.rowsPerPage) > data.length ? data.length : lim0 + page_settings.rowsPerPage;
// Assign last page row limits to returned array
element.value = [lim0, lim1];
}
element.dispatchEvent(new Event("input", {bubbles: true}));
};

return element;
}
Insert cell
html`
<div class="container">
<div class="row">
<div class="col-sm-2">Salary paid</div>
<div class="col-sm-4">greater than</div>
<div id="slider1" class="col-sm-6">slider</div>
</div>
</div>`;
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// html = htl.html
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
(1234).toLocaleString("en-US", {
style: "currency",
currency: "USD"
})
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