[CORD-1943] New service graph
- labels
- enforcing service position
- started documentation
- toggling service instances
- toggle fullscreen
Change-Id: I01b71fb2607fb58711d4624f6b5b6479609b2f4f
diff --git a/src/app/service-graph/services/renderer/node.renderer.ts b/src/app/service-graph/services/renderer/node.renderer.ts
new file mode 100644
index 0000000..17bcf70
--- /dev/null
+++ b/src/app/service-graph/services/renderer/node.renderer.ts
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as d3 from 'd3';
+import * as _ from 'lodash';
+import {IXosSgNode} from '../../interfaces';
+import {XosServiceGraphConfig as config} from '../../graph.config';
+import {IXosServiceGraphIcons} from '../d3-helpers/graph-icons.service';
+import {IXosGraphHelpers} from '../d3-helpers/graph-elements.helpers';
+
+export interface IXosNodeRenderer {
+ renderNodes(forceLayout: d3.forceLayout, nodeContainer: d3.Selection, nodes: IXosSgNode[]): void;
+}
+
+export class XosNodeRenderer {
+
+ static $inject = [
+ 'XosServiceGraphIcons',
+ 'XosGraphHelpers'
+ ];
+
+ private drag;
+
+ constructor (
+ private XosServiceGraphIcons: IXosServiceGraphIcons,
+ private XosGraphHelpers: IXosGraphHelpers
+ ) {}
+
+ public renderNodes(forceLayout: any, nodeContainer: any, nodes: IXosSgNode[]): void {
+
+ this.drag = forceLayout.drag()
+ .on('dragstart', (n: IXosSgNode) => {
+ n.fixed = true;
+ });
+
+ const node = nodeContainer
+ .selectAll('g.node')
+ .data(nodes, n => n.id);
+
+ node
+ .call(this.drag);
+
+ const entering = node.enter()
+ .append('g')
+ .attr({
+ id: n => n.id,
+ class: n => `node ${n.type} ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
+ });
+
+ this.renderServiceNodes(entering.filter('.service'));
+ this.renderServiceInstanceNodes(entering.filter('.serviceinstance'));
+
+ node.exit().remove();
+ }
+
+ private renderServiceNodes(nodes: d3.selection) {
+
+ nodes
+ .append('rect')
+ .attr({
+ rx: config.node.radius,
+ ry: config.node.radius
+ });
+
+ nodes
+ .append('path')
+ .attr({
+ d: this.XosServiceGraphIcons.get('service').path,
+ transform: this.XosServiceGraphIcons.get('service').transform,
+ class: 'icon'
+ });
+
+ this.positionServiceNodeGroup(nodes);
+ this.handleLabels(nodes);
+ }
+
+ private renderServiceInstanceNodes(nodes: d3.selection) {
+ nodes.append('rect')
+ .attr({
+ width: 40,
+ height: 40,
+ x: -20,
+ y: -20,
+ transform: `rotate(45)`
+ });
+
+ nodes
+ .append('path')
+ .attr({
+ d: this.XosServiceGraphIcons.get('serviceinstance').path,
+ class: 'icon'
+ });
+
+ this.positionServiceInstanceNodeGroup(nodes);
+ this.handleLabels(nodes); // eventually improve, padding top is wrong
+ }
+
+ private positionServiceNodeGroup(nodes: d3.selection) {
+ const self = this;
+ nodes.each(function (d: IXosSgNode) {
+ const node = d3.select(this);
+ const rect = node.select('rect');
+ const icon = node.select('path');
+ const bbox = self.XosGraphHelpers.getSiblingIconBBox(rect.node());
+
+ rect
+ .attr({
+ width: bbox.width + config.node.padding,
+ height: bbox.height + config.node.padding,
+ x: - (config.node.padding / 2),
+ y: - (config.node.padding / 2),
+ transform: `translate(${-bbox.width / 2}, ${-bbox.height / 2})`
+ });
+
+ icon
+ .attr({
+ transform: `translate(${-bbox.width / 2}, ${-bbox.height / 2})`
+ });
+ });
+ }
+
+ private positionServiceInstanceNodeGroup(nodes: d3.selection) {
+ const self = this;
+ nodes.each(function (d: IXosSgNode) {
+ const node = d3.select(this);
+ const rect = node.select('rect');
+ const icon = node.select('path');
+ const bbox = self.XosGraphHelpers.getSiblingIconBBox(rect.node());
+ const size = _.max([bbox.width, bbox.height]); // NOTE we need it to be a square
+ rect
+ .attr({
+ width: size + config.node.padding,
+ height: size + config.node.padding,
+ x: - (config.node.padding / 2),
+ y: - (config.node.padding / 2),
+ transform: `rotate(45), translate(${-bbox.width / 2}, ${-bbox.height / 2})`
+ });
+
+ icon
+ .attr({
+ transform: `translate(${-bbox.width / 2}, ${-bbox.height / 2})`
+ });
+ });
+ }
+
+ private handleLabels(nodes: d3.selection) {
+ const self = this;
+ // if (this.userConfig.labels) {
+
+ // group to contain label text and wrapper
+ const label = nodes.append('g')
+ .attr({
+ class: 'label'
+ });
+
+ // setting up the wrapper
+ label
+ .append('rect')
+ .attr({
+ class: 'label-wrapper',
+ rx: config.node.radius,
+ ry: config.node.radius
+ });
+
+ // adding text
+ label
+ .append('text')
+ .text(n => this.getNodeLabel(n))
+ .attr({
+ 'opacity': 0,
+ 'text-anchor': 'left',
+ 'alignment-baseline': 'bottom',
+ 'font-size': config.node.text,
+ y: config.node.text * 0.78
+ })
+ .transition()
+ .duration(config.duration)
+ .attr({
+ opacity: 1
+ });
+
+ // resize and position label
+ label.each(function() {
+ const text = d3.select(this).select('text').node();
+ const rect = d3.select(this).select('rect');
+ const iconRect = d3.select(this.parentNode).select('rect').node();
+ const icon = self.XosGraphHelpers.getBBox(iconRect);
+ const bbox = self.XosGraphHelpers.getBBox(text);
+
+ // scale the rectangle around the label to fit the text
+ rect
+ .attr({
+ width: bbox.width + config.node.padding,
+ height: config.node.text - 2 + config.node.padding,
+ x: -(config.node.padding / 2),
+ y: -(config.node.padding / 2),
+ });
+
+ // translate the lable group to the correct position
+ d3.select(this)
+ .attr({
+ transform: function() {
+ const label = self.XosGraphHelpers.getBBox(this);
+ const x = - (label.width - config.node.padding) / 2;
+ const y = (icon.height / 2) + config.node.padding;
+ return `translate(${x}, ${y})`;
+ }
+ });
+ });
+ // }
+ // else {
+ // node.selectAll('text')
+ // .transition()
+ // .duration(this.duration)
+ // .attr({
+ // opacity: 0
+ // })
+ // .remove();
+ // }
+ }
+
+ private getNodeLabel(n: any): string {
+ return n.data.name ? n.data.name.toUpperCase() : n.data.id;
+ // return n.data.name ? n.data.name.toUpperCase() + ` - ${n.data.id}` : n.data.id;
+ }
+}