Skip to content
This repository has been archived by the owner on Apr 23, 2024. It is now read-only.

Commit

Permalink
feat: add tooltips for cards (#46)
Browse files Browse the repository at this point in the history
* feat: add tooltips for cards

* fix: one tooltip div

* fix: tests

* fix: tooltips that move

* fix: tooltip not scaled anymore

* feat: working tooltips

* fix: tooltips in edit mode

* fix: comments

* fix: rename selections object

* fix: tooltip height constant

* fix: constraints.passive and hover delay

* fix: remove mouseout and mousedown because they weren't helping

* fix: comments

* fix: auto height and bottom positioning

* fix: no tooltip on click or pan

* fix: no extra <br>
  • Loading branch information
eliseeborn committed Feb 18, 2020
1 parent f536b21 commit 4366b0c
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/card/__tests__/card.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('card', () => {
it('should return html for node with attribute label', () => {
data.attributes.label = 'this is the label';
const result = card(data, cardStyling, selections);
expect(result).to.equal(getHtml('<div class="sn-org-card-title">this is the label</div>'));
expect(result).to.equal(getHtml(`<div class="sn-org-card-title">${data.attributes.label}</div>`));
});

it('should return html for node with id and subLabel', () => {
Expand Down
33 changes: 19 additions & 14 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import stylingUtils from './utils/styling';
import treeTransform from './utils/tree-utils';
import viewStateUtil from './utils/viewstate-utils';
import { setZooming } from './tree/transform';
import './styles/treeCss.less';
import './styles/tooltip.less';
import './styles/paths.less';
import './styles/warnings.less';
import './styles/nodes.less';
Expand All @@ -39,6 +39,7 @@ export default function supernova(env) {
const [expandedState, setExpandedState] = useState(null);
const [linked, setLinked] = useState(false);
const [selectionState, setSelectionState] = useState([]);
const [transform, setTransform] = useState(null);
const layout = useStaleLayout();
const model = useModel();
const element = useElement();
Expand All @@ -47,30 +48,34 @@ export default function supernova(env) {
const [opts] = useState(options);
const selectionsAPI = useSelections();
const constraints = useConstraints();
const [selections] = useState({ api: selectionsAPI, setState: setSelectionState, linked: false });
const [selectionsAndTransform] = useState({ api: selectionsAPI, setState: setSelectionState, linked: false, transform: {} });

const resetSelections = () => {
setSelectionState([]);
};
useEffect(() => {
if (!selections.api) {
if (!selectionsAndTransform.api) {
return () => {};
}
selections.api = selectionsAPI;
selections.api.on('canceled', resetSelections);
selections.api.on('cleared', resetSelections);
selectionsAndTransform.api = selectionsAPI;
selectionsAndTransform.api.on('canceled', resetSelections);
selectionsAndTransform.api.on('cleared', resetSelections);
// Return function called on unmount
return () => {
selections.api.removeListener('deactivated', resetSelections);
selections.api.removeListener('canceled', resetSelections);
selections.api.removeListener('cleared', resetSelections);
selectionsAndTransform.api.removeListener('deactivated', resetSelections);
selectionsAndTransform.api.removeListener('canceled', resetSelections);
selectionsAndTransform.api.removeListener('cleared', resetSelections);
};
}, [selectionsAPI]);

useEffect(() => {
selectionsAndTransform.transform = transform;
}, [transform]);

useAction(
() => ({
action() {
selections.linked = !linked;
selectionsAndTransform.linked = !linked;
setLinked(!linked);
},
icon: {
Expand All @@ -92,14 +97,14 @@ export default function supernova(env) {
useEffect(() => {
const addKeyPress = event => {
if (event.key === 'Shift') {
selections.linked = true;
selectionsAndTransform.linked = true;
setLinked(true);
}
};

const removeKeyPress = event => {
if (event.key === 'Shift') {
selections.linked = false;
selectionsAndTransform.linked = false;
setLinked(false);
}
};
Expand Down Expand Up @@ -166,7 +171,7 @@ export default function supernova(env) {
expandedState,
styling,
setStateCallback,
selections,
selections: selectionsAndTransform,
selectionState,
constraints,
useTransitions: expandedState.useTransitions,
Expand All @@ -176,7 +181,7 @@ export default function supernova(env) {

useEffect(() => {
if (objectData && layout.navigationMode === 'free') {
setZooming(objectData, !constraints.active);
setZooming(objectData, setTransform, !constraints.active);
}
}, [objectData, constraints]);

Expand Down
38 changes: 38 additions & 0 deletions src/styles/tooltip.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.sn-org-chart {
.sn-org-tooltip {
visibility: hidden;
opacity: 0;
font-family: 'QlikView Sans', sans-serif;
width: 240px;
max-height: 100px;
background-color: #646464;
color: #E6E6E6;
text-align: center;
font-size: 12px;
line-height: 15px;
padding: 5px 0;
border-radius: 6px;

overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;

/* Position the tooltip text */
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
}

/* Tooltip arrow */
.sn-org-tooltip::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -10px;
border-width: 10px;
border-style: solid;
border-color: #646464 transparent transparent transparent;
}
}
Empty file removed src/styles/treeCss.less
Empty file.
48 changes: 45 additions & 3 deletions src/tree/box.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,24 @@ export const getNewUpState = (d, isExpanded) => ({
export default function box(
{ x, y },
divBox,
tooltip,
nodes,
cardStyling,
expandedState,
setStateCallback,
selectionState,
sel,
allowInteractions,
navigationMode
allowTooltips,
navigationMode,
containerHeight
) {
const { cardWidth, cardHeight, buttonWidth, buttonHeight, buttonMargin, rootDiameter } = constants;
const { cardWidth, cardHeight, buttonWidth, buttonHeight, buttonMargin, rootDiameter, tooltipWidth, tooltipPadding } = constants;
const { topId, isExpanded } = expandedState;
const topNode = nodes.find(node => node.data.id === topId);
const ancestorIds = topNode.parent ? topNode.parent.ancestors().map(anc => anc.data.id) : [];
let tooltipOpen = -1;
let tooltipClose = -1;

// dummy root
divBox
Expand All @@ -83,6 +88,12 @@ export default function box(
.attr('style', d => `top:${y(d) - rootDiameter - buttonMargin}px;left:${x(d) + (cardWidth - rootDiameter) / 2}px`)
.attr('id', d => d.data.id);

function getTooltipStyle(d) {
const halfCardWidth = cardWidth / 2;
const halfTooltipWidth = tooltipWidth / 2;
return `bottom:${containerHeight - ((y(d)) * sel.transform.zoom + sel.transform.y - tooltipPadding)}px;left:${x(d) * sel.transform.zoom + sel.transform.x - (halfTooltipWidth - (halfCardWidth * sel.transform.zoom))}px;visibility: visible;opacity: 0.9;`;
}

// cards
divBox
.selectAll('.sn-org-nodes')
Expand All @@ -97,7 +108,38 @@ export default function box(
selections.select(node, sel, selectionState);
}
})
.html(d => card(d.data, cardStyling, sel, selectionState));
.html(d => card(d.data, cardStyling, sel, selectionState))
.on('mouseenter', d => {
if (allowTooltips && tooltipOpen === -1 && event.buttons === 0) {
tooltipOpen = setTimeout(() => {
tooltip
.html(`${d.data.attributes.label || d.data.id}<br />${d.data.attributes.subLabel ? `${d.data.attributes.subLabel}<br />` : ''}${d.data.attributes.extraLabel ? `${d.data.attributes.extraLabel}<br />` : ''}${d.data.measure || ''}`)
.attr('style', () => getTooltipStyle(d));
tooltipOpen = -1;
}, 250);
tooltipClose = setTimeout(() => {
tooltip
.html('')
.attr('style', 'visibility: hidden;opacity: 0;');
}, 7000);
}
})
.on('mouseleave', () => {
clearTimeout(tooltipOpen);
tooltipOpen = -1;
clearTimeout(tooltipClose);
tooltip
.html('')
.attr('style', 'visibility: hidden;opacity: 0;');
})
.on('mousedown', () => {
clearTimeout(tooltipOpen);
tooltipOpen = -1;
clearTimeout(tooltipClose);
tooltip
.html('')
.attr('style', 'visibility: hidden;opacity: 0;');
});

// expand/collapse
divBox
Expand Down
21 changes: 18 additions & 3 deletions src/tree/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const paintTree = ({
constraints,
useTransitions,
}) => {
const { svg, divBox, allNodes, positioning, width, height } = objectData;
const { svg, divBox, allNodes, positioning, width, height, tooltip } = objectData;
const { navigationMode } = allNodes.data;
divBox.selectAll('*').remove();
svg.selectAll('*').remove();
Expand All @@ -53,14 +53,17 @@ export const paintTree = ({
box(
positioning,
divBox,
tooltip,
nodes,
styling,
expandedState,
setStateCallback,
selectionState,
selections,
!constraints.active,
navigationMode
!constraints.passive,
navigationMode,
height
);
// Create the lines (links) between the nodes
const node = svg
Expand Down Expand Up @@ -124,12 +127,24 @@ export function preRenderTree(element, dataTree) {
.append('div')
.attr('class', 'sn-org-nodes');

const tooltip = select(element)
.selectAll('.sn-org-tooltip')
.data([{}])
.enter()
.append('div')
.attr('class', 'sn-org-tooltip')
.on('mousedown', () => {
tooltip
.html('')
.attr('style', 'visibility: hidden;opacity: 0;');
});

const svg = svgBox.append('g').attr('class', 'sn-org-paths');
// Here are the settings for the tree. For instance nodesize can be adjusted
const treemap = tree()
.size([width, height])
.nodeSize([0, positioning.depthSpacing]);

const allNodes = treemap(hierarchy(dataTree));
return { svg, divBox, allNodes, positioning, width, height, element, zoomWrapper };
return { svg, divBox, allNodes, positioning, width, height, element, tooltip, zoomWrapper };
}
2 changes: 2 additions & 0 deletions src/tree/size-constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const constants = {
buttonHeight,
rootDiameter: 20,
r: 4,
tooltipWidth: 240,
tooltipPadding: 15,
};

export default constants;
13 changes: 11 additions & 2 deletions src/tree/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,18 @@ export function applyTransform(eventTransform, svg, divBox, width, height) {
);
}

export function setZooming(objectData, allowInteractions) {
export function setZooming(objectData, settingZoom, allowInteractions) {
const { svg, divBox, width, height, allNodes, zoomWrapper } = objectData;
const maxZoom = 6;
const minZoom = 0.2;
const scaleFactor = Math.max(Math.min(maxZoom, allNodes.zoomFactor), minZoom);

const zoomed = () => {
settingZoom({
zoom: event.transform.k / scaleFactor,
x: event.transform.x,
y: event.transform.y,
});
applyTransform(
zoomIdentity.translate(event.transform.x, event.transform.y).scale(event.transform.k / scaleFactor),
svg,
Expand All @@ -63,7 +68,11 @@ export function setZooming(objectData, allowInteractions) {
.scaleExtent([minZoom * scaleFactor, maxZoom * scaleFactor])
.on('zoom', zoomed)
);

settingZoom({
zoom: 1 / scaleFactor,
x: 0,
y: 0,
});
applyTransform(zoomIdentity.translate(0, 0).scale(1 / scaleFactor), svg, divBox, width, height);
}

Expand Down

0 comments on commit 4366b0c

Please sign in to comment.