Completed
Push — master ( eca4bd...8c9b02 )
by
unknown
03:47
created

DrawHelpers.ts ➔ createTooltip   A

Complexity

Conditions 1

Size

Total Lines 12
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 12
rs 9.8
c 0
b 0
f 0
cc 1
1
import { changeZoom, getTextDimensions, getNodeDimensions } from './GraphHelpers';
2
import { DependencyLink, DependencyNode, NodeSelection } from '../../components/types';
3
import { forceCenter, forceCollide, forceLink, forceSimulation, forceY } from 'd3-force';
4
import { select, Selection } from 'd3-selection';
5
import { zoom } from 'd3-zoom';
6
import { BASE_FONT_SIZE, ElementColors, LabelColors, MAXIMUM_ZOOM_SCALE, MINIMUM_ZOOM_SCALE, ElementIds, TextColors } from '../AppConsts';
7
import { selectById } from './Selectors';
8
9
export function createLinkElements(zoomLayer: NodeSelection<SVGGElement>, links: DependencyLink[]) {
10
    return zoomLayer
11
        .append('g')
12
        .attr('id', ElementIds.LINKS)
13
        .lower()
14
        .selectAll<HTMLElement, DependencyLink>('line.link')
15
        .data(links)
16
        .enter()
17
        .append<SVGPathElement>('svg:path')
18
        .attr('class', 'link')
19
        .attr('marker-end', 'url(#provider)')
20
        .style('stroke-width', 1);
21
}
22
23
export function createLabels(labelNodesGroup: NodeSelection<SVGGElement>, nodes: DependencyNode[]) {
24
    labelNodesGroup
25
        .selectAll('g')
26
        .data(nodes)
27
        .append<SVGPathElement>('svg:path')
28
        .attr('class', 'label')
29
        .attr('fill', LabelColors.DEFAULT)
30
        .attr('d', function({ isConsumer, isProvider }) {
31
            return createLabelPath.call(this, isConsumer, isProvider);
32
        });
33
}
34
35
export function createTextElements(labelNodesGroup: NodeSelection<SVGGElement>, nodes: DependencyNode[]) {
36
    return labelNodesGroup
37
        .selectAll<HTMLElement, DependencyNode>('g#labels')
38
        .data(nodes)
39
        .enter()
40
        .append<SVGGElement>('g')
41
        .attr('cursor', 'pointer')
42
        .append('text')
43
        .attr('font-size', BASE_FONT_SIZE)
44
        .attr('fill', TextColors.DEFAULT)
45
        .text(d => d.name);
46
}
47
48
export function createMarkers(svgContainer: any): void {
49
    svgContainer
50
        .append('svg:defs')
51
        .append('svg:marker')
52
        .attr('id', 'provider')
53
        .attr('viewBox', '-5 -5 40 10')
54
        .attr('refX', 15)
55
        .attr('refY', 0)
56
        .attr('markerWidth', 40)
57
        .attr('markerHeight', 40)
58
        .attr('orient', 'auto')
59
        .append('svg:path')
60
        .attr('d', 'M0,-5L20,0L0,5,q10 -5,0 -10')
61
        .attr('fill', '#dcdee0');
62
}
63
64
export function createSimulation(nodes: DependencyNode[], links: DependencyLink[], width: number, height: number) {
65
    return forceSimulation(nodes)
66
        .force(
67
            'dependency',
68
            forceLink<DependencyNode, DependencyLink>(links)
69
                .distance(180)
70
                .id((node: DependencyNode) => node.name)
71
        )
72
        .force('center', forceCenter(width / 2, height / 2))
73
        .force('y', forceY(0.5))
74
        .force('collide', forceCollide(140))
75
        .force('nodeCollide', forceCollide(140));
76
}
77
78
export function createSVGContainer(width: number, height: number) {
79
    return selectById(ElementIds.OVERVIEW_CONTAINER_DIV)
80
        .append('svg')
81
        .attr('id', ElementIds.OVERVIEW_CONTAINER)
82
        .attr('preserveAspectRatio', 'xMidYMid meet')
83
        .attr('viewBox', `0 0 ${width} ${height}`)
84
        .attr('width', width)
85
        .attr('height', height);
86
}
87
88
export function createZoom(svgContainer: NodeSelection<SVGSVGElement>, selector: ElementIds.OVERVIEW_ZOOM | ElementIds.DETAILS_ZOOM) {
89
    const zoomLayer = svgContainer.append('g').attr('id', selector);
90
91
    svgContainer
92
        .call(
93
            zoom<SVGSVGElement, DependencyNode>()
94
                .scaleExtent([MINIMUM_ZOOM_SCALE, MAXIMUM_ZOOM_SCALE])
95
                .on(`zoom`, changeZoom(selector))
96
        )
97
        .on('dblclick.zoom', null);
98
99
    return zoomLayer;
100
}
101
102
export function createLinkPath(this: Element, link: DependencyLink): void {
103
    if (!link.source.x || !link.source.y || !link.target.x || !link.target.y) {
104
        return;
105
    }
106
107
    const xDiff = link.source.x - link.target.x;
108
    const yDiff = link.source.y - link.target.y;
109
110
    const isSourceOnTheLeft = xDiff < 0;
111
    const isSourceBelowTarget = yDiff > 0;
112
113
    const angleInRadians = Math.abs(Math.atan(yDiff / xDiff));
114
    const cosinus = Math.cos(angleInRadians);
115
    const sinus = Math.sin(angleInRadians);
116
117
    const offsetXLeft = -50 * cosinus;
118
    const offsetY = 50 * sinus;
119
    const offsetYBelow = -offsetY - 5;
120
121
    const sourceLabelWidth = getNodeDimensions(link.source).width;
122
    link.source.width = sourceLabelWidth;
123
    const targetLabelWidth = getNodeDimensions(link.target).width;
124
    link.target.width = targetLabelWidth;
125
126
    const sourceNewX = isSourceOnTheLeft ? (sourceLabelWidth + 20) * cosinus : offsetXLeft;
127
    const sourceNewY = isSourceBelowTarget ? offsetYBelow : offsetY;
128
129
    const targetNewX = isSourceOnTheLeft ? offsetXLeft : (targetLabelWidth + 20) * cosinus;
130
    const targetNewY = isSourceBelowTarget ? offsetY : offsetYBelow;
131
132
    select<Element, DependencyLink>(this)
133
        .attr(
134
            'd',
135
            'M' +
136
                (link.source.x + sourceNewX) +
137
                ',' +
138
                (link.source.y + sourceNewY) +
139
                ', ' +
140
                (link.target.x + targetNewX) +
141
                ',' +
142
                (link.target.y + targetNewY)
143
        )
144
        .attr('stroke', LabelColors.DEFAULT);
145
}
146
147
export function createLabelPath(this: SVGPathElement | null, isConsumer: boolean, isProvider: boolean) {
148
    const labelText = (this?.previousSibling || this?.nextSibling) as SVGTextElement | null;
149
    const labelTextDimensions = getTextDimensions(labelText);
150
151
    if (!labelTextDimensions) {
152
        return '';
153
    }
154
155
    const labelTextWidth = labelTextDimensions.width;
156
157
    if (isConsumer && isProvider) {
158
        return 'M4.5,35l9.37,14.59L4.5,64.18h' + (labelTextWidth + 45) + 'l9-14.59L' + (labelTextWidth + 49.5) + ',35H4.5z';
159
    }
160
161
    if (isProvider) {
162
        return 'M' + (labelTextWidth + 49.5) + ',35H4.5l9.37,14.59L4.5,64.18h' + (labelTextWidth + 45);
163
    }
164
165
    if (isConsumer) {
166
        return 'M4.5,64.18h' + (labelTextWidth + 45) + 'l9.42-14.59L' + (labelTextWidth + 49.5) + ',35H4.5';
167
    }
168
169
    return 'M4.5,64.18h' + (labelTextWidth + 55) + 'L' + (labelTextWidth + 59.5) + ',35H4.5';
170
}
171
172
export function createHighlightBackground(
173
    svgContainer: NodeSelection<SVGGElement>
174
): Selection<SVGRectElement, DependencyNode, Element, HTMLElement> {
175
    return svgContainer
176
        .append('rect')
177
        .attr('id', ElementIds.HIGHLIGHT_BACKGROUND)
178
        .attr('width', 0)
179
        .attr('height', 0)
180
        .attr('x', 0)
181
        .attr('y', 0)
182
        .attr('rx', 5)
183
        .attr('ry', 5)
184
        .attr('fill', ElementColors.HIGHLIGHT_BACKGROUND)
185
        .style('opacity', 0);
186
}
187
188
export function createDetailsButton(svgContainer: NodeSelection<SVGGElement>) {
189
    const detailsButtonWrapper = svgContainer
190
        .append('g')
191
        .attr('id', ElementIds.DETAILS_BUTTON)
192
        .attr('cursor', 'pointer');
193
    detailsButtonWrapper
194
        .append('rect')
195
        .style('opacity', 0)
196
        .attr('fill', ElementColors.BUTTON);
197
    detailsButtonWrapper
198
        .append('text')
199
        .style('opacity', 0)
200
        .style('text-anchor', 'middle')
201
        .attr('fill', TextColors.HIGHLIGHTED)
202
        .text('Details');
203
    return detailsButtonWrapper;
204
}
205
206
export function createTooltip(svgContainer: NodeSelection<SVGGElement>) {
207
    const tooltipElement = svgContainer
208
        .append('g')
209
        .attr('id', 'tooltip')
210
        .style('opacity', 0);
211
    tooltipElement
212
        .append('rect')
213
        .attr('fill', ElementColors.BUTTON)
214
        .attr('rx', 5)
215
        .attr('ry', 5);
216
    tooltipElement.append('text').attr('fill', TextColors.HIGHLIGHTED);
217
}
218