Commit ff3c173b authored by James Long's avatar James Long Committed by Commit Bot

Update dependency visualization graph to be completely reactive

Previously, the users of the graph visualization component would need to
ping it whenever there was a data update. Now, the users should supply
as props the properties of pageModel that the graph should be reactive
to. Since the page is now fully reactive, the "update" buttons for the
inbound/outbound components have been removed.

The goal of this is to make it easier to add UI components that
interface with the visualization component.

Bug: 1106107
Change-Id: I0578ad655fc6d13d7cc9cb7e444920846e02438b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2310194
Commit-Queue: James Long <yjlong@google.com>
Reviewed-by: default avatarSamuel Huang <huangs@chromium.org>
Reviewed-by: default avatarMohamed Heikal <mheikal@chromium.org>
Cr-Commit-Position: refs/heads/master@{#790560}
parent 3109a6ee
......@@ -166,18 +166,22 @@ class GraphView {
.force('chargeForce', d3.forceManyBody().strength(-3000))
.force('centerXForce',
d3.forceX(width / 2).strength(node => {
if (node.visualizationState.selectedByFilter)
if (node.visualizationState.selectedByFilter) {
return centeringStrengthX * 20;
if (node.visualizationState.outboundDepth <= 1)
}
if (node.visualizationState.outboundDepth <= 1) {
return centeringStrengthX * 5;
}
return centeringStrengthY;
}))
.force('centerYForce',
d3.forceY(height / 2).strength(node => {
if (node.visualizationState.selectedByFilter)
if (node.visualizationState.selectedByFilter) {
return centeringStrengthY * 20;
if (node.visualizationState.outboundDepth <= 1)
}
if (node.visualizationState.outboundDepth <= 1) {
return centeringStrengthY * 5;
}
return centeringStrengthY;
}));
......
......@@ -28,6 +28,7 @@ import {PagePathName} from '../url_processor.js';
import LinkToGraph from './link_to_graph.vue';
// @vue/component
const ClassDetailsPanel = {
components: {
LinkToGraph,
......@@ -41,4 +42,4 @@ const ClassDetailsPanel = {
};
export default ClassDetailsPanel;
</script>
\ No newline at end of file
</script>
......@@ -12,15 +12,13 @@
:node-filter-data="pageModel.nodeFilterData"
@[CUSTOM_EVENTS.FILTER_ELEMENT_CLICKED]="removeNodeFromFilter"/>
<GraphInboundInput
:inbound-depth-data="pageModel.inboundDepthData"
@[CUSTOM_EVENTS.INBOUND_DEPTH_UPDATED]="setInboundDepth"/>
:inbound-depth-data="pageModel.inboundDepthData"/>
<GraphOutboundInput
:outbound-depth-data="pageModel.outboundDepthData"
@[CUSTOM_EVENTS.OUTBOUND_DEPTH_UPDATED]="setOutboundDepth"/>
:outbound-depth-data="pageModel.outboundDepthData"/>
</div>
<div id="graph-and-node-details-container">
<GraphVisualization
:graph-data-update-ticker="graphDataUpdateTicker"
:graph-update-triggers="graphUpdateTriggers"
:page-model="pageModel"
@[CUSTOM_EVENTS.NODE_CLICKED]="graphNodeClicked"/>
<div id="node-details-container">
......@@ -53,9 +51,9 @@ import GraphInboundInput from './graph_inbound_input.vue';
import GraphOutboundInput from './graph_outbound_input.vue';
import GraphSelectedNodeDetails from './graph_selected_node_details.vue';
import GraphVisualization from './graph_visualization.vue';
import LinkToGraph from './link_to_graph.vue';
import PageUrlGenerator from './page_url_generator.vue';
// @vue/component
const ClassGraphPage = {
components: {
ClassDetailsPanel,
......@@ -65,7 +63,6 @@ const ClassGraphPage = {
GraphOutboundInput,
GraphSelectedNodeDetails,
GraphVisualization,
LinkToGraph,
PageUrlGenerator,
},
props: {
......@@ -77,9 +74,6 @@ const ClassGraphPage = {
* @typedef {Object} ClassPageData
* @property {PageModel} pageModel The data store for the page.
* @property {PagePathName} pagePathName The pathname for the page.
* @property {number} graphDataUpdateTicker Incremented every time we want to
* trigger a visualization update. See graph_visualization.js for further
* explanation on this variable.
*/
/**
......@@ -92,12 +86,17 @@ const ClassGraphPage = {
return {
pageModel,
pagePathName: PagePathName.CLASS,
graphDataUpdateTicker: 0,
};
},
computed: {
CUSTOM_EVENTS: () => CUSTOM_EVENTS,
PagePathName: () => PagePathName,
graphUpdateTriggers: function() {
return [
this.pageModel.nodeFilterData.nodeList,
this.pageModel.inboundDepthData.inboundDepth,
this.pageModel.outboundDepthData.outboundDepth,
];
},
},
/**
* Parses out data from the current URL to initialize the visualization with.
......@@ -117,7 +116,6 @@ const ClassGraphPage = {
}
this.setOutboundDepth(1);
this.graphDataUpdateTicker++;
},
methods: {
/**
......@@ -125,7 +123,6 @@ const ClassGraphPage = {
*/
addNodeToFilter: function(nodeName) {
this.pageModel.nodeFilterData.addNode(nodeName);
this.graphDataUpdateTicker++;
},
/**
* Adds all supplied nodes to the node filter, then increments
......@@ -136,28 +133,24 @@ const ClassGraphPage = {
for (const nodeName of nodeNames) {
this.pageModel.nodeFilterData.addNode(nodeName);
}
this.graphDataUpdateTicker++;
},
/**
* @param {string} nodeName The node to remove.
*/
removeNodeFromFilter: function(nodeName) {
this.pageModel.nodeFilterData.removeNode(nodeName);
this.graphDataUpdateTicker++;
},
/**
* @param {number} depth The new inbound depth.
*/
setInboundDepth: function(depth) {
this.pageModel.inboundDepthData.inboundDepth = depth;
this.graphDataUpdateTicker++;
},
/**
* @param {number} depth The new outbound depth.
*/
setOutboundDepth: function(depth) {
this.pageModel.outboundDepthData.outboundDepth = depth;
this.graphDataUpdateTicker++;
},
/**
* @param {?GraphNode} node The selected node. May be `null`, which will
......
......@@ -18,6 +18,7 @@ import {CUSTOM_EVENTS} from '../vue_custom_events.js';
import Autocomplete from '@trevoreyre/autocomplete-vue';
// @vue/component
const GraphFilterInput = {
components: {
Autocomplete,
......
......@@ -16,6 +16,7 @@
<script>
import {CUSTOM_EVENTS} from '../vue_custom_events.js';
// @vue/component
const GraphFilterItems = {
props: {
nodeFilterData: Object,
......
......@@ -7,31 +7,17 @@
<label for="filter-inbound">Change inbound (blue) depth:</label>
<input
id="filter-inbound"
v-model.number="inboundDepth"
v-model.number="inboundDepthData.inboundDepth"
type="number">
<button
type="button"
@click="submitInbound">
Update Inbound
</button>
</div>
</template>
<script>
import {CUSTOM_EVENTS} from '../vue_custom_events.js';
// @vue/component
const GraphInboundInput = {
props: {
inboundDepthData: Object,
},
data: function() {
return this.inboundDepthData;
},
methods: {
submitInbound: function() {
this.$emit(CUSTOM_EVENTS.INBOUND_DEPTH_UPDATED, this.inboundDepth);
},
},
};
export default GraphInboundInput;
......
......@@ -7,31 +7,17 @@
<label for="filter-outbound">Change outbound (yellow) depth:</label>
<input
id="filter-outbound"
v-model.number="outboundDepth"
v-model.number="outboundDepthData.outboundDepth"
type="number">
<button
type="button"
@click="submitOutbound">
Update Outbound
</button>
</div>
</template>
<script>
import {CUSTOM_EVENTS} from '../vue_custom_events.js';
// @vue/component
const GraphOutboundInput = {
props: {
outboundDepthData: Object,
},
data: function() {
return this.outboundDepthData;
},
methods: {
submitOutbound: function() {
this.$emit(CUSTOM_EVENTS.OUTBOUND_DEPTH_UPDATED, this.outboundDepth);
},
},
};
export default GraphOutboundInput;
......
......@@ -34,6 +34,7 @@
<script>
import {CUSTOM_EVENTS} from '../vue_custom_events.js';
// @vue/component
const GraphSelectedNodeDetails = {
props: {
selectedNodeDetailsData: Object,
......
......@@ -13,20 +13,31 @@
import {CUSTOM_EVENTS} from '../vue_custom_events.js';
import {GraphView} from '../graph_view.js';
// @vue/component
const GraphVisualization = {
props: {
graphDataUpdateTicker: Number,
/**
* `graphUpdateTriggers` is an array of model properties that trigger an
* update in the graph.
*
* Background: The need to trigger updates make it hard to integrate
* `graph_view.js` into the reactive Vue framework. This is solved with
* `graphUpdateTriggers`, which lists the parts of `pageModel` to observe
* and propagate updates to `graph_view`. This makes the graph "reactive" to
* changes in members of `graphUpdateTriggers`.
*
* Note: Observing `pageModel` in entirety is undesirable since it would
* lead to circular rerendering.
*/
graphUpdateTriggers: Array,
pageModel: Object,
},
watch: {
/**
* Watching a "ticker" variable is used for now since we don't always want
* `graphView` to be reactive with respect to `pageModel` (eg. if the user
* is typing but has not submitted yet). This ticker hence becomes the only
* way to force the visualization to update its underlying data.
*/
graphDataUpdateTicker: function() {
this.graphView.updateGraphData(this.pageModel.getDataForD3());
graphUpdateTriggers: {
handler: function() {
this.graphView.updateGraphData(this.pageModel.getDataForD3());
},
deep: true,
},
},
/**
......
......@@ -9,6 +9,7 @@
<script>
import {generateUrlFromFilter} from '../url_processor.js';
// @vue/component
const LinkToGraph = {
props: {
filter: Array,
......
......@@ -29,6 +29,7 @@ import {shortenClassName} from '../chrome_hooks.js';
import LinkToGraph from './link_to_graph.vue';
// @vue/component
const PackageDetailsPanel = {
components: {
LinkToGraph,
......
......@@ -12,15 +12,13 @@
:node-filter-data="pageModel.nodeFilterData"
@[CUSTOM_EVENTS.FILTER_ELEMENT_CLICKED]="removeNodeFromFilter"/>
<GraphInboundInput
:inbound-depth-data="pageModel.inboundDepthData"
@[CUSTOM_EVENTS.INBOUND_DEPTH_UPDATED]="setInboundDepth"/>
:inbound-depth-data="pageModel.inboundDepthData"/>
<GraphOutboundInput
:outbound-depth-data="pageModel.outboundDepthData"
@[CUSTOM_EVENTS.OUTBOUND_DEPTH_UPDATED]="setOutboundDepth"/>
:outbound-depth-data="pageModel.outboundDepthData"/>
</div>
<div id="graph-and-node-details-container">
<GraphVisualization
:graph-data-update-ticker="graphDataUpdateTicker"
:graph-update-triggers="graphUpdateTriggers"
:page-model="pageModel"
@[CUSTOM_EVENTS.NODE_CLICKED]="graphNodeClicked"/>
<div id="node-details-container">
......@@ -55,6 +53,7 @@ import GraphVisualization from './graph_visualization.vue';
import PackageDetailsPanel from './package_details_panel.vue';
import PageUrlGenerator from './page_url_generator.vue';
// @vue/component
const PackageGraphPage = {
components: {
GraphFilterInput,
......@@ -75,9 +74,6 @@ const PackageGraphPage = {
* @typedef {Object} PackagePageData
* @property {PageModel} pageModel The data store for the page.
* @property {PagePathName} pagePathName The pathname for the page.
* @property {number} graphDataUpdateTicker Incremented every time we want to
* trigger a visualization update. See graph_visualization.js for further
* explanation on this variable.
*/
/**
......@@ -90,11 +86,17 @@ const PackageGraphPage = {
return {
pageModel,
pagePathName: PagePathName.PACKAGE,
graphDataUpdateTicker: 0,
};
},
computed: {
CUSTOM_EVENTS: () => CUSTOM_EVENTS,
graphUpdateTriggers: function() {
return [
this.pageModel.nodeFilterData.nodeList,
this.pageModel.inboundDepthData.inboundDepth,
this.pageModel.outboundDepthData.outboundDepth,
];
},
},
/**
* Parses out data from the current URL to initialize the visualization with.
......@@ -124,7 +126,6 @@ const PackageGraphPage = {
*/
addNodeToFilter: function(nodeName) {
this.pageModel.nodeFilterData.addNode(nodeName);
this.graphDataUpdateTicker++;
},
/**
* Adds all supplied nodes to the node filter, then increments
......@@ -135,28 +136,24 @@ const PackageGraphPage = {
for (const nodeName of nodeNames) {
this.pageModel.nodeFilterData.addNode(nodeName);
}
this.graphDataUpdateTicker++;
},
/**
* @param {string} nodeName The node to remove.
*/
removeNodeFromFilter: function(nodeName) {
this.pageModel.nodeFilterData.removeNode(nodeName);
this.graphDataUpdateTicker++;
},
/**
* @param {number} depth The new inbound depth.
*/
setInboundDepth: function(depth) {
this.pageModel.inboundDepthData.inboundDepth = depth;
this.graphDataUpdateTicker++;
},
/**
* @param {number} depth The new outbound depth.
*/
setOutboundDepth: function(depth) {
this.pageModel.outboundDepthData.outboundDepth = depth;
this.graphDataUpdateTicker++;
},
/**
* @param {?GraphNode} node The selected node. May be `null`, which will
......
......@@ -17,6 +17,7 @@
<script>
import {generateUrlFromFilter} from '../url_processor.js';
// @vue/component
const PageUrlGenerator = {
props: {
pagePathName: String,
......
......@@ -8,9 +8,7 @@ const CUSTOM_EVENTS = {
ADD_TO_FILTER_CLICKED: 'add-to-filter-clicked',
FILTER_ELEMENT_CLICKED: 'filter-element-clicked',
FILTER_SUBMITTED: 'filter-submitted',
INBOUND_DEPTH_UPDATED: 'inbound-depth-updated',
NODE_CLICKED: 'node-clicked',
OUTBOUND_DEPTH_UPDATED: 'outbound-depth-updated',
REMOVE_FROM_FILTER_CLICKED: 'remove-from-filter-clicked',
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment