diff --git a/spikes/architecture/d3-revamp/index.html b/spikes/architecture/d3-revamp/index.html
index 208ca463..8e1d0307 100644
--- a/spikes/architecture/d3-revamp/index.html
+++ b/spikes/architecture/d3-revamp/index.html
@@ -7,7 +7,7 @@
stroke-opacity: 1;
}
-.health {
+.health, .health_warn {
font-family: LibreFranklin;
font-size: 12px;
font-weight: bold;
@@ -16,6 +16,10 @@
text-align: center;
}
+.health_warn {
+ font-size: 15px;
+}
+
.stat {
font-family: LibreFranklin;
font-size: 12px;
@@ -25,6 +29,29 @@
line-height: 1.5;
}
+.node_statistics {
+ font-family: LibreFranklin;
+ font-size: 12px;
+ font-weight: normal;
+ font-style: normal;
+ font-stretch: normal;
+ line-height: 1.5;
+}
+
+.node_statistics p {
+ margin: 0 0 0 0;
+ color: rgba(255, 255, 255, 0.8);
+}
+
+.primary, .secondary {
+ font-family: LibreFranklin;
+ font-size: 12px;
+ font-weight: normal;
+ font-style: normal;
+ font-stretch: normal;
+ line-height: 1.5;
+}
+
@@ -97,6 +124,211 @@ function rect(x, y, width, height, radius) {
d3.json('services.json', function(error, graph) {
if (error) throw error;
+ function createNode(elm) {
+ // Box where label will live
+ elm.append('path')
+ .attr('class', 'node')
+ .attr('d', topRoundedRect('0', '0', 170, 47, 4))
+ .attr('stroke', '#343434')
+ .attr('stroke-width', '1px')
+ .attr('fill', '#464646')
+
+ // Hover-over text for a node's label.
+ var text = elm.append('g')
+
+ text.append('text')
+ .attr('class', 'info_text')
+ .attr('x', '12')
+ .attr('y', '30')
+ .attr('text-anchor', 'start')
+ .attr('fill', '#fff')
+ .text(d => d.id)
+
+ text.append('circle')
+ .attr('class', 'alert')
+ .attr('cx', function () { return d3.select(this.parentNode).select('.info_text').node().getBBox().width + 30 })
+ .attr('cy', '24')
+ .attr('stroke-width', '0px')
+ .attr('fill', (d) => d.id == 'Memcached' ? 'rgb(217, 77, 68)' : 'rgb(0,175,102)')
+ .attr('r', '9px')
+
+ // An icon or label that exists within the circle, inside the infobox
+ text.append('text')
+ .attr('class', 'health')
+ .attr('x', function () { return d3.select(this.parentNode).select('.info_text').node().getBBox().width + 30 })
+ .attr('y', '29')
+ .attr('text-anchor', 'middle')
+ .attr('fill', '#fff')
+ .text((d) => d.id == 'Memcached' ? '!' : '❤')
+
+ // Box where stats will live
+ var stats = elm.append('g');
+
+ stats.append('path')
+ .attr('class', 'node')
+ .attr('d', (d) => d.id == 'Percona' ? rect('0', '-39', 170, 78, 2) : bottomRoundedRect('0', '-39', 170, 78, 4))
+ .attr('stroke', '#343434')
+ .attr('stroke-width', '1px')
+ .attr('fill', '#464646')
+
+ // An icon or label that exists within the circle, inside the infobox
+ stats.append('text')
+ .attr('class', 'cpu')
+ .attr('class', 'stat')
+ .attr('x', '12')
+ .attr('y', '65')
+ .attr('text-anchor', 'start')
+ .attr('fill', 'rgba(255, 255, 255, 0.8)')
+ .text('CPU: 63%')
+
+ // An icon or label that exists within the circle, inside the infobox
+ stats.append('text')
+ .attr('class', 'memory')
+ .attr('class', 'stat')
+ .attr('x', '12')
+ .attr('y', '85')
+ .attr('text-anchor', 'start')
+ .attr('fill', 'rgba(255, 255, 255, 0.8)')
+ .text('Memory: 50%')
+
+ // An icon or label that exists within the circle, inside the infobox
+ stats.append('text')
+ .attr('class', 'network')
+ .attr('class', 'stat')
+ .attr('x', '12')
+ .attr('y', '105')
+ .attr('text-anchor', 'start')
+ .attr('fill', 'rgba(255, 255, 255, 0.8)')
+ .text('Network: 1.23kb/sec')
+
+
+ elm.call(d3.drag()
+ .on('start', dragstarted)
+ .on('drag', dragged)
+ .on('end', dragended));
+
+ elm.append('title')
+ .text(function(d) { return d.id; });
+
+ }
+
+ function createExtendedNode(elm) {
+ elm.append('path')
+ .attr('class', 'node')
+ .attr('d', topRoundedRect('0', '0', 170, 47, 4))
+ .attr('stroke', '#343434')
+ .attr('stroke-width', '1px')
+ .attr('fill', '#464646')
+
+ // Hover-over text for a node's label.
+ var text = elm.append('g')
+
+ text.append('text')
+ .attr('class', 'info_text')
+ .attr('x', '12')
+ .attr('y', '30')
+ .attr('text-anchor', 'start')
+ .attr('fill', '#fff')
+ .text(d => d.id)
+
+ // Box where stats will live
+ var stats = elm.append('g');
+ var primary = stats.append('g')
+
+ primary.append('path')
+ .attr('class', 'node')
+ .attr('d', rect('0', '-39', 170, 78, 2))
+ .attr('stroke', '#343434')
+ .attr('stroke-width', '1px')
+ .attr('fill', '#464646')
+
+ primary.append('text')
+ .attr('class', 'primary')
+ .attr('x', '12')
+ .attr('y', '70')
+ .attr('text-anchor', 'start')
+ .attr('fill', '#fff')
+ .text('Primary')
+
+ primary.append('circle')
+ .attr('class', 'alert')
+ .attr('cx', function () { return d3.select(this.parentNode).select('.primary').node().getBBox().width + 30 })
+ .attr('cy', '64')
+ .attr('stroke-width', '0px')
+ .attr('fill', 'rgb(229, 163, 57)')
+ .attr('r', '9px')
+
+ // An icon or label that exists within the circle, inside the infobox
+ primary.append('text')
+ .attr('class', 'health_warn')
+ .attr('x', function () { return d3.select(this.parentNode).select('.primary').node().getBBox().width + 30 })
+ .attr('y', '69')
+ .attr('text-anchor', 'middle')
+ .attr('fill', '#fff')
+ .text('☇')
+
+ var secondary = stats.append('g');
+
+ secondary.append('path')
+ .attr('class', 'node')
+ .attr('d', bottomRoundedRect('0', '-113', 170, 114, 4))
+ .attr('stroke', '#343434')
+ .attr('stroke-width', '1px')
+ .attr('fill', '#464646')
+
+ secondary.append('text')
+ .attr('class', 'secondary')
+ .attr('x', '12')
+ .attr('y', '150')
+ .attr('text-anchor', 'start')
+ .attr('fill', '#fff')
+ .text('Secondary')
+
+ secondary.append('circle')
+ .attr('class', 'alert')
+ .attr('cx', function () { return d3.select(this.parentNode).select('.secondary').node().getBBox().width + 30 })
+ .attr('cy', '144')
+ .attr('stroke-width', '0px')
+ .attr('fill', 'rgb(0,175,102)')
+ .attr('r', '9px')
+
+ // An icon or label that exists within the circle, inside the infobox
+ secondary.append('text')
+ .attr('class', 'health')
+ .attr('x', function () { return d3.select(this.parentNode).select('.secondary').node().getBBox().width + 30 })
+ .attr('y', '149')
+ .attr('text-anchor', 'middle')
+ .attr('fill', '#fff')
+ .text('❤')
+
+ var html = secondary
+ .append('switch')
+ .append('foreignObject')
+ .attr('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility')
+ .attr('x', 12)
+ .attr('y', 160)
+ .attr('width', 160)
+ .attr('height', 100)
+ // From here everything will be rendered with react using a ref.
+ // However for now these values are hard-coded.
+ .append('xhtml:div')
+ .attr('class', 'node_statistics')
+
+ // Remove with react + dyanmic data.
+ html.append('p')
+ .text('CPU: 48%')
+ html.append('p')
+ .text('Memory: 54%')
+ html.append('p')
+ .text('Network: 1.75kb/sec')
+
+ elm.call(d3.drag()
+ .on('start', dragstarted)
+ .on('drag', dragged)
+ .on('end', dragended));
+
+ }
+
// Drawing the links between nodes
var link = svg.append('g')
.attr('class', 'links')
@@ -112,90 +344,16 @@ d3.json('services.json', function(error, graph) {
.append('g')
.attr('class', 'node_group');
- // Box where label will live
- node.append('path')
- .attr('class', 'node')
- .attr('d', topRoundedRect('0', '0', 170, 47, 4))
- .attr('stroke', '#343434')
- .attr('stroke-width', '1px')
- .attr('fill', '#464646')
-
- // Hover-over text for a node's label.
- var text = node.append('g')
-
- text.append('text')
- .attr('class', 'info_text')
- .attr('x', '12')
- .attr('y', '30')
- .attr('text-anchor', 'start')
- .attr('fill', '#fff')
- .text(d => d.id)
-
- text.append('circle')
- .attr('class', 'alert')
- .attr('cx', function () { return d3.select(this.parentNode).select('.info_text').node().getBBox().width + 30 })
- .attr('cy', '24')
- .attr('stroke-width', '0px')
- .attr('fill', (d) => d.id == 'Memcached' ? 'rgb(217, 77, 68)' : 'rgb(0,175,102)')
- .attr('r', '9px')
-
- // An icon or label that exists within the circle, inside the infobox
- text.append('text')
- .attr('class', 'health')
- .attr('x', function () { return d3.select(this.parentNode).select('.info_text').node().getBBox().width + 30 })
- .attr('y', '29')
- .attr('text-anchor', 'middle')
- .attr('fill', '#fff')
- .text((d) => d.id == 'Memcached' ? '!' : '❤')
-
- // Box where stats will live
- var stats = node.append('g');
-
- stats.append('path')
- .attr('class', 'node')
- .attr('d', (d) => d.id == 'Percona' ? rect('0', '-39', 170, 78, 2) : bottomRoundedRect('0', '-39', 170, 78, 4))
- .attr('stroke', '#343434')
- .attr('stroke-width', '1px')
- .attr('fill', '#464646')
-
- // An icon or label that exists within the circle, inside the infobox
- stats.append('text')
- .attr('class', 'cpu')
- .attr('class', 'stat')
- .attr('x', '12')
- .attr('y', '65')
- .attr('text-anchor', 'start')
- .attr('fill', 'rgba(255, 255, 255, 0.8)')
- .text('CPU: 63%')
-
- // An icon or label that exists within the circle, inside the infobox
- stats.append('text')
- .attr('class', 'memory')
- .attr('class', 'stat')
- .attr('x', '12')
- .attr('y', '85')
- .attr('text-anchor', 'start')
- .attr('fill', 'rgba(255, 255, 255, 0.8)')
- .text('Memory: 50%')
-
- // An icon or label that exists within the circle, inside the infobox
- stats.append('text')
- .attr('class', 'network')
- .attr('class', 'stat')
- .attr('x', '12')
- .attr('y', '105')
- .attr('text-anchor', 'start')
- .attr('fill', 'rgba(255, 255, 255, 0.8)')
- .text('Network: 1.23kb/sec')
-
-
- node.call(d3.drag()
- .on('start', dragstarted)
- .on('drag', dragged)
- .on('end', dragended));
-
- node.append('title')
- .text(function(d) { return d.id; });
+ svg.selectAll('.node_group').each(function (d) {
+ // Create different type of node for services with Primaries + Secondaries
+ // We could extend this further to allow us to have as many nested services
+ // as wanted.
+ if (d.id === 'Percona') {
+ createExtendedNode(d3.select(this));
+ } else {
+ createNode(d3.select(this));
+ }
+ });
simulation
.nodes(graph.nodes)
@@ -209,6 +367,8 @@ d3.json('services.json', function(error, graph) {
}
function ticked() {
+ // TODO: Remove these values and pull them out of the height of the boxes
+ // that the constraints belong to.
r=180
r2=130