Fuzzy search
Open in a new window.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../build/ogma.min.js"></script>
<script src="https://unpkg.com/fuzzy@0.1.3/lib/fuzzy.js"></script>
<style>
html, body {
padding: 0;
margin: 0;
font: normal 18px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
#graph-container {
top: 0; bottom: 0; left: 0; right: 0;
position: absolute; margin: 0; overflow: hidden;
}
.toolbar {
display: block;
position: absolute;
top: 20px;
right: 20px;
padding: 10px;
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
background: #ffffff;
color: #222222;
font-weight: 300;
min-width: 35%;
}
#search-input {
width: 100%;
padding: 1rem;
font-size: 1rem;
}
#search-input:focus {
outline: 0;
}
.match {
font-weight: bold;
text-decoration: underline;
}
.dropdown {
position: relative;
}
.dropdown-item {
display: block;
width: 100%;
padding: .25rem 1.5rem;
clear: both;
font-weight: 400;
color: #212529;
text-align: inherit;
white-space: nowrap;
background-color: transparent;
border: 0;
cursor: pointer;
box-sizing: border-box;
}
.dropdown-item:hover, .dropdown-item.hover {
background: #dddddd;
}
.dropdown-menu {
position: absolute;
top: 100%;
right: 0;
z-index: 1000;
display: block;
float: left;
min-width: 10rem;
padding: .5rem 0;
margin: .125rem 0 0;
font-size: 1rem;
color: #212529;
text-align: left;
list-style: none;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0,0,0,.15);
border-radius: .25rem;
max-height: 500px;
overflow-y: auto;
}
</style>
</head>
<body>
<div id="graph-container"></div>
<div class="toolbar">
<input type="search" placeholder="Search station" id="search-input" />
<div id="results" class="dropdown">
</div>
</div>
<script>
var ogma = new Ogma({
container: 'graph-container'
});
// add highlight style rule
ogma.createClass('highlighted', {
nodeAttributes: {
radius: function (node) { return node.getAttribute('radius') * 1.5; },
halo: {
color: '#cccccc',
size: 20
}
}
});
function highlightNode(id) {
ogma.getNodes().removeClass('highlighted');
if (id) {
ogma.getNode(id).addClass('highlighted');
}
}
function selectNode(id) {
ogma.getSelectedNodes().setSelected(false);
if (id) {
ogma.getNode(id).setSelected(true);
}
}
var search = {};
function init(graph) {
var resultsContainer = document.querySelector('#results');
var currentItem = 0; // current item selected in the list
var input = document.querySelector('#search-input');
// store raw data
search.data = graph;
// expose search procedure
search.query = function(value) {
// perform query using fuzzy.js
var results = fuzzy.filter(value, this.data.nodes, {
// highlight the matching substring
pre: '<span class="match">',
post: '</span>',
extract: function (item) {
// extract the text name to display in the list
return item.data.latin_name;
}
});
// render the list HTML
resultsContainer.innerHTML =
'<div class="dropdown-menu">'
+ (results.map(function (result, i) {
console.log(result);
return '<div class="dropdown-item '
+ ((i === currentItem) ? 'hover' : '') // current item
+ ' data-position="' + i + '"'
+ ' data-index="' + result.index + '"'
+ ' data-value="' + result.original.data.latin_name + '"'
+ ' data-id="' + result.original.id + '">'
+ result.string + '</div>';
}).join(''))
+ "</div>";
highlightNode(results[currentItem].original.id);
};
// listen for input
input.addEventListener('keyup', function (evt) {
if (evt.keyCode === 40) { // down arrow
currentItem++;
} else if (evt.keyCode === 38) { // up arrow
currentItem--;
} else if (evt.keyCode === 13) { // enter
var item = resultsContainer.querySelectorAll('.dropdown-item')[currentItem];
input.value = item.getAttribute('data-value');
selectNode(item.getAttribute('data-id'));
}
// wait for the next frame to throttle the search
Ogma.utils.requestAnimFrame(function () {
search.query(input.value);
});
});
// highlight on hover
resultsContainer.addEventListener('mousemove', function (evt) {
Ogma.utils.requestAnimFrame(function () {
var id = evt.target.getAttribute('data-id');
currentItem = evt.target.getAttribute('data-position') || 0;
highlightNode(id);
});
});
// select on click
resultsContainer.addEventListener('click', function (evt) {
var id = evt.target.getAttribute('data-id');
currentItem = evt.target.getAttribute('data-position') || 0;
input.value = evt.target.getAttribute('data-value');
selectNode(id);
});
return graph;
}
ogma.parse.jsonFromUrl('files/paris-metro.json') // load data
.then(init) // init search
.then(function (graph) { return ogma.setGraph(graph); }) // add graph to ogma
.then(function () { return ogma.view.locateGraph(); }) // position
.then(function () { // trigger up the search
var input = document.querySelector('#search-input');
input.value = 'Lou';
search.query('Lou');
input.focus();
});
</script>
</body>
</html>