Skip to content

Instantly share code, notes, and snippets.

@jurb
Last active May 2, 2023 05:48
Show Gist options
  • Select an option

  • Save jurb/5d42c6de467d7a71b2fc855e6aa3157f to your computer and use it in GitHub Desktop.

Select an option

Save jurb/5d42c6de467d7a71b2fc855e6aa3157f to your computer and use it in GitHub Desktop.
Filter values with checkboxes and URL parameters

This little thing creates checkboxes to filter a dataset on values of a column you specify. Also, it lets you filter the data with parameters in the URL. Checking the boxes adjusts the URL, making it easy to share a certain configuration of your data.

Also added a copy to clipboard button for convenience (works only on desktop, is hidden on mobile).

Uses lodash for convenient and cross-browser array filtering, and jQuery for the copy to clipboard button.

Name Last Name Age Occupation
Muhayr Maroun 34 Digital Consultant
Wandana Berends 35 Fire Fighter
Petr Votava 23 Fire Fighter
Mary Burns 47 Data Analist
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> Filter a table with persistent URL filtering </title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Load D3.js -->
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<style>
body {
max-width: 500px;
margin: auto;
}
#chart {
height: 250px;
}
#embedDialog,
#copyURL {
cursor: pointer;
cursor: hand;
}
.check {
display: inline;
stroke-dasharray: 140;
stroke-dashoffset: 140;
-webkit-animation: fill 0.5s linear normal;
animation: fill 0.5s linear normal;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
@-webkit-keyframes fill {
from {
stroke-dashoffset: 140;
}
to {
stroke-dashoffset: 0;
}
}
@keyframes fill {
from {
stroke-dashoffset: 140;
}
to {
stroke-dashoffset: 0;
}
}
@media only screen and (max-width: 500px) {
#copyURL {
display: none;
}
}
</style>
</head>
<body>
<div id="chart"></div>
<div id="filter" class="form-item form-checkboxes checkbox">
<form method="post" action="" class="form"></form>
</div>
<div id="share">
<table class="unstyled">
<tr>
<td>
<span id="copyURL"><button>Copy URL to clipboard</button></span>
<svg id="checkthis" class="hidden" width="12px" height="12px" viewBox="0 0 59 42" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<path d="M4.5,20.5 L21.0302753,37.0302753 L54.5,4.5" id="line" stroke="#000" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"
sketch:type="MSShapeGroup"></path>
</g>
</svg>
</td>
</tr>
</table>
</div>
<script>
////////////////////////////////////////////////
// Copy to clipboard logic
///////////////////////////////////////////////.
function copyTextToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.style.position = 'fixed';
textArea.style.top = 0;
textArea.style.left = 0;
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.padding = 0;
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
console.log('Copying text command was ' + msg);
} catch (err) {
console.log('Oops, unable to copy');
}
document.body.removeChild(textArea);
}
$('#copyURL').on('click touchstart', function () {
copyTextToClipboard(window.location.href)
$('#checkthis').attr('class', 'check');
setTimeout("$('#checkthis').attr('class', 'hidden')", 1500);
});
/////////////////////
// Begin Chart stuff
/////////////////////
d3.csv("data.csv", function (error, csv) {
// Build collator to sort arrays naturally (e.g. ['v001', 'v4', 'v6', 'v11', 'v1'].sort(collator.compare) outputs ["v001", "v1", "v4", "v6", "v11"] )
var collator = new Intl.Collator(undefined, {
numeric: true,
sensitivity: 'base'
});
/////////////////////////////////////////
// Data loading and checkbox rendering
/////////////////////////////////////////
// Filter on this column
var filter_on = 'Occupation'
// Building an array with the values to filter on
var filter_list = d3.map(csv, function (d) {
return d[filter_on];
}).keys()
// Sort filter_list naturally
filter_list.sort(collator.compare);
// Building the filter checkboxes
d3.select("#filter")
.selectAll("input")
.data(filter_list)
.enter()
.append("label")
.append("input")
.attr("type", "checkbox")
.attr("class", "filter-check")
.attr("value", function (d) {
return d
})
.attr("id", function (d) {
return d
});
d3.selectAll("label")
.data(filter_list)
.attr("class", "checkbox")
.append("text").text(function (d) {
return " " + d
})
// Parsing URL parameters and filter data: Look at the URL parameters and adjust the data accordingly
// We'll use encodeURIComponent and decodeURIComponent to get the values we want to
// filter on from the URL, and also to change the URL when we filter with the checkboxes
// URL parameter parse. Using a simple version, we don't need key and values,
// just an URI encoded version of the value we want to sort on
function parseURLParameters() {
var query = (window.location.search || '?').substr(1),
map = [];
query.replace(/([^&=]+)=?([^&]*)(?:&+|$)/g, function (match, key, value) {
map.push(key);
});
return map;
}
var choices = parseURLParameters(window.location.href)
// Bad URL parameter management (e.g. if '&foo' gets tacked onto the URL, we delete it)
choices = _.remove(choices, function (d) {
return _.includes(encodeURIComponent(filter_list), d);
})
// var to tack behind the URL if needed
var URL = choices.join('&')
if (choices == undefined) {
var choices = []
} // if no parameters, change to empty array instead of undefined
if (choices.length > 0) {
// Set the URL in case the URL was messy
window.history.pushState({
page: choices
}, choices, window.location.pathname + "?" + URL);
for (i = 0; i < choices.length; i++) {
document.getElementById(decodeURIComponent(choices[i])).checked = true;
}
data = csv.filter(function (d) {
return _.includes(decodeURIComponent(choices), d[filter_on])
});
} else {
// Set the URL in case the URL was messy
window.history.pushState({
page: choices
}, choices, window.location.pathname);
data = csv;
}
//////////////////////////////////////////
// Build table for the first time
//////////////////////////////////////////
function tabulate(data, columns) {
var table = d3.select('#chart').append('table').attr("class", "table")
var thead = table.append('thead')
var tbody = table.append('tbody');
// append the header row
thead.append('tr')
.selectAll('th')
.data(columns).enter()
.append('th')
.text(function (column) {
return column;
});
// create a row for each object in the data
var rows = tbody.selectAll('tr')
.data(data)
.enter()
.append('tr');
// create a cell in each row for each column
var cells = rows.selectAll('td')
.data(function (row) {
return columns.map(function (column) {
return {
column: column,
value: row[column]
};
});
})
.enter()
.append('td')
.text(function (d) {
return d.value;
});
return table;
}
// render the table(s)
tabulate(data, ['Name', 'Last Name', 'Age', 'Occupation']); // 4 column table
///////////////////////////////////
// Update chart on checkbox change
//////////////////////////////////
var checkBox = d3.selectAll(".filter-check")
checkBox.on("change", function () {
////////////////////////////
// Manage data and URL state
////////////////////////////
// When checkbox changes, refresh choices array with checked values
var choices = []
var checkboxes = document.querySelectorAll('input[type=checkbox]:checked')
for (var i = 0; i < checkboxes.length; i++) {
choices.push(encodeURIComponent(checkboxes[i].value))
}
// feeding data filtered from choices array
if (choices.length > 0) {
data = csv.filter(function (d, i) {
return _.includes(decodeURIComponent(choices), d[filter_on]);
});
} else {
data = csv; // so that no boxes checked shows all data
}
// update url with help of new choices array
var URL = choices.join('&').replace(/\s/g, "=")
if (choices.length > 0) {
window.history.pushState({
page: choices
}, choices, window.location.pathname + "?" + URL);
}
// remove ? at end of URL if checkboxes are empty again
else {
window.history.pushState({
page: choices
}, choices, window.location.pathname);
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Rebuilding the table
// (this is quick and dirty, adjust accordingly to add a proper enter, exit, update pattern)
/////////////////////////////////////////////////////////////////////////////////////////////
d3.select("table").remove();
tabulate(data, ['Name', 'Last Name', 'Age', 'Occupation']); // 4 column table
}); ////////////// //////////////////////////
// End on checkbox change update function
/////////////////////////////////////////
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment