Skip to content

Commit

Permalink
Merge pull request #153 from ditrit/feature/link_arrow_marker
Browse files Browse the repository at this point in the history
Feature:  Draw arrow markers on links.
  • Loading branch information
Zorin95670 authored Feb 28, 2023
2 parents 17f908a + 2a02250 commit da91c9f
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 6 deletions.
14 changes: 13 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add method `getAttribute` in template to get attribute by name for a component.
- Add method `getAttribute` to get attribute by name for a component in model template.
- Add `getUsedLinkDefinition` in `DefaultData.js` to get the link definitions used by components.
- Add `__initializeArrowMarker` method in `DefaultDrawer.js`.
- Add `actions.zoom.scale` in constructor of `DefaultDrawer.js` to get the zoom scale.
- Add marker definition in `ComponentLinkDefinition` to define the marker of the link.

### Changed

- Change `drawLinks` method in `DefaultDrawer.js` to draw arrow markers on links extremities.

### Fixed

- Fix the links width by multiplying it by the zoom scale.

## [0.13.0] - 2023/02/09

Expand Down
2 changes: 1 addition & 1 deletion demo/src/DemoMetadata.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
ComponentAttributeDefinition,
ComponentDefinition,
ComponentLinkDefinition,
DefaultMetadata
} from 'leto-modelizer-plugin-core';

Expand Down Expand Up @@ -53,6 +52,7 @@ class DemoMetadata extends DefaultMetadata {
name: 'network_link',
type: 'Link',
linkRef: 'network',
linkType: 'Reverse',
linkColor: '#ff0000',
linkWidth: 5,
linkDashStyle: [25, 5],
Expand Down
59 changes: 55 additions & 4 deletions src/draw/DefaultDrawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ class DefaultDrawer {
state: false,
target: null,
},
zoom: {
scale: 1,
},
};

this.setEvents(events);
Expand Down Expand Up @@ -563,6 +566,8 @@ class DefaultDrawer {
.attr('width', '100%')
.attr('x', 0)
.attr('y', 0);
this.svg.append('defs');
this.__initializeArrowMarker();
} else {
this.svg = d3.select(`#${this.rootId}`)
.select('svg');
Expand Down Expand Up @@ -803,6 +808,33 @@ class DefaultDrawer {
return [x, y];
}

/**
* Initialize arrow marker for links.
*
* @private
*/
__initializeArrowMarker() {
const definitions = this.pluginData.getUsedLinkDefinitions();
const arrows = this.svg.select('defs').selectAll('arrow')
.data(
definitions,
(data) => `${data.attributeRef}-${data.sourceRef}-${data.targetRef}`,
)
.join('marker')
.attr('class', 'arrow');

arrows
.attr('id', (data) => `${data.attributeRef}-${data.sourceRef}-${data.targetRef}-arrow`)
.attr('refX', (data) => data.marker.refX)
.attr('refY', (data) => data.marker.refY)
.attr('markerWidth', (data) => data.marker.width)
.attr('markerHeight', (data) => data.marker.height)
.attr('orient', (data) => data.marker.orient)
.append('path')
.attr('d', (data) => data.marker.path)
.attr('fill', (data) => data.color);
}

/**
* Render links in model view.
*/
Expand Down Expand Up @@ -831,8 +863,26 @@ class DefaultDrawer {
))
.attr('fill', 'none')
.attr('stroke', (link) => link.definition.color)
.attr('stroke-width', (link) => link.definition.width)
.attr('stroke-dasharray', (link) => link.definition.dashStyle || 'none')
.attr('stroke-width', (link) => link.definition.width * this.actions.zoom.scale)
.attr('stroke-dasharray', (link) => (
!link.definition.dashStyle
? 'none'
: link.definition.dashStyle.map((value) => value * this.actions.zoom.scale)
))
.attr('marker-start', (data) => {
const { attributeRef, sourceRef, targetRef } = data.definition;

return data.definition.type === 'Reverse'
? `url(#${attributeRef}-${sourceRef}-${targetRef}-arrow)`
: 'none';
})
.attr('marker-end', (data) => {
const { attributeRef, sourceRef, targetRef } = data.definition;

return data.definition.type !== 'Reverse'
? `url(#${attributeRef}-${sourceRef}-${targetRef}-arrow)`
: 'none';
})
.attr('cursor', 'pointer')
.on('click', (event) => this.clickHandler(event));

