- {{ (fullscreenTable) ? 'show networks' : 'fullscreen table' }} /
+ {{ (fullscreenTable) ? 'show networks' : 'fullscreen table' }}
- export articles as CSV or RIS /
+ / export articles as CSV or RIS /
download full network as JSON
@@ -227,7 +227,7 @@ Seed Articles ({{ seedArticles.length }})
x.isSource)[0]">(select)
- Data retrieved through {{ currentGraph.API + ' (' + abbreviateAPI(currentGraph.API) + ')' }} API on {{ new Date(currentGraph.timestamp).toLocaleString(undefined, {"day": "numeric", "month": "long", "year": "numeric"}) }}
+ Data retrieved through {{ currentGraph.API + ((abbreviateAPI(currentGraph.API)) ? ' (' + abbreviateAPI(currentGraph.API) + ')' : '') }} on {{ new Date(currentGraph.timestamp).toLocaleString(undefined, {"day": "numeric", "month": "long", "year": "numeric"}) }}
@@ -365,7 +365,7 @@ Reference contexts in source article, {{ currentGraph.sou
-
+
{{ column.label }}
@@ -471,17 +471,18 @@ Reference contexts in source article, {{ currentGraph.sou
- {{ (currentGraph.allCited) ? 'All ' : ((currentGraph.ricsRankCutoff) ? '' : 'Top ') }}Cited
+
+ {{ (currentGraph.allCited) ? 'All ' : ((currentGraph.ricsRankCutoff) ? '' : 'Top ') }}Cited
({{ citedArticles.length }}
)
- {{ (fullscreenTable) ? 'show networks' : 'fullscreen table' }} /
+ {{ (fullscreenTable) ? 'show networks' : 'fullscreen table' }}
- export articles as CSV or RIS /
+ / export articles as CSV or RIS /
download full network as JSON
@@ -600,7 +601,7 @@ Abstract
-
+
{{ column.label }}
@@ -706,16 +707,18 @@ Abstract
- {{ (currentGraph.allCiting) ? 'All ' : ((currentGraph.ricsRankCutoff) ? '' : 'Top ') }}Citing
+
+ {{ (currentGraph.allCiting) ? 'All ' : ((currentGraph.ricsRankCutoff) ? '' : 'Top ') }}Citing
({{ citingArticles.length }}
)
+
- {{ (fullscreenTable) ? 'show networks' : 'fullscreen table' }} /
+ {{ (fullscreenTable) ? 'show networks' : 'fullscreen table' }}
- export articles as CSV or RIS /
+ / export articles as CSV or RIS /
download full network as JSON
@@ -834,7 +837,7 @@ Abstract
-
+
{{ column.label }}
@@ -943,10 +946,10 @@ Co-Cited ({{ coCitedArticles.length }})
- {{ (fullscreenTable) ? 'show networks' : 'fullscreen table' }} /
+ {{ (fullscreenTable) ? 'show networks' : 'fullscreen table' }}
- export articles as CSV or RIS /
+ / export articles as CSV or RIS /
download full network as JSON
@@ -1067,7 +1070,7 @@ Abstract
-
+
{{ column.label }}
@@ -1176,10 +1179,10 @@ Co-Citing ({{ coCitingArticles.length }})
- {{ (fullscreenTable) ? 'show networks' : 'fullscreen table' }} /
+ {{ (fullscreenTable) ? 'show networks' : 'fullscreen table' }}
- export articles as CSV or RIS /
+ / export articles as CSV or RIS /
download full network as JSON
@@ -1300,7 +1303,7 @@ Abstract
-
+
{{ column.label }}
@@ -1554,7 +1557,7 @@ Co-authorship network settings
@@ -1579,7 +1582,7 @@ API options
- Cited Articles (References) can be retrieved through all APIs. Citing Articles (Citations) can be retrieved through OpenAlex (OA), Semantic Scholar (S2) and OpenCitations (OC). Top Cited are the most cited references by the Seed Articles. Top Citing are citing the most Seed Articles. Retrieving All References / Citations works best with OpenAlex (OA) & Semantic Scholar (S2).
+ Cited Articles (References) can be retrieved through all APIs. Citing Articles (Citations) can be retrieved through OpenAlex (OA), Semantic Scholar (S2) and OpenCitations (OC). Top Cited are the most cited references by the Seed Articles. Top Citing are citing the most Seed Articles. Retrieving All Cited (i.e. references) / All Citing (i.e. citations) works best with OpenAlex (OA) & Semantic Scholar (S2).
diff --git a/index.js b/index.js
index b03d0ca..816116c 100644
--- a/index.js
+++ b/index.js
@@ -6,7 +6,7 @@
'use strict'
-const localCitationNetworkVersion = 1.27
+const localCitationNetworkVersion = 1.28
const arrSum = arr => arr.reduce((a, b) => a + b, 0)
const arrAvg = arr => arrSum(arr) / arr.length
@@ -86,7 +86,7 @@ async function semanticScholarWrapper (ids, responseFunction, phase, retrieveCit
offset = response.next
response = response.data.map(x => x.citedPaper || x.citingPaper) // citedPaper for references, citingPaper for citations
// Semantic Scholar doesn't provide references & citations lists for citations & references endpoint
- // That's why for S2: All Citations always only have one reference and All References only have one citation (the one that called them), which are merged in responseToArray during de-duplication
+ // That's why for S2: All Citing (i.e. citations) always only have one reference and All Cited (i.e. references) only have one citation (the one that called them), which are merged in responseToArray during de-duplication
// response can be null in case of placeholders in id-list
if (phase === 'citations' && response) {
response = response.map(x => { x.references = [{ paperId: id }]; return x })
@@ -98,8 +98,8 @@ async function semanticScholarWrapper (ids, responseFunction, phase, retrieveCit
responses = responses.concat(response)
}
}
- // For phase 'input' and Top Citations (i.e. retrieveCitingArticles > 0 but not retrieveAllCiting) load seed articles one by one in order to retrieve proper citations fields to calculate top citations ids
- // With batch endpoint (below), there is a maximum of 9999 citations in total, which leads to incorrect Top Citations (compare https://github.com/allenai/s2-folks/issues/199)
+ // For phase 'input' and Top Citing (i.e. retrieveCitingArticles > 0 but not retrieveAllCiting) load seed articles one by one in order to retrieve proper citations fields to calculate Top Citing ids
+ // With batch endpoint (below), there is a maximum of 9999 citations in total, which leads to incorrect Top Citing (compare https://github.com/allenai/s2-folks/issues/199)
// However, single API calls are currently always blocked by 429 responses, even with 15s waits, which is why this part is commented out
/* } else if (phase === 'input' && retrievetopCiting) {
let response
@@ -119,12 +119,12 @@ async function semanticScholarWrapper (ids, responseFunction, phase, retrieveCit
responses = responses.concat(response)
}
vm.isLoadingTotal = 0 */
- // Phase 'source' / 'input' && retrieveAllCiting / 'references' && !retrieveAllCited (i.e. Top references only) / 'citations' && !retrieveAllCiting (i.e. Top Citations only)
+ // Phase 'source' / 'input' && retrieveAllCiting / 'references' && !retrieveAllCited (i.e. Top Cited only) / 'citations' && !retrieveAllCiting (i.e. Top Citing only)
// Use batch endpoint (https://api.semanticscholar.org/api-docs/graph#tag/Paper-Data/operation/post_graph_get_papers)
} else {
// These fields cannot be retrieved on references / citations endpoints
selectFields += ',authors.externalIds,authors.name,authors.affiliations,references.paperId,tldr'
- // Get citations ids for seed articles for Top Citations
+ // Get citations ids for seed articles for Top Citing
if (['source', 'input'].includes(phase) && retrievetopCiting) selectFields += ',citations.paperId'
// Batch endpoint allows max. 500 ids and 9999 citations at the same time
@@ -245,7 +245,7 @@ async function openAlexWrapper (ids, responseFunction, phase, retrieveCitedArtic
if (response.results?.length) responses = responses.concat(response.results)
}
}
- // Phase 'source' / 'input' / 'references' && !retrieveAllCited (i.e. Top references only) / 'citations' && !retrieveAllCiting (i.e. Top Citations only)
+ // Phase 'source' / 'input' / 'references' && !retrieveAllCited (i.e. Top Cited only) / 'citations' && !retrieveAllCiting (i.e. Top Citing only)
} else {
vm.isLoadingTotal = ids.length
for (const i of Array(ids.length).keys()) {
@@ -254,7 +254,7 @@ async function openAlexWrapper (ids, responseFunction, phase, retrieveCitedArtic
vm.isLoadingIndex = i
if (id) {
response = await openAlexWorks('/' + id.replace('openalex:', '') + '?select=' + selectFields)
- // Get citations ids for seed articles (if not all citations are retrieved anyway)
+ // Get citations ids for seed articles (if not All Citing (i.e. citations) are retrieved anyway)
if (['source', 'input'].includes(phase) && response.id && !retrieveAllCiting) {
// Careful: Citation results are incomplete when a paper is cited by >200 (current per-page upper-limit of OA), use "API options => Retrieve citations => All" for completeness
const citations = await openAlexWorks('?select=id&per-page=200&sort=referenced_works_count:desc&filter=cites:' + response.id.replace('https://openalex.org/', ''))
@@ -842,7 +842,7 @@ function initAuthorNetwork (app, minPublications = undefined) {
})))
const edges = Object.keys(links).map(authorId1 => Object.keys(links[authorId1]).map(authorId2 => {
- return { from: authorId1, to: authorId2, value: links[authorId1][authorId2], title: allAuthors[authorId1].name + ' & ' + allAuthors[authorId2].name + ' (' + links[authorId1][authorId2] / 2 + ' collaboration(s) among input & suggested articles)' }
+ return { from: authorId1, to: authorId2, value: links[authorId1][authorId2], title: allAuthors[authorId1].name + ' & ' + allAuthors[authorId2].name + ' (' + links[authorId1][authorId2] / 2 + ' collaboration(s) among seed articles)' }
})).flat(2)
const nodes = authorIdsWithMinPubs.map(authorId => {
@@ -887,10 +887,7 @@ function initAuthorNetwork (app, minPublications = undefined) {
smooth: false
},
physics: {
- barnesHut: {
- centralGravity: 10
- },
- maxVelocity: 20
+ solver: 'barnesHut'
},
interaction: {
multiselect: true,
@@ -979,6 +976,12 @@ function initAuthorNetwork (app, minPublications = undefined) {
Vue.use(Buefy)
+Vue.config.errorHandler = function (error, vm, info) {
+ console.log(error)
+ console.log(vm)
+ console.log(info)
+}
+
const vm = new Vue({
el: '#app',
data: {
@@ -1148,17 +1151,14 @@ const vm = new Vue({
},
linkToShareAppendix: function () {
let appendix = '?API=' + encodeURIComponent(this.currentGraph.API)
+ appendix += '&name=' + encodeURIComponent(this.currentGraph.tabLabel)
if (this.currentGraph.source.id) {
appendix += '&source=' + (this.currentGraph.source.doi ? this.currentGraph.source.doi : this.currentGraph.source.id)
- if (this.currentGraph.source.customListOfReferences) {
- appendix += '&listOfIds=' + this.currentGraph.source.customListOfReferences.join(',')
- }
- if (this.currentGraph.bookmarkletURL) {
- appendix += '&bookmarkletURL=' + this.currentGraph.bookmarkletURL
- }
- } else {
- appendix += '&name=' + encodeURIComponent(this.currentGraph.tabLabel) + '&listOfIds=' + this.currentGraph.source.references.join(',')
}
+ if (this.currentGraph.bookmarkletURL) {
+ appendix += '&bookmarkletURL=' + this.currentGraph.bookmarkletURL
+ }
+ appendix += '&listOfIds=' + (this.currentGraph.source.customListOfReferences ? this.currentGraph.source.customListOfReferences : this.currentGraph.source.references).join(',')
return appendix
},
showNumberInSourceReferences: function () {
@@ -1480,7 +1480,7 @@ const vm = new Vue({
if (source.id) seedArticles = seedArticles.concat(source)
const seedArticlesIds = seedArticles.map(article => article.id)
- // Temporary scope variables needed (without having been reduced like in computed.referencedCiting!) for getting id lists for Top References / Top Citations
+ // Temporary scope variables needed (without having been reduced like in computed.referencedCiting!) for getting id lists for Top Cited / Top Citing
let referenced, citing
if (!(retrieveCitedArticles === Infinity && ['OpenAlex', 'Semantic Scholar'].includes(API))) {
referenced = this.computeSeedArticlesRelationships(seedArticles, seedArticlesIds).seedArticlesA
@@ -1489,7 +1489,7 @@ const vm = new Vue({
citing = this.computeSeedArticlesRelationships(seedArticles, seedArticlesIds, 'citations').seedArticlesA
}
- // Delete citations arrays in articles to save space, they were only needed for calculating citing (above) for "Top citations"
+ // Delete citations arrays in articles to save space, they were only needed for calculating citing (above) for "Top Citing"
seedArticles = seedArticles.map(article => { delete article.citations; return article })
// Add new tab
@@ -1513,15 +1513,15 @@ const vm = new Vue({
this.listName = undefined
this.bookmarkletURL = undefined
- /* Perform API call for All / Top References (formerly References / Incoming suggestions) */
- // OA & S2: If all references are supposed to be retrieved get multiple references at once based on seedArticlesIds (faster)
+ /* Perform API call for All / Top Cited (formerly References / Incoming suggestions) */
+ // OA & S2: If All Cited (i.e. references) are supposed to be retrieved get multiple references at once based on seedArticlesIds (faster)
// Otherwise use ids derived from references
let citedArticlesIds
if (!(retrieveCitedArticles === Infinity && ['OpenAlex', 'Semantic Scholar'].includes(API))) {
citedArticlesIds = Object.keys(referenced)
- // De-duplicate vs seedArticles in case only Top References are retrieved
+ // De-duplicate vs seedArticles in case only Top Cited are retrieved
.filter(x => !seedArticlesIds.includes(x))
- // Sort and slice for Top References
+ // Sort and slice for Top Cited
.sort((a, b) => referenced[b].length - referenced[a].length).slice(0, retrieveCitedArticles)
}
this.callAPI((retrieveCitedArticles === Infinity && ['OpenAlex', 'Semantic Scholar'].includes(API)) ? seedArticlesIdsWithoutSource : citedArticlesIds, data => this.retrievedCitedArticles(data, API, newGraph, retrieveCitingArticles, citing, seedArticlesIds), API, 'references', retrieveCitedArticles, 0)
@@ -1537,17 +1537,17 @@ const vm = new Vue({
this.saveState()
if (this.currentGraph === newGraph) this.init()
- /* Perform API call for All / Top Citations (formerly Citations / Outgoing suggestions) */
+ /* Perform API call for All / Top Citing (formerly Citations / Outgoing suggestions) */
// Only works with OpenAlex, Semantic Scholar and OpenCitations
- // OA & S2: If all citations are supposed to be retrieved get multiple citations at once based on seedArticlesIds (faster)
+ // OA & S2: If All Citing (i.e. citations) are supposed to be retrieved get multiple citations at once based on seedArticlesIds (faster)
// Otherwise use ids derived from citations
if (this.retrieveCitingArticles && ['OpenAlex', 'Semantic Scholar', 'OpenCitations'].includes(API)) {
let citingArticlesIds
if (!(retrieveCitingArticles === Infinity && ['OpenAlex', 'Semantic Scholar'].includes(API))) {
citingArticlesIds = Object.keys(citing)
- // De-duplicate vs seedArticles & citedArticles in case only Top Citations are retrieved
+ // De-duplicate vs seedArticles & citedArticles in case only Top Citing are retrieved
.filter(x => !seedArticlesIds.includes(x) && !citedArticles.map(x => x.id).includes(x))
- // Sort and slice for Top Citations
+ // Sort and slice for Top Citing
.sort((a, b) => citing[b].length - citing[a].length).slice(0, retrieveCitingArticles)
}
this.callAPI((retrieveCitingArticles === Infinity && ['OpenAlex', 'Semantic Scholar'].includes(API)) ? seedArticlesIds : citingArticlesIds, data => this.retrievedCitingArticles(data, API, newGraph), API, 'citations', 0, retrieveCitingArticles)
@@ -1823,19 +1823,19 @@ const vm = new Vue({
} else if (API === 'Crossref') {
articles = crossrefResponseToArticleArray(data)
}
- // Remove duplicates - important for de-duplication of All References / All Citations (in the sense of proper duplicates within one category (not in the sense of "de-duplicated against Seed Articles and Cited"), can be duplicated mostly with S2 but also to lesser extent with OA), rarely also needed for other calls, e.g. for S2 in references of 10.1111/J.1461-0248.2009.01285.X, eebf363bc78ca7bc16a32fa339004d0ad43aa618 came up twice
+ // Remove duplicates - important for de-duplication of All Cited (i.e. references) / All Citing (i.e. citations) (in the sense of proper duplicates within one category (not in the sense of "de-duplicated against Seed Articles and Cited"), can be duplicated mostly with S2 but also to lesser extent with OA), rarely also needed for other calls, e.g. for S2 in references of 10.1111/J.1461-0248.2009.01285.X, eebf363bc78ca7bc16a32fa339004d0ad43aa618 came up twice
articles = articles.reduce((articlesKeep, article) => {
const articlesKeepIds = articlesKeep.map(x => x.id)
// Keep placeholders (id undefined) and non-duplicates
if (article.id === undefined || !articlesKeepIds.includes(article.id)) {
articlesKeep.push(article)
} else {
- // S2: All References and All Citations always only have one reference (the one that called them) - merge them on de-duplication
+ // S2: All Cited (i.e. references) and All Citing (i.e. citations) always only have one reference (the one that called them) - merge them on de-duplication
if (article.references?.length === 1) {
- // This should only occur for All Citations for S2
+ // This should only occur for All Citing (i.e. citations) for S2
if (!articlesKeep[articlesKeepIds.indexOf(article.id)].references.includes(article.references[0])) articlesKeep[articlesKeepIds.indexOf(article.id)].references.push(article.references[0])
} else if (article.citations?.length === 1) {
- // This should only occur for All References for S2
+ // This should only occur for All Cited (i.e. references) for S2
if (!articlesKeep[articlesKeepIds.indexOf(article.id)].citations.includes(article.citations[0])) articlesKeep[articlesKeepIds.indexOf(article.id)].citations.push(article.citations[0])
}
}
@@ -1860,7 +1860,7 @@ const vm = new Vue({
if (saveGraphs) {
const copiedGraphs = JSON.parse(JSON.stringify(this.graphs))
localStorage.graphs = JSON.stringify(copiedGraphs.map(graph => {
- // Delete these two possibly existing flags so that only "Top References" / "Top Citations" instead of "All references" / "All citations" will be shown
+ // Delete these two possibly existing flags so that only "Top Cited" / "Top Citing" instead of "All Cited" (i.e. references) / "All Citing" (i.e. citations) will be shown
if (graph.citedArticles === undefined || graph.citedArticles.length > maxCitedCiting) delete graph.allCited
if (graph.citingArticles === undefined || graph.citingArticles.length > maxCitedCiting) delete graph.allCiting
// Don't save suggestions still in loading phase
@@ -1947,7 +1947,7 @@ const vm = new Vue({
// Prior to v1.23 referenced and citing were cached in the graph object
delete graph.referenced
delete graph.citing
- // Prior to v1.26 seedArticles array was called input, citedArticles array was called citedArticles, and citingArticles array was called citingArticles
+ // Prior to v1.26 seedArticles array was called input, citedArticles array was called incomingSuggestions, and citingArticles array was called outgoingSuggestions
if (!graph.seedArticles) {
graph.seedArticles = graph.input
graph.citedArticles = graph.incomingSuggestions
@@ -2085,13 +2085,7 @@ const vm = new Vue({
return '"' + arr.join('";"') + '"'
}).join('\n')
- const blob = new Blob([csv], { type: 'text/csv' })
- const url = URL.createObjectURL(blob)
- const anchor = document.createElement('a')
- anchor.href = url
- anchor.download = `${vm.currentGraph.tabLabel}${filenameSuffix}.csv`
- anchor.click()
- anchor.remove()
+ return this.downloadFile(csv, 'text/csv', `${vm.currentGraph.tabLabel}${filenameSuffix}.csv`)
},
downloadRISData: function (articlesArray, filenameSuffix = '') {
// TODO consider mapping types to RIS types instead of always using "TY - JOUR" (journal article)
@@ -2118,20 +2112,17 @@ const vm = new Vue({
ris += 'ER - \n\n'
})
- const blob = new Blob([ris], { type: 'application/x-research-info-systems' })
- const url = URL.createObjectURL(blob)
- const anchor = document.createElement('a')
- anchor.href = url
- anchor.download = `${vm.currentGraph.tabLabel}${filenameSuffix}.ris`
- anchor.click()
- anchor.remove()
+ return this.downloadFile(ris, 'application/x-research-info-systems', `${vm.currentGraph.tabLabel}${filenameSuffix}.ris`)
},
downloadJSON: function () {
- const blob = new Blob([JSON.stringify([vm.currentGraph])], { type: 'application/json' })
+ return this.downloadFile(JSON.stringify([vm.currentGraph]), 'application/json', `${vm.currentGraph.tabLabel}.json`)
+ },
+ downloadFile: function (content, type, filename) {
+ const blob = new Blob([content], { type: type })
const url = URL.createObjectURL(blob)
const anchor = document.createElement('a')
anchor.href = url
- anchor.download = `${vm.currentGraph.tabLabel}.json`
+ anchor.download = filename
anchor.click()
anchor.remove()
}