Skip to content

Commit b2dd6e5

Browse files
author
Joshua Danish
authored
Merge pull request #285 from netcreateorg/dev-ds/edge-weights
Feature: Edge Weights
2 parents f6ae71a + 3bf6592 commit b2dd6e5

10 files changed

+377
-71
lines changed

build/app/unisys/server-database.js

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ function m_LoadTOMLTemplate(templateFilePath) {
334334
// Migrate v1.4 to v2.0
335335
// v2.0 added `provenance` and `comments` -- so we add the template definitions if the toml template does not already have them
336336
// hides them by default if they were not previously added
337+
// FIXME: There is related migration code in m_MigrateTemplate() that needs to be merged...if possible
338+
337339
const DEFAULT_TEMPLATE = TEMPLATE_SCHEMA.ParseTemplateSchema();
338340
const NODEDEFS = DEFAULT_TEMPLATE.nodeDefs;
339341
if (json.nodeDefs.provenance === undefined) {
@@ -353,6 +355,10 @@ function m_LoadTOMLTemplate(templateFilePath) {
353355
json.edgeDefs.comments = EDGEDEFS.comments;
354356
json.edgeDefs.comments.hidden = true;
355357
}
358+
if (json.edgeDefs.weight === undefined) {
359+
json.edgeDefs.weight = EDGEDEFS.weight;
360+
json.edgeDefs.weight.hidden = true;
361+
}
356362
// NOTE: We are not modifying the template permanently, only temporarily inserting definitions so the system can validate
357363

358364
TEMPLATE = json;
@@ -397,8 +403,12 @@ async function m_LoadTemplate() {
397403
/// REVIEW: Should this be moved to a separate server-template module?
398404
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
399405
/*/ Migrate Template File
400-
Updates older templates to the current template-schema specification.
406+
Updates older templates to the current template-schema specification by
407+
inserting missing properties needed by the UI.
401408
Any changes to template-schema should be reflected here.
409+
410+
FIXME: There is code in m_LoadTOMLTemplate() that also does migration that
411+
needs to be moved here!
402412
/*/
403413
function m_MigrateTemplate() {
404414
// 2023-0602 Filter Labels
@@ -419,8 +429,9 @@ function m_MigrateTemplate() {
419429

420430
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
421431
/*/ Validate Template File
422-
This mostly makes sure that nodes and edges have the properties that the UI expects.
423-
If a property is missing, we merely throw an error. We don't do any error recovery.
432+
Lazy check of template object definitions to make sure they are of
433+
expected types and values so the UI doesn't choke and die. Throws an error
434+
if property is missing.
424435
/*/
425436
// eslint-disable-next-line complexity
426437
function m_ValidateTemplate() {
@@ -486,6 +497,8 @@ DB.PKT_GetDatabase = function(pkt) {
486497
let nodes = NODES.chain().data({ removeMeta: false });
487498
let edges = EDGES.chain().data({ removeMeta: false });
488499
if (DBG) console.log(PR,`PKT_GetDatabase ${pkt.Info()} (loaded ${nodes.length} nodes, ${edges.length} edges)`);
500+
m_MigrateNodes(nodes);
501+
m_MigrateEdges(edges);
489502
LOGGER.Write(pkt.Info(), `getdatabase`);
490503
return { d3data: { nodes, edges }, template: TEMPLATE };
491504
};
@@ -1165,6 +1178,47 @@ DB.ReleaseEditLock = pkt => {
11651178
return DB.GetEditStatus();
11661179
}
11671180

1181+
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1182+
/// utility functions for loading data
1183+
/*/ Migrates old network data to new formats based on the template defintion.
1184+
This will automatically migrate any field/property that is marked `isRequired`
1185+
and has a `defaultValue` defined.
1186+
1187+
The basic check is this:
1188+
1. If the TEMPLATE property `isRequired`
1189+
2. ...and the TEMPLATE propert has `defaultValue` defined
1190+
2. ...and the node/edge property is currently undefined or ``
1191+
3. ...then we set the property to the defaultValue
1192+
1193+
The key parameters:
1194+
property.isRequired
1195+
property.defaultValue
1196+
1197+
If `isRequired` or `defaultValue` is not defined on the property, we skip migration.
1198+
1199+
REVIEW: We might consider also adding type coercion.
1200+
/*/
1201+
function m_MigrateNodes(nodes) { // modifies `nodes` by reference
1202+
// Migrate v1.4 to v2.0
1203+
for (const [propertyName, property] of Object.entries(TEMPLATE.nodeDefs)) {
1204+
if (property.isRequired && property.defaultValue !== undefined) {
1205+
nodes.forEach(n => {
1206+
if (n[propertyName] === undefined || n[propertyName] === '') n[propertyName] = property.defaultValue;
1207+
});
1208+
}
1209+
}
1210+
}
1211+
function m_MigrateEdges(edges) { // modifies `edges` by reference
1212+
// Migrate v1.4 to v2.0
1213+
for (const [propertyName, property] of Object.entries(TEMPLATE.edgeDefs)) {
1214+
if (property.isRequired && property.defaultValue !== undefined) {
1215+
edges.forEach(e => {
1216+
if (e[propertyName] === undefined || e[propertyName] === '') e[propertyName] = property.defaultValue;
1217+
});
1218+
}
1219+
}
1220+
}
1221+
11681222
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
11691223
/// utility function for cleaning nodes with numeric id property
11701224
function m_CleanObjID(prompt, obj) {

build/app/view/netcreate/NetCreate.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const InfoPanel = require('./components/InfoPanel');
5353
const FiltersPanel = require('./components/filter/FiltersPanel');
5454
const NCLOGIC = require('./nc-logic'); // require to bootstrap data loading
5555
const FILTERLOGIC = require('./filter-logic'); // handles filtering functions
56+
const EDGELOGIC = require('./edge-logic'); // handles edge synthesis
5657
const FILTER = require('./components/filter/FilterEnums');
5758

5859
/// REACT COMPONENT ///////////////////////////////////////////////////////////

build/app/view/netcreate/components/EdgeEditor.jsx

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ class EdgeEditor extends UNISYS.Component {
228228
targetId: '',
229229
type: '',
230230
info: '',
231+
weight: 1,
231232
provenance: '',
232233
comments: '',
233234
notes: '',
@@ -292,6 +293,7 @@ class EdgeEditor extends UNISYS.Component {
292293
this.onRelationshipChange = this.onRelationshipChange.bind(this);
293294
this.onNotesChange = this.onNotesChange.bind(this);
294295
this.onInfoChange = this.onInfoChange.bind(this);
296+
this.onWeightChange = this.onWeightChange.bind(this);
295297
this.onProvenanceChange = this.onProvenanceChange.bind(this);
296298
this.onCommentsChange = this.onCommentsChange.bind(this);
297299
this.onCitationChange = this.onCitationChange.bind(this);
@@ -342,6 +344,7 @@ class EdgeEditor extends UNISYS.Component {
342344
targetId: '',
343345
type: '',
344346
info: '',
347+
weight: 1,
345348
provenance: '',
346349
comments: '',
347350
notes: '',
@@ -450,6 +453,7 @@ class EdgeEditor extends UNISYS.Component {
450453
type: '',
451454
notes: '',
452455
info: '',
456+
weight: 1,
453457
provenance: provenance_str,
454458
comments: '',
455459
citation: '',
@@ -501,6 +505,7 @@ class EdgeEditor extends UNISYS.Component {
501505
targetId: edge.target,
502506
type: edge.type || '', // Make sure there's valid data
503507
info: edge.info || '',
508+
weight: edge.weight || 1,
504509
provenance: edge.provenance || '',
505510
comments: edge.comments || '',
506511
citation: edge.citation || '',
@@ -855,10 +860,21 @@ class EdgeEditor extends UNISYS.Component {
855860
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
856861
/*/
857862
/*/ onInfoChange (event) {
858-
let formData = this.state.formData;
859-
formData.info = event.target.value;
860-
this.setState({formData: formData});
861-
}
863+
let formData = this.state.formData;
864+
formData.info = event.target.value;
865+
this.setState({formData: formData});
866+
}
867+
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
868+
/*/
869+
/*/ onWeightChange (event) {
870+
// The built in <input min="0"> will keep the step buttons from going below 0,
871+
// but the user can still input "0". When editing, you need to be able to
872+
// delete the whole field, so we allow blanks, otherwise the UI will always
873+
// force a "0" in the field.
874+
let formData = this.state.formData;
875+
formData.weight = event.target.value < 1 ? "" : Number(event.target.value); // force Number type
876+
this.setState({formData: formData});
877+
}
862878
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
863879
/*/
864880
/*/ onProvenanceChange (event) {
@@ -909,6 +925,7 @@ class EdgeEditor extends UNISYS.Component {
909925
target: this.state.targetNode.id, // REVIEW: d3data 'target' is id, rename this to 'targetId'?
910926
type: formData.type,
911927
info: formData.info,
928+
weight: formData.weight,
912929
provenance: formData.provenance,
913930
comments: formData.comments,
914931
citation: formData.citation,
@@ -1082,6 +1099,7 @@ class EdgeEditor extends UNISYS.Component {
10821099
<AutoComplete
10831100
identifier={'edge'+edgeID+'target'}
10841101
disabledValue={targetNode.label}
1102+
// eslint-disable-next-line no-nested-ternary
10851103
inactiveMode={ ( parentNodeLabel===targetNode.label && !sameSourceAndTarget ) ? 'static' : this.state.isBeingEdited ? 'disabled' : 'link'}
10861104
linkID={targetNode.id}
10871105
shouldIgnoreSelection={!this.state.targetIsEditable}
@@ -1165,6 +1183,23 @@ class EdgeEditor extends UNISYS.Component {
11651183
/>
11661184
</Col>
11671185
</FormGroup>
1186+
{/** weight **/}
1187+
<FormGroup row hidden={edgeDefs.weight.hidden}>
1188+
<Col sm={3} style={{hyphens: 'auto'}} className="pr-0">
1189+
<Label for="weight" className="tooltipAnchor small text-muted">
1190+
{edgeDefs.weight.displayLabel}
1191+
<span className="tooltiptext">{this.helpText(edgeDefs.weight)}</span>
1192+
</Label>
1193+
</Col>
1194+
<Col sm={9}>
1195+
<Input type="number" name="weight" min="1"
1196+
value={formData.weight}
1197+
onChange={this.onWeightChange}
1198+
readOnly={!this.state.isBeingEdited}
1199+
/>
1200+
</Col>
1201+
</FormGroup>
1202+
{/** provenance **/}
11681203
<FormGroup row hidden={edgeDefs.provenance.hidden}>
11691204
<Col sm={3} style={{hyphens: 'auto'}} className="pr-0">
11701205
<Label for="provenance" className="tooltipAnchor small text-muted">

build/app/view/netcreate/components/EdgeTable.jsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,9 @@ class EdgeTable extends UNISYS.Component {
314314
// was changed but the full db wasn't updated
315315
bkey = b[key] || '';
316316
} else if (type === FILTER.TYPES.NUMBER) {
317-
akey = Number(a[key]); // force number for sorting
318-
bkey = Number(b[key]);
319-
} else {
317+
akey = Number(a[key]||''); // force number for sorting
318+
bkey = Number(b[key]||'');
319+
} else /* if some other type */ {
320320
akey = a[key];
321321
bkey = b[key];
322322
}
@@ -351,6 +351,7 @@ class EdgeTable extends UNISYS.Component {
351351
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
352352
/*/ If no `sortkey` is passed, the sort will use the existing state.sortkey
353353
/*/ sortTable ( sortkey=this.state.sortkey, edges, type) {
354+
354355
switch (sortkey) {
355356
case 'id':
356357
return this.sortByID(edges);
@@ -364,6 +365,9 @@ class EdgeTable extends UNISYS.Component {
364365
case 'Info':
365366
return this.sortByKey(edges, 'info', type);
366367
break;
368+
case 'Weight':
369+
return this.sortByKey(edges, 'weight', type);
370+
break;
367371
case 'provenance':
368372
return this.sortByKey(edges, 'provenance', type);
369373
break;
@@ -567,10 +571,13 @@ class EdgeTable extends UNISYS.Component {
567571
<th width="10%"hidden={edgeDefs.info.hidden}><Button size="sm"
568572
onClick={()=>this.setSortKey("Info", edgeDefs.info.type)}
569573
>{edgeDefs.info.displayLabel} {this.sortSymbol("Info")}</Button></th>
574+
<th width="4%"hidden={edgeDefs.weight.hidden}><Button size="sm"
575+
onClick={()=>this.setSortKey("Weight", edgeDefs.weight.type)}
576+
>{edgeDefs.weight.displayLabel} {this.sortSymbol("Weight")}</Button></th>
570577
<th width="7%"hidden={edgeDefs.provenance.hidden}><Button size="sm"
571578
onClick={()=>this.setSortKey("provenance", edgeDefs.provenance.type)}
572579
>{edgeDefs.provenance.displayLabel} {this.sortSymbol("provenance")}</Button></th>
573-
<th width="10%"hidden={!isLocalHost}><Button size="sm"
580+
<th width="7%"hidden={!isLocalHost}><Button size="sm"
574581
onClick={()=>this.setSortKey("Updated", FILTER.TYPES.STRING)}
575582
>Updated {this.sortSymbol("Updated")}</Button></th>
576583
<th width="10%"hidden={edgeDefs.comments.hidden}><Button size="sm"
@@ -605,6 +612,7 @@ class EdgeTable extends UNISYS.Component {
605612
{edge.notes ? <MarkdownNote text={edge.notes} /> : "" }
606613
</td>
607614
<td hidden={edgeDefs.info.hidden}>{edge.info}</td>
615+
<td hidden={edgeDefs.weight.hidden}>{edge.weight}</td>
608616
<td hidden={edgeDefs.provenance.hidden}
609617
style={{ fontSize: '9px' }}
610618
>{edge.provenance}</td>

build/app/view/netcreate/components/NodeSelector.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,10 @@
1818
.nodeEntry textarea.comments {
1919
width: 100%;
2020
background-color: #ffff66;
21+
}
22+
23+
/* Applies to NodeSelector and EdgeEditor, overrides bootstrap */
24+
.form-control:focus:invalid, .formcontrol:focus:out-of-range, input:invalid, input:out-of-range {
25+
border-color: red;
26+
box-shadow: 0 0 0 0.2rem rgba(255, 0, 0, .25);
2127
}

build/app/view/netcreate/components/NodeTable.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,9 @@ class NodeTable extends UNISYS.Component {
285285
// was changed but the full db wasn't updated
286286
bkey = b[key] || '';
287287
} else if (type === FILTER.TYPES.NUMBER) {
288-
akey = Number(a[key]); // force number for sorting
289-
bkey = Number(b[key]);
290-
} else {
288+
akey = Number(a[key]||''); // force number for sorting
289+
bkey = Number(b[key]||'');
290+
} else /* some other type */ {
291291
akey = a[key];
292292
bkey = b[key];
293293
}

build/app/view/netcreate/components/d3-simplenetgraph.js

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ class D3NetGraph {
185185

186186
// bind 'this' to function objects so event handlers can access
187187
// contents of this class+module instance
188-
this._HandleFilteredD3DataUpdate = this._HandleFilteredD3DataUpdate.bind(this);
188+
this._HandleD3DataUpdate = this._HandleD3DataUpdate.bind(this);
189189
this._HandleTemplateUpdate = this._HandleTemplateUpdate.bind(this);
190190
this._ClearSVG = this._ClearSVG.bind(this);
191191
this._SetDefaultValues = this._SetDefaultValues.bind(this);
@@ -219,7 +219,12 @@ class D3NetGraph {
219219
// this._SetData(data);
220220
// });
221221

222-
UDATA.OnAppStateChange('FILTEREDD3DATA', this._HandleFilteredD3DataUpdate);
222+
// V2.0 CHANGE
223+
// Ignore FILTEREDD3DATA updates! Only listen for SYNTHESIZEDD3DATA
224+
// which represents simplified edge data (duplicate edges removed) for rendering
225+
// UDATA.OnAppStateChange('FILTEREDD3DATA', this._HandleFilteredD3DataUpdate);
226+
227+
UDATA.OnAppStateChange('SYNTHESIZEDD3DATA', this._HandleD3DataUpdate);
223228
UDATA.OnAppStateChange('TEMPLATE', this._HandleTemplateUpdate);
224229
UDATA.OnAppStateChange('COLORMAP', this._ColorMap);
225230
UDATA.HandleMessage('ZOOM_RESET', this._ZoomReset);
@@ -239,7 +244,7 @@ class D3NetGraph {
239244
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
240245
Deregister() {
241246
if (DBG) console.log(PR, 'd3-simplenetgraph.DESTRUCT!!!')
242-
UDATA.AppStateChangeOff('FILTEREDD3DATA', this._HandleFilteredD3DataUpdate);
247+
UDATA.AppStateChangeOff('SYNTHESIZEDD3DATA', this._HandleD3DataUpdate);
243248
UDATA.AppStateChangeOff('COLORMAP', this._ColorMap);
244249
UDATA.UnhandleMessage('ZOOM_RESET', this._ZoomReset);
245250
UDATA.UnhandleMessage('ZOOM_IN', this._ZoomIn);
@@ -255,8 +260,8 @@ class D3NetGraph {
255260
* @param {array} data.nodes
256261
* @param {array} data.edges
257262
*/
258-
_HandleFilteredD3DataUpdate(data) {
259-
if (DBG) console.log(PR, 'got state FILTEREDD3DATA', data);
263+
_HandleD3DataUpdate(data) {
264+
if (DBG) console.log(PR, 'got state D3DATA', data);
260265
this._SetData(data);
261266
}
262267

@@ -669,17 +674,18 @@ _UpdateLinkStrokeWidth(edge) {
669674
(sourceId === mouseoverNodeId) ||
670675
(targetId === mouseoverNodeId)
671676
) {
672-
return Math.min(edge.size ** 2, this.edgeSizeMax); // Use **2 to make size differences more noticeable
677+
// max size checking is in edge-logic
678+
return edge.size;
679+
// return edge.size ** 2; // Use **2 to make size differences more noticeable
680+
// return Math.min(edge.size ** 2, this.edgeSizeMax); // Use **2 to make size differences more noticeable
673681
} else {
674682
return this.edgeSizeDefault; // Barely visible if not selected
675683
}
676684
}
677685

686+
/// Edge color is pre-set by edge-logic based on weights
678687
_UpdateLinkStrokeColor(edge) {
679-
if (DBG) console.log(PR, '_UpdateLinkStrokeColor', edge)
680-
let COLORMAP = UDATA.AppState('COLORMAP');
681-
let color = COLORMAP.edgeColorMap[edge.type]
682-
return color;
688+
return edge.color;
683689
}
684690

685691

0 commit comments

Comments
 (0)