pub-analyzer 0.5.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pub_analyzer/__init__.py +1 -0
- pub_analyzer/__main__.py +7 -0
- pub_analyzer/css/body.tcss +87 -0
- pub_analyzer/css/buttons.tcss +24 -0
- pub_analyzer/css/checkbox.tcss +29 -0
- pub_analyzer/css/collapsible.tcss +31 -0
- pub_analyzer/css/datatable.tcss +50 -0
- pub_analyzer/css/editor.tcss +60 -0
- pub_analyzer/css/main.tcss +50 -0
- pub_analyzer/css/report.tcss +131 -0
- pub_analyzer/css/search.tcss +81 -0
- pub_analyzer/css/summary.tcss +75 -0
- pub_analyzer/css/tabs.tcss +18 -0
- pub_analyzer/css/tree.tcss +44 -0
- pub_analyzer/internal/__init__.py +1 -0
- pub_analyzer/internal/identifier.py +106 -0
- pub_analyzer/internal/limiter.py +34 -0
- pub_analyzer/internal/render.py +41 -0
- pub_analyzer/internal/report.py +497 -0
- pub_analyzer/internal/templates/author_report.typ +591 -0
- pub_analyzer/main.py +81 -0
- pub_analyzer/models/__init__.py +1 -0
- pub_analyzer/models/author.py +87 -0
- pub_analyzer/models/concept.py +19 -0
- pub_analyzer/models/institution.py +138 -0
- pub_analyzer/models/report.py +111 -0
- pub_analyzer/models/source.py +77 -0
- pub_analyzer/models/topic.py +59 -0
- pub_analyzer/models/work.py +158 -0
- pub_analyzer/widgets/__init__.py +1 -0
- pub_analyzer/widgets/author/__init__.py +1 -0
- pub_analyzer/widgets/author/cards.py +65 -0
- pub_analyzer/widgets/author/core.py +122 -0
- pub_analyzer/widgets/author/tables.py +50 -0
- pub_analyzer/widgets/body.py +55 -0
- pub_analyzer/widgets/common/__init__.py +18 -0
- pub_analyzer/widgets/common/card.py +29 -0
- pub_analyzer/widgets/common/filesystem.py +203 -0
- pub_analyzer/widgets/common/filters.py +111 -0
- pub_analyzer/widgets/common/input.py +97 -0
- pub_analyzer/widgets/common/label.py +36 -0
- pub_analyzer/widgets/common/modal.py +43 -0
- pub_analyzer/widgets/common/selector.py +66 -0
- pub_analyzer/widgets/common/summary.py +7 -0
- pub_analyzer/widgets/institution/__init__.py +1 -0
- pub_analyzer/widgets/institution/cards.py +78 -0
- pub_analyzer/widgets/institution/core.py +122 -0
- pub_analyzer/widgets/institution/tables.py +24 -0
- pub_analyzer/widgets/report/__init__.py +1 -0
- pub_analyzer/widgets/report/author.py +43 -0
- pub_analyzer/widgets/report/cards.py +130 -0
- pub_analyzer/widgets/report/concept.py +47 -0
- pub_analyzer/widgets/report/core.py +308 -0
- pub_analyzer/widgets/report/editor.py +80 -0
- pub_analyzer/widgets/report/export.py +112 -0
- pub_analyzer/widgets/report/grants.py +85 -0
- pub_analyzer/widgets/report/institution.py +39 -0
- pub_analyzer/widgets/report/locations.py +75 -0
- pub_analyzer/widgets/report/source.py +90 -0
- pub_analyzer/widgets/report/topic.py +55 -0
- pub_analyzer/widgets/report/work.py +391 -0
- pub_analyzer/widgets/search/__init__.py +11 -0
- pub_analyzer/widgets/search/core.py +96 -0
- pub_analyzer/widgets/search/results.py +82 -0
- pub_analyzer/widgets/sidebar.py +70 -0
- pub_analyzer-0.5.6.dist-info/METADATA +102 -0
- pub_analyzer-0.5.6.dist-info/RECORD +70 -0
- pub_analyzer-0.5.6.dist-info/WHEEL +4 -0
- pub_analyzer-0.5.6.dist-info/entry_points.txt +3 -0
- pub_analyzer-0.5.6.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
// This document was generated using Pub Analyzer.
|
|
2
|
+
//
|
|
3
|
+
// Pub Analyzer is a tool designed to retrieve, process and present in a concise and understandable
|
|
4
|
+
// way the scientific production of a researcher, including detailed information about their articles,
|
|
5
|
+
// citations, collaborations and other relevant metrics.
|
|
6
|
+
//
|
|
7
|
+
// See more here: https://pub-analyzer.com
|
|
8
|
+
|
|
9
|
+
// Packages
|
|
10
|
+
//
|
|
11
|
+
// This document uses the Cetz package to render plots and graphs. For more information
|
|
12
|
+
// on how to edit the plots see: https://typst.app/universe/package/cetz/
|
|
13
|
+
|
|
14
|
+
#import "@preview/cetz:0.3.4"
|
|
15
|
+
#import "@preview/cetz-plot:0.1.1": plot, chart
|
|
16
|
+
|
|
17
|
+
// Colors
|
|
18
|
+
//
|
|
19
|
+
// The following variables control all colors used in the document.
|
|
20
|
+
// You can modify the color codes by specifying the four RGB(A) components or by
|
|
21
|
+
// using the hexadecimal code.
|
|
22
|
+
//
|
|
23
|
+
// See more here: https://typst.app/docs/reference/visualize/color/#definitions-rgb
|
|
24
|
+
|
|
25
|
+
#let SUCCESS = rgb("#909d63")
|
|
26
|
+
#let ERROR = rgb("#bc5653")
|
|
27
|
+
|
|
28
|
+
#let CATEGORY_1 = rgb("#42a2f8")
|
|
29
|
+
#let CATEGORY_2 = rgb("#82d452")
|
|
30
|
+
#let CATEGORY_3 = rgb("#929292")
|
|
31
|
+
#let CATEGORY_4 = rgb("#f0bb40")
|
|
32
|
+
#let CATEGORY_5 = rgb("#eb4025")
|
|
33
|
+
#let CATEGORY_6 = rgb("#c33375")
|
|
34
|
+
|
|
35
|
+
#let PALETTE = (CATEGORY_1, CATEGORY_2, CATEGORY_3, CATEGORY_4, CATEGORY_5, CATEGORY_6)
|
|
36
|
+
|
|
37
|
+
// Get data
|
|
38
|
+
#let report = json(bytes(sys.inputs.report))
|
|
39
|
+
#let version = str(bytes(sys.inputs.version))
|
|
40
|
+
#let author = report.at("author")
|
|
41
|
+
#let works = report.at("works")
|
|
42
|
+
#let citation_summary = report.at("citation_summary")
|
|
43
|
+
#let open_access_summary = report.at("open_access_summary")
|
|
44
|
+
#let works_type_summary = report.at("works_type_summary")
|
|
45
|
+
#let sources_summary = report.at("sources_summary")
|
|
46
|
+
|
|
47
|
+
// Set document metadata.
|
|
48
|
+
#let description = "This document was generated using Pub Analyzer version " + version + "."
|
|
49
|
+
#set document(
|
|
50
|
+
title: "Pub Analyzer",
|
|
51
|
+
description: description,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
// Page Layout
|
|
55
|
+
#set page("us-letter")
|
|
56
|
+
#set page(flipped: true)
|
|
57
|
+
|
|
58
|
+
#set page(footer: grid(
|
|
59
|
+
columns: (1fr, 1fr),
|
|
60
|
+
align(left)[Made with #link("https://pub-analyzer.com")[_pub-analyzer_] version #version],
|
|
61
|
+
align(right)[#context counter(page).display("1")],
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// Text config
|
|
66
|
+
#set heading(numbering: "1.")
|
|
67
|
+
#set text(size: 10.5pt)
|
|
68
|
+
#set par(linebreaks: "simple", justify: true)
|
|
69
|
+
#set text(lang: "en", overhang: true, font: "New Computer Modern")
|
|
70
|
+
|
|
71
|
+
// Override reference
|
|
72
|
+
#show ref: it => {
|
|
73
|
+
let el = it.element
|
|
74
|
+
if el != none and el.func() == heading {
|
|
75
|
+
// Override heading references.
|
|
76
|
+
numbering(
|
|
77
|
+
el.numbering,
|
|
78
|
+
..counter(heading).at(el.location())
|
|
79
|
+
)
|
|
80
|
+
} else {
|
|
81
|
+
// Other references as usual.
|
|
82
|
+
it
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Shortcuts
|
|
87
|
+
#let capitalize(input) = {
|
|
88
|
+
return upper(input.first()) + input.slice(1)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
// Header
|
|
93
|
+
#grid(
|
|
94
|
+
columns: (1fr),
|
|
95
|
+
row-gutter: 11pt,
|
|
96
|
+
align: center,
|
|
97
|
+
|
|
98
|
+
[
|
|
99
|
+
#text(size: 17pt, weight: "bold")[#author.at("display_name")]
|
|
100
|
+
],
|
|
101
|
+
if author.at("last_known_institutions") != none and author.at("last_known_institutions").len() >= 1 [
|
|
102
|
+
#let last_known_institution = author.at("last_known_institutions").first()
|
|
103
|
+
#text(size: 15pt, weight: "thin")[#last_known_institution.at("display_name")]
|
|
104
|
+
]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
// Author Summary
|
|
108
|
+
= Author.
|
|
109
|
+
|
|
110
|
+
#let summary-card(title: "Title", body) = {
|
|
111
|
+
return block(
|
|
112
|
+
width: 100%, height: 150pt,
|
|
113
|
+
stroke: 1pt, radius: 2pt,
|
|
114
|
+
inset: (top: 20pt),
|
|
115
|
+
fill: rgb("e5e7eb"),
|
|
116
|
+
)[
|
|
117
|
+
#align(center)[#text(size: 12pt)[#title]]
|
|
118
|
+
#v(5pt)
|
|
119
|
+
#block(width: 100%, inset: (x: 20pt))[#body]
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Cards
|
|
124
|
+
#grid(
|
|
125
|
+
columns: (1fr, 1fr, 1fr),
|
|
126
|
+
column-gutter: 15pt,
|
|
127
|
+
|
|
128
|
+
// Last institution.
|
|
129
|
+
[
|
|
130
|
+
#summary-card(title:"Last institution:")[
|
|
131
|
+
#if author.at("last_known_institutions") != none and author.at("last_known_institutions").len() >= 1 [
|
|
132
|
+
#let last_known_institution = author.at("last_known_institutions").first()
|
|
133
|
+
#let institution_type_name = capitalize(last_known_institution.at("type"))
|
|
134
|
+
|
|
135
|
+
#grid(
|
|
136
|
+
rows: auto, row-gutter: 10pt,
|
|
137
|
+
|
|
138
|
+
[*Name:* #last_known_institution.at("display_name")],
|
|
139
|
+
[*Country:* #last_known_institution.at("country_code")],
|
|
140
|
+
[*Type:* #institution_type_name],
|
|
141
|
+
)
|
|
142
|
+
] else [
|
|
143
|
+
#text(size: 9pt, fill: luma(50%))[No associated institutions were found.]
|
|
144
|
+
]
|
|
145
|
+
]
|
|
146
|
+
],
|
|
147
|
+
|
|
148
|
+
// Author identifiers.
|
|
149
|
+
[
|
|
150
|
+
#summary-card(title:"Identifiers:")[
|
|
151
|
+
#grid(
|
|
152
|
+
rows: auto, row-gutter: 10pt,
|
|
153
|
+
..(
|
|
154
|
+
author.at("ids").pairs().filter(id => id.last() != none).map(
|
|
155
|
+
((k, v)) => grid.cell[
|
|
156
|
+
- #underline( [#link(v)[#k]] )
|
|
157
|
+
]
|
|
158
|
+
).flatten()
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
]
|
|
162
|
+
],
|
|
163
|
+
|
|
164
|
+
// Citation metrics.
|
|
165
|
+
[
|
|
166
|
+
#summary-card(title: "Citation metrics:")[
|
|
167
|
+
#let summary_stats = author.at("summary_stats")
|
|
168
|
+
|
|
169
|
+
#grid(
|
|
170
|
+
rows: auto, row-gutter: 10pt,
|
|
171
|
+
|
|
172
|
+
[*2-year mean:* #calc.round(summary_stats.at("2yr_mean_citedness"), digits: 5)],
|
|
173
|
+
[*h-index:* #summary_stats.at("h_index")],
|
|
174
|
+
[*i10 index:* #summary_stats.at("i10_index")],
|
|
175
|
+
)
|
|
176
|
+
]
|
|
177
|
+
]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
#align(center, text(11pt)[_Counts by year_])
|
|
181
|
+
#grid(
|
|
182
|
+
columns: (1fr, 1fr),
|
|
183
|
+
column-gutter: 15pt,
|
|
184
|
+
align: (auto, horizon),
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
[
|
|
188
|
+
#table(
|
|
189
|
+
columns: (1fr, 2fr, 2fr),
|
|
190
|
+
inset: 8pt,
|
|
191
|
+
align: horizon,
|
|
192
|
+
// Headers
|
|
193
|
+
[*Year*], [*Works count*], [*Cited by count*],
|
|
194
|
+
|
|
195
|
+
// Content
|
|
196
|
+
..author.at("counts_by_year").rev().slice(0, calc.min(author.at("counts_by_year").len(), 8)).map(
|
|
197
|
+
((year, works_count, cited_by_count)) => (
|
|
198
|
+
table.cell([#year]),
|
|
199
|
+
table.cell([#works_count]),
|
|
200
|
+
table.cell([#cited_by_count]),
|
|
201
|
+
)
|
|
202
|
+
).flatten()
|
|
203
|
+
)
|
|
204
|
+
],
|
|
205
|
+
grid.cell(
|
|
206
|
+
inset: (x: 10pt, bottom: 10pt, top: 2.5pt),
|
|
207
|
+
stroke: 1pt
|
|
208
|
+
)[
|
|
209
|
+
#align(center, text(10pt)[Cites by year])
|
|
210
|
+
#v(5pt)
|
|
211
|
+
#cetz.canvas(length: 100%, {
|
|
212
|
+
plot.plot(
|
|
213
|
+
size: (0.90, 0.48),
|
|
214
|
+
axis-style: "scientific-auto",
|
|
215
|
+
plot-style: (stroke: (1pt + PALETTE.at(0)),),
|
|
216
|
+
x-min: auto, x-max: auto,
|
|
217
|
+
x-tick-step: 1, y-tick-step: auto,
|
|
218
|
+
x-label: none, y-label: none,
|
|
219
|
+
{
|
|
220
|
+
plot.add((
|
|
221
|
+
..author.at("counts_by_year").rev().slice(0, calc.min(author.at("counts_by_year").len(), 8)).map(
|
|
222
|
+
((year, works_count, cited_by_count)) => (
|
|
223
|
+
(year, cited_by_count)
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
))
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
]
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
// Works
|
|
233
|
+
#pagebreak()
|
|
234
|
+
= Works.
|
|
235
|
+
|
|
236
|
+
#let works_metrics_card(title: "Title", graph, body) = {
|
|
237
|
+
grid(
|
|
238
|
+
rows: (18pt, 175pt, 60pt),
|
|
239
|
+
columns: (100%),
|
|
240
|
+
|
|
241
|
+
[
|
|
242
|
+
#block(width: 100%, height: 100%)[
|
|
243
|
+
#align(center + horizon)[#text(style: "italic")[#title]]
|
|
244
|
+
]
|
|
245
|
+
],
|
|
246
|
+
[
|
|
247
|
+
#block(width: 100%, height: 100%)[
|
|
248
|
+
#align(center + horizon)[#graph]
|
|
249
|
+
]
|
|
250
|
+
],
|
|
251
|
+
[
|
|
252
|
+
#set text(size: 9.5pt)
|
|
253
|
+
#block(width: 100%, height: 100%, inset: (x: 0pt, y: 10pt))[#body]
|
|
254
|
+
],
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#let leyend_box(color: rgb) = {
|
|
259
|
+
box(height: 7pt, width: 7pt, fill: color)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
#grid(
|
|
263
|
+
columns: (1fr, 1fr, 1fr),
|
|
264
|
+
column-gutter: 15pt,
|
|
265
|
+
|
|
266
|
+
[
|
|
267
|
+
#let graph = {
|
|
268
|
+
let type_a = citation_summary.at("type_a_count")
|
|
269
|
+
let type_b = citation_summary.at("type_b_count")
|
|
270
|
+
let total = type_a + type_b
|
|
271
|
+
|
|
272
|
+
if total == 0 {
|
|
273
|
+
cetz.canvas(length: 35%, {
|
|
274
|
+
cetz.draw.circle(
|
|
275
|
+
(0,0),
|
|
276
|
+
radius: 1,
|
|
277
|
+
stroke: luma(90%),
|
|
278
|
+
fill: luma(98%),
|
|
279
|
+
)
|
|
280
|
+
cetz.draw.content(
|
|
281
|
+
(0, 0), text("No citations found", size: 9pt, fill: luma(50%))
|
|
282
|
+
)
|
|
283
|
+
})
|
|
284
|
+
} else {
|
|
285
|
+
cetz.canvas(length: 35%, {
|
|
286
|
+
chart.piechart(
|
|
287
|
+
(type_a, type_b),
|
|
288
|
+
radius: 1,
|
|
289
|
+
slice-style: (PALETTE.at(0), PALETTE.at(1)),
|
|
290
|
+
outer-label: (content: "%", radius: 115%),
|
|
291
|
+
)
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#works_metrics_card(title: "Citation metrics", graph)[
|
|
297
|
+
#grid(
|
|
298
|
+
rows: auto, row-gutter: 10pt,
|
|
299
|
+
columns: (1fr, 1fr),
|
|
300
|
+
|
|
301
|
+
grid.cell(colspan: 2)[
|
|
302
|
+
*Count:* #citation_summary.values().sum()
|
|
303
|
+
],
|
|
304
|
+
[
|
|
305
|
+
#leyend_box(color: PALETTE.at(0)) *Type A:* #citation_summary.at("type_a_count")
|
|
306
|
+
],
|
|
307
|
+
[
|
|
308
|
+
#leyend_box(color: PALETTE.at(1)) *Type B:* #citation_summary.at("type_b_count")
|
|
309
|
+
],
|
|
310
|
+
)
|
|
311
|
+
]
|
|
312
|
+
],
|
|
313
|
+
[
|
|
314
|
+
#let graph = {
|
|
315
|
+
cetz.canvas(length: 35%, {
|
|
316
|
+
chart.columnchart(
|
|
317
|
+
size: (2.45, 2.0),
|
|
318
|
+
y-grid: false,
|
|
319
|
+
bar-style: cetz.palette.new(
|
|
320
|
+
base: (stroke: none, fill: none),
|
|
321
|
+
colors: PALETTE
|
|
322
|
+
),
|
|
323
|
+
(
|
|
324
|
+
works_type_summary.slice(0, calc.min(4, works_type_summary.len())).map(
|
|
325
|
+
((type_name, count)) => (
|
|
326
|
+
(capitalize(type_name.slice(0,2)), count)
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
#works_metrics_card(title: "Work Type", graph)[
|
|
334
|
+
#grid(
|
|
335
|
+
rows: auto, row-gutter: 10pt,
|
|
336
|
+
columns: (1fr, 1fr),
|
|
337
|
+
column-gutter: 5pt,
|
|
338
|
+
|
|
339
|
+
grid.cell(colspan: 2)[
|
|
340
|
+
*Count:* #open_access_summary.values().sum()
|
|
341
|
+
],
|
|
342
|
+
..works_type_summary.slice(0, calc.min(4, works_type_summary.len())).enumerate().map(
|
|
343
|
+
((idx, work_type)) => (
|
|
344
|
+
grid.cell([#leyend_box(color: PALETTE.at(idx)) *#capitalize(work_type.type_name):* #work_type.count])
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
]
|
|
349
|
+
],
|
|
350
|
+
[
|
|
351
|
+
#let graph = {
|
|
352
|
+
cetz.canvas(length: 35%, {
|
|
353
|
+
chart.piechart(
|
|
354
|
+
(
|
|
355
|
+
open_access_summary.diamond, // diamond
|
|
356
|
+
open_access_summary.gold, // Gold
|
|
357
|
+
open_access_summary.green, // Green
|
|
358
|
+
open_access_summary.hybrid, // Hybrid
|
|
359
|
+
open_access_summary.bronze, // Bronze
|
|
360
|
+
open_access_summary.closed, // Closed
|
|
361
|
+
),
|
|
362
|
+
radius: 1,
|
|
363
|
+
inner-radius: .4,
|
|
364
|
+
slice-style: (PALETTE.at(0), PALETTE.at(3), PALETTE.at(1), PALETTE.at(4), PALETTE.at(5), PALETTE.at(2)),
|
|
365
|
+
outer-label: (content: "%", radius: 115%),
|
|
366
|
+
)
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
#works_metrics_card(title: "Open Access", graph)[
|
|
371
|
+
#grid(
|
|
372
|
+
rows: auto, row-gutter: 10pt,
|
|
373
|
+
columns: (1.17fr, 1fr, 1fr),
|
|
374
|
+
column-gutter: 5pt,
|
|
375
|
+
|
|
376
|
+
grid.cell(colspan: 3)[
|
|
377
|
+
*Count:* #open_access_summary.values().sum()
|
|
378
|
+
],
|
|
379
|
+
|
|
380
|
+
[#leyend_box(color: PALETTE.at(0)) *Diamond:* #open_access_summary.diamond],
|
|
381
|
+
[#leyend_box(color: PALETTE.at(3)) *Gold:* #open_access_summary.gold],
|
|
382
|
+
[#leyend_box(color: PALETTE.at(1)) *Green:* #open_access_summary.green],
|
|
383
|
+
[#leyend_box(color: PALETTE.at(5)) *Bronze:* #open_access_summary.bronze],
|
|
384
|
+
[#leyend_box(color: PALETTE.at(2)) *Closed:* #open_access_summary.closed],
|
|
385
|
+
[#leyend_box(color: PALETTE.at(4)) *Hybrid:* #open_access_summary.hybrid],
|
|
386
|
+
)
|
|
387
|
+
]
|
|
388
|
+
]
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
#let first-pub-year(works, default: "-") = {
|
|
392
|
+
for w in works {
|
|
393
|
+
if w.work.publication_year != none {
|
|
394
|
+
return w.work.publication_year
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
default
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
#let last-pub-year(works, default: "-") = {
|
|
401
|
+
for w in works.rev() {
|
|
402
|
+
if w.work.publication_year != none {
|
|
403
|
+
return w.work.publication_year
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
default
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
#align(
|
|
410
|
+
center,
|
|
411
|
+
text(11pt)[
|
|
412
|
+
Works from #first-pub-year(works) to #last-pub-year(works)
|
|
413
|
+
]
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
#{
|
|
417
|
+
set text(size: 10pt)
|
|
418
|
+
table(
|
|
419
|
+
columns: (auto, 2fr, auto, auto, auto, auto, auto, auto, auto),
|
|
420
|
+
inset: 8pt,
|
|
421
|
+
align: horizon,
|
|
422
|
+
// Headers
|
|
423
|
+
[], [*Title*], [*Type*], [*DOI*], [*Publication Date*], [*Cited by count*], [*Type A*], [*Type B*], [*OA*],
|
|
424
|
+
|
|
425
|
+
// Content
|
|
426
|
+
..works.enumerate().map(
|
|
427
|
+
((idx, work)) => (
|
|
428
|
+
table.cell([#underline[#link(label("work_" + str(idx)))[#ref(label("work_" + str(idx)))]]]),
|
|
429
|
+
table.cell([#work.work.title]),
|
|
430
|
+
table.cell([#work.work.type]),
|
|
431
|
+
table.cell([#if work.work.ids.doi != none [#underline[#link(work.work.ids.doi)[DOI]]] else [#align(center)[-]]]),
|
|
432
|
+
table.cell([#work.work.publication_date]),
|
|
433
|
+
table.cell([#work.citation_summary.values().sum()]),
|
|
434
|
+
table.cell([#work.citation_summary.type_a_count]),
|
|
435
|
+
table.cell([#work.citation_summary.type_b_count]),
|
|
436
|
+
table.cell([#work.work.open_access.oa_status]),
|
|
437
|
+
)
|
|
438
|
+
).flatten()
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Works Extended
|
|
443
|
+
#let work_driven_version = (
|
|
444
|
+
submittedVersion: "submitted",
|
|
445
|
+
acceptedVersion: "accepted",
|
|
446
|
+
publishedVersion: "published"
|
|
447
|
+
)
|
|
448
|
+
#for (idx, work_report) in works.enumerate() [
|
|
449
|
+
#let work = work_report.work
|
|
450
|
+
|
|
451
|
+
#pagebreak()
|
|
452
|
+
#heading(level: 2)[#work.title] #label("work_" + str(idx))
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
#if work.abstract != none [
|
|
456
|
+
#v(5pt)
|
|
457
|
+
#work.abstract
|
|
458
|
+
]
|
|
459
|
+
|
|
460
|
+
// Cards
|
|
461
|
+
#v(5pt)
|
|
462
|
+
#grid(
|
|
463
|
+
columns: (1fr, 1fr, 1fr),
|
|
464
|
+
column-gutter: 30pt,
|
|
465
|
+
|
|
466
|
+
[
|
|
467
|
+
#align(center)[_Authorships_]
|
|
468
|
+
#block()[
|
|
469
|
+
#for authorship in work.authorships.slice(0, calc.min(10, work.authorships.len())) [
|
|
470
|
+
#let author_link = if authorship.author.at("orcid") != none {
|
|
471
|
+
authorship.author.orcid
|
|
472
|
+
} else {
|
|
473
|
+
authorship.author.id
|
|
474
|
+
}
|
|
475
|
+
- *#authorship.author_position:* #underline[#link(author_link)[#if authorship.author.display_name == author.display_name [#text(rgb(SUCCESS))[#authorship.author.display_name]] else [#authorship.author.display_name]]]
|
|
476
|
+
]
|
|
477
|
+
#if work.authorships.len() > 10 [- *...*]
|
|
478
|
+
]
|
|
479
|
+
],
|
|
480
|
+
[
|
|
481
|
+
#align(center)[_Open Access_]
|
|
482
|
+
|
|
483
|
+
- *Status:* #capitalize(work.open_access.oa_status)
|
|
484
|
+
#if work.open_access.oa_url != none [- *URL:* #underline[#link(work.open_access.oa_url)[#work.open_access.oa_url.find(regex("^(https?:\/\/[^\/]+\/)"))]]]
|
|
485
|
+
],
|
|
486
|
+
[
|
|
487
|
+
#align(center)[_Citation_]
|
|
488
|
+
|
|
489
|
+
- *Count:* #work_report.citation_summary.values().sum()
|
|
490
|
+
- *Type A:* #work_report.citation_summary.type_a_count
|
|
491
|
+
- *Type B:* #work_report.citation_summary.type_b_count
|
|
492
|
+
]
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
// Cited by Table
|
|
496
|
+
#if work_report.cited_by.len() >= 1 [
|
|
497
|
+
#align(center, text(11pt)[_Cited by_])
|
|
498
|
+
|
|
499
|
+
#table(
|
|
500
|
+
columns: (auto, 3fr, 0.8fr, auto, auto, auto, auto),
|
|
501
|
+
inset: 8pt,
|
|
502
|
+
align: horizon,
|
|
503
|
+
// Headers
|
|
504
|
+
[], [*Title*], [*Type*], [*DOI*], [*Cite Type*], [*Publication Date*], [*Cited by count*],
|
|
505
|
+
|
|
506
|
+
// Content
|
|
507
|
+
..work_report.cited_by.enumerate(start: 1).map(
|
|
508
|
+
((idx, cited_by)) => (
|
|
509
|
+
table.cell([#idx]),
|
|
510
|
+
table.cell([#cited_by.work.title]),
|
|
511
|
+
table.cell([#cited_by.work.type]),
|
|
512
|
+
table.cell([#if cited_by.work.ids.doi != none [#underline[#link(cited_by.work.ids.doi)[DOI]]] else [#align(center)[-]]]),
|
|
513
|
+
table.cell([#if cited_by.citation_type == 0 [#text(rgb(SUCCESS))[Type A]] else [#text(rgb(ERROR))[Type B]]]),
|
|
514
|
+
table.cell([#cited_by.work.publication_date]),
|
|
515
|
+
table.cell([#cited_by.work.cited_by_count]),
|
|
516
|
+
)
|
|
517
|
+
).flatten()
|
|
518
|
+
)
|
|
519
|
+
]
|
|
520
|
+
|
|
521
|
+
// Sources Table
|
|
522
|
+
#if work.locations.len() >= 1 [
|
|
523
|
+
#align(center, text(11pt)[_Sources_])
|
|
524
|
+
#table(
|
|
525
|
+
columns: (auto, 3fr, 2.5fr, 1fr, auto, auto, 1.2fr, auto),
|
|
526
|
+
inset: 8pt,
|
|
527
|
+
align: horizon,
|
|
528
|
+
// Headers
|
|
529
|
+
[], [*Name*], [*Publisher or institution*], [*Type*], [*ISSN-L*], [*Is OA*], [*License*], [*Version*],
|
|
530
|
+
|
|
531
|
+
// Content
|
|
532
|
+
..work.locations.enumerate(start: 1).filter((location => location.last().source != none)).map(
|
|
533
|
+
((idx, location)) => (
|
|
534
|
+
table.cell([#underline[#link(label("source_" + location.source.id.find(regex("S\d+$"))))[#idx]]]),
|
|
535
|
+
table.cell([#location.source.display_name]),
|
|
536
|
+
table.cell([#if location.source.host_organization_name != none [#location.source.host_organization_name] else [-]]),
|
|
537
|
+
table.cell([#location.source.type]),
|
|
538
|
+
table.cell([#if location.source.issn_l != none [#location.source.issn_l] else [-]]),
|
|
539
|
+
table.cell([#if location.is_oa [#text(rgb(SUCCESS))[True]] else [#text(rgb(ERROR))[False]]]),
|
|
540
|
+
table.cell([#if location.license != none [#location.license] else [-]]),
|
|
541
|
+
table.cell([#if location.version != none [#work_driven_version.at(location.version)]]),
|
|
542
|
+
)
|
|
543
|
+
).flatten(),
|
|
544
|
+
..work.locations.enumerate(start: 1).filter((location => location.last().source == none)).map(
|
|
545
|
+
((idx, location)) => (
|
|
546
|
+
table.cell([#idx]),
|
|
547
|
+
table.cell([#underline([#link(location.landing_page_url)[#location.landing_page_url]])]),
|
|
548
|
+
table.cell([-]),
|
|
549
|
+
table.cell([-]),
|
|
550
|
+
table.cell([-]),
|
|
551
|
+
table.cell([#if location.is_oa [#text(rgb(SUCCESS))[True]] else [#text(rgb(ERROR))[False]]]),
|
|
552
|
+
table.cell([#if location.license != none [#location.license] else [-]]),
|
|
553
|
+
table.cell([#if location.version != none [#work_driven_version.at(location.version)]]),
|
|
554
|
+
)
|
|
555
|
+
).flatten()
|
|
556
|
+
)
|
|
557
|
+
]
|
|
558
|
+
]
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
// Sources
|
|
562
|
+
#pagebreak()
|
|
563
|
+
= Sources.
|
|
564
|
+
|
|
565
|
+
#table(
|
|
566
|
+
columns: (auto, 2.7fr, 2.56fr, 1.2fr, auto, auto, auto, auto),
|
|
567
|
+
inset: 8pt,
|
|
568
|
+
align: horizon,
|
|
569
|
+
// Headers
|
|
570
|
+
[], [*Name*], [*Publisher or institution*], [*Type*], [*ISSN-L*], [*Impact factor*], [*h-index*], [*Is OA*],
|
|
571
|
+
|
|
572
|
+
// Content
|
|
573
|
+
..sources_summary.sources.enumerate(start: 1).map(
|
|
574
|
+
((idx, source)) => (
|
|
575
|
+
table.cell([3.#idx. #label("source_" + source.id.find(regex("S\d+$")))]),
|
|
576
|
+
table.cell([#if source.homepage_url != none [#underline[#link(source.homepage_url)[#source.display_name]]] else [#underline[#link(source.id)[#source.display_name]]]]),
|
|
577
|
+
table.cell([#if source.host_organization_name != none [#source.host_organization_name] else [-]]),
|
|
578
|
+
table.cell([#if source.type != none [#source.type] else [-]]),
|
|
579
|
+
table.cell([#if source.issn_l != none [#source.issn_l] else [-]]),
|
|
580
|
+
table.cell([#calc.round(source.summary_stats.at("2yr_mean_citedness"), digits: 3)]),
|
|
581
|
+
table.cell([#source.summary_stats.h_index]),
|
|
582
|
+
table.cell([#if source.is_oa [#text(rgb(SUCCESS))[True]] else [#text(rgb(ERROR))[False]]]),
|
|
583
|
+
)
|
|
584
|
+
).flatten()
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
#pagebreak()
|
|
589
|
+
= Bibliography
|
|
590
|
+
|
|
591
|
+
Priem, J., Piwowar, H., & Orr, R. (2022). OpenAlex: A fully-open index of scholarly works, authors, venues, institutions, and concepts. ArXiv. https://arxiv.org/abs/2205.01833
|
pub_analyzer/main.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Entry Point."""
|
|
2
|
+
|
|
3
|
+
import urllib.parse
|
|
4
|
+
import webbrowser
|
|
5
|
+
from typing import ClassVar
|
|
6
|
+
|
|
7
|
+
from textual import log
|
|
8
|
+
from textual._path import CSSPathType
|
|
9
|
+
from textual.app import App, ComposeResult
|
|
10
|
+
from textual.binding import Binding, BindingType
|
|
11
|
+
from textual.dom import DOMNode
|
|
12
|
+
from textual.reactive import Reactive
|
|
13
|
+
from textual.widgets import Footer
|
|
14
|
+
|
|
15
|
+
from pub_analyzer.widgets.body import Body
|
|
16
|
+
from pub_analyzer.widgets.sidebar import SideBar
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PubAnalyzerApp(App[DOMNode]):
|
|
20
|
+
"""Pub Analyzer App entrypoint."""
|
|
21
|
+
|
|
22
|
+
TITLE = "Pub Analyzer"
|
|
23
|
+
ENABLE_COMMAND_PALETTE = False
|
|
24
|
+
|
|
25
|
+
CSS_PATH: ClassVar[CSSPathType | None] = [
|
|
26
|
+
"css/body.tcss",
|
|
27
|
+
"css/buttons.tcss",
|
|
28
|
+
"css/checkbox.tcss",
|
|
29
|
+
"css/collapsible.tcss",
|
|
30
|
+
"css/datatable.tcss",
|
|
31
|
+
"css/editor.tcss",
|
|
32
|
+
"css/main.tcss",
|
|
33
|
+
"css/report.tcss",
|
|
34
|
+
"css/search.tcss",
|
|
35
|
+
"css/summary.tcss",
|
|
36
|
+
"css/tabs.tcss",
|
|
37
|
+
"css/tree.tcss",
|
|
38
|
+
]
|
|
39
|
+
BINDINGS: ClassVar[list[BindingType]] = [
|
|
40
|
+
Binding(key="ctrl+d", action="toggle_dark", description="Dark mode"),
|
|
41
|
+
Binding(key="ctrl+s", action="toggle_sidebar", description="Sidebar"),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
dark: Reactive[bool] = Reactive(False)
|
|
45
|
+
|
|
46
|
+
def compose(self) -> ComposeResult:
|
|
47
|
+
"""Create child widgets for the app."""
|
|
48
|
+
yield Body()
|
|
49
|
+
yield Footer(show_command_palette=False)
|
|
50
|
+
|
|
51
|
+
def action_toggle_dark(self) -> None:
|
|
52
|
+
"""Toggle dark mode."""
|
|
53
|
+
self.dark = not self.dark
|
|
54
|
+
|
|
55
|
+
def action_toggle_sidebar(self) -> None:
|
|
56
|
+
"""Toggle sidebar."""
|
|
57
|
+
self.set_focus(None)
|
|
58
|
+
|
|
59
|
+
sidebar = self.query_one(SideBar)
|
|
60
|
+
sidebar.toggle()
|
|
61
|
+
|
|
62
|
+
def action_save_screenshot(self) -> None:
|
|
63
|
+
"""Take Screenshot."""
|
|
64
|
+
file_path = self.app.save_screenshot()
|
|
65
|
+
self.app.notify(
|
|
66
|
+
title="Screenshot saved!", message=f"You can see the screenshot at {file_path}", severity="information", timeout=10.0
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def action_open_link(self, link: str) -> None:
|
|
70
|
+
"""Open a link in the browser."""
|
|
71
|
+
log.info(f"Opening link: {link}")
|
|
72
|
+
if link and (link != "None"):
|
|
73
|
+
webbrowser.open(urllib.parse.unquote(link))
|
|
74
|
+
else:
|
|
75
|
+
log.warning("Link cannot be empty!")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def run() -> None:
|
|
79
|
+
"""Run Pub Analyzer App."""
|
|
80
|
+
app = PubAnalyzerApp()
|
|
81
|
+
app.run()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Scholarly entities models definitions from OpenAlex."""
|