From ceba9cd0479d3da9ba77663b9166138aa64c4195 Mon Sep 17 00:00:00 2001 From: benloh Date: Mon, 29 May 2023 22:18:15 -0700 Subject: [PATCH 01/21] collapse: Move filter help text to FilterEnums --- .../netcreate/components/filter/FilterEnums.js | 8 ++++++-- .../components/filter/FiltersPanel.jsx | 18 ++++++++++-------- build/app/view/netcreate/filter-logic.js | 3 +-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/build/app/view/netcreate/components/filter/FilterEnums.js b/build/app/view/netcreate/components/filter/FilterEnums.js index 6deec870..9051c547 100644 --- a/build/app/view/netcreate/components/filter/FilterEnums.js +++ b/build/app/view/netcreate/components/filter/FilterEnums.js @@ -2,8 +2,12 @@ const FILTER = {}; // Determines whether filter action is to highlight/fade or remove (filter) nodes and edges FILTER.ACTION = {}; -FILTER.ACTION.HIGHLIGHT = 'highlight'; -FILTER.ACTION.FILTER = 'filter'; +FILTER.ACTION.HIGHLIGHT = 'HIGHLIGHTING'; +FILTER.ACTION.FILTER = 'FILTERING'; +FILTER.ACTION.HELP = {}; +FILTER.ACTION.HELP.HIGHLIGHT = 'Show (Highlight) matches, Fade others'; +FILTER.ACTION.HELP.FILTER = 'Shows matches, Hide (Filter) others (keep physics and degrees)'; +FILTER.ACTION.HELP.COLLAPSE = 'Show matches, Remove (collapse) others & recalculate sizes'; // Types of filters definable in template files. FILTER.TYPES = {}; diff --git a/build/app/view/netcreate/components/filter/FiltersPanel.jsx b/build/app/view/netcreate/components/filter/FiltersPanel.jsx index 79d67944..77cdc94e 100644 --- a/build/app/view/netcreate/components/filter/FiltersPanel.jsx +++ b/build/app/view/netcreate/components/filter/FiltersPanel.jsx @@ -45,7 +45,8 @@ class FiltersPanel extends UNISYS.Component { this.state = { nodes: FDATA.nodes, edges: FDATA.edges, - filterAction: FILTER.ACTION.HIGHLIGHT + filterAction: FILTER.ACTION.HIGHLIGHT, + filterActionHelp: FILTER.ACTION.HELP.HIGHLIGHT }; UDATA.OnAppStateChange("FDATA", this.UpdateFilterDefs); } // constructor @@ -60,7 +61,8 @@ class FiltersPanel extends UNISYS.Component { return { nodes: data.nodes, edges: data.edges, - filterAction: data.filterAction || state.filterAction + filterAction: data.filterAction || state.filterAction, + filterActionHelp: data.filterActionHelp || state.filterActionHelp } }); } @@ -70,12 +72,15 @@ class FiltersPanel extends UNISYS.Component { } SelectFilterAction(filterAction) { - this.setState({ filterAction }); + let filterActionHelp; + if (filterAction === FILTER.ACTION.HIGHLIGHT) filterActionHelp = FILTER.ACTION.HELP.HIGHLIGHT; + if (filterAction === FILTER.ACTION.FILTER) filterActionHelp = FILTER.ACTION.HELP.FILTER; + this.setState({ filterAction, filterActionHelp }); UDATA.LocalCall('FILTERS_UPDATE', { filterAction }); } render() { - const { filterAction } = this.state; + const { filterAction, filterActionHelp } = this.state; const defs = [this.state.nodes, this.state.edges]; return (
Filter
diff --git a/build/app/view/netcreate/filter-logic.js b/build/app/view/netcreate/filter-logic.js index 828eeb5f..b6adb613 100644 --- a/build/app/view/netcreate/filter-logic.js +++ b/build/app/view/netcreate/filter-logic.js @@ -333,8 +333,7 @@ function m_UpdateFilterSummary() { const nodeFilters = FDATA.nodes.filters; const edgeFilters = FDATA.edges.filters; - const typeSummary = FDATA.filterAction === FILTER.ACTION.HIGHLIGHT - ? 'HIGHLIGHTING ' : 'FILTERING '; + const typeSummary = FDATA.filterAction; // text for filter action is the label, e.g. 'HIGHLIGHT' const nodeSummary = m_FiltersToString(FDATA.nodes.filters); const edgeSummary = m_FiltersToString(FDATA.edges.filters); let summary = ''; From 600ceac9ac2082ea3286d474bc68c7e253496c01 Mon Sep 17 00:00:00 2001 From: benloh Date: Mon, 29 May 2023 22:20:48 -0700 Subject: [PATCH 02/21] collapse: Add Collapse filter UI --- .../view/netcreate/components/filter/FilterEnums.js | 1 + .../view/netcreate/components/filter/FiltersPanel.jsx | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/build/app/view/netcreate/components/filter/FilterEnums.js b/build/app/view/netcreate/components/filter/FilterEnums.js index 9051c547..71fde7f3 100644 --- a/build/app/view/netcreate/components/filter/FilterEnums.js +++ b/build/app/view/netcreate/components/filter/FilterEnums.js @@ -4,6 +4,7 @@ const FILTER = {}; FILTER.ACTION = {}; FILTER.ACTION.HIGHLIGHT = 'HIGHLIGHTING'; FILTER.ACTION.FILTER = 'FILTERING'; +FILTER.ACTION.COLLAPSE = 'COLLAPSING'; FILTER.ACTION.HELP = {}; FILTER.ACTION.HELP.HIGHLIGHT = 'Show (Highlight) matches, Fade others'; FILTER.ACTION.HELP.FILTER = 'Shows matches, Hide (Filter) others (keep physics and degrees)'; diff --git a/build/app/view/netcreate/components/filter/FiltersPanel.jsx b/build/app/view/netcreate/components/filter/FiltersPanel.jsx index 77cdc94e..7357fd56 100644 --- a/build/app/view/netcreate/components/filter/FiltersPanel.jsx +++ b/build/app/view/netcreate/components/filter/FiltersPanel.jsx @@ -75,6 +75,7 @@ class FiltersPanel extends UNISYS.Component { let filterActionHelp; if (filterAction === FILTER.ACTION.HIGHLIGHT) filterActionHelp = FILTER.ACTION.HELP.HIGHLIGHT; if (filterAction === FILTER.ACTION.FILTER) filterActionHelp = FILTER.ACTION.HELP.FILTER; + if (filterAction === FILTER.ACTION.COLLAPSE) filterActionHelp = FILTER.ACTION.HELP.COLLAPSE; this.setState({ filterAction, filterActionHelp }); UDATA.LocalCall('FILTERS_UPDATE', { filterAction }); } @@ -112,6 +113,16 @@ class FiltersPanel extends UNISYS.Component { backgroundColor: filterAction === FILTER.ACTION.FILTER ? 'transparent' : '#6c757d88' }} >Filter +
@@ -208,7 +209,7 @@ const FILTERLOGIC = require('./filter-logic'); // handles filtering functions + >< {FILTER.PANEL_LABEL}
} diff --git a/build/app/view/netcreate/components/filter/FilterEnums.js b/build/app/view/netcreate/components/filter/FilterEnums.js index 71fde7f3..679bd411 100644 --- a/build/app/view/netcreate/components/filter/FilterEnums.js +++ b/build/app/view/netcreate/components/filter/FilterEnums.js @@ -1,5 +1,8 @@ const FILTER = {}; +// Filter Panel Label +FILTER.PANEL_LABEL = 'VIEWS'; + // Determines whether filter action is to highlight/fade or remove (filter) nodes and edges FILTER.ACTION = {}; FILTER.ACTION.HIGHLIGHT = 'HIGHLIGHTING'; From 5b420930cb64c74cbc0a7cdf43645598b29361ac Mon Sep 17 00:00:00 2001 From: benloh Date: Wed, 31 May 2023 09:33:05 -0700 Subject: [PATCH 07/21] collapse: Fix typo --- build/app/view/netcreate/components/EdgeTable.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/app/view/netcreate/components/EdgeTable.jsx b/build/app/view/netcreate/components/EdgeTable.jsx index a00a8039..2ff352e8 100644 --- a/build/app/view/netcreate/components/EdgeTable.jsx +++ b/build/app/view/netcreate/components/EdgeTable.jsx @@ -193,7 +193,7 @@ class EdgeTable extends UNISYS.Component { /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ Handle FILTEREDD3DATA updates sent by filters-logic.m_FiltersApply - Note that edge.soourceLabel and edge.targetLabe should already be set + Note that edge.soourceLabel and edge.targetLabel should already be set by filter-logic. /*/ handleFilterDataUpdate(data) { From 0bd8e975f8c7d8e8cc673cca468a45d3315cccb6 Mon Sep 17 00:00:00 2001 From: benloh Date: Thu, 1 Jun 2023 13:56:20 -0700 Subject: [PATCH 08/21] collapse: Always recalculate degrees and sizes when switching between filtering views. (REVIEW: This can be a problem for the `Filter/Hide` filter, which is not supposed to recalculate. But we are assuming that we are removing that feature) --- build/app/view/netcreate/filter-logic.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/build/app/view/netcreate/filter-logic.js b/build/app/view/netcreate/filter-logic.js index 4d793f98..1315355e 100644 --- a/build/app/view/netcreate/filter-logic.js +++ b/build/app/view/netcreate/filter-logic.js @@ -316,11 +316,16 @@ function m_FiltersApply() { m_FiltersApplyToNodes(FDATA, FILTEREDD3DATA); m_FiltersApplyToEdges(FDATA, FILTEREDD3DATA); + + + // REVIEW 2023-0530 + // -- If "Filter/Hide" functionality is going to be kept, this needs to be reworked! + // We SHOULD NOT recalculate sizes in "Filter/Hide" mode, otherwise, the size will change. + // // Recalculate sizes - if ( FDATA.filterAction === FILTER.ACTION.COLLAPSE) { - UTILS.RecalculateAllEdgeSizes(FILTEREDD3DATA); - UTILS.RecalculateAllNodeDegrees(FILTEREDD3DATA); - } + // ALWAYS recalculate, e.g. if switching from Collapse to Highlight or clearing data + UTILS.RecalculateAllEdgeSizes(FILTEREDD3DATA); + UTILS.RecalculateAllNodeDegrees(FILTEREDD3DATA); // Update FILTEREDD3DATA UDATA.SetAppState("FILTEREDD3DATA", FILTEREDD3DATA); From 4f9f69996d83ed849d0332492daba61017546b3c Mon Sep 17 00:00:00 2001 From: benloh Date: Thu, 1 Jun 2023 15:03:26 -0700 Subject: [PATCH 09/21] collapse: lint/doc --- build/app/view/netcreate/nc-logic.js | 6 +++++- build/app/view/netcreate/nc-utils.js | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/build/app/view/netcreate/nc-logic.js b/build/app/view/netcreate/nc-logic.js index add766e8..28a049b3 100644 --- a/build/app/view/netcreate/nc-logic.js +++ b/build/app/view/netcreate/nc-logic.js @@ -1,5 +1,9 @@ /*//////////////////////////////// ABOUT \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*\ + nc-logic + + Net.Create application logic + * EVENTS: D3 Graph Updates Mark Node/Edge Nodes in the graph are marked via a stroke around @@ -1158,7 +1162,7 @@ function m_MarkNodeById(id, color) { // to override the properties m_SetMatchingNodesByProp({ id }, marked, normal); - UDATA.SetAppState("NCDATA", NCDATA); + UDATA.SetAppState("NCDATA", NCDATA); } /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ Sets the `node.selected` property to `color` so it is hilited on graph diff --git a/build/app/view/netcreate/nc-utils.js b/build/app/view/netcreate/nc-utils.js index 15a0969e..3acd88af 100644 --- a/build/app/view/netcreate/nc-utils.js +++ b/build/app/view/netcreate/nc-utils.js @@ -3,7 +3,9 @@ nc-utils General purpose utilities for manipulating NCDATA. - Used by both nc-logic and importexport-logic. + Used by: + * nc-logic + * filter-logic \*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * //////////////////////////////////////*/ From 0b881f23b59d11bff8e8ff98517b2c141a6fa924 Mon Sep 17 00:00:00 2001 From: benloh Date: Thu, 1 Jun 2023 15:05:36 -0700 Subject: [PATCH 10/21] collapse: Hide "Filter" (aka "Hide") function --- build/app/view/netcreate/components/filter/FiltersPanel.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/app/view/netcreate/components/filter/FiltersPanel.jsx b/build/app/view/netcreate/components/filter/FiltersPanel.jsx index 7357fd56..21d97808 100644 --- a/build/app/view/netcreate/components/filter/FiltersPanel.jsx +++ b/build/app/view/netcreate/components/filter/FiltersPanel.jsx @@ -103,6 +103,7 @@ class FiltersPanel extends UNISYS.Component { backgroundColor: filterAction === FILTER.ACTION.HIGHLIGHT ? 'transparent' : '#6c757d88' }} >Highlight + {/* Hide "Filter" panel. We will probably remove this functionality. + >Filter */} +
- {defs.map(def => )} + {FilterControlPanel}
diff --git a/build/app/view/netcreate/components/filter/FocusFilter.jsx b/build/app/view/netcreate/components/filter/FocusFilter.jsx new file mode 100644 index 00000000..ff3319e0 --- /dev/null +++ b/build/app/view/netcreate/components/filter/FocusFilter.jsx @@ -0,0 +1,96 @@ +/*//////////////////////////////// ABOUT \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*\ + + FOCUSFILTER + + FocusFilter provides the UI for entering the numeric range value for + the focus filter. + + Selection changes directly trigger a UDATA.LocalCall('FILTER_DEFINE',...). + +\*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * //////////////////////////////////////*/ + + +import React from 'react'; +const ReactStrap = require('reactstrap'); +const { Form, FormGroup, Input, Label } = ReactStrap; + +const UNISYS = require('unisys/client'); +var UDATA = null; + + +/// CLASS ///////////////////////////////////////////////////////////////////// +class FocusFilter extends React.Component { + + constructor({ + focusRange + }) { + super(); + this.OnChangeValue = this.OnChangeValue.bind(this); + this.TriggerChangeHandler = this.TriggerChangeHandler.bind(this); + this.OnSubmit = this.OnSubmit.bind(this); + + this.state = { + focusRange: focusRange // Used locally to define result + }; + + /// Initialize UNISYS DATA LINK for REACT + UDATA = UNISYS.NewDataLink(this); + } + + OnChangeValue(e) { + // The built in will keep the step buttons from going below 0, + // but the user can still input "0". We can't just use Math.min() because the + // user would not be allowed to use backspace to delete the value before + // entering a new number. Replacing invalid numbers with a blank value + // feels like a more natural way of editing. + const focusRange = e.target.value < 1 ? "" : e.target.value; + this.setState({ focusRange }, this.TriggerChangeHandler); + } + + TriggerChangeHandler() { + // even though we allow "" in the field, we always define the range to be 1 + // so that something will show + const focusRange = this.state.focusRange < 1 ? 1 : this.state.focusRange; + if (UDATA) UDATA.LocalCall('FILTER_DEFINE', { + group: "focus", + filter: { + value: focusRange + } + }); // set a SINGLE filter + } + + OnSubmit(e) { + // Prevent "ENTER" from triggering form submission! + e.preventDefault(); + e.stopPropagation(); + } + + render() { + const { focusSourceLabel } = this.props; + const { focusRange } = this.state; + return ( +
+
+ {/* FormGroup needs to unset flexFlow or fields will overflow + https://getbootstrap.com/docs/4.5/utilities/flex/ + */} + +
+
+ + + + +
+
+ ); + } +} + +/// EXPORT CLASS DEFINITION /////////////////////////////////////////////////// +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +export default FocusFilter; From 80dc928142c26b5f68678b5a316946dca3bf0c76 Mon Sep 17 00:00:00 2001 From: benloh Date: Thu, 1 Jun 2023 15:39:04 -0700 Subject: [PATCH 14/21] focus: Add filter logic for handling focus. --- build/app/view/netcreate/filter-logic.js | 175 +++++++++++++++++++++-- 1 file changed, 162 insertions(+), 13 deletions(-) diff --git a/build/app/view/netcreate/filter-logic.js b/build/app/view/netcreate/filter-logic.js index 1315355e..4cb0e0bc 100644 --- a/build/app/view/netcreate/filter-logic.js +++ b/build/app/view/netcreate/filter-logic.js @@ -35,6 +35,11 @@ label: "Edge Filters", filters: [...] } + focus: { + source: undefined, + sourceLabel: '', + range: 1 + } } @@ -98,7 +103,7 @@ var FDATA_RESTORE; // pristine FDATA for clearing let NODE_DEFAULT_TRANSPARENCY; let EDGE_DEFAULT_TRANSPARENCY; -let removedNodes = []; // nodes removed via COLLAPSE filter action +let RemovedNodes = []; // nodes removed via COLLAPSE filter action /// CONSTANTS ///////////////////////////////////////////////////////////////// /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -140,6 +145,20 @@ MOD.Hook("INITIALIZE", () => { UDATA.HandleMessage("FILTERS_UPDATE", data => { const FDATA = UDATA.AppState("FDATA"); FDATA.filterAction = data.filterAction; + // if the Focus panel is being selected, grab update the selection so that + // the selected node is immediately focused on (otherwise the system ignores + // the currently selecte dnode and you have to click on it again) + if (data.filterAction === FILTER.ACTION.FOCUS) { + const SELECT = UDATA.AppState("SELECTION"); + const selectedNode = SELECT.nodes ? SELECT.nodes[0] : undefined; + if (selectedNode) { + FDATA.focus = { + source: selectedNode.id, + sourceLabel: selectedNode.label, + range: FDATA.focus.range + }; + } + } UDATA.SetAppState("FDATA", FDATA); }); /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -157,6 +176,18 @@ MOD.Hook("INITIALIZE", () => { m_ImportFilters(); }); + /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /*/ 2023-06 Interim Approach -- eventually should convert to new selection mgr + Listen for SELECTION changes for setting Focus + /*/ + UDATA.OnAppStateChange("SELECTION", data => { + // Only if Focus is active + const FDATA = UDATA.AppState("FDATA"); + if (FDATA.filterAction === FILTER.ACTION.FOCUS) { + m_SetFocus(data); + } + }); + }); // end UNISYS_INIT @@ -185,6 +216,11 @@ function m_ImportFilters() { label: "Edge Filters", filters: m_ImportPrompts(edgeDefs), transparency: 0.03 // Default transparency form for Highlight should be 0.03, not template default which is usu 0.3 + }, + focus: { + source: undefined, // nothing focused by default + sourceLabel: '', + range: 1 } }; @@ -285,6 +321,8 @@ function m_FilterDefine(data) { edgeFilters.splice(index, 1, data.filter); FDATA.edges.filters = edgeFilters; } + } else if (data.group === "focus") { + FDATA.focus.range = data.filter.value; } else { throw `FILTER_DEFINE called with unknown group: ${data.group}`; @@ -316,8 +354,6 @@ function m_FiltersApply() { m_FiltersApplyToNodes(FDATA, FILTEREDD3DATA); m_FiltersApplyToEdges(FDATA, FILTEREDD3DATA); - - // REVIEW 2023-0530 // -- If "Filter/Hide" functionality is going to be kept, this needs to be reworked! // We SHOULD NOT recalculate sizes in "Filter/Hide" mode, otherwise, the size will change. @@ -441,15 +477,21 @@ function m_MatchNumber(operator, filterVal, objVal) { * @param {Array} filters */ function m_FiltersApplyToNodes(FDATA, FILTEREDD3DATA) { - removedNodes = []; - const { filterAction } = FDATA; - const { filters, transparency } = FDATA.nodes; + RemovedNodes = []; + + // if current filter is focus, calculate bacon_values + if (FDATA.filterAction === FILTER.ACTION.FOCUS) m_FocusPrep(FDATA, FILTEREDD3DATA); + FILTEREDD3DATA.nodes = FILTEREDD3DATA.nodes.filter(node => { - return m_NodeIsFiltered(node, filters, transparency, filterAction); + return m_NodeIsFiltered(node, FDATA); }); } -function m_NodeIsFiltered(node, filters, transparency, filterAction) { +function m_NodeIsFiltered(node, FDATA) { + const { filterAction } = FDATA; + const { filters, transparency } = FDATA.nodes; + const { source, range } = FDATA.focus; + // let all_no_op = true; let keepNode = true; @@ -478,11 +520,18 @@ function m_NodeIsFiltered(node, filters, transparency, filterAction) { return true; // don't filter out } else if (filterAction === FILTER.ACTION.COLLAPSE) { if (keepNode) return true; // matched, so keep - // filter out (remove) and add to `renovedNodes` for later removal of linked edge - removedNodes.push(node.id); + // filter out (remove) and add to `RemovedNodes` for later removal of linked edge + RemovedNodes.push(node.id); return false; + } else if (filterAction === FILTER.ACTION.FOCUS) { + // Remove nodes outside of range + if (source !== undefined && (node.bacon_value === undefined || node.bacon_value > range)) { + RemovedNodes.push(node.id); + return false; + } + return true; } else { - // collapse?!?!?! + // no filter, keep the node! return true; } @@ -561,8 +610,8 @@ function m_EdgeIsFiltered(edge, filters, transparency, filterAction, FILTEREDD3D // 1. If source or target are missing, then remove the edge if (source === undefined || target === undefined ) return false; - // 2. If source or target have been removed via collapse, remove the edge - if (removedNodes.includes(source.id) || removedNodes.includes(target.id)) return false; + // 2. If source or target have been removed via collapse or focus, remove the edge + if (RemovedNodes.includes(source.id) || RemovedNodes.includes(target.id)) return false; // 3. if source or target is transparent, then we are transparent too if ( source.filteredTransparency < 1.0 || target.filteredTransparency < 1.0) { @@ -660,6 +709,106 @@ function m_IsEdgeMatchedByFilter(edge, filter) { } } +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/*/ FOCUS FILTERS +/*/ + +/** + * Returns an Map of node ids that are directly connected to the passed `nodeId` + * Uses a Map so there are no redundancies. + * A more efficient search targeted on looking up nodes + * @param {object} puredata Raw/pure data from NCData + * @param {array} puredata.nodes + * @param {array} puredata.edges where edge.source and edge.target are numeric ids + * @param {string} nodeId The source nodeId to start the search from + * @returns {map} Map of matching nodeIds {number} + */ +function m_FindConnectedNodeIds(puredata, nodeId) { + let returnMatches = new Map(); + puredata.edges.forEach(edge => { + if (edge.source === nodeId) returnMatches.set(edge.target, nodeId); // nodeId in returnMatches is not necessary + if (edge.target === nodeId) returnMatches.set(edge.source, nodeId); + }) + return returnMatches; +} + + +/** + * Recursively walks down the network starting from the sourceNodes + * There can be more than one sourceNodes, e.g. this can set values starting with any number of nodes + * Modifies puredata by reference + * @param {object} puredata {nodes, edges} + * @param {array} sourceNodes {string} + * @param {number} range + */ +function m_SetBaconValue(bacon_value, max_bacon_value, puredata, sourceNodes) { + if (bacon_value > max_bacon_value) return; + sourceNodes.forEach(source => { + const newNodes = []; // collect new nodes that we need to walk down + const connectedNodeIds = m_FindConnectedNodeIds(puredata, source); // map + puredata.nodes = puredata.nodes.map(node => { + if (node.bacon_value !== undefined) return node; // skip bacon_value if ready set + + if (node.id === source) { + node.bacon_value = 0; // the focused node has a value of 0 + } else if (connectedNodeIds.has(node.id)) { + node.bacon_value = bacon_value; + newNodes.push(node.id); + } + return node; // returns node with updated bacon_value + }); + + // recursive call + if (newNodes.length > 0 && bacon_value + 1 <= max_bacon_value) m_SetBaconValue(bacon_value + 1, max_bacon_value, puredata, newNodes); + }); +} + +/** + * Prepares `puredata` (aka FILTEREDD3DATA) for filtering by + * seeding node data with "degrees of separation" (aka "bacon_value") from the selected node + * Uses FDATA specifications for the focus selection and range + * Modifies puredata by reference + * This should generally be called right before filtering is applied + * @param {*} FDATA + * @param {*} puredata + */ +function m_FocusPrep(FDATA, puredata) { + const { source, range } = FDATA.focus; + // first clear bacon_value + puredata.nodes = puredata.nodes.map(node => { + node.bacon_value = undefined; + return node; + }) + if (range < 1) { + return; // show all if range=0 + } + // Then set bacon_value + // Initiate the crawl starting at 1 with the source node + m_SetBaconValue(1, range, puredata, [source]); +} + +/** + * Called when SELECTION appState changes, e.g. user has clicked on a node + * while in FOCUS View. + * @param {object} data + * @param {array} data.nodes array of node objects + */ +function m_SetFocus(data) { + const selectedNode = data.nodes[0]; + const selectedNodeId = selectedNode ? selectedNode.id : undefined; + const selectedNodeLabel = selectedNode ? selectedNode.label : ''; + + // Set FDATA + const FDATA = UDATA.AppState("FDATA"); + FDATA.focus = { + source: selectedNodeId, + sourceLabel: selectedNodeLabel, + range: FDATA.focus.range + }; + UDATA.SetAppState("FDATA", FDATA); + + // Actual filtering is done by m_FiltersApply call after FDATA change +} /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From c0159376bd2fb8a4cd82737dd9847eb17b203df8 Mon Sep 17 00:00:00 2001 From: benloh Date: Thu, 1 Jun 2023 21:32:51 -0700 Subject: [PATCH 15/21] focus: Move Provenance next to Updated in Nodes Table. --- build/app/view/netcreate/components/NodeTable.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build/app/view/netcreate/components/NodeTable.jsx b/build/app/view/netcreate/components/NodeTable.jsx index da449688..64c42261 100644 --- a/build/app/view/netcreate/components/NodeTable.jsx +++ b/build/app/view/netcreate/components/NodeTable.jsx @@ -450,14 +450,14 @@ render() { - - + + @@ -485,12 +485,12 @@ render() { >{node.label} {node.type} {node.info} - {node.provenance} {node.notes ? : "" } + {node.provenance} {this.displayUpdated(node)} From 060acdc8aa44307e61a3736eb6101e88c9098a68 Mon Sep 17 00:00:00 2001 From: benloh Date: Fri, 2 Jun 2023 16:12:36 -0700 Subject: [PATCH 16/21] focus: lint fixes --- build/app/view/netcreate/components/NodeTable.jsx | 3 +-- build/app/view/netcreate/filter-logic.js | 15 +++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/build/app/view/netcreate/components/NodeTable.jsx b/build/app/view/netcreate/components/NodeTable.jsx index 64c42261..c26abb36 100644 --- a/build/app/view/netcreate/components/NodeTable.jsx +++ b/build/app/view/netcreate/components/NodeTable.jsx @@ -129,7 +129,7 @@ class NodeTable extends UNISYS.Component { /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - displayUpdated(nodeEdge) { // Prevent error if `meta` info is not defined yet, or not properly imported - if (!nodeEdge.meta) return; + if (!nodeEdge.meta) return ''; var d = new Date(nodeEdge.meta.revision > 0 ? nodeEdge.meta.updated : nodeEdge.meta.created); @@ -142,7 +142,6 @@ class NodeTable extends UNISYS.Component { var tag = {dateTime} ; return tag; - } /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build/app/view/netcreate/filter-logic.js b/build/app/view/netcreate/filter-logic.js index 4cb0e0bc..ec42cfb2 100644 --- a/build/app/view/netcreate/filter-logic.js +++ b/build/app/view/netcreate/filter-logic.js @@ -299,11 +299,9 @@ function m_FilterDefine(data) { FDATA.filterAction = data.filterAction || FDATA.filterAction; // if 'transparency' then filterAction is not passed, so default to existing if (data.group === "nodes") { - if (data.type === "transparency") - { + if (data.type === "transparency") { FDATA.nodes.transparency = data.transparency; - } - else{ + } else { let nodeFilters = FDATA.nodes.filters; const index = nodeFilters.findIndex(f => f.id === data.filter.id); nodeFilters.splice(index, 1, data.filter); @@ -311,11 +309,9 @@ function m_FilterDefine(data) { } } else if (data.group === "edges") { - if (data.type === "transparency") - { + if (data.type === "transparency") { FDATA.edges.transparency = data.transparency; - } - else{ + } else { let edgeFilters = FDATA.edges.filters; const index = edgeFilters.findIndex(f => f.id === data.filter.id); edgeFilters.splice(index, 1, data.filter); @@ -323,8 +319,7 @@ function m_FilterDefine(data) { } } else if (data.group === "focus") { FDATA.focus.range = data.filter.value; - } - else { + } else { throw `FILTER_DEFINE called with unknown group: ${data.group}`; } UDATA.SetAppState("FDATA", FDATA); From f0de238a03b1e8c4a71b3dfde686d322c4581f01 Mon Sep 17 00:00:00 2001 From: benloh Date: Fri, 2 Jun 2023 16:28:19 -0700 Subject: [PATCH 17/21] focus: Fix node and edge table updates while switching back and forth between filter modes. --- .../view/netcreate/components/EdgeTable.jsx | 58 ++++++++++------- .../view/netcreate/components/NodeTable.jsx | 62 ++++++++++++------- build/app/view/netcreate/filter-logic.js | 5 +- 3 files changed, 80 insertions(+), 45 deletions(-) diff --git a/build/app/view/netcreate/components/EdgeTable.jsx b/build/app/view/netcreate/components/EdgeTable.jsx index 0269cba2..58c3d53e 100644 --- a/build/app/view/netcreate/components/EdgeTable.jsx +++ b/build/app/view/netcreate/components/EdgeTable.jsx @@ -158,11 +158,21 @@ class EdgeTable extends UNISYS.Component { updateEdgeFilterState(edges, filteredEdges) { // add highlight/filter status if (filteredEdges.length > 0) { - edges = edges.map(edge => { - const filteredEdge = filteredEdges.find(n => n.id === edge.id); - edge.isFiltered = !filteredEdge; - return edge; - }); + // If we're transitioning from "HILIGHT/FADE" to "COLLAPSE" or "FOCUS", then we + // also need to remove edges that are not in filteredEdges + const FDATA = UDATA.AppState("FDATA"); + if (FDATA.filterAction === FILTER.ACTION.COLLAPSE || FDATA.filterAction === FILTER.ACTION.FOCUS) { + edges = edges.filter(edge => { + const filteredEdge = filteredEdges.find(n => n.id === edge.id); + return edge; + }); + } else { + edges = edges.map(edge => { + const filteredEdge = filteredEdges.find(n => n.id === edge.id); + edge.isFiltered = !filteredEdge; + return edge; + }); + } } this.setState({edges}); } @@ -199,22 +209,28 @@ class EdgeTable extends UNISYS.Component { handleFilterDataUpdate(data) { if (data.edges) { const filteredEdges = data.edges; - - // OLD METHOD: Keep a pure edges object, and apply filtering to them - // this.setState({ filteredEdges }, () => { - // const edges = this.sortTable(this.state.sortkey, this.state.edges); - // this.updateEdgeFilterState(edges, filteredEdges); - // }); - - // NEW METHOD: Just replace pure edges with filtered edges - // This way edges that have been filtered out are also removed from the table - this.setState({ - edges: filteredEdges, - filteredEdges - }, () => { - const edges = this.sortTable(this.state.sortkey, this.state.edges); - this.updateEdgeFilterState(edges, filteredEdges); - }); + // If we're transitioning from "COLLAPSE" or "FOCUS" to "HILIGHT/FADE", then we + // also need to add back in edges that are not in filteredEdges + // (because "COLLAPSE" and "FOCUS" removes edges that are not matched) + const FDATA = UDATA.AppState("FDATA"); + if (FDATA.filterAction === FILTER.ACTION.HIGHLIGHT) { + const NCDATA = UDATA.AppState("NCDATA"); + this.setState({ + edges: NCDATA.edges, + filteredEdges + }, () => { + const edges = this.sortTable(this.state.sortkey, NCDATA.edges); + this.updateEdgeFilterState(edges, filteredEdges); + }); + } else { + this.setState({ + edges: filteredEdges, + filteredEdges + }, () => { + const edges = this.sortTable(this.state.sortkey, filteredEdges); + this.updateEdgeFilterState(edges, filteredEdges); + }); + } } } diff --git a/build/app/view/netcreate/components/NodeTable.jsx b/build/app/view/netcreate/components/NodeTable.jsx index c26abb36..0fc1e4a8 100644 --- a/build/app/view/netcreate/components/NodeTable.jsx +++ b/build/app/view/netcreate/components/NodeTable.jsx @@ -149,13 +149,26 @@ class NodeTable extends UNISYS.Component { updateNodeFilterState(nodes, filteredNodes) { // set filter status if (filteredNodes.length > 0) { - nodes = nodes.map(node => { - const filteredNode = filteredNodes.find(n => n.id === node.id); - node.isFiltered = !filteredNode; // not in filteredNode, so it's been removed - return node - }); + + // If we're transitioning from "HILIGHT/FADE" to "COLLAPSE" or "FOCUS", then we + // also need to remove nodes that are not in filteredNodes + const FDATA = UDATA.AppState("FDATA"); + if (FDATA.filterAction === FILTER.ACTION.COLLAPSE || FDATA.filterAction === FILTER.ACTION.FOCUS) { + nodes = nodes.filter(node => { + const filteredNode = filteredNodes.find(n => n.id === node.id); + return filteredNode; // keep if it's in the list of filtered nodes + }); + } else { + nodes = nodes.map(node => { + const filteredNode = filteredNodes.find(n => n.id === node.id); + node.isFiltered = !filteredNode; // not in filteredNode, so it's been removed + return node + }); + } + + } - this.setState({ nodes }); + this.setState({ nodes, filteredNodes }); } /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -174,22 +187,29 @@ class NodeTable extends UNISYS.Component { handleFilterDataUpdate(data) { if (data.nodes) { const filteredNodes = data.nodes; + // If we're transitioning from "COLLAPSE" or "FOCUS" to "HILIGHT/FADE", then we + // also need to add back in nodes that are not in filteredNodes + // (because "COLLAPSE" and "FOCUS" removes nodes that are not matched) + const FDATA = UDATA.AppState("FDATA"); + if (FDATA.filterAction === FILTER.ACTION.HIGHLIGHT) { + const NCDATA = UDATA.AppState("NCDATA"); + this.setState({ + nodes: NCDATA.nodes, + filteredNodes + }, () => { + const nodes = this.sortTable(this.state.sortkey, NCDATA.nodes); + this.updateNodeFilterState(nodes, filteredNodes); + }); - // OLD METHOD: Keep a pure nodes object, and apply filtering to them - // this.setState({ filteredNodes }, () => { - // const nodes = this.sortTable(this.state.sortkey, this.state.nodes); - // this.updateNodeFilterState(nodes, filteredNodes); - // }); - - // NEW METHOD: Just replace pure nodes with filtered nodes - // This way nodes that have been filtered out are also removed from the table - this.setState({ - nodes: filteredNodes, - filteredNodes - }, () => { - const nodes = this.sortTable(this.state.sortkey, this.state.nodes); - this.updateNodeFilterState(nodes, filteredNodes); - }); + } else { + this.setState({ + nodes: filteredNodes, + filteredNodes + }, () => { + const nodes = this.sortTable(this.state.sortkey, filteredNodes); + this.updateNodeFilterState(nodes, filteredNodes); + }); + } } } diff --git a/build/app/view/netcreate/filter-logic.js b/build/app/view/netcreate/filter-logic.js index ec42cfb2..2ac6c434 100644 --- a/build/app/view/netcreate/filter-logic.js +++ b/build/app/view/netcreate/filter-logic.js @@ -501,16 +501,15 @@ function m_NodeIsFiltered(node, FDATA) { }); // 2. Decide based on filterAction + node.isFiltered = false; // always reset if not HIGHLIGHT + node.filteredTransparency = NODE_DEFAULT_TRANSPARENCY; // always reset if not HIGHLIGHT if (filterAction === FILTER.ACTION.FILTER) { // not using highlight, so restore transparency - node.filteredTransparency = NODE_DEFAULT_TRANSPARENCY; // opaque, not transparent if (keepNode) return true; return false; // remove from array } else if (filterAction === FILTER.ACTION.HIGHLIGHT) { if (!keepNode) { node.filteredTransparency = transparency; // set the transparency value ... right now it is inefficient to set this at the node / edge level, but that's more flexible - } else { - node.filteredTransparency = NODE_DEFAULT_TRANSPARENCY; // opaque } return true; // don't filter out } else if (filterAction === FILTER.ACTION.COLLAPSE) { From 6fa9bea37ac982deb3e065a616c39b609626e0a3 Mon Sep 17 00:00:00 2001 From: benloh Date: Fri, 2 Jun 2023 16:28:36 -0700 Subject: [PATCH 18/21] focus: lint fix --- build/app/view/netcreate/components/EdgeTable.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/app/view/netcreate/components/EdgeTable.jsx b/build/app/view/netcreate/components/EdgeTable.jsx index 58c3d53e..0d9a2326 100644 --- a/build/app/view/netcreate/components/EdgeTable.jsx +++ b/build/app/view/netcreate/components/EdgeTable.jsx @@ -137,7 +137,7 @@ class EdgeTable extends UNISYS.Component { /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - displayUpdated(nodeEdge) { // Prevent error if `meta` info is not defined yet, or not properly imported - if (!nodeEdge.meta) return; + if (!nodeEdge.meta) return ''; var d = new Date(nodeEdge.meta.revision > 0 ? nodeEdge.meta.updated : nodeEdge.meta.created); From ccb1604b8de55bdc365cb447e1183beee8cb02ae Mon Sep 17 00:00:00 2001 From: benloh Date: Fri, 2 Jun 2023 16:36:26 -0700 Subject: [PATCH 19/21] focus: Rename filters to "FADE" (aka Highlight) and "REMOVE" (aka Collapse) --- .../view/netcreate/components/filter/FilterEnums.js | 10 +++++----- .../view/netcreate/components/filter/FiltersPanel.jsx | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/app/view/netcreate/components/filter/FilterEnums.js b/build/app/view/netcreate/components/filter/FilterEnums.js index 8f35b42d..63af94af 100644 --- a/build/app/view/netcreate/components/filter/FilterEnums.js +++ b/build/app/view/netcreate/components/filter/FilterEnums.js @@ -5,14 +5,14 @@ FILTER.PANEL_LABEL = 'VIEWS'; // Determines whether filter action is to highlight/fade or remove (filter) nodes and edges FILTER.ACTION = {}; -FILTER.ACTION.HIGHLIGHT = 'HIGHLIGHTING'; +FILTER.ACTION.HIGHLIGHT = 'FADE'; FILTER.ACTION.FILTER = 'FILTERING'; -FILTER.ACTION.COLLAPSE = 'COLLAPSING'; -FILTER.ACTION.FOCUS = 'FOCUSING'; +FILTER.ACTION.COLLAPSE = 'REMOVE'; +FILTER.ACTION.FOCUS = 'FOCUS'; FILTER.ACTION.HELP = {}; -FILTER.ACTION.HELP.HIGHLIGHT = 'Show (Highlight) matches, Fade others'; +FILTER.ACTION.HELP.HIGHLIGHT = 'Show matches, Fade others'; FILTER.ACTION.HELP.FILTER = 'Shows matches, Hide (Filter) others (keep physics and degrees)'; -FILTER.ACTION.HELP.COLLAPSE = 'Show matches, Remove (collapse) others & recalculate sizes'; +FILTER.ACTION.HELP.COLLAPSE = 'Show matches, Remove others & recalculate sizes'; FILTER.ACTION.HELP.FOCUS = 'Show only nodes connected to the selected node within range'; // Types of filters definable in template files. diff --git a/build/app/view/netcreate/components/filter/FiltersPanel.jsx b/build/app/view/netcreate/components/filter/FiltersPanel.jsx index 6e8d09aa..3b1e6801 100644 --- a/build/app/view/netcreate/components/filter/FiltersPanel.jsx +++ b/build/app/view/netcreate/components/filter/FiltersPanel.jsx @@ -127,7 +127,7 @@ class FiltersPanel extends UNISYS.Component { color: filterAction === FILTER.ACTION.HIGHLIGHT ? '#333' : '#fff', backgroundColor: filterAction === FILTER.ACTION.HIGHLIGHT ? 'transparent' : '#6c757d88' }} - >Highlight + >{FILTER.ACTION.HIGHLIGHT} {/* Hide "Filter" panel. We will probably remove this functionality. + >{FILTER.ACTION.COLLAPSE} + >{FILTER.ACTION.FOCUS}