|
13 | 13 | (:require [clojure.string :as str]
|
14 | 14 | [nextjournal.clerk :as clerk]
|
15 | 15 | [nextjournal.clerk.viewer :as v]
|
16 |
| - [mundaneum.query :refer [describe entity label property query]] |
| 16 | + [applied-science.mundaneum.properties :refer [wdt]] |
| 17 | + [applied-science.mundaneum.query :refer [describe entity label query]] |
17 | 18 | [arrowic.core :as arr]))
|
18 | 19 |
|
19 | 20 | ;; Now we can ask questions, like "what is James Clerk Maxwell famous
|
20 | 21 | ;; for having invented or discovered?"
|
21 | 22 |
|
22 |
| -(query '[:select ?what |
23 |
| - :where [[?what (wdt :discoverer-or-inventor) (entity "James Clerk Maxwell")]]]) |
| 23 | +(query `{:select [?what] |
| 24 | + :where [[?what ~(wdt :discoverer-or-inventor) ~(entity "James Clerk Maxwell")]]}) |
24 | 25 |
|
25 |
| -;; The WikiData internal ID `Q1080745` doesn't immediately mean much |
| 26 | +;; The WikiData internal ID `:wd/Q1080745` doesn't immediately mean much |
26 | 27 | ;; to a human, so we'll try again by appending `Label` to the end of
|
27 | 28 | ;; the `?what` logic variable so we can see a human readable label
|
28 | 29 | ;; that item:
|
29 | 30 |
|
30 |
| -(query '[:select ?whatLabel |
31 |
| - :where [[?what (wdt :discoverer-or-inventor) (entity "James Clerk Maxwell")]]]) |
| 31 | +(query `{:select [?whatLabel] |
| 32 | + :where [[?what ~(wdt :discoverer-or-inventor) ~(entity "James Clerk Maxwell")]]}) |
32 | 33 |
|
33 | 34 | ;; Ah, better. 😊 This ceremony is required because WikiData uses a
|
34 | 35 | ;; language-neutral data representation internally, leaving us with an
|
|
37 | 38 | ;; label in every language for which it has been specified in
|
38 | 39 | ;; WikiData:
|
39 | 40 |
|
40 |
| -(query '[:select ?what ?label |
41 |
| - :where [[?what (wdt :discoverer-or-inventor) (entity "James Clerk Maxwell")] |
42 |
| - [?what rdfs:label ?label]]]) |
| 41 | +(query `{:select [?what ?label] |
| 42 | + :where [[?what ~(wdt :discoverer-or-inventor) ~(entity "James Clerk Maxwell")] |
| 43 | + [?what :rdfs/label ?label]]}) |
43 | 44 |
|
44 | 45 | ;; One of the nice things about data encoded as a knowledge graph is
|
45 | 46 | ;; that we can ask questions that are difficult to pose any other way,
|
|
49 | 50 | ;; invented by anyone who has as one of their occupations "physicist":
|
50 | 51 |
|
51 | 52 | (def inventions-and-discoveries
|
52 |
| - (->> (query '[:select ?whatLabel ?whomLabel |
53 |
| - :where [[?what (wdt :discoverer-or-inventor) ?whom] |
54 |
| - [?whom (wdt :occupation) (entity "physicist")]] |
55 |
| - :limit 500]))) |
| 53 | + (->> (query `{:select [?whatLabel ?whomLabel] |
| 54 | + :where [[?what ~(wdt :discoverer-or-inventor) ?whom] |
| 55 | + [?whom ~(wdt :occupation) ~(entity "physicist")]] |
| 56 | + :limit 500}))) |
56 | 57 |
|
57 | 58 | ;; ## Tabular data
|
58 | 59 |
|
|
81 | 82 | ;; language.
|
82 | 83 |
|
83 | 84 | (def slavic-place-names
|
84 |
| - (->> (query |
85 |
| - '[:select * |
86 |
| - :where [[?ort (wdt :instance-of) / (wdt :subclass-of) * (entity "human settlement") |
87 |
| - _ (wdt :country) (entity "Germany") |
88 |
| - _ rdfs:label ?name |
89 |
| - _ (wdt :coordinate-location) ?lonlat] |
90 |
| - :filter ((lang ?name) = "de") |
91 |
| - :filter ((regex ?name "(ow|itz)$"))] |
92 |
| - :limit 1000]) |
| 85 | + (->> `{:select * |
| 86 | + :where [{?ort {(cat ~(wdt :instance-of) (* ~(wdt :subclass-of))) #{~(entity "human settlement")} |
| 87 | + ~(wdt :country) #{~(entity "Germany")} |
| 88 | + :rdfs/label #{?name} |
| 89 | + ~(wdt :coordinate-location) #{?lonlat}}} |
| 90 | + [:filter (= (lang ?name) "de")] |
| 91 | + [:filter (regex ?name "(ow|itz)$")]] |
| 92 | + :limit 1000} |
| 93 | + query |
93 | 94 | ;; cleanup lon-lat formatting for map plot!
|
94 |
| - (mapv #(let [[lon lat] (-> (:lonlat %) |
| 95 | + (mapv #(let [[lon lat] (-> % |
| 96 | + :lonlat |
| 97 | + :value |
95 | 98 | (str/replace #"[^0-9 \.]" "")
|
96 | 99 | (str/split #" "))]
|
97 | 100 | {:name (:name %) :latitude lat :longitude lon}))))
|
|
114 | 117 | :data {:values slavic-place-names}}]})
|
115 | 118 |
|
116 | 119 | ;; Sometimes the data needs a more customized view. Happily, we can
|
117 |
| -;; write arbitrary hiccup to be rendered in Clerk. First, we'll make a |
118 |
| -;; helper function to convert the style of image url we receive from |
119 |
| -;; WikiData into proper Wiki Commons URLs with a fixed width: |
120 |
| - |
121 |
| -(defn wiki-image |
122 |
| - "Helper that takes an image path `url` and creates a full wikimedia commons URL from it, optionally specifying a particular `width`." |
123 |
| - ([url width] (str "https://commons.wikimedia.org/w/index.php?title=Special:Redirect/file/" |
124 |
| - url |
125 |
| - "&width=" |
126 |
| - width)) |
127 |
| - ([url] (wiki-image url 300))) |
128 |
| - |
129 |
| -;; And now we can use this query to fetch a list of different species |
130 |
| -;; of _Apodiformes_ (swifts and hummingbirds), returning a name, |
131 |
| -;; image, and map of home range for each one. |
132 |
| - |
133 |
| -(->> (query '[:select :distinct ?item ?itemLabel ?pic ?range |
134 |
| - :where [[?item (wdt :parent-taxon) * (entity "Apodiformes") |
135 |
| - _ (wdt :taxon-rank) (entity "species") |
136 |
| - _ rdfs:label ?englishName |
137 |
| - _ (wdt :image) ?pic |
138 |
| - _ (wdt :taxon-range-map-image) ?range] |
139 |
| - :filter ((lang ?englishName) = "en")] |
140 |
| - :limit 10]) |
| 120 | +;; write arbitrary hiccup to be rendered in Clerk. We'll use this |
| 121 | +;; query to fetch a list of different species of _Apodiformes_ (swifts |
| 122 | +;; and hummingbirds), returning a name, image, and map of home range |
| 123 | +;; for each one. |
| 124 | + |
| 125 | +(->> (query `{:select-distinct [?item ?itemLabel ?pic ?range] |
| 126 | + :where [[?item (* ~(wdt :parent-taxon)) ~(entity "Apodiformes")] |
| 127 | + [?item ~(wdt :taxon-rank) ~(entity "species")] |
| 128 | + [?item :rdfs/label ?englishName] |
| 129 | + [?item ~(wdt :image) ?pic] |
| 130 | + [?item ~(wdt :taxon-range-map-image) ?range] |
| 131 | + [:filter (= (lang ?englishName) "en")]] |
| 132 | + :limit 11}) |
141 | 133 | (mapv #(vector :tr
|
142 |
| - [:td (:itemLabel %)] |
143 |
| - [:td [:img {:src (wiki-image (:pic %))}]] |
144 |
| - [:td [:img {:src (wiki-image (:range %))}]])) |
| 134 | + [:td.w-32 (:itemLabel %)] |
| 135 | + [:td [:img.w-80 {:src (:pic %)}]] |
| 136 | + [:td [:img.w-80 {:src (:range %)}]])) |
145 | 137 | (into [:table])
|
146 | 138 | clerk/html)
|
147 | 139 |
|
|
161 | 153 | ;; see all the languages.
|
162 | 154 |
|
163 | 155 | (-> (clerk/html
|
164 |
| - (let [data (query '[:select ?itemLabel ?influencedByLabel |
165 |
| - :where [[?item (wdt :influenced-by) * (entity "Lisp") |
166 |
| - _ (wdt :influenced-by) ?influencedBy] |
167 |
| - [?influencedBy (wdt :influenced-by) * (entity "Lisp")]]])] |
| 156 | + (let [data (query `{:select [?itemLabel ?influencedByLabel] |
| 157 | + :where [[?item (* ~(wdt :influenced-by)) ~(entity "Lisp")] |
| 158 | + [?item ~(wdt :influenced-by) ?influencedBy] |
| 159 | + [?influencedBy (* ~(wdt :influenced-by)) ~(entity "Lisp")]]})] |
168 | 160 | (arr/as-svg
|
169 | 161 | (arr/with-graph (arr/create-graph)
|
170 | 162 | (let [vertex (->> (mapcat (juxt :itemLabel :influencedByLabel) data)
|
|
0 commit comments