blob: fb29046d47623f47325d81cdb4257c0e69e9995e [file] [log] [blame]
Matteo Scandolo8cf33a32017-11-14 15:52:29 -08001/*
2 * Copyright 2017-present Open Networking Foundation
3
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7
8 * http://www.apache.org/licenses/LICENSE-2.0
9
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import * as d3 from 'd3';
18import * as _ from 'lodash';
19import {IXosSgNode} from '../../interfaces';
20import {XosServiceGraphConfig as config} from '../../graph.config';
21import {IXosServiceGraphIcons} from '../d3-helpers/graph-icons.service';
22import {IXosGraphHelpers} from '../d3-helpers/graph-elements.helpers';
23
24export interface IXosNodeRenderer {
25 renderNodes(forceLayout: d3.forceLayout, nodeContainer: d3.Selection, nodes: IXosSgNode[]): void;
26}
27
28export class XosNodeRenderer {
29
30 static $inject = [
31 'XosServiceGraphIcons',
32 'XosGraphHelpers'
33 ];
34
35 private drag;
36
37 constructor (
38 private XosServiceGraphIcons: IXosServiceGraphIcons,
39 private XosGraphHelpers: IXosGraphHelpers
40 ) {}
41
42 public renderNodes(forceLayout: any, nodeContainer: any, nodes: IXosSgNode[]): void {
43
44 this.drag = forceLayout.drag()
45 .on('dragstart', (n: IXosSgNode) => {
46 n.fixed = true;
47 });
48
49 const node = nodeContainer
50 .selectAll('g.node')
51 .data(nodes, n => n.id);
52
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080053 const entering = node.enter()
54 .append('g')
55 .attr({
56 id: n => n.id,
57 class: n => `node ${n.type} ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
Matteo Scandolo35fdf242017-11-30 12:29:45 -080058 })
59 .call(this.drag);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080060
61 this.renderServiceNodes(entering.filter('.service'));
62 this.renderServiceInstanceNodes(entering.filter('.serviceinstance'));
63
64 node.exit().remove();
65 }
66
67 private renderServiceNodes(nodes: d3.selection) {
68
69 nodes
70 .append('rect')
71 .attr({
72 rx: config.node.radius,
73 ry: config.node.radius
74 });
75
76 nodes
77 .append('path')
78 .attr({
79 d: this.XosServiceGraphIcons.get('service').path,
80 transform: this.XosServiceGraphIcons.get('service').transform,
81 class: 'icon'
82 });
83
84 this.positionServiceNodeGroup(nodes);
85 this.handleLabels(nodes);
86 }
87
88 private renderServiceInstanceNodes(nodes: d3.selection) {
89 nodes.append('rect')
90 .attr({
91 width: 40,
92 height: 40,
93 x: -20,
94 y: -20,
95 transform: `rotate(45)`
96 });
97
98 nodes
99 .append('path')
100 .attr({
101 d: this.XosServiceGraphIcons.get('serviceinstance').path,
102 class: 'icon'
103 });
104
105 this.positionServiceInstanceNodeGroup(nodes);
106 this.handleLabels(nodes); // eventually improve, padding top is wrong
107 }
108
109 private positionServiceNodeGroup(nodes: d3.selection) {
110 const self = this;
111 nodes.each(function (d: IXosSgNode) {
112 const node = d3.select(this);
113 const rect = node.select('rect');
114 const icon = node.select('path');
115 const bbox = self.XosGraphHelpers.getSiblingIconBBox(rect.node());
116
117 rect
118 .attr({
119 width: bbox.width + config.node.padding,
120 height: bbox.height + config.node.padding,
121 x: - (config.node.padding / 2),
122 y: - (config.node.padding / 2),
123 transform: `translate(${-bbox.width / 2}, ${-bbox.height / 2})`
124 });
125
126 icon
127 .attr({
128 transform: `translate(${-bbox.width / 2}, ${-bbox.height / 2})`
129 });
130 });
131 }
132
133 private positionServiceInstanceNodeGroup(nodes: d3.selection) {
134 const self = this;
135 nodes.each(function (d: IXosSgNode) {
136 const node = d3.select(this);
137 const rect = node.select('rect');
138 const icon = node.select('path');
139 const bbox = self.XosGraphHelpers.getSiblingIconBBox(rect.node());
140 const size = _.max([bbox.width, bbox.height]); // NOTE we need it to be a square
141 rect
142 .attr({
143 width: size + config.node.padding,
144 height: size + config.node.padding,
145 x: - (config.node.padding / 2),
146 y: - (config.node.padding / 2),
147 transform: `rotate(45), translate(${-bbox.width / 2}, ${-bbox.height / 2})`
148 });
149
150 icon
151 .attr({
152 transform: `translate(${-bbox.width / 2}, ${-bbox.height / 2})`
153 });
154 });
155 }
156
157 private handleLabels(nodes: d3.selection) {
158 const self = this;
159 // if (this.userConfig.labels) {
160
161 // group to contain label text and wrapper
162 const label = nodes.append('g')
163 .attr({
164 class: 'label'
165 });
166
167 // setting up the wrapper
168 label
169 .append('rect')
170 .attr({
171 class: 'label-wrapper',
172 rx: config.node.radius,
173 ry: config.node.radius
174 });
175
176 // adding text
177 label
178 .append('text')
179 .text(n => this.getNodeLabel(n))
180 .attr({
181 'opacity': 0,
182 'text-anchor': 'left',
183 'alignment-baseline': 'bottom',
184 'font-size': config.node.text,
185 y: config.node.text * 0.78
186 })
187 .transition()
188 .duration(config.duration)
189 .attr({
190 opacity: 1
191 });
192
193 // resize and position label
194 label.each(function() {
195 const text = d3.select(this).select('text').node();
196 const rect = d3.select(this).select('rect');
197 const iconRect = d3.select(this.parentNode).select('rect').node();
198 const icon = self.XosGraphHelpers.getBBox(iconRect);
199 const bbox = self.XosGraphHelpers.getBBox(text);
200
201 // scale the rectangle around the label to fit the text
202 rect
203 .attr({
204 width: bbox.width + config.node.padding,
205 height: config.node.text - 2 + config.node.padding,
206 x: -(config.node.padding / 2),
207 y: -(config.node.padding / 2),
208 });
209
210 // translate the lable group to the correct position
211 d3.select(this)
212 .attr({
213 transform: function() {
214 const label = self.XosGraphHelpers.getBBox(this);
215 const x = - (label.width - config.node.padding) / 2;
216 const y = (icon.height / 2) + config.node.padding;
217 return `translate(${x}, ${y})`;
218 }
219 });
220 });
221 // }
222 // else {
223 // node.selectAll('text')
224 // .transition()
225 // .duration(this.duration)
226 // .attr({
227 // opacity: 0
228 // })
229 // .remove();
230 // }
231 }
232
233 private getNodeLabel(n: any): string {
234 return n.data.name ? n.data.name.toUpperCase() : n.data.id;
235 // return n.data.name ? n.data.name.toUpperCase() + ` - ${n.data.id}` : n.data.id;
236 }
237}