123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- import React from 'react';
- import { connect } from 'react-redux';
- import theme from 'weaveworks-ui-components/lib/theme';
- import NodeResourcesMetricBoxInfo from './node-resources-metric-box-info';
- import { clickNode } from '../../actions/request-actions';
- import { trackAnalyticsEvent } from '../../utils/tracking-utils';
- import { applyTransform } from '../../utils/transform-utils';
- import { RESOURCE_VIEW_MODE } from '../../constants/naming';
- import {
- RESOURCES_LAYER_TITLE_WIDTH,
- RESOURCES_LABEL_MIN_SIZE,
- RESOURCES_LABEL_PADDING,
- } from '../../constants/styles';
- // Transforms the rectangle box according to the zoom state forwarded by
- // the zooming wrapper. Two main reasons why we're doing it per component
- // instead of on the parent group are:
- // 1. Due to single-precision SVG coordinate system implemented by most browsers,
- // the resource boxes would be incorrectly rendered on extreme zoom levels (it's
- // not just about a few pixels here and there, the whole layout gets screwed). So
- // we don't actually use the native SVG transform but transform the coordinates
- // ourselves (with `applyTransform` helper).
- // 2. That also enables us to do the resources info label clipping, which would otherwise
- // not be possible with pure zooming.
- //
- // The downside is that the rendering becomes slower as the transform prop needs to be forwarded
- // down to this component, so a lot of stuff gets rerendered/recalculated on every zoom action.
- // On the other hand, this enables us to easily leave out the nodes that are not in the viewport.
- const transformedDimensions = (props) => {
- const {
- width, height, x, y
- } = applyTransform(props.transform, props);
- // Trim the beginning of the resource box just after the layer topology
- // name to the left and the viewport width to the right. That enables us
- // to make info tags 'sticky', but also not to render the nodes with no
- // visible part in the viewport.
- const xStart = Math.max(RESOURCES_LAYER_TITLE_WIDTH, x);
- const xEnd = Math.min(x + width, props.viewportWidth);
- // Update the horizontal transform with trimmed values.
- return {
- height,
- width: xEnd - xStart,
- x: xStart,
- y,
- };
- };
- class NodeResourcesMetricBox extends React.Component {
- constructor(props, context) {
- super(props, context);
- this.state = transformedDimensions(props);
- this.handleClick = this.handleClick.bind(this);
- this.saveNodeRef = this.saveNodeRef.bind(this);
- }
- componentWillReceiveProps(nextProps) {
- this.setState(transformedDimensions(nextProps));
- }
- handleClick(ev) {
- ev.stopPropagation();
- trackAnalyticsEvent('scope.node.click', {
- layout: RESOURCE_VIEW_MODE,
- topologyId: this.props.topologyId,
- });
- this.props.clickNode(
- this.props.id,
- this.props.label,
- this.nodeRef.getBoundingClientRect(),
- this.props.topologyId,
- this.props.shape
- );
- }
- saveNodeRef(ref) {
- this.nodeRef = ref;
- }
- defaultRectProps(relativeHeight = 1) {
- const {
- x, y, width, height
- } = this.state;
- const translateY = height * (1 - relativeHeight);
- return {
- height: height * relativeHeight,
- opacity: this.props.contrastMode ? 1 : 0.85,
- stroke: this.props.contrastMode ? 'black' : 'white',
- transform: `translate(0, ${translateY})`,
- width,
- x,
- y,
- };
- }
- render() {
- const { x, y, width } = this.state;
- const {
- id, selectedNodeId, label, color, metricSummary
- } = this.props;
- const { showCapacity, relativeConsumption, type } = metricSummary.toJS();
- const opacity = (selectedNodeId && selectedNodeId !== id) ? 0.35 : 1;
- const showInfo = width >= RESOURCES_LABEL_MIN_SIZE;
- const showNode = width >= 1; // hide the thin nodes
- // Don't display the nodes which are less than 1px wide.
- // TODO: Show `+ 31 nodes` kind of tag in their stead.
- if (!showNode) return null;
- const resourceUsageTooltipInfo = showCapacity
- ? metricSummary.get('humanizedRelativeConsumption')
- : metricSummary.get('humanizedAbsoluteConsumption');
- return (
- <g
- className="node-resources-metric-box"
- style={{ opacity }}
- onClick={this.handleClick}
- ref={this.saveNodeRef}
- >
- <title>
- {label}
- {' '}
- -
- {' '}
- {type}
- {' '}
- usage at
- {' '}
- {resourceUsageTooltipInfo}
- </title>
- {showCapacity && (
- <rect
- className="frame"
- rx={theme.borderRadius.soft}
- ry={theme.borderRadius.soft}
- {...this.defaultRectProps()}
- />
- )}
- <rect
- className="bar"
- fill={color}
- rx={theme.borderRadius.soft}
- ry={theme.borderRadius.soft}
- {...this.defaultRectProps(relativeConsumption)}
- />
- {showInfo && (
- <NodeResourcesMetricBoxInfo
- label={label}
- metricSummary={metricSummary}
- width={width - (2 * RESOURCES_LABEL_PADDING)}
- x={x + RESOURCES_LABEL_PADDING}
- y={y + RESOURCES_LABEL_PADDING}
- />
- )}
- </g>
- );
- }
- }
- function mapStateToProps(state) {
- return {
- contrastMode: state.get('contrastMode'),
- selectedNodeId: state.get('selectedNodeId'),
- viewportWidth: state.getIn(['viewport', 'width']),
- };
- }
- export default connect(
- mapStateToProps,
- { clickNode }
- )(NodeResourcesMetricBox);
|