Implement multi-tier info boxes

These use HTML inside of SVG, this allows us to style the SVG's much
easier, it also enables us to reference the dom nodes from within react.
This commit is contained in:
Tom Gallacher 2016-12-06 12:38:17 +00:00
parent 1f3c6c2e41
commit 62aef3eec6

View File

@ -7,7 +7,7 @@
stroke-opacity: 1; stroke-opacity: 1;
} }
.health { .health, .health_warn {
font-family: LibreFranklin; font-family: LibreFranklin;
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
@ -16,6 +16,10 @@
text-align: center; text-align: center;
} }
.health_warn {
font-size: 15px;
}
.stat { .stat {
font-family: LibreFranklin; font-family: LibreFranklin;
font-size: 12px; font-size: 12px;
@ -25,6 +29,29 @@
line-height: 1.5; 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;
}
</style> </style>
<svg width='960' height='700'></svg> <svg width='960' height='700'></svg>
<script src='https://d3js.org/d3.v4.min.js'></script> <script src='https://d3js.org/d3.v4.min.js'></script>
@ -97,23 +124,9 @@ function rect(x, y, width, height, radius) {
d3.json('services.json', function(error, graph) { d3.json('services.json', function(error, graph) {
if (error) throw error; if (error) throw error;
// Drawing the links between nodes function createNode(elm) {
var link = svg.append('g')
.attr('class', 'links')
.selectAll('line')
.data(graph.links)
.enter().append('line')
.attr('stroke-width', '2px')
// And svg group, to contain all of the attributes in @antonas' first prototype
var node = svg.selectAll('.node')
.data(graph.nodes)
.enter()
.append('g')
.attr('class', 'node_group');
// Box where label will live // Box where label will live
node.append('path') elm.append('path')
.attr('class', 'node') .attr('class', 'node')
.attr('d', topRoundedRect('0', '0', 170, 47, 4)) .attr('d', topRoundedRect('0', '0', 170, 47, 4))
.attr('stroke', '#343434') .attr('stroke', '#343434')
@ -121,7 +134,7 @@ d3.json('services.json', function(error, graph) {
.attr('fill', '#464646') .attr('fill', '#464646')
// Hover-over text for a node's label. // Hover-over text for a node's label.
var text = node.append('g') var text = elm.append('g')
text.append('text') text.append('text')
.attr('class', 'info_text') .attr('class', 'info_text')
@ -149,7 +162,7 @@ d3.json('services.json', function(error, graph) {
.text((d) => d.id == 'Memcached' ? '!' : '❤') .text((d) => d.id == 'Memcached' ? '!' : '❤')
// Box where stats will live // Box where stats will live
var stats = node.append('g'); var stats = elm.append('g');
stats.append('path') stats.append('path')
.attr('class', 'node') .attr('class', 'node')
@ -189,14 +202,159 @@ d3.json('services.json', function(error, graph) {
.text('Network: 1.23kb/sec') .text('Network: 1.23kb/sec')
node.call(d3.drag() elm.call(d3.drag()
.on('start', dragstarted) .on('start', dragstarted)
.on('drag', dragged) .on('drag', dragged)
.on('end', dragended)); .on('end', dragended));
node.append('title') elm.append('title')
.text(function(d) { return d.id; }); .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')
.selectAll('line')
.data(graph.links)
.enter().append('line')
.attr('stroke-width', '2px')
// And svg group, to contain all of the attributes in @antonas' first prototype
var node = svg.selectAll('.node')
.data(graph.nodes)
.enter()
.append('g')
.attr('class', 'node_group');
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 simulation
.nodes(graph.nodes) .nodes(graph.nodes)
.on('tick', ticked); .on('tick', ticked);
@ -209,6 +367,8 @@ d3.json('services.json', function(error, graph) {
} }
function ticked() { function ticked() {
// TODO: Remove these values and pull them out of the height of the boxes
// that the constraints belong to.
r=180 r=180
r2=130 r2=130