Skip to content

Commit 8d3a91d

Browse files
authored
Merge pull request #6 from iris-liu0312/master
Editable Sidebar
2 parents 7a5f202 + 99fdab6 commit 8d3a91d

File tree

17 files changed

+461
-178
lines changed

17 files changed

+461
-178
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ venv
55
env
66
dist
77
temp.txt
8-
notes.py
8+
scratch.py

README.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,21 @@ The tool mainly uses 3 resources:
3737

3838
## Usage
3939

40-
Currently the tool has 4 sections, namely Home, Viewer, Compare, Github. GitHub links to this repository.
40+
Currently only the **Viewer** is available for use. **Home** is a landing page and **GitHub** links to this repository.
4141

4242
### Viewer
43-
* An **Upload Schema** button that opens a modal to upload file from the local file system.
44-
* A **Canvas** where the schema is represented. The canvas allows one to zoom-in and zoom-out, move one node at a time and move the entire structure. There is a **reload** icon on the top right corner of the canvas that resets the graph to its original position. Nodes can be clicked to show subtrees of children and participants.
45-
* A **JSON viewer** that gives the json view of the schema. This viewer allows editing of the schema which in turn updates the graph. The viewer allows 4 main funtionalities.
46-
* **Copy**: A "Copy-to-Clipboard" icon shows up on every object of the json structure which allows one to copy the entire value. For object, number and string, it copies the value assigned to the property. For arrays, it copies all the entries in the list.
47-
* **Add**: A "+" icon signifies adding an entry. Within objects, it expects a key and initialize it with "NULL" which can then be edited to the required value. In arrays, it expects a value in the form of an object or a string.
48-
* **Edit**: Selecting "notepad-with-pen" icon allows editing the value of the respective key. This option is missing in arrays.
49-
* **Delete**: Clicking on "X" icon will delete that entry in the object. Using it in array will remove the entire object and reduce the length of the list by 1.
50-
51-
For an interactive [demo](https://mac-s-g.github.io/react-json-view/demo/dist/).
52-
* A **SideBar** opens up on the left side of the canvas giving information about the selected node. This window opens only when a node is right-clicked. It gives the details about the nodes like name, id, description, comments, explanation from TA1, importance etc. These information will only be visible if they are mentioned in the schema.
43+
- **Upload Schema** to upload a JSON file from your local file system, and **download** it when you are done with curation.
44+
- **Canvas** shows a graphical representation of the uploaded JSON file. The canvas allows for zoom and node drag-and-drop. **Reloading** the canvas is supported with a reload icon on the top right corner of the canvas. Nodes can be clicked to show subtrees of children nodes and participants.
45+
- **JSON Editor**[[1]](https://github.com/josdejong/jsoneditor/tree/master) showing the uploaded JSON. Editing the JSON will update the graph in the canvas. Functionalities include:
46+
- **⚠ A bug crashes the website when a field with an empty value is instantiated, so *please duplicate existing fields* instead of adding, for example, empty strings when adding new information.**
47+
- **Expand and Collapse All** lists and dictionaries of the JSON.
48+
- **Sort contents**
49+
- **History** for accidents.
50+
- **Search Bar** to locate events more easily.
51+
- **Drag** to move fields and their values between lists and dictionaries.
52+
- **Templates** of events, children, participants, etc. for easier curation.
53+
- **Duplication**
54+
- **Deletion**
55+
- A **Sidebar** is available on the left side of the canvas giving information about the selected node. This window opens only when a node is right-clicked. It gives the details about the node, such as its name, id, description, comments, explanation from TA1, importance, etc. All information is directly taken from the JSON.
56+
---
57+
[[1]](https://github.com/josdejong/jsoneditor/tree/master) A web-based tool to view, edit, format, and validate JSON by Jos de Jong

app.py

Lines changed: 145 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@
1111

1212
nodes = {}
1313
edges = []
14-
15-
# TODO: handle multiple root nodes
14+
schemaJson = {}
1615

1716
# SDF version 1.3
1817
schema_key_dict = {
19-
'root': ['@id', 'name', 'comment', 'description', 'aka', 'qnode', 'qlabel', 'minDuration',
20-
'maxDuration', 'goal', 'ta1explanation', 'importance', 'children_gate'],
21-
# TODO: handle xor children_gates
18+
'root': ['@id', 'name', 'comment', 'description', 'aka', 'qnode', 'qlabel', 'minDuration', 'maxDuration', 'goal', 'ta1explanation', 'importance', 'children_gate'],
2219
'participant': ['@id', 'roleName', 'entity'],
2320
'child': ['child', 'comment', 'optional', 'importance', 'outlinks', 'outlink_gate'],
2421
'privateData': ['@type', 'template', 'repeatable', 'importance']
@@ -100,6 +97,7 @@ def get_nodes_and_edges(schema):
10097
nodes = {}
10198
edges = []
10299
containers = []
100+
parentless_xor = []
103101

104102
for scheme in schema:
105103
# create event node
@@ -133,21 +131,25 @@ def get_nodes_and_edges(schema):
133131
nodes[scheme_id]['data']['_type'] = 'child'
134132
nodes[scheme_id]['data']['_shape'] = 'ellipse'
135133
# handle repeatable
136-
if nodes[scheme_id]['data']['repeatable']:
134+
if 'repeatable' in nodes[scheme_id]['data'] and nodes[scheme_id]['data']['repeatable']:
137135
edges.append(create_edge(scheme_id, scheme_id, _edge_type='child_outlink'))
138136

139137
# participants
140138
if 'participants' in scheme:
141139
for participant in scheme['participants']:
142-
_label = participant['roleName'].split('/')[-1].replace('_', '')
140+
_label = participant['roleName'].split('/')[-1]
143141
nodes[participant['@id']] = extend_node(create_node(participant['@id'], _label, 'participant', 'square'), participant)
144142

145143
edges.append(create_edge(scheme_id, participant['@id'], _edge_type='step_participant'))
146144

147145
# children
148146
if 'children' in scheme:
147+
gate = 'or'
148+
if nodes[scheme_id]['data']['children_gate'] == 'xor':
149+
gate = 'xor'
150+
elif nodes[scheme_id]['data']['children_gate'] == 'and':
151+
gate = 'and'
149152
for child in scheme['children']:
150-
151153
child_id = child['child']
152154
# node already exists
153155
if child_id in nodes:
@@ -158,7 +160,19 @@ def get_nodes_and_edges(schema):
158160
# new node
159161
else:
160162
nodes[child_id] = extend_node(create_node(child_id, child['comment'], 'child', 'ellipse'), child)
161-
edges.append(create_edge(scheme_id, child_id, _edge_type='step_child'))
163+
164+
# handle xor gate
165+
if gate == 'xor':
166+
xor_id = f'{scheme_id}xor'
167+
nodes[xor_id] = create_node(xor_id, 'XOR', 'gate', 'rectangle')
168+
edges.append(create_edge(xor_id, child_id, _edge_type='child_outlink'))
169+
if 'Container' in scheme_id:
170+
edges.append(create_edge(scheme_id, xor_id, _edge_type='child_outlink'))
171+
parentless_xor.append((xor_id, child_id))
172+
else:
173+
edges.append(create_edge(scheme_id, xor_id, _edge_type='step_child'))
174+
175+
edges.append(create_edge(scheme_id, child_id, _edge_type='child_outlink' if gate == 'and' else 'step_child'))
162176

163177
# check for outlinks
164178
if len(child['outlinks']):
@@ -171,22 +185,37 @@ def get_nodes_and_edges(schema):
171185
# handle containers, ie. connect previous node to all their successors
172186
edges_to_remove = []
173187
for container in containers:
188+
edge_type = False
189+
source_id = False
174190
for edge in edges:
175191
if 'searched' in edge['data'] and edge['data']['searched']:
176192
continue
177193
if edge['data']['target'] == container:
178194
source_id = edge['data']['source']
179-
nodes[source_id]['data']['children_gate'] = nodes[container]['data']['children_gate']
180-
edges_to_remove.append(edge)
181195
edge['data']['searched'] = True
182-
if edge['data']['source'] == container:
183-
edges.append(create_edge(source_id, edge['data']['target'], _edge_type='step_child'))
184196
edges_to_remove.append(edge)
197+
if edge['data']['source'] == container:
198+
edge_type = edge['data']['_edge_type']
185199
edge['data']['searched'] = True
200+
if source_id and edge_type:
201+
edges_to_remove.append(edge)
202+
edges.append(create_edge(source_id, edge['data']['target'], _edge_type=edge_type))
203+
edge_type = False
186204

187205
for index in edges_to_remove:
188206
edges.remove(index)
189207

208+
# give xor nodes a parent node they belong to
209+
parent_found = []
210+
for xor_id, child_id in parentless_xor:
211+
if xor_id in parent_found:
212+
continue
213+
for edge in edges:
214+
if edge['data']['target'] == child_id and edge['data']['_edge_type'] == 'step_child':
215+
edges.append(create_edge(edge['data']['source'], xor_id, _edge_type='step_child'))
216+
parent_found.append(xor_id)
217+
break
218+
190219
# === are these two necessary? / what are these for ===
191220
# TODO: entities
192221
# TODO: relations
@@ -204,6 +233,85 @@ def get_nodes_and_edges(schema):
204233

205234
return nodes, edges
206235

236+
def update_json(values):
237+
"""Updates JSON with values.
238+
239+
Parameters:
240+
values (dict): contains node id, key, and value to change key to.
241+
e.g. {id: node_id, key: name, value: Test}
242+
243+
Returns:
244+
schemaJson (dict): new JSON
245+
"""
246+
global schemaJson
247+
new_json = schemaJson
248+
node_id = values['id']
249+
key = values['key']
250+
new_value = values['value']
251+
node_type = node_id.split('/')[0].split(':')[-1]
252+
array_to_modify = False
253+
isRoot = new_json['events'][0]['@id'] == node_id
254+
255+
# what key is it?
256+
# special cases
257+
if key in ['@id', 'child']:
258+
key = '@id'
259+
array_to_modify = 'id'
260+
elif key == 'importance':
261+
array_to_modify = 'importance'
262+
elif key == 'name':
263+
array_to_modify = 'name'
264+
# look for the key in schema_key_dict
265+
if not array_to_modify:
266+
if node_type == 'Participants':
267+
array_to_modify = 'participant'
268+
else:
269+
for keys in schema_key_dict:
270+
if key in schema_key_dict[keys]:
271+
array_to_modify = keys
272+
break
273+
274+
# change schemaJson
275+
for scheme in new_json['events']:
276+
# scheme data
277+
if scheme['@id'] == node_id:
278+
if array_to_modify in ['root', 'name', 'privateData']:
279+
if key in schema_key_dict['privateData']:
280+
scheme['privateData'][key] = new_value
281+
break
282+
else:
283+
scheme[key] = new_value
284+
if key != 'name':
285+
break
286+
if isRoot:
287+
break
288+
elif array_to_modify == 'importance':
289+
if isRoot:
290+
scheme['privateData'][key] = new_value
291+
break
292+
elif array_to_modify == 'id':
293+
scheme[key] = new_value
294+
if isRoot:
295+
break
296+
# participant data
297+
if array_to_modify in ['participant', 'id'] and 'participants' in scheme:
298+
for participant in scheme['participants']:
299+
if participant['@id'] == node_id:
300+
scheme[key] = new_value
301+
# children data
302+
if array_to_modify in ['child', 'id', 'name'] and 'children' in scheme:
303+
for child in scheme['children']:
304+
if child['child'] == node_id:
305+
if array_to_modify == 'name':
306+
child['comment'] = new_value
307+
elif array_to_modify == 'id':
308+
child['child'] = new_value
309+
else:
310+
child[key] = new_value
311+
312+
schemaJson = new_json
313+
return schemaJson
314+
207315
def get_connected_nodes(selected_node):
208316
"""Constructs graph to be visualized by the viewer.
209317
@@ -259,12 +367,14 @@ def homepage():
259367

260368
@app.route('/upload', methods=['POST'])
261369
def upload():
370+
"""Uploads JSON and processes it for graph view."""
262371
file = request.files['file']
263372
schema_string = file.read().decode("utf-8")
264-
schemaJson = json.loads(schema_string)
265-
schema = schemaJson['events']
373+
global schemaJson
266374
global nodes
267375
global edges
376+
schemaJson = json.loads(schema_string)
377+
schema = schemaJson['events']
268378
nodes, edges = get_nodes_and_edges(schema)
269379
schema_name = schema[0]['name']
270380
parsed_schema = get_connected_nodes('root')
@@ -274,28 +384,39 @@ def upload():
274384
'schemaJson': schemaJson
275385
})
276386

277-
@app.route('/node', methods=['GET'])
278-
def get_subtree():
279-
"""Gets subtree of the selected node."""
387+
@app.route('/node', methods=['GET', 'POST'])
388+
def get_subtree_or_update_node():
280389
if not (bool(nodes) and bool(edges)):
281390
return 'Parsing error! Upload the file again.', 400
282-
node_id = request.args.get('ID')
283-
subtree = get_connected_nodes(node_id)
284-
return json.dumps(subtree)
391+
392+
if request.method == 'GET':
393+
"""Gets subtree of the selected node."""
394+
node_id = request.args.get('ID')
395+
subtree = get_connected_nodes(node_id)
396+
return json.dumps(subtree)
397+
# it won't work and i don't know why
398+
else:
399+
"""Posts updates to selected node and reloads schema."""
400+
values = json.loads(request.data.decode("utf-8"))
401+
new_json = update_json(values)
402+
return json.dumps(new_json)
403+
404+
285405

286406
@app.route('/reload', methods=['POST'])
287407
def reload_schema():
288408
"""Reloads schema; does the same thing as upload."""
289409
schema_string = request.data.decode("utf-8")
290-
schemaJson = json.loads(schema_string)
291-
schema = schemaJson['events']
410+
global schemaJson
292411
global nodes
293412
global edges
413+
schemaJson = json.loads(schema_string)
414+
schema = schemaJson['events']
294415
nodes, edges = get_nodes_and_edges(schema)
295416
schema_name = schema[0]['name']
296417
parsed_schema = get_connected_nodes('root')
297418
return json.dumps({
298419
'parsedSchema': parsed_schema,
299420
'name': schema_name,
300421
'schemaJson': schemaJson
301-
})
422+
})

documentation/curation.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Curation
2+
SDF v1.3 | Notes on curation fields and where to get them. Some descriptions from SDF documentation
3+
---
4+
- [Curation](#curation)
5+
- [SDF v1.3 | Notes on curation fields and where to get them. Some descriptions from SDF documentation](#sdf-v13--notes-on-curation-fields-and-where-to-get-them-some-descriptions-from-sdf-documentation)
6+
- [Events](#events)
7+
- [Participants](#participants)
8+
- [Children](#children)
9+
- [Entities](#entities)
10+
- [Relations](#relations)
11+
12+
---
13+
Generally one event has at least 2 references in the JSON: as an event and as a child of some other event. The event reference gives more specific information about the event (node information), while the child reference simply links the event to its parent and its siblings (edge information).
14+
15+
## Events
16+
⚠ denotes required fields.
17+
-`@id`: `prefix:Events/<unique-5-digit-number>/<anything>`, The 5-digit number is unique per event and belongs to the Events list.
18+
- `resin:Events/10000/resin:Events_disease-outbreak`
19+
-`name`: human-readable label
20+
- `description` or `comment`: human-readable description.
21+
- Generally only the first event uses `description`, all other events seem to use `comment`.
22+
- It appears that this is where all container nodes have the comment `container node`.
23+
- `qnode` and `qlabel`: q-node from Wikidata. `qnode` denotes the QID and `qlabel` denotes the name, eg. [disease outbreak (Q3241045)](https://www.wikidata.org/wiki/Q3241045)
24+
- `minDuration` and `maxDuration`: minimum and maximum duration of the event.
25+
- `goal`: string for TA2 defining when the event achieves a goal
26+
- (⚠) `ta1explanation`: Explanation of the event. Required for events without children.
27+
- `privateData`:
28+
- `template`: ?
29+
- `repeatable`: whether the event can occur multiple times.
30+
- `importance`: [0, 1.0] represents importance. Also present in the child reference for the event.
31+
- (⚠) `participants`: [participants](#participants) of an event. Required when there are no children.
32+
- `children`: [children](#children) of an event.
33+
- (⚠) `children_gate`: logical processing of node's children. Can be `and`, `or`, or `xor`. Required when there are children.
34+
35+
## Participants
36+
- `@id`: `prefix:Participants/<unique-5-digit-number>/event@id_participantName`; the 5-digit number is unique per participant and belongs to the Participants list.
37+
- `roleName`: taken from the XPO overlay. ask for the JSON.
38+
- `entity`: @id reference to [entity](#entities)
39+
40+
## Children
41+
- `child`: @id reference to event
42+
- `comment`: human-readable description.
43+
- `optional`: whether an event is optional.
44+
- `importance`: [0, 1.0] represents importance of the event in the subevent.
45+
- `outlinks`: list of @ids of other events. Generally the next event in the sequence.
46+
47+
## Entities
48+
Forms coreference links between events. For example, if two different events have participants with the same `victim` entity, they are referencing the same `victim`.
49+
-`@id`: `prefix:Entities/<unique-5-digit-number>`; the 5-digit number is unique per entity and belongs to the Entities list.
50+
-`name`: human-readable label
51+
-`qnode` and `qlabel`: q-node from Wikidata. `qnode` denotes the QID and `qlabel` denotes the name, eg. [disease outbreak (Q3241045)](https://www.wikidata.org/wiki/Q3241045)
52+
53+
## Relations
54+
Specifies event-event / entity-entity relations.
55+
-`relationSubject`: @id reference to event / entity
56+
-`relationPredicate`: Wikidata q-node or p-node
57+
-`relationObject`: @id reference to event / entity
58+
-`@id`: `prefix:Relations/<unique-5-digit-number>`; the 5-digit number is unique per entity and belongs to the Relations list.

documentation/expanded.png

732 KB
Loading

documentation/for_development.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# For Development
2+
Where do I even start?!
3+
---
4+
- [For Development](#for-development)
5+
- [Where do I even start?!](#where-do-i-even-start)
6+
- [Top level](#top-level)
7+
- [static/src/template](#staticsrctemplate)
8+
9+
---
10+
## Top level
11+
- Documentation folder for documentation
12+
- `app.py` (Flask) for data management to process and send data to front-end
13+
14+
## static/src/template
15+
- `Canvas.jsx` for the Cytoscape graph
16+
- `Compare.jsx` for Compare page
17+
- `Home.jsx` for Home page
18+
- `JsonEdit.jsx` for Json Editor [[1]](https://github.com/josdejong/jsoneditor/tree/master)
19+
- `SideBar.jsx` for Sidebar
20+
- `UploadModal.jsx` for Upload pop-up window
21+
- `Viewer.jsx` to combine all of the above into the Viewer page
22+
- `SchemaModal.jsx` for Schema edit pop-up window (not in use)

documentation/upload.png

26.4 KB
Loading

0 commit comments

Comments
 (0)