|
const selectors = (function() { |
|
|
|
var tokenSearchSimilarityCache = {}; |
|
|
|
var typeSymbols = { |
|
search: 's', |
|
keywords: 'J', |
|
year: '}', |
|
author: 'f', |
|
entity: 'K', |
|
warning: '!', |
|
series: 'w', |
|
cluster: 'W', |
|
citations_incoming: 'o', |
|
citations_outgoing: 'o' |
|
}; |
|
|
|
return { |
|
|
|
selectors: {}, |
|
|
|
nSelectors: 6, |
|
|
|
updateSelectors: function () { |
|
$('.selector').remove(); |
|
var selectors = this; |
|
var clearButton = $('#clear_selectors'); |
|
for (var i = 0; i < this.nSelectors; i++) { |
|
var selectorDiv = $('<div></div>', { |
|
class: 'selector selector' + i, |
|
id: 'selector' + i |
|
}).insertBefore(clearButton); |
|
var selector = selectors.getSelectors()[i]; |
|
if (selector) { |
|
var invertedClass = (selector['inverted'] ? ' inverted' : ''); |
|
selectorDiv.addClass(invertedClass); |
|
var lockedClass = (selector['lock'] ? ' locked' : ''); |
|
selectorDiv.addClass(lockedClass); |
|
var text = ''; |
|
text = latexUtil.latexToHtml(selector['text']); |
|
if (selector.type == 'citations_incoming') { |
|
text = 'citing ' + text; |
|
} else if (selector.type == 'citations_outgoing') { |
|
text = 'cited by ' + text; |
|
} |
|
$('<span>', { |
|
class: 'selector_type symbol', |
|
text: typeSymbols[selector['type']] |
|
}).appendTo(selectorDiv); |
|
var contentDiv = $('<div>', { |
|
class: 'selector_content' |
|
}).appendTo(selectorDiv); |
|
$('<div>', { |
|
class: 'text', |
|
html: text |
|
}).appendTo(contentDiv); |
|
$('<div>', { |
|
class: 'tooltip invert' + invertedClass, |
|
title: 'invert: identify non-matched entries' |
|
}).appendTo(contentDiv); |
|
$('<div>', { |
|
class: 'tooltip lock' + lockedClass, |
|
title: 'lock: apply as a filter to restrict the collection to matched entries' |
|
}).appendTo(contentDiv); |
|
$('<div>', { |
|
class: 'tooltip remove', |
|
title: 'remove: discard the selector' |
|
}).appendTo(contentDiv); |
|
} else { |
|
$('<div>', { |
|
class: 'selector_type' |
|
}).appendTo(selectorDiv); |
|
} |
|
} |
|
$('.selector .invert').click(function () { |
|
var i = parseInt($(this).parent().parent().attr('id').substring(8)); |
|
selectors.getSelectors()[i]['inverted'] = !selectors.getSelectors()[i]['inverted']; |
|
$(this).toggleClass('inverted'); |
|
page.updateShowPart(); |
|
}); |
|
$('.selector .lock').click(function () { |
|
var i = parseInt($(this).parent().parent().attr('id').substring(8)); |
|
selectors.getSelectors()[i]['lock'] = !selectors.getSelectors()[i]['lock']; |
|
$(this).toggleClass('locked'); |
|
page.updateShowPart(); |
|
}); |
|
$('.selector .remove').click(function (event) { |
|
var i = parseInt($(this).parent().parent().attr('id').substring(8)); |
|
selectors.getSelectors()[i] = null; |
|
page.updateShowPart(); |
|
}); |
|
this.computeEntrySelectorSimilarities(); |
|
this.applyFilter(); |
|
page.generateTooltips($('#selectors_container').find('.selector')); |
|
}, |
|
|
|
readQueryFromUrl: function () { |
|
var query = browserUtil.getUrlParameter('q'); |
|
if (query) { |
|
toggleSelector('search', query); |
|
} |
|
}, |
|
|
|
getNActiveSelectors: function () { |
|
var count = 0; |
|
for (var i = 0; i < this.nSelectors; i++) { |
|
if (this.selectors[i] && !this.selectors[i]['lock']) { |
|
count++; |
|
} |
|
} |
|
return count; |
|
}, |
|
|
|
getSelectors: function () { |
|
return this.selectors; |
|
}, |
|
|
|
getSelectorsOfType: function (type) { |
|
var filteredSelectors = []; |
|
for (var i = 0; i < this.nSelectors; i++) { |
|
if (this.selectors[i] && this.selectors[i]['type'] == type) { |
|
filteredSelectors.push(this.selectors[i]); |
|
} |
|
} |
|
return filteredSelectors; |
|
}, |
|
|
|
getTotalSimilarity: function (bib, id) { |
|
var similaritySum = 0.0; |
|
var count = 0.0; |
|
$.each(this.selectors, function (i, selector) { |
|
if (selector && !selector['lock']) { |
|
similaritySum += bib.entrySelectorSimilarities[id][i]; |
|
count++; |
|
} |
|
}); |
|
if (count > 0) { |
|
return similaritySum / count; |
|
} |
|
return 0; |
|
}, |
|
|
|
resetSelectors: function () { |
|
selectors.selectors = {}; |
|
page.updateShowPart(); |
|
}, |
|
|
|
toggleSelector: function (type, text, event) { |
|
if (!typeSymbols[type]) { |
|
type = 'search'; |
|
} |
|
for (var i = 0; i < this.nSelectors; i++) { |
|
if (this.selectors[i] && this.selectors[i]['type'] == type && this.selectors[i]['text'] == text) { |
|
this.selectors[i] = null; |
|
page.updateShowPart(); |
|
return; |
|
} |
|
} |
|
var selector = this.nextFreeSelector(); |
|
if (!selector) { |
|
page.notify('The maximum number of selectors that can be active at the same time is ' + this.nSelectors + '. Please close at least one selector before activating another.', 'error'); |
|
return; |
|
} |
|
selector['type'] = type; |
|
selector['text'] = text; |
|
selector['inverted'] = false; |
|
selector['lock'] = event && event.ctrlKey; |
|
selector['count'] = 0; |
|
page.updateShowPart(); |
|
}, |
|
|
|
nextFreeSelector: function () { |
|
for (var i = 0; i < this.nSelectors; i++) { |
|
if (!this.selectors[i]) { |
|
this.selectors[i] = {}; |
|
return this.selectors[i]; |
|
} |
|
} |
|
return null; |
|
}, |
|
|
|
computeEntrySelectorSimilarities: function () { |
|
var selectors = this; |
|
bib.entrySelectorSimilarities = {}; |
|
$.each(bib.entries, function (id, entry) { |
|
bib.entrySelectorSimilarities[id] = {}; |
|
$.each(selectors.getSelectors(), function (i, selector) { |
|
if (selector) { |
|
var text = selector['text'] === '?' ? '' : selector['text']; |
|
var similarity = 0; |
|
if (selector['type'] == 'search') { |
|
if (!selector['tokenized_text']) { |
|
selector['tokenized_text'] = tokenizeSearchString(text); |
|
} |
|
similarity = computeSearchSimilarity(id, entry, selector['tokenized_text']); |
|
} else if (selector['type'] == 'keywords') { |
|
similarity = computeTagSimilarity(bib, id, text); |
|
} else if (selector['type'] == 'year') { |
|
similarity = computeYearSimilarity(entry, text); |
|
} else if (selector['type'] == 'author') { |
|
similarity = computeAuthorSimilarity(id, text); |
|
} else if (selector['type'] == 'entity') { |
|
if (!selector['tokenized_text']) { |
|
var searchEntry = bib.entries[text]; |
|
var searchText = searchEntry['title']; |
|
searchText += searchEntry['abstract'] ? searchEntry['abstract'] : ''; |
|
searchText += searchEntry['keywords'] ? searchEntry['keywords'] : ''; |
|
searchText += searchEntry['authors'] ? searchEntry['authors'] : ''; |
|
selector['tokenized_text'] = tokenizeSearchString(searchText); |
|
} |
|
similarity = computeSearchSimilarity(id, entry, selector['tokenized_text']); |
|
} else if (selector['type'] == 'warning') { |
|
similarity = computeWarningSimilarity(entry, id, text) |
|
} else if (selector['type'] == 'series') { |
|
similarity = computeSeriesSimilarity(entry, text); |
|
} else if (selector['type'] == 'cluster') { |
|
similarity = computeClusterSimilarity(bib, id, text); |
|
} else if (selector['type'] == 'citations_incoming') { |
|
similarity = computeCitationIncomingSimilarity(entry, id, selector['text']); |
|
} else if (selector['type'] == 'citations_outgoing') { |
|
similarity = computeCitationOutgoingSimilarity(entry, id, selector['text']); |
|
} |
|
if (selector['inverted']) { |
|
similarity = 1 - similarity; |
|
} |
|
bib.entrySelectorSimilarities[id][i] = similarity; |
|
} |
|
}); |
|
}); |
|
bib.sortedIDs = Object.keys(bib.entries).sort(function (a, b) { |
|
var lowerA = a.toLowerCase(); |
|
var lowerB = b.toLowerCase(); |
|
var similarityA = selectors.getTotalSimilarity(bib, a); |
|
var similarityB = selectors.getTotalSimilarity(bib, b); |
|
if (similarityA > similarityB) |
|
return -1; |
|
if (similarityA < similarityB) |
|
return 1; |
|
if (lowerA < lowerB) |
|
return -1; |
|
if (lowerA > lowerB) |
|
return 1; |
|
return 0; |
|
}); |
|
}, |
|
|
|
applyFilter: function () { |
|
var selectors = this; |
|
bib.filteredEntries = {}; |
|
$.each(bib.sortedIDs, function (i, id) { |
|
if (!selectors.filteredOut(id)) { |
|
bib.filteredEntries[id] = bib.entries[id]; |
|
} |
|
}); |
|
bib.nEntries = Object.keys(bib.filteredEntries).length; |
|
}, |
|
|
|
filteredOut: function (id) { |
|
var filteredOut = false; |
|
$.each(this.getSelectors(), function (i, selector) { |
|
if (selector) { |
|
if (selector['lock'] && bib.entrySelectorSimilarities[id][i] <= 0) { |
|
filteredOut = true; |
|
} |
|
} |
|
}); |
|
return filteredOut; |
|
}, |
|
|
|
vis: function (elem, similarities) { |
|
var selectors = this; |
|
var sparkline = elem.hasClass('sparkline'); |
|
var count = 0; |
|
if (!similarities) { |
|
return; |
|
} |
|
var container = $('<div>', { |
|
class: 'container' |
|
}); |
|
var height = sparkline ? 14 : 140; |
|
$.each(similarities, function (i, similarity) { |
|
if (selectors.getSelectors()[i] && !selectors.getSelectors()[i]['lock']) { |
|
var selectorDiv = $('<div>', { |
|
class: 'selector' |
|
}).appendTo(container); |
|
var labelText = selectors.getSelectors()[i].text; |
|
if (labelText.length > 20) { |
|
labelText = labelText.substring(0, 15) + '...'; |
|
} |
|
if (!sparkline) { |
|
$('<div>', { |
|
class: 'label', |
|
text: labelText |
|
}).appendTo(selectorDiv); |
|
var iconDiv = $('<div>', { |
|
class: 'selector_icon selector' + i |
|
}).appendTo(selectorDiv); |
|
$('<div>', { |
|
class: 'symbol', |
|
text: typeSymbols[selectors.getSelectors()[i].type] |
|
}).appendTo(iconDiv); |
|
$('<div>', { |
|
class: 'value', |
|
text: similarity.toFixed(2) |
|
}).appendTo(selectorDiv) |
|
} |
|
var barDiv = $('<div>', { |
|
class: 'bar selector' + i |
|
}).appendTo(selectorDiv); |
|
if (similarity > 0) { |
|
barDiv.css('height', Math.round(height * similarity + 0.49) + 'px'); |
|
count++; |
|
} |
|
} |
|
|
|
}); |
|
if (count >= 1) { |
|
container.appendTo(elem); |
|
if (!sparkline) { |
|
elem.height(195); |
|
} |
|
} |
|
}, |
|
|
|
computeTotalSimilarity: function (similarities) { |
|
if (!similarities) { |
|
return 0.0; |
|
} |
|
var totalSimilarity = 0.0; |
|
var count = 0; |
|
$.each(this.getSelectors(), function (i, selector) { |
|
if (selector && !selector['lock']) { |
|
totalSimilarity += similarities[i]; |
|
count++; |
|
} |
|
}); |
|
if (count == 0) { |
|
return 0.0; |
|
} |
|
totalSimilarity = totalSimilarity / count; |
|
return totalSimilarity; |
|
}, |
|
|
|
getActiveTags: function (field) { |
|
var activeTags = {}; |
|
$.each(this.getSelectorsOfType(field), function (i, selector) { |
|
activeTags[selector['text']] = selector['inverted'] ? 'inverted' : 'normal'; |
|
}); |
|
return activeTags; |
|
} |
|
|
|
}; |
|
|
|
|
|
function tokenizeSearchString(text) { |
|
text = text.toLowerCase(); |
|
var re = /(\W|_)+/; |
|
var words = text.split(re); |
|
words = $.grep(words, function (word) { |
|
return word.length > 1 && !($.inArray(word, bib.stopwords) >= 0); |
|
}); |
|
return words; |
|
} |
|
|
|
function computeSearchSimilarity(id, entry, tokens) { |
|
if (!tokenSearchSimilarityCache[id]) { |
|
tokenSearchSimilarityCache[id] = {}; |
|
} |
|
var matchCount = 0; |
|
$.each(tokens, function (i, token) { |
|
var containedInValues = false; |
|
var wordStartsWithToken = false; |
|
var matchInImportantFields = false; |
|
if (tokenSearchSimilarityCache[id][token] != undefined) { |
|
matchCount += tokenSearchSimilarityCache[id][token]; |
|
} else { |
|
var similarity = 0; |
|
if (id.toLowerCase().indexOf(token) >= 0) { |
|
similarity = 1; |
|
} else { |
|
$.each(entry, function (key, value) { |
|
if (key != 'references' && key != 'referencedby') { |
|
var index = value.toLowerCase().indexOf(token); |
|
if (index >= 0) { |
|
containedInValues = true; |
|
if (key == 'title' || key == 'author' || key == 'keywords') { |
|
matchInImportantFields = true; |
|
} |
|
if (index == 0 || value[index - 1].match(/\W/i)) { |
|
wordStartsWithToken = true; |
|
} |
|
} |
|
} |
|
}); |
|
var matchFactor = matchInImportantFields ? 2 : 1; |
|
similarity = containedInValues ? 0.25 * matchFactor : 0; |
|
similarity += wordStartsWithToken ? 0.25 * matchFactor : 0; |
|
} |
|
matchCount += similarity; |
|
tokenSearchSimilarityCache[id][token] = similarity; |
|
} |
|
}); |
|
return matchCount / tokens.length; |
|
} |
|
|
|
function computeTagSimilarity(bib, id, text) { |
|
if (!bib.parsedEntries[id]) { |
|
return 0.0; |
|
} |
|
var tags = bib.parsedEntries[id]['keywords']; |
|
if (tags.indexOf(text) >= 0) { |
|
return 1.0; |
|
} |
|
return 0.0; |
|
} |
|
|
|
function computeYearSimilarity(entry, text) { |
|
if (entry["year"] == text) { |
|
return 1.0; |
|
} |
|
return 0.0; |
|
} |
|
|
|
function computeAuthorSimilarity(id, text) { |
|
if (!bib.entries[id]['author']) { |
|
return 0.0; |
|
} |
|
text = tagUtil.simplifyTag(text); |
|
var similarity = 0.0; |
|
$.each(bib.parsedEntries[id]['author'], function (i, author) { |
|
if (text === tagUtil.simplifyTag(author)) { |
|
similarity = 1.0; |
|
return; |
|
} |
|
}); |
|
return similarity; |
|
} |
|
|
|
function computeClusterSimilarity(bib, id, text) { |
|
var clusters = bib.clusterAssignment[id]; |
|
if ($.inArray(text, clusters) > -1) { |
|
return 1.0; |
|
} |
|
return 0.0; |
|
} |
|
|
|
|
|
function computeCitationIncomingSimilarity(entry, id, text) { |
|
if (bib.references[id] && |
|
bib.references[id].referencesOutgoing && |
|
bib.references[id].referencesOutgoing.indexOf(text) >= 0) { |
|
return 1.0; |
|
} |
|
return 0.0; |
|
} |
|
|
|
function computeCitationOutgoingSimilarity(entry, id, text) { |
|
if (bib.references[id] && |
|
bib.references[id].referencesIncoming && |
|
bib.references[id].referencesIncoming.indexOf(text) >= 0) { |
|
return 1.0; |
|
} |
|
return 0.0; |
|
} |
|
|
|
function computeWarningSimilarity(entry, id, text) { |
|
var warnings = bib.warnings[id] ? bib.warnings[id] : []; |
|
if (!warnings) { |
|
return 0.0; |
|
} |
|
var similarity = 0.0; |
|
if (text) { |
|
$.each(warnings, function () { |
|
var warningText = this; |
|
if (warningText['type']) { |
|
warningText = warningText['type']; |
|
} |
|
if (latexUtil.latexToHtml(warningText) === latexUtil.latexToHtml(text)) { |
|
similarity = 1.0; |
|
} |
|
}) |
|
} else { |
|
similarity = 1.0 - 1.0 / warnings.length; |
|
} |
|
return similarity; |
|
} |
|
|
|
function computeSeriesSimilarity(entry, text) { |
|
var series = entry["series"] ? entry["series"] : ''; |
|
if (tagUtil.simplifyTag(series) === tagUtil.simplifyTag(text)) { |
|
return 1.0; |
|
} |
|
return 0.0; |
|
} |
|
|
|
})(); |