Skip to content

Commit

Permalink
center the map on the origin node
Browse files Browse the repository at this point in the history
  • Loading branch information
oatkiller committed Jul 19, 2020
1 parent 92d406c commit 3ea8ede
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
import { IsometricTaxiLayout } from '../../types';
import { LegacyEndpointEvent } from '../../../../common/endpoint/types';
import { isometricTaxiLayout } from './isometric_taxi_layout';
import { isometricTaxiLayoutFactory } from './isometric_taxi_layout';
import { mockProcessEvent } from '../../models/process_event_test_helpers';
import { factory } from './index';

Expand Down Expand Up @@ -107,7 +107,7 @@ describe('resolver graph layout', () => {
unique_ppid: 0,
},
});
layout = () => isometricTaxiLayout(factory(events));
layout = () => isometricTaxiLayoutFactory(factory(events));
events = [];
});
describe('when rendering no nodes', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import * as vector2 from '../../models/vector2';
import {
IndexedProcessTree,
Vector2,
Expand All @@ -17,14 +16,17 @@ import {
} from '../../types';
import * as event from '../../../../common/endpoint/models/event';
import { ResolverEvent } from '../../../../common/endpoint/types';
import * as model from './index';
import * as vector2 from '../vector2';
import * as indexedProcessTreeModel from './index';
import { getFriendlyElapsedTime as elapsedTime } from '../../lib/date';
import { uniquePidForProcess } from '../process_event';

/**
* Graph the process tree
*/
export function isometricTaxiLayout(indexedProcessTree: IndexedProcessTree): IsometricTaxiLayout {
export function isometricTaxiLayoutFactory(
indexedProcessTree: IndexedProcessTree
): IsometricTaxiLayout {
/**
* Walk the tree in reverse level order, calculating the 'width' of subtrees.
*/
Expand Down Expand Up @@ -83,8 +85,8 @@ export function isometricTaxiLayout(indexedProcessTree: IndexedProcessTree): Iso
*/
function ariaLevels(indexedProcessTree: IndexedProcessTree): Map<ResolverEvent, number> {
const map: Map<ResolverEvent, number> = new Map();
for (const node of model.levelOrder(indexedProcessTree)) {
const parentNode = model.parent(indexedProcessTree, node);
for (const node of indexedProcessTreeModel.levelOrder(indexedProcessTree)) {
const parentNode = indexedProcessTreeModel.parent(indexedProcessTree, node);
if (parentNode === undefined) {
// nodes at the root have a level of 1
map.set(node, 1);
Expand Down Expand Up @@ -143,16 +145,19 @@ function ariaLevels(indexedProcessTree: IndexedProcessTree): Map<ResolverEvent,
function widthsOfProcessSubtrees(indexedProcessTree: IndexedProcessTree): ProcessWidths {
const widths = new Map<ResolverEvent, number>();

if (model.size(indexedProcessTree) === 0) {
if (indexedProcessTreeModel.size(indexedProcessTree) === 0) {
return widths;
}

const processesInReverseLevelOrder: ResolverEvent[] = [
...model.levelOrder(indexedProcessTree),
...indexedProcessTreeModel.levelOrder(indexedProcessTree),
].reverse();

for (const process of processesInReverseLevelOrder) {
const children = model.children(indexedProcessTree, uniquePidForProcess(process));
const children = indexedProcessTreeModel.children(
indexedProcessTree,
uniquePidForProcess(process)
);

const sumOfWidthOfChildren = function sumOfWidthOfChildren() {
return children.reduce(function sum(currentValue, child) {
Expand Down Expand Up @@ -229,7 +234,10 @@ function processEdgeLineSegments(
metadata: edgeLineMetadata,
};

const siblings = model.children(indexedProcessTree, uniquePidForProcess(parent));
const siblings = indexedProcessTreeModel.children(
indexedProcessTree,
uniquePidForProcess(parent)
);
const isFirstChild = process === siblings[0];

if (metadata.isOnlyChild) {
Expand Down Expand Up @@ -384,8 +392,8 @@ function* levelOrderWithWidths(
tree: IndexedProcessTree,
widths: ProcessWidths
): Iterable<ProcessWithWidthMetadata> {
for (const process of model.levelOrder(tree)) {
const parent = model.parent(tree, process);
for (const process of indexedProcessTreeModel.levelOrder(tree)) {
const parent = indexedProcessTreeModel.parent(tree, process);
const width = widths.get(process);

if (width === undefined) {
Expand Down Expand Up @@ -423,7 +431,7 @@ function* levelOrderWithWidths(
parentWidth,
};

const siblings = model.children(tree, uniquePidForProcess(parent));
const siblings = indexedProcessTreeModel.children(tree, uniquePidForProcess(parent));
if (siblings.length === 1) {
metadata.isOnlyChild = true;
metadata.lastChildWidth = width;
Expand Down Expand Up @@ -479,3 +487,27 @@ const distanceBetweenNodesInUnits = 2;
* The distance in pixels (at scale 1) between nodes. Change this to space out nodes more
*/
const distanceBetweenNodes = distanceBetweenNodesInUnits * unit;

export function nodePosition(model: IsometricTaxiLayout, node: ResolverEvent): Vector2 | undefined {
return model.processNodePositions.get(node);
}

/**
* Return a clone of `model` with all positions incremented by `position`.
*/
export function translated(model: IsometricTaxiLayout, translation: Vector2): IsometricTaxiLayout {
return {
processNodePositions: new Map(
[...model.processNodePositions.entries()].map(([node, position]) => [
node,
vector2.add(position, translation),
])
),
edgeLineSegments: model.edgeLineSegments.map(({ points, metadata }) => ({
points: points.map((point) => vector2.add(point, translation)),
metadata,
})),
// these are unchanged
ariaLevels: model.ariaLevels,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ import {
ResolverRelatedEvents,
} from '../../../../common/endpoint/types';
import * as resolverTreeModel from '../../models/resolver_tree';
import { isometricTaxiLayout } from '../../models/indexed_process_tree/isometric_taxi_layout';
import * as isometricTaxiLayoutModel from '../../models/indexed_process_tree/isometric_taxi_layout';
import { allEventCategories } from '../../../../common/endpoint/models/event';
import * as vector2 from '../../models/vector2';

/**
* If there is currently a request.
Expand Down Expand Up @@ -70,6 +71,21 @@ const resolverTreeResponse = (state: DataState): ResolverTree | undefined => {
}
};

/**
* the node ID of the node representing the databaseDocumentID.
* NB: this could be stale if the last response is stale
*/
export const originID: (state: DataState) => string | undefined = createSelector(
resolverTreeResponse,
function (resolverTree?) {
if (resolverTree) {
// This holds the entityID (aka nodeID) of the node related to the last fetched `_id`
return resolverTree.entityID;
}
return undefined;
}
);

/**
* Process events that will be displayed as terminated.
*/
Expand Down Expand Up @@ -320,13 +336,44 @@ export function databaseDocumentIDToFetch(state: DataState): string | null {
}
}

export const layout = createSelector(tree, function processNodePositionsAndEdgeLineSegments(
/* eslint-disable no-shadow */
indexedProcessTree
/* eslint-enable no-shadow */
) {
return isometricTaxiLayout(indexedProcessTree);
});
export const layout = createSelector(
tree,
originID,
function processNodePositionsAndEdgeLineSegments(
/* eslint-disable no-shadow */
indexedProcessTree,
originID
/* eslint-enable no-shadow */
) {
// use the isometric taxi layout as a base
const taxiLayout = isometricTaxiLayoutModel.isometricTaxiLayoutFactory(indexedProcessTree);

if (!originID) {
// TODO, this should only happen when no data has loaded.
return taxiLayout;
}

// find the origin node
const originNode = indexedProcessTreeModel.processEvent(indexedProcessTree, originID);

if (!originNode) {
// this should only happen if the `ResolverTree` from the server has an entity ID with no matching lifecycle events.
throw new Error('Origin node not found in ResolverTree');
}

// Find the position of the origin, we'll center the map on it intrinsically
const originPosition = isometricTaxiLayoutModel.nodePosition(taxiLayout, originNode);
// adjust the position of everything so that the origin node is at `(0, 0)`

if (originPosition === undefined) {
// not sure how this could happen.
return taxiLayout;
}

// subtract the origin position from the layout, centering the layout around the origin node
return isometricTaxiLayoutModel.translated(taxiLayout, vector2.scale(originPosition, -1));
}
);

/**
* Given a nodeID (aka entity_id) get the indexed process event.
Expand Down Expand Up @@ -566,18 +613,3 @@ export const relatedEventTotalForProcess: (
};
}
);

/**
* the node ID of the node representing the databaseDocumentID.
* NB: this could be stale if the last response is stale
*/
export const originID: (state: DataState) => string | undefined = createSelector(
resolverTreeResponse,
function (resolverTree?) {
if (resolverTree) {
// This holds the entityID (aka nodeID) of the node related to the last fetched `_id`
return resolverTree.entityID;
}
return undefined;
}
);

0 comments on commit 3ea8ede

Please sign in to comment.