From 82e373777afbb8402356ea3d4133b3715066365c Mon Sep 17 00:00:00 2001 From: Filip Barl Date: Thu, 6 Apr 2017 14:44:52 +0200 Subject: [PATCH] Increase cloud node thickness in graph view (#2418) * Unified node shapes rendering templates. * Addressed @foot's comments (fix shadow thickness across all shapes). * Made getClipPathDefinition slightly more readable. --- .../app/scripts/charts/node-shape-circle.js | 39 ---------- client/app/scripts/charts/node-shape-cloud.js | 28 -------- .../app/scripts/charts/node-shape-heptagon.js | 41 ----------- .../app/scripts/charts/node-shape-hexagon.js | 41 ----------- .../charts/node-shape-rounded-square.js | 11 --- .../app/scripts/charts/node-shape-square.js | 47 ------------ client/app/scripts/charts/node-shape-stack.js | 1 + client/app/scripts/charts/node-shapes.js | 71 +++++++++++++++++++ client/app/scripts/charts/node.js | 16 +++-- client/app/scripts/constants/styles.js | 9 +-- client/app/scripts/utils/metric-utils.js | 12 ++-- client/app/scripts/utils/node-shape-utils.js | 27 +++++-- client/app/styles/_base.scss | 43 ++++++----- client/app/styles/_contrast-overrides.scss | 5 +- client/app/styles/_variables.scss | 5 +- 15 files changed, 141 insertions(+), 255 deletions(-) delete mode 100644 client/app/scripts/charts/node-shape-circle.js delete mode 100644 client/app/scripts/charts/node-shape-cloud.js delete mode 100644 client/app/scripts/charts/node-shape-heptagon.js delete mode 100644 client/app/scripts/charts/node-shape-hexagon.js delete mode 100644 client/app/scripts/charts/node-shape-rounded-square.js delete mode 100644 client/app/scripts/charts/node-shape-square.js create mode 100644 client/app/scripts/charts/node-shapes.js diff --git a/client/app/scripts/charts/node-shape-circle.js b/client/app/scripts/charts/node-shape-circle.js deleted file mode 100644 index 8e034c734..000000000 --- a/client/app/scripts/charts/node-shape-circle.js +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; - -import { - getMetricValue, - getMetricColor, - getClipPathDefinition, - renderMetricValue, -} from '../utils/metric-utils'; -import { - NODE_SHAPE_HIGHLIGHT_RADIUS, - NODE_SHAPE_BORDER_RADIUS, - NODE_SHAPE_SHADOW_RADIUS, -} from '../constants/styles'; - - -export default function NodeShapeCircle({ id, highlighted, color, metric }) { - const { height, hasMetric, formattedValue } = getMetricValue(metric); - const metricStyle = { fill: getMetricColor(metric) }; - - const className = classNames('shape', 'shape-circle', { metrics: hasMetric }); - const clipId = `mask-${id}`; - - return ( - - {hasMetric && getClipPathDefinition(clipId, height)} - {highlighted && } - - - {hasMetric && } - {renderMetricValue(formattedValue, highlighted && hasMetric)} - - ); -} diff --git a/client/app/scripts/charts/node-shape-cloud.js b/client/app/scripts/charts/node-shape-cloud.js deleted file mode 100644 index 0b2efdffb..000000000 --- a/client/app/scripts/charts/node-shape-cloud.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import { - NODE_SHAPE_HIGHLIGHT_RADIUS, - NODE_SHAPE_BORDER_RADIUS, - NODE_SHAPE_SHADOW_RADIUS, - NODE_SHAPE_DOT_RADIUS, - NODE_BASE_SIZE, -} from '../constants/styles'; - -// This path is already normalized so no dynamic rescaling is needed. -const CLOUD_PATH = 'M-125 23.333Q-125 44.036-110.352 58.685-95.703 73.333-75 73.333H66.667Q90.755 ' - + '73.333 107.878 56.211 125 39.089 125 15 125-2.188 115.755-16.445 106.51-30.703 91.406-37.734q' - + '0.26-3.646 0.261-5.599 0-27.604-19.532-47.136-19.531-19.531-47.135-19.531-20.573 0-37.305 ' - + '11.458-16.732 11.458-24.414 29.948-9.115-8.073-21.614-8.073-13.802 0-23.568 9.766-9.766 9.766-' - + '9.766 23.568 0 9.766 5.339 17.968-16.797 3.906-27.735 17.513-10.938 13.607-10.937 31.185z'; - -export default function NodeShapeCloud({ highlighted, color }) { - const pathProps = r => ({ d: CLOUD_PATH, transform: `scale(${r / NODE_BASE_SIZE})` }); - - return ( - - {highlighted && } - - - - - ); -} diff --git a/client/app/scripts/charts/node-shape-heptagon.js b/client/app/scripts/charts/node-shape-heptagon.js deleted file mode 100644 index 074fbc3e3..000000000 --- a/client/app/scripts/charts/node-shape-heptagon.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; - -import { nodeShapePolygon } from '../utils/node-shape-utils'; -import { - getMetricValue, - getMetricColor, - getClipPathDefinition, - renderMetricValue, -} from '../utils/metric-utils'; -import { - NODE_SHAPE_HIGHLIGHT_RADIUS, - NODE_SHAPE_BORDER_RADIUS, - NODE_SHAPE_SHADOW_RADIUS, -} from '../constants/styles'; - - -export default function NodeShapeHeptagon({ id, highlighted, color, metric }) { - const { height, hasMetric, formattedValue } = getMetricValue(metric); - const metricStyle = { fill: getMetricColor(metric) }; - - const className = classNames('shape', 'shape-heptagon', { metrics: hasMetric }); - const pathProps = r => ({ d: nodeShapePolygon(r, 7) }); - const clipId = `mask-${id}`; - - return ( - - {hasMetric && getClipPathDefinition(clipId, height)} - {highlighted && } - - - {hasMetric && } - {renderMetricValue(formattedValue, highlighted && hasMetric)} - - ); -} diff --git a/client/app/scripts/charts/node-shape-hexagon.js b/client/app/scripts/charts/node-shape-hexagon.js deleted file mode 100644 index 511f103ae..000000000 --- a/client/app/scripts/charts/node-shape-hexagon.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; - -import { nodeShapePolygon } from '../utils/node-shape-utils'; -import { - getMetricValue, - getMetricColor, - getClipPathDefinition, - renderMetricValue, -} from '../utils/metric-utils'; -import { - NODE_SHAPE_HIGHLIGHT_RADIUS, - NODE_SHAPE_BORDER_RADIUS, - NODE_SHAPE_SHADOW_RADIUS, -} from '../constants/styles'; - - -export default function NodeShapeHexagon({ id, highlighted, color, metric }) { - const { height, hasMetric, formattedValue } = getMetricValue(metric); - const metricStyle = { fill: getMetricColor(metric) }; - - const className = classNames('shape', 'shape-hexagon', { metrics: hasMetric }); - const pathProps = r => ({ d: nodeShapePolygon(r, 6) }); - const clipId = `mask-${id}`; - - return ( - - {hasMetric && getClipPathDefinition(clipId, height)} - {highlighted && } - - - {hasMetric && } - {renderMetricValue(formattedValue, highlighted && hasMetric)} - - ); -} diff --git a/client/app/scripts/charts/node-shape-rounded-square.js b/client/app/scripts/charts/node-shape-rounded-square.js deleted file mode 100644 index dbbe4da81..000000000 --- a/client/app/scripts/charts/node-shape-rounded-square.js +++ /dev/null @@ -1,11 +0,0 @@ -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). -// HOC! - -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 deleted file mode 100644 index 1400487ea..000000000 --- a/client/app/scripts/charts/node-shape-square.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; - -import { - getMetricValue, - getMetricColor, - getClipPathDefinition, - renderMetricValue, -} from '../utils/metric-utils'; -import { - NODE_SHAPE_HIGHLIGHT_RADIUS, - NODE_SHAPE_BORDER_RADIUS, - NODE_SHAPE_SHADOW_RADIUS, -} from '../constants/styles'; - - -export default function NodeShapeSquare({ id, highlighted, color, rx = 0, ry = 0, metric }) { - const { height, hasMetric, formattedValue } = getMetricValue(metric); - const metricStyle = { fill: getMetricColor(metric) }; - - const className = classNames('shape', 'shape-square', { metrics: hasMetric }); - const rectProps = (scale, borderRadiusAdjustmentFactor = 1) => ({ - width: scale * 2, - height: scale * 2, - rx: scale * rx * borderRadiusAdjustmentFactor, - ry: scale * ry * borderRadiusAdjustmentFactor, - x: -scale, - y: -scale - }); - const clipId = `mask-${id}`; - - return ( - - {hasMetric && getClipPathDefinition(clipId, height)} - {highlighted && } - - - {hasMetric && } - {renderMetricValue(formattedValue, highlighted && hasMetric)} - - ); -} diff --git a/client/app/scripts/charts/node-shape-stack.js b/client/app/scripts/charts/node-shape-stack.js index f49e6cb40..48de1d6b9 100644 --- a/client/app/scripts/charts/node-shape-stack.js +++ b/client/app/scripts/charts/node-shape-stack.js @@ -2,6 +2,7 @@ import React from 'react'; import { NODE_BASE_SIZE } from '../constants/styles'; + export default function NodeShapeStack(props) { const shift = props.contrastMode ? 0.15 : 0.1; const highlightScale = [1, 1 + shift]; diff --git a/client/app/scripts/charts/node-shapes.js b/client/app/scripts/charts/node-shapes.js new file mode 100644 index 000000000..1e39d4f86 --- /dev/null +++ b/client/app/scripts/charts/node-shapes.js @@ -0,0 +1,71 @@ +import React from 'react'; +import classNames from 'classnames'; + +import { NODE_BASE_SIZE } from '../constants/styles'; +import { + getMetricValue, + getMetricColor, + getClipPathDefinition, +} from '../utils/metric-utils'; +import { + pathElement, + circleElement, + rectangleElement, + cloudShapeProps, + circleShapeProps, + squareShapeProps, + hexagonShapeProps, + heptagonShapeProps, +} from '../utils/node-shape-utils'; + + +function NodeShape(shapeType, shapeElement, shapeProps, { id, highlighted, color, metric }) { + const { height, hasMetric, formattedValue } = getMetricValue(metric); + const className = classNames('shape', `shape-${shapeType}`, { metrics: hasMetric }); + const metricStyle = { fill: getMetricColor(metric) }; + const clipId = `metric-clip-${id}`; + + return ( + + {highlighted && shapeElement({ + className: 'highlight', + transform: `scale(${NODE_BASE_SIZE * 0.7})`, + ...shapeProps, + })} + {shapeElement({ + className: 'background', + transform: `scale(${NODE_BASE_SIZE * 0.48})`, + ...shapeProps, + })} + {hasMetric && getClipPathDefinition(clipId, height, 0.48)} + {hasMetric && shapeElement({ + className: 'metric-fill', + transform: `scale(${NODE_BASE_SIZE * 0.48})`, + clipPath: `url(#${clipId})`, + style: metricStyle, + ...shapeProps, + })} + {shapeElement({ + className: 'shadow', + transform: `scale(${NODE_BASE_SIZE * 0.49})`, + ...shapeProps, + })} + {shapeElement({ + className: 'border', + transform: `scale(${NODE_BASE_SIZE * 0.5})`, + stroke: color, + ...shapeProps, + })} + {hasMetric && highlighted ? + {formattedValue} : + + } + + ); +} + +export const NodeShapeCloud = props => NodeShape('cloud', pathElement, cloudShapeProps, props); +export const NodeShapeCircle = props => NodeShape('circle', circleElement, circleShapeProps, props); +export const NodeShapeHexagon = props => NodeShape('hexagon', pathElement, hexagonShapeProps, props); +export const NodeShapeHeptagon = props => NodeShape('heptagon', pathElement, heptagonShapeProps, props); +export const NodeShapeSquare = props => NodeShape('square', rectangleElement, squareShapeProps, props); diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js index 84f3866de..41c4cc36d 100644 --- a/client/app/scripts/charts/node.js +++ b/client/app/scripts/charts/node.js @@ -9,13 +9,15 @@ import MatchedText from '../components/matched-text'; import MatchedResults from '../components/matched-results'; import { NODE_BASE_SIZE } from '../constants/styles'; -import NodeShapeCircle from './node-shape-circle'; import NodeShapeStack from './node-shape-stack'; -import NodeShapeRoundedSquare from './node-shape-rounded-square'; -import NodeShapeHexagon from './node-shape-hexagon'; -import NodeShapeHeptagon from './node-shape-heptagon'; -import NodeShapeCloud from './node-shape-cloud'; import NodeNetworksOverlay from './node-networks-overlay'; +import { + NodeShapeCloud, + NodeShapeCircle, + NodeShapeSquare, + NodeShapeHexagon, + NodeShapeHeptagon, +} from './node-shapes'; const labelWidth = 1.2 * NODE_BASE_SIZE; @@ -23,8 +25,8 @@ const nodeShapes = { circle: NodeShapeCircle, hexagon: NodeShapeHexagon, heptagon: NodeShapeHeptagon, - square: NodeShapeRoundedSquare, - cloud: NodeShapeCloud + square: NodeShapeSquare, + cloud: NodeShapeCloud, }; function stackedShape(Shape) { diff --git a/client/app/scripts/constants/styles.js b/client/app/scripts/constants/styles.js index e952a32eb..53d951037 100644 --- a/client/app/scripts/constants/styles.js +++ b/client/app/scripts/constants/styles.js @@ -17,10 +17,11 @@ export const RESOURCES_LABEL_MIN_SIZE = 50; export const RESOURCES_LABEL_PADDING = 10; // Node shapes -export const NODE_SHAPE_HIGHLIGHT_RADIUS = 70; -export const NODE_SHAPE_BORDER_RADIUS = 50; -export const NODE_SHAPE_SHADOW_RADIUS = 45; -export const NODE_SHAPE_DOT_RADIUS = 10; +export const UNIT_CLOUD_PATH = 'M-1.25 0.233Q-1.25 0.44-1.104 0.587-0.957 0.733-0.75 0.733H0.667Q' + + '0.908 0.733 1.079 0.562 1.25 0.391 1.25 0.15 1.25-0.022 1.158-0.164 1.065-0.307 0.914-0.377q' + + '0.003-0.036 0.003-0.056 0-0.276-0.196-0.472-0.195-0.195-0.471-0.195-0.206 0-0.373 0.115-0.167' + + ' 0.115-0.244 0.299-0.091-0.081-0.216-0.081-0.138 0-0.236 0.098-0.098 0.098-0.098 0.236 0 0.098' + + ' 0.054 0.179-0.168 0.039-0.278 0.175-0.109 0.136-0.109 0.312z'; // NOTE: This value represents the node unit radius (in pixels). Since zooming is // controlled at the top level now, this renormalization would be obsolete (i.e. // value 1 could be used instead), if it wasn't for the following factors: diff --git a/client/app/scripts/utils/metric-utils.js b/client/app/scripts/utils/metric-utils.js index e67852a3f..69edfedd0 100644 --- a/client/app/scripts/utils/metric-utils.js +++ b/client/app/scripts/utils/metric-utils.js @@ -2,24 +2,20 @@ import { includes } from 'lodash'; import { scaleLog } from 'd3-scale'; import React from 'react'; -import { NODE_BASE_SIZE, NODE_SHAPE_DOT_RADIUS } from '../constants/styles'; import { formatMetricSvg } from './string-utils'; import { colors } from './color-utils'; -export function getClipPathDefinition(clipId, height) { +export function getClipPathDefinition(clipId, height, radius) { + const barHeight = 1 - (2 * height); // in the interval [-1, 1] return ( - - + + ); } -export function renderMetricValue(value, condition) { - return condition ? {value} : ; -} - // // loadScale(1) == 0.5; E.g. a nicely balanced system :). const loadScale = scaleLog().domain([0.01, 100]).range([0, 1]); diff --git a/client/app/scripts/utils/node-shape-utils.js b/client/app/scripts/utils/node-shape-utils.js index 45dbf6b2b..20b59f755 100644 --- a/client/app/scripts/utils/node-shape-utils.js +++ b/client/app/scripts/utils/node-shape-utils.js @@ -1,12 +1,27 @@ -import { line, curveCardinalClosed } from 'd3-shape'; +import React from 'react'; import range from 'lodash/range'; +import { line, curveCardinalClosed } from 'd3-shape'; -const shapeSpline = line().curve(curveCardinalClosed.tension(0.65)); +import { UNIT_CLOUD_PATH } from '../constants/styles'; -export function nodeShapePolygon(radius, n) { + +export const pathElement = React.createFactory('path'); +export const circleElement = React.createFactory('circle'); +export const rectangleElement = React.createFactory('rect'); + +function curvedUnitPolygonPath(n) { + const curve = curveCardinalClosed.tension(0.65); + const spline = line().curve(curve); const innerAngle = (2 * Math.PI) / n; - return shapeSpline(range(0, n).map(k => [ - radius * Math.sin(k * innerAngle), - -radius * Math.cos(k * innerAngle) + + return spline(range(0, n).map(k => [ + Math.sin(k * innerAngle), + -Math.cos(k * innerAngle), ])); } + +export const squareShapeProps = { width: 1.8, height: 1.8, rx: 0.4, ry: 0.4, x: -0.9, y: -0.9 }; +export const heptagonShapeProps = { d: curvedUnitPolygonPath(7) }; +export const hexagonShapeProps = { d: curvedUnitPolygonPath(6) }; +export const cloudShapeProps = { d: UNIT_CLOUD_PATH }; +export const circleShapeProps = { r: 1 }; diff --git a/client/app/styles/_base.scss b/client/app/styles/_base.scss index cdedc17ff..d2e860236 100644 --- a/client/app/styles/_base.scss +++ b/client/app/styles/_base.scss @@ -424,7 +424,7 @@ } } - .stack .shape .highlighted { + .stack .shape .highlight { display: none; } @@ -432,7 +432,7 @@ .border { display: none; } .shadow { display: none; } .node { display: none; } - .highlighted { display: inline; } + .highlight { display: inline; } } .stack .shape .metric-fill { @@ -443,16 +443,17 @@ transform: scale(1); cursor: pointer; - .border { - stroke-width: $node-border-stroke-width; - fill: $background-color; - transition: stroke-opacity 0.333s $base-ease, fill 0.333s $base-ease; - stroke-opacity: 1; + .highlight { + fill: $weave-blue; + fill-opacity: $node-highlight-fill-opacity; + stroke: $weave-blue; + stroke-width: $node-highlight-stroke-width; + stroke-opacity: $node-highlight-stroke-opacity; } - &.metrics .border { + .background { + stroke: none; fill: $background-lighter-color; - stroke-opacity: 0.3; } .metric-fill { @@ -461,9 +462,21 @@ fill-opacity: 0.7; } + .border { + fill: none; + stroke-opacity: 1; + stroke-width: $node-border-stroke-width; + transition: stroke-opacity 0.333s $base-ease, fill 0.333s $base-ease; + } + + &.metrics .border { + stroke-opacity: 0.3; + } + .shadow { - stroke: none; - fill: $background-lighter-color; + fill: none; + stroke: $background-color; + stroke-width: $node-shadow-stroke-width; } .node { @@ -478,14 +491,6 @@ dominant-baseline: middle; text-anchor: middle; } - - .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; - } } .stack .shape .border { diff --git a/client/app/styles/_contrast-overrides.scss b/client/app/styles/_contrast-overrides.scss index f957a2604..4860c5a5f 100644 --- a/client/app/styles/_contrast-overrides.scss +++ b/client/app/styles/_contrast-overrides.scss @@ -14,8 +14,9 @@ $edge-color: black; $node-highlight-fill-opacity: 0.3; $node-highlight-stroke-opacity: 0.5; -$node-highlight-stroke-width: 8; -$node-border-stroke-width: 10; +$node-highlight-stroke-width: 0.16; +$node-border-stroke-width: 0.2; +$node-shadow-stroke-width: 0.25; $node-pseudo-opacity: 1; $edge-highlight-opacity: 0.3; $edge-opacity-blurred: 0; diff --git a/client/app/styles/_variables.scss b/client/app/styles/_variables.scss index 91ced8c55..2a9f759dc 100644 --- a/client/app/styles/_variables.scss +++ b/client/app/styles/_variables.scss @@ -32,8 +32,9 @@ $terminal-header-height: 44px; $node-highlight-fill-opacity: 0.1; $node-highlight-stroke-opacity: 0.4; -$node-highlight-stroke-width: 2; -$node-border-stroke-width: 6; +$node-highlight-stroke-width: 0.04; +$node-border-stroke-width: 0.12; +$node-shadow-stroke-width: 0.18; $node-pseudo-opacity: 0.8; $node-text-scale: 2; $edge-highlight-opacity: 0.1;