import React from 'react'; import ReactDOM from 'react-dom'; 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 = { 'circle': NodeShapeCircle, 'hexagon': NodeShapeHex, 'square': NodeShapeRoundedSquare, 'cloud': NodeShapeCloud }; function getNodeShape({shape, stack}) { const nodeShape = nodeShapes[shape]; if (!nodeShape) { throw new Error(`Unkown shape: ${shape}!`); } return stack ? stackedShape(nodeShape) : nodeShape; } export default class Node extends React.Component { constructor(props, context) { super(props, context); this.handleMouseClick = this.handleMouseClick.bind(this); this.handleMouseEnter = this.handleMouseEnter.bind(this); this.handleMouseLeave = this.handleMouseLeave.bind(this); } render() { const props = this.props; const nodeScale = props.focused ? props.selectedNodeScale : props.nodeScale; const zoomScale = this.props.zoomScale; let scaleFactor = 1; if (props.focused) { scaleFactor = 1.25 / zoomScale; } else if (props.blurred) { scaleFactor = 0.75; } let labelOffsetY = 18; let subLabelOffsetY = 35; const color = getNodeColor(this.props.rank, this.props.label, this.props.pseudo); const onMouseEnter = this.handleMouseEnter; const onMouseLeave = this.handleMouseLeave; const onMouseClick = this.handleMouseClick; const classNames = ['node']; const animConfig = [80, 20]; // stiffness, bounce const label = this.ellipsis(props.label, 14, nodeScale(4 * scaleFactor)); const subLabel = this.ellipsis(props.subLabel, 12, nodeScale(4 * scaleFactor)); let labelFontSize = 14; let subLabelFontSize = 12; if (props.focused) { labelFontSize /= zoomScale; subLabelFontSize /= zoomScale; labelOffsetY /= zoomScale; subLabelOffsetY /= zoomScale; } if (this.props.highlighted) { classNames.push('highlighted'); } if (this.props.blurred) { classNames.push('blurred'); } if (this.props.pseudo) { classNames.push('pseudo'); } const classes = classNames.join(' '); const NodeShapeType = getNodeShape(this.props); return ( {function(interpolated) { const transform = `translate(${interpolated.x},${interpolated.y})`; return ( {label} {subLabel} ); }} ); } ellipsis(text, fontSize, maxWidth) { const averageCharLength = fontSize / 1.5; const allowedChars = maxWidth / averageCharLength; let truncatedText = text; if (text && text.length > allowedChars) { truncatedText = text.slice(0, allowedChars) + '...'; } return truncatedText; } handleMouseClick(ev) { ev.stopPropagation(); clickNode(this.props.id, this.props.label, ReactDOM.findDOMNode(this).getBoundingClientRect()); } handleMouseEnter() { enterNode(this.props.id); } handleMouseLeave() { leaveNode(this.props.id); } }