You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
11 KiB
344 lines
11 KiB
/*
|
|
* The PMParentNodeView creates the visual display of a parent node in the tree
|
|
*/
|
|
|
|
define(["pm/contrib/d3/d3.amd",
|
|
'/static/app/DA-ITSI-CP-vmware-dashboards/libs/backbone.js',
|
|
"pm/PMLeafNodeView",
|
|
'/static/app/DA-ITSI-CP-vmware-dashboards/libs/underscore.js'],
|
|
function (d3, Backbone, LeafNodeView, _) {
|
|
var ParentNode = Backbone.View.extend({
|
|
tagName: "g",
|
|
className: "proactive-monitoring-parent-node",
|
|
options: {
|
|
tree_controller: undefined,
|
|
parent_node_r: 13,
|
|
penultimate_parent_node_r: 11.5,
|
|
colors: {
|
|
critical: "#C44545",
|
|
warning: "#DE9400",
|
|
normal: "#57A116",
|
|
unknown: "#D5D5D5"
|
|
},
|
|
color_scale: d3.scale.ordinal().domain(d3.range(4)).range(["#C44545", "#DE9400", "#57A116", "#D5D5D5"]),
|
|
node_labels: {
|
|
HostSystem: "HS",
|
|
ClusterComputeResource: "CL",
|
|
RootFolder: "VC",
|
|
VirtualMachine: "VM"
|
|
},
|
|
unselected_stroke_width: 1,
|
|
selected_stroke_width: 2,
|
|
unselected_stroke: "#FFFFFF",
|
|
selected_stroke: "#000000",
|
|
label_rotation: -35,
|
|
ring_width: 5
|
|
},
|
|
first_render: true,
|
|
initialize: function(options) {
|
|
this.tree_controller = options.tree_controller;
|
|
Backbone.View.prototype.initialize.apply(this, arguments);
|
|
//Custom event binding to the dispatcher goes here
|
|
},
|
|
/*
|
|
* idempotently render the content of the parent node
|
|
*/
|
|
render: function(data) {
|
|
var d3g = d3.select(this.$el.get(0));
|
|
var that = this;
|
|
var name_label;
|
|
|
|
//Handle the Environment Node special
|
|
if (data.id === "__ENV__:__ROOT__") {
|
|
if (this.first_render) {
|
|
//Custom rendering for the environment node
|
|
name_label = d3g.append("text")
|
|
.attr("text-anchor", "middle")
|
|
.attr("font-size", "16px")
|
|
.text("Environment");
|
|
|
|
this.first_render = false;
|
|
}
|
|
}
|
|
else {
|
|
//Create elements on first render only
|
|
var radius = data.penultimate ? this.options.penultimate_parent_node_r : this.options.parent_node_r;
|
|
if (this.first_render) {
|
|
//Make the pie g
|
|
d3g.append("g")
|
|
.attr("class", "donut");
|
|
//FIXME: needs to make this work with nodes being selected/unselected
|
|
//Make Node Circle
|
|
d3g.append("circle")
|
|
.attr("r", radius)
|
|
.attr("fill", this.options.colors.unknown)
|
|
.attr("stroke", this.options.unselected_stroke)
|
|
.attr("stroke-width", this.options.unselected_stroke_width);
|
|
|
|
//Make Type Label
|
|
d3g.append("text")
|
|
.attr("class", "node-label")
|
|
.attr("dy", "5px")
|
|
.text(this.options.node_labels[data.type]);
|
|
|
|
//Make Name Label
|
|
name_label = d3g.append("text")
|
|
.attr("class", "name-label")
|
|
.attr("x", radius + this.options.unselected_stroke_width + this.options.ring_width + 4)
|
|
.attr("transform", "rotate(" + this.options.label_rotation + ")")
|
|
.text(data.name);
|
|
|
|
//Check on the status of children
|
|
this.toggleChildren(data);
|
|
|
|
//Bake the pie!
|
|
this.bakePie(data);
|
|
|
|
this.first_render = false;
|
|
}
|
|
|
|
//Handle the value based rendering
|
|
var value_index = this._getValueIndex(data.value);
|
|
|
|
//Update the circle fill
|
|
d3g.select("circle").attr("fill", this.options.color_scale(value_index));
|
|
}
|
|
},
|
|
_getValueIndex: function(value) {
|
|
//Handle the value based rendering
|
|
var value_index;
|
|
if (value[0] > 0) {
|
|
value_index = 0;
|
|
}
|
|
else if (value[1] > 0) {
|
|
value_index = 1;
|
|
}
|
|
else if (value[2] > 0) {
|
|
value_index = 2;
|
|
}
|
|
else {
|
|
value_index = 3;
|
|
}
|
|
return value_index;
|
|
},
|
|
bakePie: function(data) {
|
|
//Do not render a ring for things without children or if our pie is marked baked
|
|
if (data._children !== null && data._children.length > 0) {
|
|
var d3g = d3.select(this.$el.get(0));
|
|
var value_index = this._getValueIndex(data.value);
|
|
var radius = data.penultimate ? this.options.penultimate_parent_node_r : this.options.parent_node_r;
|
|
|
|
//Bake the pie
|
|
var pie_data;
|
|
if (value_index === 3 && data.value[3] === 0) {
|
|
//If there just isn't perf data fake some data
|
|
pie_data = [0, 0, 0, 1];
|
|
}
|
|
else {
|
|
pie_data = data.value;
|
|
}
|
|
var pie_outer_radius = radius + this.options.unselected_stroke_width + this.options.ring_width - 1;
|
|
var d3pie_g = d3g.select("g.donut");
|
|
this._bakePie(d3pie_g, pie_data, pie_outer_radius);
|
|
}
|
|
},
|
|
/*
|
|
* Bake the donut-pie chart into the g
|
|
* -> d3g should be the g in which to draw the pie
|
|
* -> data should be the value array to form the pie
|
|
* -> outer_radius is the outer radius of the donut
|
|
*/
|
|
_bakePie: function(d3g, data, outer_radius) {
|
|
//Bake the Pie!
|
|
var donut = d3.layout.pie().sort(null);
|
|
var color_scale = this.options.color_scale;
|
|
var ring_width = this.options.ring_width;
|
|
var arc = d3.svg.arc()
|
|
.outerRadius(outer_radius)
|
|
.innerRadius(outer_radius - ring_width);
|
|
|
|
//Put the slices in
|
|
var arcs = d3g.selectAll("g.arc")
|
|
.data(donut(data));
|
|
|
|
var arcs_enter = arcs.enter().append("g")
|
|
.attr("class", "arc");
|
|
|
|
//Color them
|
|
arcs_enter.append("path")
|
|
.attr("fill", function(d, i) { return color_scale(i); });
|
|
|
|
//Update
|
|
arcs.select("path").attr("d", arc);
|
|
|
|
//Remove old we have a static array length of 4 so there should never be anything to remove, this is a no op
|
|
//arcs.exit().remove();
|
|
},
|
|
/*
|
|
* Render the leaf children of a penultimate node
|
|
* Accepts the hierarchy data structure as an arg
|
|
*/
|
|
toggleChildren: function(d) {
|
|
//Little bit of validation
|
|
if (!d.penultimate) {
|
|
return;
|
|
}
|
|
|
|
if (d._children.length === 0) {
|
|
console.log("[PMParentNode] cannot expand penultimate node with no children");
|
|
return;
|
|
}
|
|
|
|
//Check what we are toggling, note that the expanded prop is
|
|
//set by our caller, so we just respect whatever it tells us to do
|
|
var d3g = d3.select(this.$el.get(0));
|
|
var base_vertical_displacement = 7;
|
|
var leaf_height = 17;
|
|
var leaf_padding_bottom = 2;
|
|
var leaf_padding_left = 13;
|
|
var that = this;
|
|
var d3g_children, leaf_node, leaf_bar;
|
|
if (d.expanded) {
|
|
//Create a children container
|
|
d3g_children = d3g.append("g")
|
|
.attr("class", "proactive-monitoring-leaf-container")
|
|
.attr("transform", "translate(0," + (this.options.penultimate_parent_node_r + this.options.unselected_stroke_width + this.options.ring_width) + ")");
|
|
|
|
//Create vertical bar
|
|
leaf_bar = d3g_children.append("rect")
|
|
.attr("width", 1)
|
|
.attr("height", 1)
|
|
.attr("fill", "#D4D3D4");
|
|
|
|
//Create the lead nodes
|
|
leaf_node = d3g_children.selectAll("g.proactive-monitoring-leaf-node")
|
|
.data(d._children, function(d, ii) { return d.id || (d.id = ++ii); });
|
|
|
|
var leaf_node_enter = leaf_node.enter().append("g")
|
|
.attr("class", "proactive-monitoring-leaf-node")
|
|
.attr("transform", "translate(" + leaf_padding_left + "," + base_vertical_displacement + ") scale(0.2)")
|
|
.each(function(d) {
|
|
d.node_view = new LeafNodeView();
|
|
d.node_view.setElement(this);
|
|
d.node_view.render(d);
|
|
})
|
|
.on("click", function() {
|
|
//Prevent parent expand collapse
|
|
d3.event.stopPropagation();
|
|
})
|
|
.on("mouseover", function(d) {
|
|
//Prevent parent tooltip
|
|
d3.event.stopPropagation();
|
|
var d3this = d3.select(this);
|
|
var parent_transform = d3.transform(d3g.attr("transform"));
|
|
var node_transform = d3.transform(d3this.attr("transform"));
|
|
var offset = [node_transform.translate[0] + parent_transform.translate[0], node_transform.translate[1] + parent_transform.translate[1] + 10];
|
|
that.tree_controller._showNodeTooltip(d, offset);
|
|
})
|
|
.on("mouseout", function(d) {
|
|
//Prevent parent tooltip
|
|
d3.event.stopPropagation();
|
|
that.tree_controller._hideNodeTooltip(d);
|
|
});
|
|
|
|
//Transition everybody
|
|
leaf_bar.transition()
|
|
.attr("height", base_vertical_displacement + (leaf_height + leaf_padding_bottom) * (d._children.length -1 ));
|
|
|
|
var getValueIndex = this._getValueIndex;
|
|
var sorted_children = _.sortBy(d._children, function(node) {
|
|
return getValueIndex(node.value);
|
|
});
|
|
var findIndexOfNode = function(list, test_id) {
|
|
for (var ii = 0; ii < list.length; ii++) {
|
|
var node = list[ii];
|
|
if (node.id === test_id) {
|
|
return ii;
|
|
}
|
|
}
|
|
};
|
|
|
|
leaf_node.transition()
|
|
.attr("transform", function(d, ii) {
|
|
var node_id = d.id;
|
|
var sorted_index = findIndexOfNode(sorted_children, node_id);
|
|
return "translate(" + leaf_padding_left + "," + (base_vertical_displacement + (leaf_height + leaf_padding_bottom) * sorted_index) + ") scale(1)";
|
|
});
|
|
}
|
|
else {
|
|
//Collapse the things
|
|
d3g_children = d3g.select("g.proactive-monitoring-leaf-container");
|
|
leaf_node = d3g_children.selectAll("g.proactive-monitoring-leaf-node");
|
|
//Wipe out old views
|
|
leaf_node.each(function(d) {
|
|
d.node_view = null;
|
|
});
|
|
|
|
//Transition and remove
|
|
leaf_node.transition()
|
|
.attr("transform", "translate(" + leaf_padding_left + ",0) scale(0.2)")
|
|
.remove();
|
|
leaf_bar = d3g_children.selectAll("rect").transition()
|
|
.attr("height", 1)
|
|
.remove()
|
|
.each("end", function() {
|
|
//Remove the entire container at the end
|
|
d3g_children.remove();
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
/*
|
|
* When performance data is updated we need to be able to just reorder and color the children
|
|
*/
|
|
updateChildren: function(d) {
|
|
if (d.expanded) {
|
|
var d3g = d3.select(this.$el.get(0));
|
|
var base_vertical_displacement = 7;
|
|
var leaf_height = 17;
|
|
var leaf_padding_bottom = 2;
|
|
var leaf_padding_left = 13;
|
|
var d3g_children = d3g.select("g.proactive-monitoring-leaf-container");
|
|
var leaf_node = d3g_children.selectAll("g.proactive-monitoring-leaf-node")
|
|
.data(d._children, function(d, ii) { return d.id || (d.id = ++ii); });
|
|
|
|
var getValueIndex = this._getValueIndex;
|
|
var sorted_children = _.sortBy(d._children, function(node) {
|
|
return getValueIndex(node.value);
|
|
});
|
|
var findIndexOfNode = function(list, test_id) {
|
|
for (var ii = 0; ii < list.length; ii++) {
|
|
var node = list[ii];
|
|
if (node.id === test_id) {
|
|
return ii;
|
|
}
|
|
}
|
|
};
|
|
|
|
//Update Colors
|
|
leaf_node.each(function(d) { d.node_view.render(d); });
|
|
//Update position
|
|
leaf_node.transition()
|
|
.attr("transform", function(d, ii) {
|
|
var node_id = d.id;
|
|
var sorted_index = findIndexOfNode(sorted_children, node_id);
|
|
return "translate(" + leaf_padding_left + "," + (base_vertical_displacement + (leaf_height + leaf_padding_bottom) * sorted_index) + ") scale(1)";
|
|
});
|
|
}
|
|
},
|
|
highlightNode: function() {
|
|
var d3this = d3.select(this.$el.get(0));
|
|
d3this.select("circle").attr("stroke", this.options.selected_stroke);
|
|
d3this.select("circle").attr("stroke-width", this.options.selected_stroke_width);
|
|
},
|
|
unhighlightNode: function() {
|
|
var d3this = d3.select(this.$el.get(0));
|
|
d3this.select("circle").attr("stroke", this.options.unselected_stroke);
|
|
d3this.select("circle").attr("stroke-width", this.options.unselected_stroke_width);
|
|
}
|
|
});
|
|
|
|
return ParentNode;
|
|
});
|