Initial Commit
Browse files- app.py +26 -0
- config.json +251 -0
- data.py +68 -0
- network.py +61 -0
- requirements.txt +7 -0
app.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
import gradio as gr
|
5 |
+
import pygwalker as pyg
|
6 |
+
|
7 |
+
|
8 |
+
from pygwalker.api.gradio import PYGWALKER_ROUTE, get_html_on_gradio
|
9 |
+
|
10 |
+
|
11 |
+
from data import df
|
12 |
+
from network import analysis
|
13 |
+
|
14 |
+
|
15 |
+
with gr.Blocks() as demo:
|
16 |
+
# with gr.Tab("π About"):
|
17 |
+
# # gr.Markdown(ABOUT_TEXT)
|
18 |
+
with gr.Tab("π Dashboard"):
|
19 |
+
gr.Label("Visually explore witches family data")
|
20 |
+
gr.Markdown("You can use drag-and-drop operations to explore the data, start your analysis now!")
|
21 |
+
pyg_app = get_html_on_gradio(df, spec="./config.json")
|
22 |
+
gr.HTML(pyg_app)
|
23 |
+
with gr.Tab("πͺ Family Connection"):
|
24 |
+
gr.HTML(analysis)
|
25 |
+
|
26 |
+
demo.launch(app_kwargs={"routes": [PYGWALKER_ROUTE]}).queue()
|
config.json
ADDED
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
{
|
3 |
+
"config": {
|
4 |
+
"defaultAggregated": true,
|
5 |
+
"geoms": [
|
6 |
+
"poi"
|
7 |
+
],
|
8 |
+
"coordSystem": "geographic",
|
9 |
+
"limit": -1
|
10 |
+
},
|
11 |
+
"encodings": {
|
12 |
+
"dimensions": [
|
13 |
+
{
|
14 |
+
"dragId": "gw_0oFN",
|
15 |
+
"fid": "GW_153M2HTVSG",
|
16 |
+
"name": "item",
|
17 |
+
"basename": "item",
|
18 |
+
"semanticType": "nominal",
|
19 |
+
"analyticType": "dimension"
|
20 |
+
},
|
21 |
+
{
|
22 |
+
"dragId": "gw_TfYx",
|
23 |
+
"fid": "GW_2X73E2RH6IPJOOK4G8G",
|
24 |
+
"name": "accusedurl",
|
25 |
+
"basename": "accusedurl",
|
26 |
+
"semanticType": "nominal",
|
27 |
+
"analyticType": "dimension"
|
28 |
+
},
|
29 |
+
{
|
30 |
+
"dragId": "gw_MK3G",
|
31 |
+
"fid": "GW_G0O5DCTUJ01U6ZK8G",
|
32 |
+
"name": "itemLabel",
|
33 |
+
"basename": "itemLabel",
|
34 |
+
"semanticType": "nominal",
|
35 |
+
"analyticType": "dimension"
|
36 |
+
},
|
37 |
+
{
|
38 |
+
"dragId": "gw_4Ud1",
|
39 |
+
"fid": "GW_6RT7I4HK7073D9RPOESM5CH80",
|
40 |
+
"name": "residence",
|
41 |
+
"basename": "residence",
|
42 |
+
"semanticType": "nominal",
|
43 |
+
"analyticType": "dimension"
|
44 |
+
},
|
45 |
+
{
|
46 |
+
"dragId": "gw_fW-1",
|
47 |
+
"fid": "GW_M27TBC9KBOWJRWX5WS34",
|
48 |
+
"name": "gender",
|
49 |
+
"basename": "gender",
|
50 |
+
"semanticType": "nominal",
|
51 |
+
"analyticType": "dimension"
|
52 |
+
},
|
53 |
+
{
|
54 |
+
"dragId": "gw_YRLn",
|
55 |
+
"fid": "GW_2ZE8EA85LWGIU48N1GW",
|
56 |
+
"name": "class",
|
57 |
+
"basename": "class",
|
58 |
+
"semanticType": "nominal",
|
59 |
+
"analyticType": "dimension"
|
60 |
+
},
|
61 |
+
{
|
62 |
+
"dragId": "gw_WKTM",
|
63 |
+
"fid": "GW_5498MX0A38QWHP82WQA4T7971KAQCP16WRJZZU8",
|
64 |
+
"name": "place_of_detention",
|
65 |
+
"basename": "place_of_detention",
|
66 |
+
"semanticType": "nominal",
|
67 |
+
"analyticType": "dimension"
|
68 |
+
},
|
69 |
+
{
|
70 |
+
"dragId": "gw_u9Xa",
|
71 |
+
"fid": "GW_R8BM6NZ296ISPYXOCTXCNQEAVA6142MCW7RHG7RLLBK26Z9POPYCZ4",
|
72 |
+
"name": "manner_of_inhumane_treatment",
|
73 |
+
"basename": "manner_of_inhumane_treatment",
|
74 |
+
"semanticType": "nominal",
|
75 |
+
"analyticType": "dimension"
|
76 |
+
},
|
77 |
+
{
|
78 |
+
"dragId": "gw_DUxe",
|
79 |
+
"fid": "GW_2AJUGNLE6MEPTZNHZAYG8IEPPSCE4MO1C",
|
80 |
+
"name": "cause_of_death",
|
81 |
+
"basename": "cause_of_death",
|
82 |
+
"semanticType": "nominal",
|
83 |
+
"analyticType": "dimension"
|
84 |
+
},
|
85 |
+
{
|
86 |
+
"dragId": "gw_wtxd",
|
87 |
+
"fid": "GW_1AW6HAPIZKT3VHC18C33DQM3968",
|
88 |
+
"name": "occupation",
|
89 |
+
"basename": "occupation",
|
90 |
+
"semanticType": "nominal",
|
91 |
+
"analyticType": "dimension"
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"dragId": "gw_EEfP",
|
95 |
+
"fid": "GW_OMQ0RHNFV2PIFPFR6A0G",
|
96 |
+
"name": "spouse",
|
97 |
+
"basename": "spouse",
|
98 |
+
"semanticType": "nominal",
|
99 |
+
"analyticType": "dimension"
|
100 |
+
},
|
101 |
+
{
|
102 |
+
"dragId": "gw_AtER",
|
103 |
+
"fid": "GW_GGYX5I6DWC5400P4G",
|
104 |
+
"name": "longitude",
|
105 |
+
"basename": "longitude",
|
106 |
+
"semanticType": "quantitative",
|
107 |
+
"analyticType": "dimension"
|
108 |
+
},
|
109 |
+
{
|
110 |
+
"dragId": "gw_q-LU",
|
111 |
+
"fid": "GW_2BCEDJ2KYSIN2ZHS",
|
112 |
+
"name": "latitude",
|
113 |
+
"basename": "latitude",
|
114 |
+
"semanticType": "quantitative",
|
115 |
+
"analyticType": "dimension"
|
116 |
+
},
|
117 |
+
{
|
118 |
+
"fid": "GW_1K1RWLJMRKVG0",
|
119 |
+
"name": "father",
|
120 |
+
"semanticType": "nominal",
|
121 |
+
"analyticType": "dimension",
|
122 |
+
"basename": "father",
|
123 |
+
"dragId": "GW_BFrlUJKd"
|
124 |
+
},
|
125 |
+
{
|
126 |
+
"fid": "GW_CHAWSEHZCMSO4W",
|
127 |
+
"name": "sibling",
|
128 |
+
"semanticType": "nominal",
|
129 |
+
"analyticType": "dimension",
|
130 |
+
"basename": "sibling",
|
131 |
+
"dragId": "GW_enUhIyMF"
|
132 |
+
},
|
133 |
+
{
|
134 |
+
"fid": "GW_7NIDHER5RU8",
|
135 |
+
"name": "child",
|
136 |
+
"semanticType": "nominal",
|
137 |
+
"analyticType": "dimension",
|
138 |
+
"basename": "child",
|
139 |
+
"dragId": "GW_c7Noxzdq"
|
140 |
+
},
|
141 |
+
{
|
142 |
+
"fid": "GW_1NWT9FT2YJVAO",
|
143 |
+
"name": "mother",
|
144 |
+
"semanticType": "nominal",
|
145 |
+
"analyticType": "dimension",
|
146 |
+
"basename": "mother",
|
147 |
+
"dragId": "GW_YApxzLXH"
|
148 |
+
}
|
149 |
+
],
|
150 |
+
"measures": [
|
151 |
+
{
|
152 |
+
"dragId": "gw_count_fid",
|
153 |
+
"fid": "gw_count_fid",
|
154 |
+
"name": "Row count",
|
155 |
+
"analyticType": "measure",
|
156 |
+
"semanticType": "quantitative",
|
157 |
+
"aggName": "sum",
|
158 |
+
"computed": true,
|
159 |
+
"expression": {
|
160 |
+
"op": "one",
|
161 |
+
"params": [],
|
162 |
+
"as": "gw_count_fid"
|
163 |
+
}
|
164 |
+
},
|
165 |
+
{
|
166 |
+
"dragId": "gw_mea_val_fid",
|
167 |
+
"fid": "GW_4KYB9KVI3CK37NQAUJ65UJWVK",
|
168 |
+
"name": "Measure values",
|
169 |
+
"analyticType": "measure",
|
170 |
+
"semanticType": "quantitative",
|
171 |
+
"aggName": "sum"
|
172 |
+
}
|
173 |
+
],
|
174 |
+
"rows": [],
|
175 |
+
"columns": [],
|
176 |
+
"color": [
|
177 |
+
{
|
178 |
+
"dragId": "gw_I8g7",
|
179 |
+
"fid": "GW_2ZE8EA85LWGIU48N1GW",
|
180 |
+
"name": "class",
|
181 |
+
"basename": "class",
|
182 |
+
"semanticType": "nominal",
|
183 |
+
"analyticType": "dimension"
|
184 |
+
}
|
185 |
+
],
|
186 |
+
"opacity": [],
|
187 |
+
"size": [],
|
188 |
+
"shape": [],
|
189 |
+
"radius": [],
|
190 |
+
"theta": [],
|
191 |
+
"longitude": [
|
192 |
+
{
|
193 |
+
"dragId": "gw_y2hj",
|
194 |
+
"fid": "GW_GGYX5I6DWC5400P4G",
|
195 |
+
"name": "longitude",
|
196 |
+
"basename": "longitude",
|
197 |
+
"semanticType": "quantitative",
|
198 |
+
"analyticType": "dimension"
|
199 |
+
}
|
200 |
+
],
|
201 |
+
"latitude": [
|
202 |
+
{
|
203 |
+
"dragId": "gw_vshP",
|
204 |
+
"fid": "GW_2BCEDJ2KYSIN2ZHS",
|
205 |
+
"name": "latitude",
|
206 |
+
"basename": "latitude",
|
207 |
+
"semanticType": "quantitative",
|
208 |
+
"analyticType": "dimension"
|
209 |
+
}
|
210 |
+
],
|
211 |
+
"geoId": [],
|
212 |
+
"details": [
|
213 |
+
{
|
214 |
+
"dragId": "gw_KQnh",
|
215 |
+
"fid": "GW_6RT7I4HK7073D9RPOESM5CH80",
|
216 |
+
"name": "residence",
|
217 |
+
"basename": "residence",
|
218 |
+
"semanticType": "nominal",
|
219 |
+
"analyticType": "dimension"
|
220 |
+
}
|
221 |
+
],
|
222 |
+
"filters": [],
|
223 |
+
"text": []
|
224 |
+
},
|
225 |
+
"layout": {
|
226 |
+
"showActions": false,
|
227 |
+
"showTableSummary": false,
|
228 |
+
"stack": "stack",
|
229 |
+
"interactiveScale": false,
|
230 |
+
"zeroScale": true,
|
231 |
+
"size": {
|
232 |
+
"mode": "auto",
|
233 |
+
"width": 800,
|
234 |
+
"height": 800
|
235 |
+
},
|
236 |
+
"format": {},
|
237 |
+
"geoKey": "name",
|
238 |
+
"resolve": {
|
239 |
+
"x": false,
|
240 |
+
"y": false,
|
241 |
+
"color": false,
|
242 |
+
"opacity": false,
|
243 |
+
"shape": false,
|
244 |
+
"size": false
|
245 |
+
},
|
246 |
+
"scaleIncludeUnmatchedChoropleth": false
|
247 |
+
},
|
248 |
+
"visId": "gw_b4Sl",
|
249 |
+
"name": "Chart 1"
|
250 |
+
}
|
251 |
+
]
|
data.py
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import pandas as pd
|
3 |
+
|
4 |
+
from SPARQLWrapper import SPARQLWrapper, JSON
|
5 |
+
|
6 |
+
|
7 |
+
endpoint_url = "https://query.wikidata.org/sparql"
|
8 |
+
|
9 |
+
query = """
|
10 |
+
# Places of residence of accused witches in Scotland 1563-1736
|
11 |
+
SELECT ?accusedurl ?item ?itemLabel ?residenceLabel ?genderLabel ?occupationLabel ?classLabel ?manner_of_inhumane_treatmentLabel ?place_of_detentionLabel ?cause_of_deathLabel ?fatherLabel ?motherLabel ?siblingLabel ?childLabel ?spouseLabel ?coords WHERE {
|
12 |
+
?item wdt:P31 wd:Q5;
|
13 |
+
wdt:P4478 ?accused.
|
14 |
+
wd:P4478 wdt:P1630 ?formatterurl.
|
15 |
+
BIND(IRI(REPLACE(?accused, "^(.+)$", ?formatterurl)) AS ?accusedurl)
|
16 |
+
?item wdt:P551 ?residence.
|
17 |
+
?residence wdt:P625 ?coords.
|
18 |
+
|
19 |
+
OPTIONAL { ?item wdt:P21 ?gender. }
|
20 |
+
OPTIONAL { ?item wdt:P106 ?occupation. }
|
21 |
+
OPTIONAL { ?item wdt:P3716 ?class. }
|
22 |
+
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
|
23 |
+
OPTIONAL { ?item wdt:P7160 ?manner_of_inhumane_treatment. }
|
24 |
+
OPTIONAL { ?item wdt:P2632 ?place_of_detention. }
|
25 |
+
OPTIONAL { ?item wdt:P509 ?cause_of_death. }
|
26 |
+
OPTIONAL { ?item wdt:P22 ?father. }
|
27 |
+
OPTIONAL { ?item wdt:P25 ?mother. }
|
28 |
+
OPTIONAL { ?item wdt:P3373 ?sibling. }
|
29 |
+
OPTIONAL { ?item wdt:P40 ?child. }
|
30 |
+
OPTIONAL { ?item wdt:P26 ?spouse. }
|
31 |
+
OPTIONAL { ?item wdt:P551 ?residence. }
|
32 |
+
}
|
33 |
+
"""
|
34 |
+
|
35 |
+
|
36 |
+
def get_results(endpoint_url, query):
|
37 |
+
"""
|
38 |
+
Obtain SPARQL query results.
|
39 |
+
"""
|
40 |
+
user_agent = "WDQS-example Python/%s.%s" % (sys.version_info[0], sys.version_info[1])
|
41 |
+
# TODO adjust user agent; see https://w.wiki/CX6
|
42 |
+
sparql = SPARQLWrapper(endpoint_url, agent=user_agent)
|
43 |
+
sparql.setQuery(query)
|
44 |
+
sparql.setReturnFormat(JSON)
|
45 |
+
return sparql.query().convert()
|
46 |
+
|
47 |
+
|
48 |
+
def load_data():
|
49 |
+
"""
|
50 |
+
Obtain data for accused witches charged with witchcraft.
|
51 |
+
"""
|
52 |
+
lst = []
|
53 |
+
results = get_results(endpoint_url, query)
|
54 |
+
for result in results["results"]["bindings"]:
|
55 |
+
d = {}
|
56 |
+
for k, v in result.items():
|
57 |
+
d[k] = v['value']
|
58 |
+
lst.append(d)
|
59 |
+
data = pd.DataFrame(lst)
|
60 |
+
data.dropna(subset=['siblingLabel', 'spouseLabel', 'childLabel', 'fatherLabel', 'motherLabel'], how="all", inplace=True)
|
61 |
+
data['longitude'] = data['coords'].str.replace("Point", "").apply(lambda x: x.split()[0].lstrip("(")).astype(float)
|
62 |
+
data['latitude'] = data['coords'].str.replace("Point", "").apply(lambda x: x.split()[-1].rstrip(")")).astype(float)
|
63 |
+
data.drop(['coords'], axis=1, inplace=True)
|
64 |
+
data.columns = [col.replace("Label", "") if col != "itemLabel" else col for col in data.columns.tolist()]
|
65 |
+
return data
|
66 |
+
|
67 |
+
|
68 |
+
df = load_data()
|
network.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import networkx as nx
|
3 |
+
import matplotlib.pyplot as plt
|
4 |
+
from pyvis.network import Network
|
5 |
+
|
6 |
+
from data import df
|
7 |
+
|
8 |
+
|
9 |
+
def analysis():
|
10 |
+
G = nx.DiGraph()
|
11 |
+
|
12 |
+
# Define the columns to consider for relationships
|
13 |
+
relationship_columns = ['father', 'sibling', 'spouse', 'mother', 'child']
|
14 |
+
|
15 |
+
G.add_nodes_from(df["itemLabel"])
|
16 |
+
|
17 |
+
for index, row in df.iterrows():
|
18 |
+
main_entity = row['itemLabel']
|
19 |
+
for relationship in relationship_columns:
|
20 |
+
if pd.notna(row[relationship]):
|
21 |
+
G.add_edge(main_entity, row[relationship], relationship=str(relationship), label=relationship)
|
22 |
+
|
23 |
+
plt.figure(figsize=(50, 20)) # Set the size of the plot
|
24 |
+
# pos = nx.kamada_kawai_layout(G) # Compute the positions of the nodes
|
25 |
+
|
26 |
+
# # Draw the nodes and edges with labels
|
27 |
+
# nx.draw(G, pos, with_labels=True, node_size=20, node_color='skyblue', font_size=10, font_color='black', font_weight='bold', alpha=0.7)
|
28 |
+
|
29 |
+
# # Draw edge labels (the relationships)
|
30 |
+
# edge_labels = nx.get_edge_attributes(G, 'relationship')
|
31 |
+
# nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='red')
|
32 |
+
|
33 |
+
# plt.title('Relationship Graph with Labels')
|
34 |
+
# plt.axis('off') # Hide the axes for clarity
|
35 |
+
# plt.show()
|
36 |
+
|
37 |
+
|
38 |
+
net = Network(bgcolor="#222222", font_color="white", directed=True)
|
39 |
+
net.from_nx(G)
|
40 |
+
|
41 |
+
for node in net.nodes:
|
42 |
+
node["title"] = node["id"]
|
43 |
+
node["value"] = len(G[node["id"]])
|
44 |
+
|
45 |
+
|
46 |
+
for edge in net.edges:
|
47 |
+
# Set edge title to the relationship from your graph
|
48 |
+
relationship = G[edge["from"]][edge["to"]].get("relationship", "Unknown")
|
49 |
+
edge["title"] = relationship # This will show as a tooltip
|
50 |
+
edge["color"] = "blue"
|
51 |
+
edge["width"] = 2 if relationship != "Unknown" else 1
|
52 |
+
|
53 |
+
html = net.generate_html()
|
54 |
+
#need to remove ' from HTML
|
55 |
+
html = html.replace("'", "\"")
|
56 |
+
|
57 |
+
return f"""<iframe style="width: 100%; height: 1000px;margin:0 auto" name="result" allow="midi; geolocation; microphone; camera;
|
58 |
+
display-capture; encrypted-media;" sandbox="allow-modals allow-forms
|
59 |
+
allow-scripts allow-same-origin allow-popups
|
60 |
+
allow-top-navigation-by-user-activation allow-downloads" allowfullscreen=""
|
61 |
+
allowpaymentrequest="" frameborder="0" srcdoc='{html}'></iframe>"""
|
requirements.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
pandas
|
2 |
+
sparqlwrapper
|
3 |
+
gradio
|
4 |
+
pygwalker
|
5 |
+
datasets
|
6 |
+
pyvis
|
7 |
+
networkx
|