Skip to content

Commit

Permalink
Minor enhancement : Use Arrow Link for the Force Dependency graph (#373)
Browse files Browse the repository at this point in the history
Minor enhancement : Use Arrow Link for the Force Dependency graph
  • Loading branch information
tiffon committed Jul 15, 2019
2 parents b8ca41d + 4af19d4 commit e197f15
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
// limitations under the License.

import React, { Component } from 'react';
import { InteractiveForceGraph, ForceGraphNode, ForceGraphLink } from 'react-vis-force';
import { InteractiveForceGraph, ForceGraphNode } from 'react-vis-force';
import { window } from 'global';
import { debounce } from 'lodash';
import ForceGraphArrowLink from './ForceGraphArrowLink';

import { nodesPropTypes, linksPropTypes } from '../../propTypes/dependencies';

Expand Down Expand Up @@ -59,6 +60,7 @@ export default class DependencyForceGraph extends Component {
render() {
const { nodes, links } = this.props;
const { width, height } = this.state;
const nodesMap = new Map(nodes.map(node => [node.id, node]));

return (
<div
Expand Down Expand Up @@ -102,7 +104,12 @@ export default class DependencyForceGraph extends Component {
/>
))}
{links.map(({ opacity, ...link }) => (
<ForceGraphLink key={`${link.source}=>${link.target}`} opacity={opacity} link={link} />
<ForceGraphArrowLink
key={`${link.source}=>${link.target}`}
opacity={opacity}
link={link}
targetRadius={nodesMap.get(link.target).radius}
/>
))}
</InteractiveForceGraph>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@

import React from 'react';
import { shallow } from 'enzyme';
import { InteractiveForceGraph, ForceGraphNode, ForceGraphLink } from 'react-vis-force';
import { InteractiveForceGraph, ForceGraphNode } from 'react-vis-force';

import DependencyForceGraph, { chargeStrength } from './DependencyForceGraph';
import ForceGraphArrowLink from './ForceGraphArrowLink';

describe('chargeStrength', () => {
it('returns a number', () => {
Expand Down Expand Up @@ -109,8 +110,8 @@ describe('<DependencyForceGraph>', () => {
expect(wrapper.find(ForceGraphNode).length).toBe(nodes.length);
});

it('renders a <ForceGraphLink> for each link', () => {
expect(wrapper.find(ForceGraphLink).length).toBe(links.length);
it('renders a <ForceGraphArrowLink> for each link', () => {
expect(wrapper.find(ForceGraphArrowLink).length).toBe(links.length);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React from 'react';
import { shallow } from 'enzyme';

import ForceGraphArrowLink from './ForceGraphArrowLink';

const defaultProps = {
link: {
source: 'a',
target: 'b',
value: 5,
},
};

describe('<ForceGraphArrowLink />', () => {
it('should a standard size of the arrow', () => {
const wrapper = shallow(
<ForceGraphArrowLink {...defaultProps} link={{ ...defaultProps.link, value: 9 }} />
);

const marker = wrapper
.find('g')
.first()
.find('defs')
.first()
.find('marker');
expect(marker.prop('markerWidth')).toEqual(6);
expect(marker.prop('markerHeight')).toEqual(4);
expect(marker.prop('markerUnits')).toEqual('strokeWidth');
});

it('should not have arrow overlapping with target node', () => {
const wrapper = shallow(
<ForceGraphArrowLink {...defaultProps} link={{ ...defaultProps.link, value: 9 }} targetRadius={2} />
);

const marker = wrapper
.find('g')
.first()
.find('defs')
.first()
.find('marker');
expect(marker.prop('refX')).toEqual(2 + 5);
});

it('should have an id with the name of source and target', () => {
const testLink = { source: 's_node', target: 't_node', value: 10 };

const wrapper = shallow(<ForceGraphArrowLink {...defaultProps} link={testLink} />);
const marker = wrapper
.find('g')
.first()
.find('defs')
.first()
.find('marker');
expect(marker.prop('id')).toEqual('arrow-s_node=>t_node');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import * as React from 'react';
import { ForceGraphLink } from 'react-vis-force';

type TLink = {
source: string | { id: string };
target: string | { id: string };
value: number;
};

type TProps = {
className?: string;
color?: string;
edgeOffset?: number;
link: TLink;
opacity?: number;
stroke?: string;
strokeWidth?: number;
targetRadius?: number;
};

function linkId(link: TLink) {
const { source, target } = link;
const srcId = typeof source === 'string' ? source : source.id;
const targetId = typeof target === 'string' ? target : target.id;
return `${srcId}=>${targetId}`;
}

export default class ForceGraphArrowLink extends React.PureComponent<TProps> {
static defaultProps = {
className: '',
edgeOffset: 2,
opacity: 0.6,
stroke: '#999',
strokeWidth: 1,
targetRadius: 2,
};

render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { link, targetRadius, edgeOffset: _, ...spreadable } = this.props;
const id = `arrow-${linkId(link)}`;
return (
<g>
<defs>
<marker
id={id}
markerWidth={6}
markerHeight={4}
refX={5 + (targetRadius || 0)}
refY={2}
orient="auto"
markerUnits="strokeWidth"
>
{Number(targetRadius) > 0 && (
<path d="M0,0 L0,4 L6,2 z" fill={spreadable.stroke || spreadable.color} />
)}
</marker>
</defs>

<ForceGraphLink {...spreadable} link={link} markerEnd={`url(#${id})`} />
</g>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ describe('mapStateToProps()', () => {
expect(mapStateToProps(state)).toEqual({
...state.dependencies,
nodes: [
expect.objectContaining({ callCount, orphan: false, id: parentId }),
expect.objectContaining({ callCount, orphan: false, id: childId }),
expect.objectContaining({ callCount, orphan: false, id: parentId, radius: 3 }),
expect.objectContaining({ callCount, orphan: false, id: childId, radius: 3 }),
],
links: [{ callCount, source: parentId, target: childId, value: 1 }],
links: [{ callCount, source: parentId, target: childId, value: 1, target_node_size: 3 }],
});
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/jaeger-ui/src/propTypes/dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const linksPropTypes = PropTypes.arrayOf(
PropTypes.shape({
source: PropTypes.string.isRequired,
target: PropTypes.string.isRequired,
target_node_size: PropTypes.number,
value: PropTypes.number.isRequired,
})
);
1 change: 1 addition & 0 deletions packages/jaeger-ui/src/selectors/dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const formatDependenciesAsNodesAndLinks = createSelector(
target: link.child,
callCount: link.callCount,
value: Math.max(Math.sqrt(link.callCount / 10000), 1),
target_node_size: Math.max(Math.log(nodeMap[link.child] / 1000), 3),
},
]);
}
Expand Down
1 change: 1 addition & 0 deletions packages/jaeger-ui/typings/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ declare module 'combokeys' {

declare module 'react-helmet';
declare module 'json-markup';
declare module 'react-vis-force';
declare module 'tween-functions';

0 comments on commit e197f15

Please sign in to comment.