Expand Down Expand Up @@ -1103,8 +1153,9 @@ class DefaultDrawer {

this.svg.call(d3
.zoom()
.on('zoom', function zoomHandler(event) {
d3.select(this).select('.container').attr('transform', event.transform);
.on('zoom', (event) => {
this.svg.select('.container').attr('transform', event.transform);
this.actions.zoom.scale = event.transform.k;
drawLinks();
}));
}
Expand Down
30 changes: 30 additions & 0 deletions src/models/ComponentLinkDefinition.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ class ComponentLinkDefinition {
* @param {string} [props.color='black'] - Color of the link.
* @param {number} [props.width=2] - Width of the link.
* @param {number[]} [props.dashStyle] - Dash style of the link. See stroke-dasharray of svg.
* @param {object} [props.marker] - Marker of the link, see
* {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker official documentation}.
* @param {number} [props.marker.width=5] - Width of the marker.
* @param {number} [props.marker.height=5] - Height of the marker.
* @param {number} [props.marker.refX=4] - X offset of the marker from the edge of the link path.
* @param {number} [props.marker.refY=2.5] - Y offset of the marker from
* the edge of the link path.
* @param {string} [props.marker.orient='auto-start-reverse'] - Orientation of the marker.
* @param {string} [props.marker.path='M 0 0 L 5 2.5 L 0 5'] - Path of the shape of the marker.
*/
constructor(props = {
attributeRef: null,
Expand All @@ -22,6 +31,14 @@ class ComponentLinkDefinition {
color: 'black',
width: 2,
dashStyle: null,
marker: {
width: 5,
height: 5,
refX: 4,
refY: 2.5,
orient: 'auto-start-reverse',
path: 'M 0 0 L 5 2.5 L 0 5',
},
}) {
const {
attributeRef,
Expand All @@ -31,6 +48,7 @@ class ComponentLinkDefinition {
color,
width,
dashStyle,
marker,
} = props;

/**
Expand Down Expand Up @@ -75,6 +93,18 @@ class ComponentLinkDefinition {
* @type {number[]}
*/
this.dashStyle = dashStyle || null;

/**
* Marker of the link.
*/
this.marker = marker || {
width: 5,
height: 5,
refX: 4,
refY: 2.5,
orient: 'auto-start-reverse',
path: 'M 0 0 L 5 2.5 L 0 5',
};
}
}

Expand Down
21 changes: 21 additions & 0 deletions src/models/DefaultData.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,27 @@ class DefaultData {
}, []);
}

/**
* Uniquely get the definitions used for existing links.
*
* @returns {ComponentLinkDefinition[]} - List of link definitions.
*/
getUsedLinkDefinitions() {
return this.getLinks()
.map((link) => link.definition)
.reduce((acc, definition) => {
if (!acc.some((used) => (
used.attributeRef === definition.attributeRef
&& used.sourceRef === definition.sourceRef
&& used.targetRef === definition.targetRef
))) {
acc.push(definition);
}

return acc;
}, []);
}

/**
* Initialize all link definitions from all component attribute definitions.
*/
Expand Down
83 changes: 83 additions & 0 deletions tests/unit/models/DefaultData.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -917,5 +917,88 @@ describe('Test class: DefaultData', () => {
expect(pluginData.getWorkflowLinks()).toEqual([]);
});
});

describe('Test method: getUsedLinkDefinitions', () => {
const pluginData = new DefaultData();
const linkDefinition1 = new ComponentAttributeDefinition({
name: 'link1',
type: 'Link',
linkRef: 'server',
});
const link1 = new ComponentLinkDefinition({
sourceRef: 'server',
targetRef: 'server',
attributeRef: 'link1',
});
const linkDefinition2 = new ComponentAttributeDefinition({
name: 'link2',
type: 'Link',
linkRef: 'server',
});
const link2 = new ComponentLinkDefinition({
sourceRef: 'server',
targetRef: 'server',
attributeRef: 'link2',
});
const linkDefinition3 = new ComponentAttributeDefinition({
name: 'link3',
type: 'Link',
linkRef: 'server',
linkType: 'Default',
});
const serverDefinition = new ComponentDefinition({
type: 'server',
definedAttributes: [
linkDefinition1,
linkDefinition2,
linkDefinition3,
],
});

pluginData.definitions.components = [serverDefinition];

pluginData.initLinkDefinitions();

pluginData.components = [
new Component({
definition: serverDefinition,
id: 'server1',
attributes: [new ComponentAttribute({
definition: linkDefinition1,
name: 'link1',
value: ['server2'],
type: 'Array',
})],
}),
new Component({
definition: serverDefinition,
id: 'server2',
attributes: [new ComponentAttribute({
definition: linkDefinition2,
name: 'link2',
value: ['server1'],
type: 'Array',
})],
}),
new Component({
definition: serverDefinition,
id: 'server3',
attributes: [new ComponentAttribute({
definition: linkDefinition1,
name: 'link1',
value: ['server3'],
type: 'Array',
})],
}),
];

it('Should return the link definitions used by the components', () => {
const usedLinkDefinitions = pluginData.getUsedLinkDefinitions();

expect(usedLinkDefinitions.length).toEqual(2);
expect(usedLinkDefinitions[0]).toEqual(link1);
expect(usedLinkDefinitions[1]).toEqual(link2);
});
});
});
});

0 comments on commit da91c9f

Please sign in to comment.