Skip to content

Commit

Permalink
SankeyDiagram SankeyNodeLabel update - if nodeLabelText returns an el…
Browse files Browse the repository at this point in the history
…ement, render it as SVG rather than HTML wrapped in foreignObject
  • Loading branch information
Dan Delany committed Mar 12, 2018
1 parent c1f4b64 commit 9f77123
Showing 1 changed file with 20 additions and 34 deletions.
54 changes: 20 additions & 34 deletions src/SankeyDiagram.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,37 +90,41 @@ const SankeyNodeLabel = props => {
const distance = getWithNode(props.nodeLabelDistance) || 0;
const labelContent = getWithNode(getLabelText);
// don't render empty labels
if(_.isNull(labelContent) || _.isUndefined(labelContent) || labelContent === false || labelContent === "") {
if (_.isNull(labelContent) || _.isUndefined(labelContent) || labelContent === false || labelContent === "") {
return null;
}

// if `labelContent` is a string or number, it is rendered as text within a SVG <text> element
// otherwise, it is rendered as arbitrary SVG content
// allows users to render components inside a node label (eg. to add icon or link)
const isTextLabel = _.isString(labelContent) || _.isNumber(labelContent);
if(!isTextLabel) {
return labelContent;
}

const baseClassName = `sankey-node-label ${getWithNode(props.nodeLabelClassName)}`;
const baseStyle = getWithNode(props.nodeLabelStyle);
let position;
let textStyle;
let translate;

// use placement prop to determine x, y, alignmentBaseline and
// use placement prop to determine x, y, alignmentBaseline and textAnchor
if (placement === "above") {
// render label above node, centered horizontally
textStyle = {alignmentBaseline: "baseline", textAnchor: "middle", ...baseStyle};
translate = '-50%, -100%';
position = {
x: node.x0 + Math.abs(node.x1 - node.x0) / 2,
y: node.y0 - distance
};
} else if (placement === "below") {
// render label above node, centered horizontally
textStyle = {alignmentBaseline: "hanging", textAnchor: "middle", ...baseStyle};
translate = '-50%, 0';
position = {
x: node.x0 + Math.abs(node.x1 - node.x0) / 2,
y: node.y1 + distance
};
} else if (placement === "before") {
// render label before (to left of) node, centered vertically
textStyle = {alignmentBaseline: "middle", textAnchor: "end", ...baseStyle};
translate = '-100%, -50%';
position = {
x: node.x0 - distance,
y: node.y0 + Math.abs(node.y1 - node.y0) / 2
Expand All @@ -130,39 +134,18 @@ const SankeyNodeLabel = props => {
console.warn(`${placement} is not a valid value for nodeLabelPlacement - defaulting to "after"`);
// render label after (to right of) node, centered vertically
textStyle = {alignmentBaseline: "middle", textAnchor: "start", ...baseStyle};
translate = '0, -50%';
position = {
x: node.x1 + distance,
y: node.y0 + Math.abs(node.y1 - node.y0) / 2
};
}

// if `labelContent` is a string or number, it is rendered as text within a SVG <text> element
// otherwise, it is rendered as arbitrary HTML content inside of a <foreignObject />
// allows users to render arbitrary components inside a node label (eg. to add an icon or link)
const isTextLabel = _.isString(labelContent) || _.isNumber(labelContent);

if(isTextLabel) {
const className = `${baseClassName} sankey-node-label-text`;
return (
<text {...position} className={className} style={textStyle}>
{labelContent}
</text>
);

} else {
const className = `${baseClassName} sankey-node-label-html`;
// wrap HTML labels in a div with "inline-block" so that translation (%) is relative to width of its content
const style = {...baseStyle, display: "inline-block", transform: `translate(${translate})`};
// give foreignObject container a large width/height to prevent unintentional line breaks/cut off content
return (
<foreignObject {...position} style={{overflow: "visible"}} width="5000" height="5000">
<div className={className} style={style}>
{labelContent}
</div>
</foreignObject>
);
}
const className = `${baseClassName} sankey-node-label-text`;
return (
<text {...position} className={className} style={textStyle}>
{labelContent}
</text>
);
};

const SankeyLinkLabel = props => {
Expand Down Expand Up @@ -466,7 +449,10 @@ export default class SankeyDiagram extends React.Component {
/**
* Accessor function `nodeLabelText(node, graph)` which returns the content to be used for node labels.
* The function may return a string/number (rendered as SVG `<text>`),
* or arbitrary React HTML element(s) (rendered as HTML wrapped in SVG `<foreignObject>`).
* or arbitrary React SVG element(s) (rendered as-is inside the SVG).
* NOTE: in the latter case (returning arbitrary SVG), `nodeLabelPlacement`, `nodeLabelDistance`,
* `nodeLabelClassName` and `nodeLabelStyle` props will not be applied -
* user is responsible for all positioning and attributes on this element.
*/
nodeLabelText: PropTypes.func,
/**
Expand Down

0 comments on commit 9f77123

Please sign in to comment.