From a34d9c97b8100b36ddb24dbf75a715374f9919ec Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Thu, 14 Jan 2016 19:56:22 +0100 Subject: [PATCH] Adds node-shapes to the canvas - circle - rect (w/ radius) - fluffy cloud shape - hexagons - stacks --- .../app/scripts/charts/node-shape-circle.js | 14 ++++++ client/app/scripts/charts/node-shape-cloud.js | 41 ++++++++++++++++ client/app/scripts/charts/node-shape-hex.js | 44 +++++++++++++++++ .../charts/node-shape-rounded-square.js | 10 ++++ .../app/scripts/charts/node-shape-square.js | 24 ++++++++++ client/app/scripts/charts/node-shape-stack.js | 19 ++++++++ client/app/scripts/charts/node.js | 47 +++++++++++++++++-- client/app/scripts/charts/nodes-chart.js | 1 + client/app/styles/main.less | 41 +++++++++------- 9 files changed, 219 insertions(+), 22 deletions(-) create mode 100644 client/app/scripts/charts/node-shape-circle.js create mode 100644 client/app/scripts/charts/node-shape-cloud.js create mode 100644 client/app/scripts/charts/node-shape-hex.js create mode 100644 client/app/scripts/charts/node-shape-rounded-square.js create mode 100644 client/app/scripts/charts/node-shape-square.js create mode 100644 client/app/scripts/charts/node-shape-stack.js diff --git a/client/app/scripts/charts/node-shape-circle.js b/client/app/scripts/charts/node-shape-circle.js new file mode 100644 index 000000000..0d4f53839 --- /dev/null +++ b/client/app/scripts/charts/node-shape-circle.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export default function NodeShapeCircle({highlighted, size, color}) { + return ( + + {highlighted && + } + + + + + + ); +} diff --git a/client/app/scripts/charts/node-shape-cloud.js b/client/app/scripts/charts/node-shape-cloud.js new file mode 100644 index 000000000..770fc588b --- /dev/null +++ b/client/app/scripts/charts/node-shape-cloud.js @@ -0,0 +1,41 @@ +import React from 'react'; +import d3 from 'd3'; + +const CLOUD_PATH = 'M 1920,384 Q 1920,225 1807.5,112.5 1695,0 1536,0 H 448 Q 263,0 131.5,131.5 0,263 0,448 0,580 71,689.5 142,799 258,853 q -2,28 -2,43 0,212 150,362 150,150 362,150 158,0 286.5,-88 128.5,-88 187.5,-230 70,62 166,62 106,0 181,-75 75,-75 75,-181 0,-75 -41,-138 129,-30 213,-134.5 84,-104.5 84,-239.5 z'; + +function toPoint(stringPair) { + return stringPair.split(',').map(p => parseFloat(p, 10)); +} + +function getExtents(svgPath) { + const points = svgPath.split(' ').filter(s => s.length > 1).map(toPoint); + return [d3.extent(points, p => p[0]), d3.extent(points, p => p[1])]; +} + +export default function NodeShapeCloud({highlighted, size, color}) { + const [[minx, maxx], [miny, maxy]] = getExtents(CLOUD_PATH); + const width = (maxx - minx); + const height = (maxy - miny); + const cx = width / 2; + const cy = height / 2; + const pathSize = (width + height) / 2; + const baseScale = (size * 2) / pathSize; + + const pathProps = (v) => { + return { + d: CLOUD_PATH, + transform: `scale(-${v * baseScale}) translate(-${cx},-${cy})`, + style: {strokeWidth: 1 / baseScale} + }; + }; + + return ( + + {highlighted && + } + + + + + ); +} diff --git a/client/app/scripts/charts/node-shape-hex.js b/client/app/scripts/charts/node-shape-hex.js new file mode 100644 index 000000000..5878544ed --- /dev/null +++ b/client/app/scripts/charts/node-shape-hex.js @@ -0,0 +1,44 @@ +import React from 'react'; +import d3 from 'd3'; + +const line = d3.svg.line() + .interpolate('cardinal-closed') + .tension(0.25); + +function getWidth(h) { + return (Math.sqrt(3) / 2) * h; +} + +function getPoints(h) { + const w = getWidth(h); + const points = [ + [w * 0.5, 0], + [w, 0.25 * h], + [w, 0.75 * h], + [w * 0.5, h], + [0, 0.75 * h], + [0, 0.25 * h] + ]; + + return line(points); +} + + +export default function NodeShapeHex({highlighted, size, color}) { + const pathProps = (v) => { + return { + d: getPoints(size * v * 2), + transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})` + }; + }; + + return ( + + {highlighted && + } + + + + + ); +} diff --git a/client/app/scripts/charts/node-shape-rounded-square.js b/client/app/scripts/charts/node-shape-rounded-square.js new file mode 100644 index 000000000..4542595b7 --- /dev/null +++ b/client/app/scripts/charts/node-shape-rounded-square.js @@ -0,0 +1,10 @@ +import React from 'react'; +import NodeShapeSquare from './node-shape-square'; + +// TODO how to express a cmp in terms of another cmp? (Rather than a sub-cmp as here). + +export default function NodeShapeRoundedSquare(props) { + return ( + + ); +} diff --git a/client/app/scripts/charts/node-shape-square.js b/client/app/scripts/charts/node-shape-square.js new file mode 100644 index 000000000..286fa176d --- /dev/null +++ b/client/app/scripts/charts/node-shape-square.js @@ -0,0 +1,24 @@ +import React from 'react'; + +export default function NodeShapeSquare({highlighted, size, color, rx = 0, ry = 0}) { + const rectProps = (v) => { + return { + width: v * size * 2, + height: v * size * 2, + rx: v * size * rx, + ry: v * size * ry, + transform: `translate(-${size * v}, -${size * v})` + }; + }; + + return ( + + {highlighted && + } + + + + + + ); +} diff --git a/client/app/scripts/charts/node-shape-stack.js b/client/app/scripts/charts/node-shape-stack.js new file mode 100644 index 000000000..031fa50cb --- /dev/null +++ b/client/app/scripts/charts/node-shape-stack.js @@ -0,0 +1,19 @@ +import React from 'react'; +import _ from 'lodash'; + +export default function NodeShapeCircleStack(props) { + const propsNoHighlight = _.clone(props); + const Shape = props.shape; + delete propsNoHighlight.highlighted; + return ( + + + + + + + + + + ); +} diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js index f78f9f4ab..b1c1b7940 100644 --- a/client/app/scripts/charts/node.js +++ b/client/app/scripts/charts/node.js @@ -5,6 +5,42 @@ import { Motion, spring } from 'react-motion'; import { clickNode, enterNode, leaveNode } from '../actions/app-actions'; import { getNodeColor } from '../utils/color-utils'; +import NodeShapeCircle from './node-shape-circle'; +import NodeShapeStack from './node-shape-stack'; +import NodeShapeRoundedSquare from './node-shape-rounded-square'; +import NodeShapeHex from './node-shape-hex'; +import NodeShapeCloud from './node-shape-cloud'; + +function stackedShape(Shape) { + const factory = React.createFactory(NodeShapeStack); + + return function(props) { + return factory(Object.assign({}, props, {shape: Shape})); + }; +} + +const nodeShapes = { + 'hosts': NodeShapeCircle, + 'containers': NodeShapeHex, + 'containers-by-hostname': stackedShape(NodeShapeHex), + 'containers-by-image': stackedShape(NodeShapeHex), + 'applications': NodeShapeRoundedSquare, + 'applications-by-name': stackedShape(NodeShapeRoundedSquare) +}; + +function isTheInternet(id) { + return id === 'theinternet'; +} + +function getNodeShape({id, pseudo, topologyId}) { + if (isTheInternet(id)) { + return NodeShapeCloud; + } else if (pseudo) { + return NodeShapeCircle; + } + return nodeShapes[topologyId]; +} + export default class Node extends React.Component { constructor(props, context) { super(props, context); @@ -52,8 +88,11 @@ export default class Node extends React.Component { if (this.props.pseudo) { classNames.push('pseudo'); } + const classes = classNames.join(' '); + const NodeShapeType = getNodeShape(this.props); + return ( - {props.highlighted && } - - - + {label} diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js index adb804830..e61c44fcc 100644 --- a/client/app/scripts/charts/nodes-chart.js +++ b/client/app/scripts/charts/nodes-chart.js @@ -142,6 +142,7 @@ export default class NodesChart extends React.Component { blurred={node.get('blurred')} focused={node.get('focused')} highlighted={node.get('highlighted')} + topologyId={this.props.topologyId} onClick={onNodeClick} key={node.get('id')} id={node.get('id')} diff --git a/client/app/styles/main.less b/client/app/styles/main.less index bafac4127..1391d1ab7 100644 --- a/client/app/styles/main.less +++ b/client/app/styles/main.less @@ -303,6 +303,10 @@ h2 { } } + g.stack g.shape .border { + stroke-width: 2px; + } + g.node { cursor: pointer; transition: opacity .5s ease-in-out; @@ -357,28 +361,29 @@ h2 { } - circle.border { - stroke-width: @node-border-stroke-width; - fill: none; - } + .shape { + .border { + stroke-width: @node-border-stroke-width; + fill: @background-color; + } - circle.shadow { - stroke: none; - fill: @background-lighter-color; - } + .shadow { + stroke: none; + fill: @background-lighter-color; + } - circle.node { - fill: @text-color; - } + .node { + fill: @text-color; + } - circle.highlighted { - fill: @weave-blue; - fill-opacity: @node-highlight-fill-opacity; - stroke: @weave-blue; - stroke-width: @node-highlight-stroke-width; - stroke-opacity: @node-highlight-stroke-opacity; + .highlighted { + fill: @weave-blue; + fill-opacity: @node-highlight-fill-opacity; + stroke: @weave-blue; + stroke-width: @node-highlight-stroke-width; + stroke-opacity: @node-highlight-stroke-opacity; + } } - } .details {