Sample Node(id=${d.id}), override using
+ nodeWidth: (d3Node) => 250,
+ nodeHeight: (d) => 150,
+ siblingsMargin: (d3Node) => 20,
+ childrenMargin: (d) => 60,
+ neightbourMargin: (n1, n2) => 80,
+ compactMarginPair: (d) => 100,
+ compactMarginBetween: (d3Node) => 20,
+ onNodeClick: (d) => d,
+ linkGroupArc: d3
+ .linkHorizontal()
+ .x((d) => d.x)
+ .y((d) => d.y),
+ // ({ source, target }) => {
+ // return
+ // return `M ${source.x} , ${source.y} Q ${(source.x + target.x) / 2 + 100},${source.y-100} ${target.x}, ${target.y}`;
+ // },
+ nodeContent: (
+ d,
+ ) => `
Sample Node(id=${d.id}), override using
chart
.nodeContent({data}=>{
return '' // Custom HTML
})
Or check different
layout examples
-
+
`,
- layout: "top",// top, left,right, bottom
- buttonContent: ({ node, state }) => {
- const icons = {
- "left": d => d ? `
‹
` : `
›
`,
- "bottom": d => d ? `
ˬ
` : `
ˆ
`,
- "right": d => d ? `
›
` : `
‹
`,
- "top": d => d ? `
ˆ
` : `
ˬ
`,
- }
- return `
${icons[state.layout](node.children)}
`
- },
- layoutBindings: {
- "left": {
- "nodeLeftX": node => 0,
- "nodeRightX": node => node.width,
- "nodeTopY": node => - node.height / 2,
- "nodeBottomY": node => node.height / 2,
- "nodeJoinX": node => node.x + node.width,
- "nodeJoinY": node => node.y - node.height / 2,
- "linkJoinX": node => node.x + node.width,
- "linkJoinY": node => node.y,
- "linkX": node => node.x,
- "linkY": node => node.y,
- "linkCompactXStart": node => node.x + node.width / 2,//node.x + (node.compactEven ? node.width / 2 : -node.width / 2),
- "linkCompactYStart": node => node.y + (node.compactEven ? node.height / 2 : -node.height / 2),
- "compactLinkMidX": (node, state) => node.firstCompactNode.x,// node.firstCompactNode.x + node.firstCompactNode.flexCompactDim[0] / 4 + state.compactMarginPair(node) / 4,
- "compactLinkMidY": (node, state) => node.firstCompactNode.y + node.firstCompactNode.flexCompactDim[0] / 4 + state.compactMarginPair(node) / 4,
- "linkParentX": node => node.parent.x + node.parent.width,
- "linkParentY": node => node.parent.y,
- "buttonX": node => node.width,
- "buttonY": node => node.height / 2,
- "centerTransform": ({ root, rootMargin, centerY, scale, centerX }) => `translate(${rootMargin},${centerY}) scale(${scale})`,
- "compactDimension": {
- sizeColumn: node => node.height,
- sizeRow: node => node.width,
- reverse: arr => arr.slice().reverse()
- },
- "nodeFlexSize": ({ height, width, siblingsMargin, childrenMargin, state, node }) => {
- if (state.compact && node.flexCompactDim) {
- const result = [node.flexCompactDim[0], node.flexCompactDim[1]]
- return result;
- };
- return [height + siblingsMargin, width + childrenMargin]
- },
- "zoomTransform": ({ centerY, scale }) => `translate(${0},${centerY}) scale(${scale})`,
- "diagonal": this.hdiagonal.bind(this),
- "swap": d => { const x = d.x; d.x = d.y; d.y = x; },
- "nodeUpdateTransform": ({ x, y, width, height }) => `translate(${x},${y - height / 2})`,
- },
- "top": {
- "nodeLeftX": node => -node.width / 2,
- "nodeRightX": node => node.width / 2,
- "nodeTopY": node => 0,
- "nodeBottomY": node => node.height,
- "nodeJoinX": node => node.x - node.width / 2,
- "nodeJoinY": node => node.y + node.height,
- "linkJoinX": node => node.x,
- "linkJoinY": node => node.y + node.height,
- "linkCompactXStart": node => node.x + (node.compactEven ? node.width / 2 : -node.width / 2),
- "linkCompactYStart": node => node.y + node.height / 2,
- "compactLinkMidX": (node, state) => node.firstCompactNode.x + node.firstCompactNode.flexCompactDim[0] / 4 + state.compactMarginPair(node) / 4,
- "compactLinkMidY": node => node.firstCompactNode.y,
- "compactDimension": {
- sizeColumn: node => node.width,
- sizeRow: node => node.height,
- reverse: arr => arr,
- },
- "linkX": node => node.x,
- "linkY": node => node.y,
- "linkParentX": node => node.parent.x,
- "linkParentY": node => node.parent.y + node.parent.height,
- "buttonX": node => node.width / 2,
- "buttonY": node => node.height,
- "centerTransform": ({ root, rootMargin, centerY, scale, centerX }) => `translate(${centerX},${rootMargin}) scale(${scale})`,
- "nodeFlexSize": ({ height, width, siblingsMargin, childrenMargin, state, node, compactViewIndex }) => {
- if (state.compact && node.flexCompactDim) {
- const result = [node.flexCompactDim[0], node.flexCompactDim[1]]
- return result;
- };
- return [width + siblingsMargin, height + childrenMargin];
- },
- "zoomTransform": ({ centerX, scale }) => `translate(${centerX},0}) scale(${scale})`,
- "diagonal": this.diagonal.bind(this),
- "swap": d => { },
- "nodeUpdateTransform": ({ x, y, width, height }) => `translate(${x - width / 2},${y})`,
-
- },
- "bottom": {
- "nodeLeftX": node => -node.width / 2,
- "nodeRightX": node => node.width / 2,
- "nodeTopY": node => -node.height,
- "nodeBottomY": node => 0,
- "nodeJoinX": node => node.x - node.width / 2,
- "nodeJoinY": node => node.y - node.height - node.height,
- "linkJoinX": node => node.x,
- "linkJoinY": node => node.y - node.height,
- "linkCompactXStart": node => node.x + (node.compactEven ? node.width / 2 : -node.width / 2),
- "linkCompactYStart": node => node.y - node.height / 2,
- "compactLinkMidX": (node, state) => node.firstCompactNode.x + node.firstCompactNode.flexCompactDim[0] / 4 + state.compactMarginPair(node) / 4,
- "compactLinkMidY": node => node.firstCompactNode.y,
- "linkX": node => node.x,
- "linkY": node => node.y,
- "compactDimension": {
- sizeColumn: node => node.width,
- sizeRow: node => node.height,
- reverse: arr => arr,
- },
- "linkParentX": node => node.parent.x,
- "linkParentY": node => node.parent.y - node.parent.height,
- "buttonX": node => node.width / 2,
- "buttonY": node => 0,
- "centerTransform": ({ root, rootMargin, centerY, scale, centerX, chartHeight }) => `translate(${centerX},${chartHeight - rootMargin}) scale(${scale})`,
- "nodeFlexSize": ({ height, width, siblingsMargin, childrenMargin, state, node }) => {
- if (state.compact && node.flexCompactDim) {
- const result = [node.flexCompactDim[0], node.flexCompactDim[1]]
- return result;
- };
- return [width + siblingsMargin, height + childrenMargin]
- },
- "zoomTransform": ({ centerX, scale }) => `translate(${centerX},0}) scale(${scale})`,
- "diagonal": this.diagonal.bind(this),
- "swap": d => { d.y = -d.y; },
- "nodeUpdateTransform": ({ x, y, width, height }) => `translate(${x - width / 2},${y - height})`,
- },
- "right": {
- "nodeLeftX": node => -node.width,
- "nodeRightX": node => 0,
- "nodeTopY": node => - node.height / 2,
- "nodeBottomY": node => node.height / 2,
- "nodeJoinX": node => node.x - node.width - node.width,
- "nodeJoinY": node => node.y - node.height / 2,
- "linkJoinX": node => node.x - node.width,
- "linkJoinY": node => node.y,
- "linkX": node => node.x,
- "linkY": node => node.y,
- "linkParentX": node => node.parent.x - node.parent.width,
- "linkParentY": node => node.parent.y,
- "buttonX": node => 0,
- "buttonY": node => node.height / 2,
- "linkCompactXStart": node => node.x - node.width / 2,//node.x + (node.compactEven ? node.width / 2 : -node.width / 2),
- "linkCompactYStart": node => node.y + (node.compactEven ? node.height / 2 : -node.height / 2),
- "compactLinkMidX": (node, state) => node.firstCompactNode.x,// node.firstCompactNode.x + node.firstCompactNode.flexCompactDim[0] / 4 + state.compactMarginPair(node) / 4,
- "compactLinkMidY": (node, state) => node.firstCompactNode.y + node.firstCompactNode.flexCompactDim[0] / 4 + state.compactMarginPair(node) / 4,
- "centerTransform": ({ root, rootMargin, centerY, scale, centerX, chartWidth }) => `translate(${chartWidth - rootMargin},${centerY}) scale(${scale})`,
- "nodeFlexSize": ({ height, width, siblingsMargin, childrenMargin, state, node }) => {
- if (state.compact && node.flexCompactDim) {
- const result = [node.flexCompactDim[0], node.flexCompactDim[1]]
- return result;
- };
- return [height + siblingsMargin, width + childrenMargin]
- },
- "compactDimension": {
- sizeColumn: node => node.height,
- sizeRow: node => node.width,
- reverse: arr => arr.slice().reverse()
- },
- "zoomTransform": ({ centerY, scale }) => `translate(${0},${centerY}) scale(${scale})`,
- "diagonal": this.hdiagonal.bind(this),
- "swap": d => { const x = d.x; d.x = -d.y; d.y = x; },
- "nodeUpdateTransform": ({ x, y, width, height }) => `translate(${x - width},${y - height / 2})`,
- },
+ layout: 'top', // top, left,right, bottom
+ buttonContent: ({ node, state }) => {
+ const icons = {
+ left: (d) =>
+ d
+ ? `
‹
`
+ : `
›
`,
+ bottom: (d) =>
+ d
+ ? `
ˬ
`
+ : `
ˆ
`,
+ right: (d) =>
+ d
+ ? `
›
`
+ : `
‹
`,
+ top: (d) =>
+ d
+ ? `
ˆ
`
+ : `
ˬ
`,
+ };
+ return `
${icons[
+ state.layout
+ ](node.children)}
`;
+ },
+ layoutBindings: {
+ left: {
+ nodeLeftX: (node) => node.width / 0.4,
+ nodeRightX: (node) => node.width,
+ nodeTopY: (node) => -node.height / 2,
+ nodeBottomY: (node) => node.height / 2,
+ nodeJoinX: (node) => node.x + node.width,
+ nodeJoinY: (node) => node.y - node.height / 2,
+ linkJoinX: (node) => node.x + node.width,
+ linkJoinY: (node) => node.y,
+ linkX: (node) => node.x,
+ linkY: (node) => node.y,
+ linkCompactXStart: (node) => node.x + node.width / 2, //node.x + (node.compactEven ? node.width / 2 : -node.width / 2),
+ linkCompactYStart: (node) =>
+ node.y + (node.compactEven ? node.height / 2 : -node.height / 2),
+ compactLinkMidX: (node, state) => node.firstCompactNode.x, // node.firstCompactNode.x + node.firstCompactNode.flexCompactDim[0] / 4 + state.compactMarginPair(node) / 4,
+ compactLinkMidY: (node, state) =>
+ node.firstCompactNode.y +
+ node.firstCompactNode.flexCompactDim[0] / 4 +
+ state.compactMarginPair(node) / 4,
+ linkParentX: (node) => node.parent.x + node.parent.width,
+ linkParentY: (node) => node.parent.y,
+ buttonX: (node) => node.width,
+ buttonY: (node) => node.height / 2,
+ centerTransform: ({ root, rootMargin, centerY, scale, centerX }) =>
+ `translate(${rootMargin},${centerY}) scale(${scale})`,
+ compactDimension: {
+ sizeColumn: (node) => node.height,
+ sizeRow: (node) => node.width,
+ reverse: (arr) => arr.slice().reverse(),
+ },
+ nodeFlexSize: ({
+ height,
+ width,
+ siblingsMargin,
+ childrenMargin,
+ state,
+ node,
+ }) => {
+ if (state.compact && node.flexCompactDim) {
+ const result = [node.flexCompactDim[0], node.flexCompactDim[1]];
+ return result;
}
+ return [height + siblingsMargin, width + childrenMargin];
+ },
+ zoomTransform: ({ centerY, scale }) =>
+ `translate(${0},${centerY}) scale(${scale})`,
+ diagonal: this.hdiagonal.bind(this),
+ swap: (d) => {
+ const x = d.x;
+ d.x = d.y;
+ d.y = x;
+ },
+ nodeUpdateTransform: ({ x, y, width, height }) =>
+ `translate(${x},${y - height / 2})`,
+ },
+ top: {
+ nodeLeftX: (node) => -node.width / 2,
+ nodeRightX: (node) => node.width / 2,
+ nodeTopY: (node) => 0,
+ // "nodeTopY": node => node.height/0.4,
+ nodeBottomY: (node) => node.height,
+ nodeJoinX: (node) => node.x - node.width / 2,
+ nodeJoinY: (node) => node.y + node.height,
+ linkJoinX: (node) => node.x,
+ linkJoinY: (node) => node.y + node.height,
+ linkCompactXStart: (node) =>
+ node.x + (node.compactEven ? node.width / 2 : -node.width / 2),
+ linkCompactYStart: (node) => node.y + node.height / 2,
+ compactLinkMidX: (node, state) =>
+ node.firstCompactNode.x +
+ node.firstCompactNode.flexCompactDim[0] / 4 +
+ state.compactMarginPair(node) / 4,
+ compactLinkMidY: (node) => node.firstCompactNode.y,
+ compactDimension: {
+ sizeColumn: (node) => node.width,
+ sizeRow: (node) => node.height,
+ reverse: (arr) => arr,
+ },
+ linkX: (node) => node.x,
+ linkY: (node) => node.y,
+ linkParentX: (node) => node.parent.x,
+ linkParentY: (node) => node.parent.y + node.parent.height,
+ buttonX: (node) => node.width / 2,
+ buttonY: (node) => node.height,
+ centerTransform: ({ root, rootMargin, centerY, scale, centerX }) =>
+ `translate(${centerX},${rootMargin}) scale(${scale})`,
+ nodeFlexSize: ({
+ height,
+ width,
+ siblingsMargin,
+ childrenMargin,
+ state,
+ node,
+ compactViewIndex,
+ }) => {
+ if (state.compact && node.flexCompactDim) {
+ const result = [node.flexCompactDim[0], node.flexCompactDim[1]];
+ return result;
+ }
+ return [width + siblingsMargin, height + childrenMargin];
+ },
+ zoomTransform: ({ centerX, scale }) =>
+ `translate(${centerX},0}) scale(${scale})`,
+ diagonal: this.diagonal.bind(this),
+ swap: (d) => {},
+ nodeUpdateTransform: ({ x, y, width, height }) =>
+ `translate(${x - width / 2},${y})`,
+ },
+ bottom: {
+ nodeLeftX: (node) => -node.width / 2,
+ nodeRightX: (node) => node.width / 2,
+ nodeTopY: (node) => -node.height,
+ nodeBottomY: (node) => 0,
+ nodeJoinX: (node) => node.x - node.width / 2,
+ nodeJoinY: (node) => node.y - node.height - node.height,
+ linkJoinX: (node) => node.x,
+ linkJoinY: (node) => node.y - node.height,
+ linkCompactXStart: (node) =>
+ node.x + (node.compactEven ? node.width / 2 : -node.width / 2),
+ linkCompactYStart: (node) => node.y - node.height / 2,
+ compactLinkMidX: (node, state) =>
+ node.firstCompactNode.x +
+ node.firstCompactNode.flexCompactDim[0] / 4 +
+ state.compactMarginPair(node) / 4,
+ compactLinkMidY: (node) => node.firstCompactNode.y,
+ linkX: (node) => node.x,
+ linkY: (node) => node.y,
+ compactDimension: {
+ sizeColumn: (node) => node.width,
+ sizeRow: (node) => node.height,
+ reverse: (arr) => arr,
+ },
+ linkParentX: (node) => node.parent.x,
+ linkParentY: (node) => node.parent.y - node.parent.height,
+ buttonX: (node) => node.width / 2,
+ buttonY: (node) => 0,
+ centerTransform: ({
+ root,
+ rootMargin,
+ centerY,
+ scale,
+ centerX,
+ chartHeight,
+ }) =>
+ `translate(${centerX},${chartHeight - rootMargin}) scale(${scale})`,
+ nodeFlexSize: ({
+ height,
+ width,
+ siblingsMargin,
+ childrenMargin,
+ state,
+ node,
+ }) => {
+ if (state.compact && node.flexCompactDim) {
+ const result = [node.flexCompactDim[0], node.flexCompactDim[1]];
+ return result;
+ }
+ return [width + siblingsMargin, height + childrenMargin];
+ },
+ zoomTransform: ({ centerX, scale }) =>
+ `translate(${centerX},0}) scale(${scale})`,
+ diagonal: this.diagonal.bind(this),
+ swap: (d) => {
+ d.y = -d.y;
+ },
+ nodeUpdateTransform: ({ x, y, width, height }) =>
+ `translate(${x - width / 2},${y - height})`,
+ },
+ right: {
+ nodeLeftX: (node) => -node.width,
+ nodeRightX: (node) => 0,
+ nodeTopY: (node) => -node.height / 2,
+ nodeBottomY: (node) => node.height / 2,
+ nodeJoinX: (node) => node.x - node.width - node.width,
+ nodeJoinY: (node) => node.y - node.height / 2,
+ linkJoinX: (node) => node.x - node.width,
+ linkJoinY: (node) => node.y,
+ linkX: (node) => node.x,
+ linkY: (node) => node.y,
+ linkParentX: (node) => node.parent.x - node.parent.width,
+ linkParentY: (node) => node.parent.y,
+ buttonX: (node) => 0,
+ buttonY: (node) => node.height / 2,
+ linkCompactXStart: (node) => node.x - node.width / 2, //node.x + (node.compactEven ? node.width / 2 : -node.width / 2),
+ linkCompactYStart: (node) =>
+ node.y + (node.compactEven ? node.height / 2 : -node.height / 2),
+ compactLinkMidX: (node, state) => node.firstCompactNode.x, // node.firstCompactNode.x + node.firstCompactNode.flexCompactDim[0] / 4 + state.compactMarginPair(node) / 4,
+ compactLinkMidY: (node, state) =>
+ node.firstCompactNode.y +
+ node.firstCompactNode.flexCompactDim[0] / 4 +
+ state.compactMarginPair(node) / 4,
+ centerTransform: ({
+ root,
+ rootMargin,
+ centerY,
+ scale,
+ centerX,
+ chartWidth,
+ }) =>
+ `translate(${chartWidth - rootMargin},${centerY}) scale(${scale})`,
+ nodeFlexSize: ({
+ height,
+ width,
+ siblingsMargin,
+ childrenMargin,
+ state,
+ node,
+ }) => {
+ if (state.compact && node.flexCompactDim) {
+ const result = [node.flexCompactDim[0], node.flexCompactDim[1]];
+ return result;
+ }
+ return [height + siblingsMargin, width + childrenMargin];
+ },
+ compactDimension: {
+ sizeColumn: (node) => node.height,
+ sizeRow: (node) => node.width,
+ reverse: (arr) => arr.slice().reverse(),
+ },
+ zoomTransform: ({ centerY, scale }) =>
+ `translate(${0},${centerY}) scale(${scale})`,
+ diagonal: this.hdiagonal.bind(this),
+ swap: (d) => {
+ const x = d.x;
+ d.x = -d.y;
+ d.y = x;
+ },
+ nodeUpdateTransform: ({ x, y, width, height }) =>
+ `translate(${x - width},${y - height / 2})`,
+ },
+ },
+ };
+
+ this.getChartState = () => attrs;
+
+ // Dynamically set getter and setter functions for Chart class
+ Object.keys(attrs).forEach((key) => {
+ //@ts-ignore
+ this[key] = function (_) {
+ if (!arguments.length) {
+ return attrs[key];
+ } else {
+ attrs[key] = _;
+ }
+ return this;
+ };
+ });
+
+ this.initializeEnterExitUpdatePattern();
+ }
+
+ initializeEnterExitUpdatePattern() {
+ d3.selection.prototype.patternify = function (params) {
+ var container = this;
+ var selector = params.selector;
+ var elementTag = params.tag;
+ var data = params.data || [selector];
+
+ // Pattern in action
+ var selection = container.selectAll('.' + selector).data(data, (d, i) => {
+ if (typeof d === 'object') {
+ if (d.id) {
+ return d.id;
+ }
+ }
+ return i;
+ });
+ selection.exit().remove();
+ selection = selection.enter().append(elementTag).merge(selection);
+ selection.attr('class', selector);
+ return selection;
+ };
+ }
+
+ // This method retrieves passed node's children IDs (including node)
+ getNodeChildren({ data, children, _children }, nodeStore) {
+ // Store current node ID
+ nodeStore.push(data);
+
+ // Loop over children and recursively store descendants id (expanded nodes)
+ if (children) {
+ children.forEach((d) => {
+ this.getNodeChildren(d, nodeStore);
+ });
+ }
+
+ // Loop over _children and recursively store descendants id (collapsed nodes)
+ if (_children) {
+ _children.forEach((d) => {
+ this.getNodeChildren(d, nodeStore);
+ });
+ }
+
+ // Return result
+ return nodeStore;
+ }
+
+ // This method can be invoked via chart.setZoomFactor API, it zooms to particulat scale
+ initialZoom(zoomLevel) {
+ const attrs = this.getChartState();
+ attrs.lastTransform.k = zoomLevel;
+ return this;
+ }
+
+ render() {
+ //InnerFunctions which will update visuals
+ const attrs = this.getChartState();
+ if (!attrs.data || attrs.data.length == 0) {
+ console.log('ORG CHART - Data is empty');
+ return this;
+ }
+
+ //Drawing containers
+ const container = d3.select(attrs.container);
+ const containerRect = container.node().getBoundingClientRect();
+ if (containerRect.width > 0) attrs.svgWidth = containerRect.width;
+
+ //Calculated properties
+ const calc = {
+ id: `ID${Math.floor(Math.random() * 1000000)}`, // id for event handlings,
+ chartWidth: attrs.svgWidth,
+ chartHeight: attrs.svgHeight,
+ };
+ attrs.calc = calc;
+
+ // Calculate max node depth (it's needed for layout heights calculation)
+ calc.centerX = calc.chartWidth / 2;
+ calc.centerY = calc.chartHeight / 2;
+
+ // ******************* BEHAVIORS **********************
+ if (attrs.firstDraw) {
+ const behaviors = {
+ zoom: null,
+ };
+
+ // Get zooming function
+ behaviors.zoom = d3
+ .zoom()
+ .on('zoom', (event, d) => this.zoomed(event, d))
+ .scaleExtent(attrs.scaleExtent);
+ attrs.zoomBehavior = behaviors.zoom;
+ }
+
+ //****************** ROOT node work ************************
+
+ attrs.flexTreeLayout = flextree({
+ nodeSize: (node) => {
+ const width = attrs.nodeWidth(node);
+ const height = attrs.nodeHeight(node);
+ const siblingsMargin = attrs.siblingsMargin(node);
+ const childrenMargin = attrs.childrenMargin(node);
+ return attrs.layoutBindings[attrs.layout].nodeFlexSize({
+ state: attrs,
+ node: node,
+ width,
+ height,
+ siblingsMargin,
+ childrenMargin,
+ });
+ },
+ }).spacing((nodeA, nodeB) =>
+ nodeA.parent == nodeB.parent ? 0 : attrs.neightbourMargin(nodeA, nodeB),
+ );
+
+ this.setLayouts({ expandNodesFirst: false });
+
+ // ************************* DRAWING **************************
+ //Add svg
+ const svg = container
+ .patternify({
+ tag: 'svg',
+ selector: 'svg-chart-container',
+ })
+ .style('background-color', attrs.backgroundColor)
+ .attr('width', attrs.svgWidth)
+ .attr('height', attrs.svgHeight)
+ .attr('font-family', attrs.defaultFont);
+ if (attrs.firstDraw) {
+ svg
+ .call(attrs.zoomBehavior)
+ .on('dblclick.zoom', null)
+ .attr('cursor', 'move');
+ }
+
+ attrs.svg = svg;
+
+ //Add container g element
+ const chart = svg.patternify({
+ tag: 'g',
+ selector: 'chart',
+ });
+
+ // Add one more container g element, for better positioning controls
+ attrs.centerG = chart.patternify({
+ tag: 'g',
+ selector: 'center-group',
+ });
+
+ attrs.linksWrapper = attrs.centerG.patternify({
+ tag: 'g',
+ selector: 'links-wrapper',
+ });
+
+ attrs.nodesWrapper = attrs.centerG.patternify({
+ tag: 'g',
+ selector: 'nodes-wrapper',
+ });
+
+ attrs.connectionsWrapper = attrs.centerG.patternify({
+ tag: 'g',
+ selector: 'connections-wrapper',
+ });
+
+ attrs.defsWrapper = svg.patternify({
+ tag: 'g',
+ selector: 'defs-wrapper',
+ });
+
+ if (attrs.firstDraw) {
+ attrs.centerG.attr('transform', () => {
+ return attrs.layoutBindings[attrs.layout].centerTransform({
+ centerX: calc.centerX,
+ centerY: calc.centerY,
+ scale: attrs.lastTransform.k,
+ rootMargin: attrs.rootMargin,
+ root: attrs.root,
+ chartHeight: calc.chartHeight,
+ chartWidth: calc.chartWidth,
+ });
+ });
+ }
+
+ attrs.chart = chart;
+
+ // Display tree contenrs
+ this.update(attrs.root);
+
+ //######################################### UTIL FUNCS ##################################
+ // This function restyles foreign object elements ()
+
+ d3.select(window).on(`resize.${attrs.id}`, () => {
+ const containerRect = d3
+ .select(attrs.container)
+ .node()
+ .getBoundingClientRect();
+ attrs.svg.attr('width', containerRect.width);
+ });
+
+ if (attrs.firstDraw) {
+ attrs.firstDraw = false;
+ }
+
+ return this;
+ }
+
+ // This function can be invoked via chart.addNode API, and it adds node in tree at runtime
+ addNode(obj) {
+ const attrs = this.getChartState();
+ const nodeFound = attrs.allNodes.filter(
+ ({ data }) => attrs.nodeId(data) === attrs.nodeId(obj),
+ )[0];
+ const parentFound = attrs.allNodes.filter(
+ ({ data }) => attrs.nodeId(data) === attrs.parentNodeId(obj),
+ )[0];
+ if (nodeFound) {
+ console.log(
+ `ORG CHART - ADD - Node with id "${attrs.nodeId(
+ obj,
+ )}" already exists in tree`,
+ );
+ return this;
+ }
+ if (!parentFound) {
+ console.log(
+ `ORG CHART - ADD - Parent node with id "${attrs.parentNodeId(
+ obj,
+ )}" not found in the tree`,
+ );
+ return this;
+ }
+ if (obj._centered && !obj._expanded) obj._expanded = true;
+ attrs.data.push(obj);
+
+ // Update state of nodes and redraw graph
+ this.updateNodesState();
+
+ return this;
+ }
+
+ addAyncNode(obj) {
+ const attrs = this.getChartState();
+ const nodeFound = attrs.allNodes.filter(
+ ({ data }) => attrs.nodeId(data) === attrs.nodeId(obj),
+ )[0];
+ const parentFound = attrs.allNodes.filter(
+ ({ data }) => attrs.nodeId(data) === attrs.parentNodeId(obj),
+ )[0];
+ if (nodeFound) {
+ console.log(
+ `ORG CHART - ADD - Node with id "${attrs.nodeId(
+ obj,
+ )}" already exists in tree`,
+ );
+ return this;
+ }
+ if (!parentFound) {
+ console.log(
+ `ORG CHART - ADD - Parent node with id "${attrs.parentNodeId(
+ obj,
+ )}" not found in the tree`,
+ );
+ return this;
+ }
+ obj._expanded = true;
+ attrs.data.push(obj);
+
+ // Update state of nodes and redraw graph
+ this.updateNodesState();
+
+ return this;
+ }
+
+ // This function can be invoked via chart.removeNode API, and it removes node from tree at runtime
+ removeNode(nodeId) {
+ const attrs = this.getChartState();
+ const node = attrs.allNodes.filter(
+ ({ data }) => attrs.nodeId(data) == nodeId,
+ )[0];
+ if (!node) {
+ console.log(
+ `ORG CHART - REMOVE - Node with id "${nodeId}" not found in the tree`,
+ );
+ return this;
+ }
+
+ // Remove all node childs
+ // Retrieve all children nodes ids (including current node itself)
+ node.descendants().forEach((d) => (d.data._filteredOut = true));
+
+ const descendants = this.getNodeChildren(node, [], attrs.nodeId);
+ descendants.forEach((d) => (d._filtered = true));
+
+ // Filter out retrieved nodes and reassign data
+ attrs.data = attrs.data.filter((d) => !d._filtered);
+
+ const updateNodesState = this.updateNodesState.bind(this);
+ // Update state of nodes and redraw graph
+ updateNodesState();
+
+ return this;
+ }
+
+ groupBy(array, accessor, aggegator) {
+ const grouped = {};
+ array.forEach((item) => {
+ const key = accessor(item);
+ if (!grouped[key]) {
+ grouped[key] = [];
+ }
+ grouped[key].push(item);
+ });
+
+ Object.keys(grouped).forEach((key) => {
+ grouped[key] = aggegator(grouped[key]);
+ });
+ return Object.entries(grouped);
+ }
+ calculateCompactFlexDimensions(root) {
+ const attrs = this.getChartState();
+ root.eachBefore((node) => {
+ node.firstCompact = null;
+ node.compactEven = null;
+ node.flexCompactDim = null;
+ node.firstCompactNode = null;
+ });
+ root.eachBefore((node) => {
+ if (node.children && node.children.length > 1) {
+ const compactChildren = node.children.filter((d) => !d.children);
+ if (compactChildren.length < 2) return;
+ compactChildren.forEach((child, i) => {
+ if (!i) child.firstCompact = true;
+ if (i % 2) child.compactEven = false;
+ else child.compactEven = true;
+ child.row = Math.floor(i / 2);
+ });
+ const evenMaxColumnDimension = d3.max(
+ compactChildren.filter((d) => d.compactEven),
+ attrs.layoutBindings[attrs.layout].compactDimension.sizeColumn,
+ );
+ const oddMaxColumnDimension = d3.max(
+ compactChildren.filter((d) => !d.compactEven),
+ attrs.layoutBindings[attrs.layout].compactDimension.sizeColumn,
+ );
+ const columnSize =
+ Math.max(evenMaxColumnDimension, oddMaxColumnDimension) * 2;
+ const rowsMapNew = this.groupBy(
+ compactChildren,
+ (d) => d.row,
+ (reducedGroup) =>
+ d3.max(
+ reducedGroup,
+ (d) =>
+ attrs.layoutBindings[attrs.layout].compactDimension.sizeRow(d) +
+ attrs.compactMarginBetween(d),
+ ),
+ );
+ const rowSize = d3.sum(rowsMapNew.map((v) => v[1]));
+ compactChildren.forEach((node) => {
+ node.firstCompactNode = compactChildren[0];
+ if (node.firstCompact) {
+ node.flexCompactDim = [
+ columnSize + attrs.compactMarginPair(node),
+ rowSize - attrs.compactMarginBetween(node),
+ ];
+ } else {
+ node.flexCompactDim = [0, 0];
+ }
+ });
+ node.flexCompactDim = null;
+ }
+ });
+ }
+
+ calculateCompactFlexPositions(root) {
+ const attrs = this.getChartState();
+ root.eachBefore((node) => {
+ if (node.children) {
+ const compactChildren = node.children.filter((d) => d.flexCompactDim);
+ const fch = compactChildren[0];
+ if (!fch) return;
+ compactChildren.forEach((child, i, arr) => {
+ if (i == 0) fch.x -= fch.flexCompactDim[0] / 2;
+ if (i & ((i % 2) - 1))
+ child.x =
+ fch.x +
+ fch.flexCompactDim[0] * 0.25 -
+ attrs.compactMarginPair(child) / 4;
+ else if (i)
+ child.x =
+ fch.x +
+ fch.flexCompactDim[0] * 0.75 +
+ attrs.compactMarginPair(child) / 4;
+ });
+ const centerX = fch.x + fch.flexCompactDim[0] * 0.5;
+ fch.x =
+ fch.x +
+ fch.flexCompactDim[0] * 0.25 -
+ attrs.compactMarginPair(fch) / 4;
+ const offsetX = node.x - centerX;
+ if (Math.abs(offsetX) < 10) {
+ compactChildren.forEach((d) => (d.x += offsetX));
+ }
+
+ const rowsMapNew = this.groupBy(
+ compactChildren,
+ (d) => d.row,
+ (reducedGroup) =>
+ d3.max(reducedGroup, (d) =>
+ attrs.layoutBindings[attrs.layout].compactDimension.sizeRow(d),
+ ),
+ );
+ const cumSum = d3.cumsum(
+ rowsMapNew.map((d) => d[1] + attrs.compactMarginBetween(d)),
+ );
+ compactChildren.forEach((node, i) => {
+ if (node.row) {
+ node.y = fch.y + cumSum[node.row - 1];
+ } else {
+ node.y = fch.y;
+ }
+ });
+ }
+ });
+ }
+
+ // This function basically redraws visible graph, based on nodes state
+ update({ x0, y0, x = 0, y = 0, width, height }, type) {
+ const attrs = this.getChartState();
+ const calc = attrs.calc;
+
+ if (attrs.compact) {
+ this.calculateCompactFlexDimensions(attrs.root);
+ }
+
+ // Assigns the x and y position for the nodes
+ const treeData = attrs.flexTreeLayout(attrs.root);
+
+ // Reassigns the x and y position for the based on the compact layout
+ if (attrs.compact) {
+ this.calculateCompactFlexPositions(attrs.root);
+ }
+
+ const nodes = treeData.descendants();
+
+ // console.table(nodes.map(d => ({ x: d.x, y: d.y, width: d.width, height: d.height, flexCompactDim: d.flexCompactDim + "" })))
+
+ // Get all links
+ const links = treeData.descendants().slice(1);
+ nodes.forEach(attrs.layoutBindings[attrs.layout].swap);
+
+ // Connections
+ const connections = attrs.connections;
+ const allNodesMap = {};
+ attrs.allNodes.forEach((d) => (allNodesMap[attrs.nodeId(d.data)] = d));
+
+ const visibleNodesMap = {};
+ nodes.forEach((d) => (visibleNodesMap[attrs.nodeId(d.data)] = d));
+
+ connections.forEach((connection) => {
+ const source = allNodesMap[connection.from];
+ const target = allNodesMap[connection.to];
+ connection._source = source;
+ connection._target = target;
+ });
+ const visibleConnections = connections.filter(
+ (d) => visibleNodesMap[d.from] && visibleNodesMap[d.to],
+ );
+ const defsString = attrs.defs.bind(this)(attrs, visibleConnections);
+ const existingString = attrs.defsWrapper.html();
+ if (defsString !== existingString) {
+ attrs.defsWrapper.html(defsString);
+ }
+
+ // -------------------------- LINKS ----------------------
+ // Get links selection
+ const linkSelection = attrs.linksWrapper
+ .selectAll('path.link')
+ .data(links, (d) => attrs.nodeId(d.data));
+
+ // Enter any new links at the parent's previous position.
+ const linkEnter = linkSelection
+ .enter()
+ .insert('path', 'g')
+ .attr('class', 'link')
+ .attr('d', (d) => {
+ const xo = attrs.layoutBindings[attrs.layout].linkJoinX({
+ x: x0,
+ y: y0,
+ width,
+ height,
+ });
+ const yo = attrs.layoutBindings[attrs.layout].linkJoinY({
+ x: x0,
+ y: y0,
+ width,
+ height,
+ });
+ const o = { x: xo, y: yo };
+ return attrs.layoutBindings[attrs.layout].diagonal(o, o, o);
+ });
+
+ // Get links update selection
+ const linkUpdate = linkEnter.merge(linkSelection);
+
+ // Styling links
+ linkUpdate.attr('fill', 'none');
+
+ // Allow external modifications
+ linkUpdate.each(attrs.linkUpdate);
+
+ // Transition back to the parent element position
+ linkUpdate
+ .transition()
+ .duration(attrs.duration)
+ .attr('d', (d) => {
+ const n =
+ attrs.compact && d.flexCompactDim
+ ? {
+ x: attrs.layoutBindings[attrs.layout].compactLinkMidX(d, attrs),
+ y: attrs.layoutBindings[attrs.layout].compactLinkMidY(d, attrs),
+ }
+ : {
+ x: attrs.layoutBindings[attrs.layout].linkX(d),
+ y: attrs.layoutBindings[attrs.layout].linkY(d),
+ };
+
+ const p = {
+ x: attrs.layoutBindings[attrs.layout].linkParentX(d),
+ y: attrs.layoutBindings[attrs.layout].linkParentY(d),
};
- this.getChartState = () => attrs;
+ const m =
+ attrs.compact && d.flexCompactDim
+ ? {
+ x: attrs.layoutBindings[attrs.layout].linkCompactXStart(d),
+ y: attrs.layoutBindings[attrs.layout].linkCompactYStart(d),
+ }
+ : n;
+ return attrs.layoutBindings[attrs.layout].diagonal(n, p, m);
+ });
- // Dynamically set getter and setter functions for Chart class
- Object.keys(attrs).forEach((key) => {
- //@ts-ignore
- this[key] = function (_) {
- if (!arguments.length) {
- return attrs[key];
- } else {
- attrs[key] = _;
- }
- return this;
- };
+ // Remove any links which is exiting after animation
+ const linkExit = linkSelection
+ .exit()
+ .transition()
+ .duration(attrs.duration)
+ .attr('d', (d) => {
+ const xo = attrs.layoutBindings[attrs.layout].linkJoinX({
+ x,
+ y,
+ width,
+ height,
});
-
- this.initializeEnterExitUpdatePattern();
- }
-
- initializeEnterExitUpdatePattern() {
- d3.selection.prototype.patternify = function (params) {
- var container = this;
- var selector = params.selector;
- var elementTag = params.tag;
- var data = params.data || [selector];
-
- // Pattern in action
- var selection = container.selectAll("." + selector).data(data, (d, i) => {
- if (typeof d === "object") {
- if (d.id) { return d.id; }
- }
- return i;
- });
- selection.exit().remove();
- selection = selection.enter().append(elementTag).merge(selection);
- selection.attr("class", selector);
- return selection;
- };
- }
-
- // This method retrieves passed node's children IDs (including node)
- getNodeChildren({ data, children, _children }, nodeStore) {
- // Store current node ID
- nodeStore.push(data);
-
- // Loop over children and recursively store descendants id (expanded nodes)
- if (children) {
- children.forEach((d) => {
- this.getNodeChildren(d, nodeStore);
- });
- }
-
- // Loop over _children and recursively store descendants id (collapsed nodes)
- if (_children) {
- _children.forEach((d) => {
- this.getNodeChildren(d, nodeStore);
- });
- }
-
- // Return result
- return nodeStore;
- }
-
- // This method can be invoked via chart.setZoomFactor API, it zooms to particulat scale
- initialZoom(zoomLevel) {
- const attrs = this.getChartState();
- attrs.lastTransform.k = zoomLevel;
- return this;
- }
-
- render() {
- //InnerFunctions which will update visuals
- const attrs = this.getChartState();
- if (!attrs.data || attrs.data.length == 0) {
- console.log('ORG CHART - Data is empty')
- return this;
- }
-
- //Drawing containers
- const container = d3.select(attrs.container);
- const containerRect = container.node().getBoundingClientRect();
- if (containerRect.width > 0) attrs.svgWidth = containerRect.width;
-
- //Calculated properties
- const calc = {
- id: `ID${Math.floor(Math.random() * 1000000)}`, // id for event handlings,
- chartWidth: attrs.svgWidth,
- chartHeight: attrs.svgHeight
- };
- attrs.calc = calc;
-
- // Calculate max node depth (it's needed for layout heights calculation)
- calc.centerX = calc.chartWidth / 2;
- calc.centerY = calc.chartHeight / 2;
-
- // ******************* BEHAVIORS **********************
- if (attrs.firstDraw) {
- const behaviors = {
- zoom: null
- };
-
- // Get zooming function
- behaviors.zoom = d3.zoom().on("zoom", (event, d) => this.zoomed(event, d)).scaleExtent(attrs.scaleExtent)
- attrs.zoomBehavior = behaviors.zoom;
- }
-
- //****************** ROOT node work ************************
-
- attrs.flexTreeLayout = flextree({
- nodeSize: node => {
- const width = attrs.nodeWidth(node);;
- const height = attrs.nodeHeight(node);
- const siblingsMargin = attrs.siblingsMargin(node)
- const childrenMargin = attrs.childrenMargin(node);
- return attrs.layoutBindings[attrs.layout].nodeFlexSize({
- state: attrs,
- node: node,
- width,
- height,
- siblingsMargin,
- childrenMargin
- });
- }
- })
- .spacing((nodeA, nodeB) => nodeA.parent == nodeB.parent ? 0 : attrs.neightbourMargin(nodeA, nodeB));
-
- this.setLayouts({ expandNodesFirst: false });
-
- // ************************* DRAWING **************************
- //Add svg
- const svg = container
- .patternify({
- tag: "svg",
- selector: "svg-chart-container"
- })
- .style('background-color', attrs.backgroundColor)
- .attr("width", attrs.svgWidth)
- .attr("height", attrs.svgHeight)
- .attr("font-family", attrs.defaultFont)
-
- if (attrs.firstDraw) {
- svg.call(attrs.zoomBehavior)
- .on("dblclick.zoom", null)
- .attr("cursor", "move")
- }
-
- attrs.svg = svg;
-
- //Add container g element
- const chart = svg
- .patternify({
- tag: "g",
- selector: "chart"
- })
-
- // Add one more container g element, for better positioning controls
- attrs.centerG = chart
- .patternify({
- tag: "g",
- selector: "center-group"
- })
-
- attrs.linksWrapper = attrs.centerG.patternify({
- tag: "g",
- selector: "links-wrapper"
- })
-
- attrs.nodesWrapper = attrs.centerG.patternify({
- tag: "g",
- selector: "nodes-wrapper"
- })
-
- attrs.connectionsWrapper = attrs.centerG.patternify({
- tag: "g",
- selector: "connections-wrapper"
- })
-
- attrs.defsWrapper = svg.patternify({
- tag: "g",
- selector: "defs-wrapper"
- })
-
- if (attrs.firstDraw) {
- attrs.centerG.attr("transform", () => {
- return attrs.layoutBindings[attrs.layout].centerTransform({
- centerX: calc.centerX,
- centerY: calc.centerY,
- scale: attrs.lastTransform.k,
- rootMargin: attrs.rootMargin,
- root: attrs.root,
- chartHeight: calc.chartHeight,
- chartWidth: calc.chartWidth
- })
- });
- }
-
- attrs.chart = chart;
-
- // Display tree contenrs
- this.update(attrs.root);
-
-
- //######################################### UTIL FUNCS ##################################
- // This function restyles foreign object elements ()
-
- d3.select(window).on(`resize.${attrs.id}`, () => {
- const containerRect = d3.select(attrs.container).node().getBoundingClientRect();
- attrs.svg.attr('width', containerRect.width)
+ const yo = attrs.layoutBindings[attrs.layout].linkJoinY({
+ x,
+ y,
+ width,
+ height,
});
-
- if (attrs.firstDraw) {
- attrs.firstDraw = false;
- }
-
- return this;
- }
-
- // This function can be invoked via chart.addNode API, and it adds node in tree at runtime
- addNode(obj) {
- const attrs = this.getChartState();
- const nodeFound = attrs.allNodes.filter(({ data }) => attrs.nodeId(data) === attrs.nodeId(obj))[0];
- const parentFound = attrs.allNodes.filter(({ data }) => attrs.nodeId(data) === attrs.parentNodeId(obj))[0];
- if (nodeFound) {
- console.log(`ORG CHART - ADD - Node with id "${attrs.nodeId(obj)}" already exists in tree`)
- return this;
- }
- if (!parentFound) {
- console.log(`ORG CHART - ADD - Parent node with id "${attrs.parentNodeId(obj)}" not found in the tree`)
- return this;
- }
- if (obj._centered && !obj._expanded) obj._expanded = true;
- attrs.data.push(obj);
-
- // Update state of nodes and redraw graph
- this.updateNodesState();
-
- return this;
- }
-
- addAyncNode(obj) {
- const attrs = this.getChartState();
- const nodeFound = attrs.allNodes.filter(({ data }) => attrs.nodeId(data) === attrs.nodeId(obj))[0];
- const parentFound = attrs.allNodes.filter(({ data }) => attrs.nodeId(data) === attrs.parentNodeId(obj))[0];
- if (nodeFound) {
- console.log(`ORG CHART - ADD - Node with id "${attrs.nodeId(obj)}" already exists in tree`)
- return this;
- }
- if (!parentFound) {
- console.log(`ORG CHART - ADD - Parent node with id "${attrs.parentNodeId(obj)}" not found in the tree`)
- return this;
- }
- obj._expanded = true;
- attrs.data.push(obj);
-
- // Update state of nodes and redraw graph
- this.updateNodesState();
-
- return this;
- }
-
- // This function can be invoked via chart.removeNode API, and it removes node from tree at runtime
- removeNode(nodeId) {
- const attrs = this.getChartState();
- const node = attrs.allNodes.filter(({ data }) => attrs.nodeId(data) == nodeId)[0];
- if (!node) {
- console.log(`ORG CHART - REMOVE - Node with id "${nodeId}" not found in the tree`);
- return this;
- }
-
- // Remove all node childs
- // Retrieve all children nodes ids (including current node itself)
- node.descendants()
- .forEach(d => d.data._filteredOut = true)
-
- const descendants = this.getNodeChildren(node, [], attrs.nodeId);
- descendants.forEach(d => d._filtered = true)
-
- // Filter out retrieved nodes and reassign data
- attrs.data = attrs.data.filter(d => !d._filtered);
-
- const updateNodesState = this.updateNodesState.bind(this);
- // Update state of nodes and redraw graph
- updateNodesState();
-
- return this;
- }
-
- groupBy(array, accessor, aggegator) {
- const grouped = {}
- array.forEach(item => {
- const key = accessor(item)
- if (!grouped[key]) {
- grouped[key] = []
- }
- grouped[key].push(item)
- })
-
- Object.keys(grouped).forEach(key => {
- grouped[key] = aggegator(grouped[key])
- })
- return Object.entries(grouped);
- }
- calculateCompactFlexDimensions(root) {
- const attrs = this.getChartState();
- root.eachBefore(node => {
- node.firstCompact = null;
- node.compactEven = null;
- node.flexCompactDim = null;
- node.firstCompactNode = null;
- })
- root.eachBefore(node => {
- if (node.children && node.children.length > 1) {
- const compactChildren = node.children.filter(d => !d.children);
- if (compactChildren.length < 2) return;
- compactChildren.forEach((child, i) => {
- if (!i) child.firstCompact = true;
- if (i % 2) child.compactEven = false;
- else child.compactEven = true;
- child.row = Math.floor(i / 2);
- })
- const evenMaxColumnDimension = d3.max(compactChildren.filter(d => d.compactEven), attrs.layoutBindings[attrs.layout].compactDimension.sizeColumn);
- const oddMaxColumnDimension = d3.max(compactChildren.filter(d => !d.compactEven), attrs.layoutBindings[attrs.layout].compactDimension.sizeColumn);
- const columnSize = Math.max(evenMaxColumnDimension, oddMaxColumnDimension) * 2;
- const rowsMapNew = this.groupBy(compactChildren, d => d.row, reducedGroup => d3.max(reducedGroup, d => attrs.layoutBindings[attrs.layout].compactDimension.sizeRow(d) + attrs.compactMarginBetween(d)));
- const rowSize = d3.sum(rowsMapNew.map(v => v[1]))
- compactChildren.forEach(node => {
- node.firstCompactNode = compactChildren[0];
- if (node.firstCompact) {
- node.flexCompactDim = [
- columnSize + attrs.compactMarginPair(node),
- rowSize - attrs.compactMarginBetween(node)
- ];
- } else {
- node.flexCompactDim = [0, 0];
- }
- })
- node.flexCompactDim = null;
- }
- })
- }
-
- calculateCompactFlexPositions(root) {
- const attrs = this.getChartState();
- root.eachBefore(node => {
- if (node.children) {
- const compactChildren = node.children.filter(d => d.flexCompactDim);
- const fch = compactChildren[0];
- if (!fch) return;
- compactChildren.forEach((child, i, arr) => {
- if (i == 0) fch.x -= fch.flexCompactDim[0] / 2;
- if (i & i % 2 - 1) child.x = fch.x + fch.flexCompactDim[0] * 0.25 - attrs.compactMarginPair(child) / 4;
- else if (i) child.x = fch.x + fch.flexCompactDim[0] * 0.75 + attrs.compactMarginPair(child) / 4;
- })
- const centerX = fch.x + fch.flexCompactDim[0] * 0.5;
- fch.x = fch.x + fch.flexCompactDim[0] * 0.25 - attrs.compactMarginPair(fch) / 4;
- const offsetX = node.x - centerX;
- if (Math.abs(offsetX) < 10) {
- compactChildren.forEach(d => d.x += offsetX);
- }
-
- const rowsMapNew = this.groupBy(compactChildren, d => d.row, reducedGroup => d3.max(reducedGroup, d => attrs.layoutBindings[attrs.layout].compactDimension.sizeRow(d)));
- const cumSum = d3.cumsum(rowsMapNew.map(d => d[1] + attrs.compactMarginBetween(d)));
- compactChildren
- .forEach((node, i) => {
- if (node.row) {
- node.y = fch.y + cumSum[node.row - 1]
- } else {
- node.y = fch.y;
- }
- })
-
- }
- })
- }
-
- // This function basically redraws visible graph, based on nodes state
- update({ x0, y0, x = 0, y = 0, width, height }) {
- const attrs = this.getChartState();
- const calc = attrs.calc;
-
-
- if (attrs.compact) {
- this.calculateCompactFlexDimensions(attrs.root);
- }
-
- // Assigns the x and y position for the nodes
- const treeData = attrs.flexTreeLayout(attrs.root);
-
- // Reassigns the x and y position for the based on the compact layout
- if (attrs.compact) {
- this.calculateCompactFlexPositions(attrs.root);
- }
-
- const nodes = treeData.descendants();
-
- // console.table(nodes.map(d => ({ x: d.x, y: d.y, width: d.width, height: d.height, flexCompactDim: d.flexCompactDim + "" })))
-
- // Get all links
- const links = treeData.descendants().slice(1);
- nodes.forEach(attrs.layoutBindings[attrs.layout].swap)
-
- // Connections
- const connections = attrs.connections;
- const allNodesMap = {};
- attrs.allNodes.forEach(d => allNodesMap[attrs.nodeId(d.data)] = d);
-
- const visibleNodesMap = {}
- nodes.forEach(d => visibleNodesMap[attrs.nodeId(d.data)] = d);
-
- connections.forEach(connection => {
- const source = allNodesMap[connection.from];
- const target = allNodesMap[connection.to];
- connection._source = source;
- connection._target = target;
- })
- const visibleConnections = connections.filter(d => visibleNodesMap[d.from] && visibleNodesMap[d.to]);
- const defsString = attrs.defs.bind(this)(attrs, visibleConnections);
- const existingString = attrs.defsWrapper.html();
- if (defsString !== existingString) {
- attrs.defsWrapper.html(defsString)
- }
-
- // -------------------------- LINKS ----------------------
- // Get links selection
- const linkSelection = attrs.linksWrapper
- .selectAll("path.link")
- .data(links, (d) => attrs.nodeId(d.data));
-
- // Enter any new links at the parent's previous position.
- const linkEnter = linkSelection
- .enter()
- .insert("path", "g")
- .attr("class", "link")
- .attr("d", (d) => {
- const xo = attrs.layoutBindings[attrs.layout].linkJoinX({ x: x0, y: y0, width, height });
- const yo = attrs.layoutBindings[attrs.layout].linkJoinY({ x: x0, y: y0, width, height });
- const o = { x: xo, y: yo };
- return attrs.layoutBindings[attrs.layout].diagonal(o, o, o);
- });
-
- // Get links update selection
- const linkUpdate = linkEnter.merge(linkSelection);
-
- // Styling links
- linkUpdate
- .attr("fill", "none")
-
- // Allow external modifications
- linkUpdate.each(attrs.linkUpdate);
-
- // Transition back to the parent element position
- linkUpdate
- .transition()
- .duration(attrs.duration)
- .attr("d", (d) => {
- const n = attrs.compact && d.flexCompactDim ?
- {
- x: attrs.layoutBindings[attrs.layout].compactLinkMidX(d, attrs),
- y: attrs.layoutBindings[attrs.layout].compactLinkMidY(d, attrs)
- } :
- {
- x: attrs.layoutBindings[attrs.layout].linkX(d),
- y: attrs.layoutBindings[attrs.layout].linkY(d)
- };
-
- const p = {
- x: attrs.layoutBindings[attrs.layout].linkParentX(d),
- y: attrs.layoutBindings[attrs.layout].linkParentY(d),
- };
-
- const m = attrs.compact && d.flexCompactDim ? {
- x: attrs.layoutBindings[attrs.layout].linkCompactXStart(d),
- y: attrs.layoutBindings[attrs.layout].linkCompactYStart(d),
- } : n;
- return attrs.layoutBindings[attrs.layout].diagonal(n, p, m);
- });
-
- // Remove any links which is exiting after animation
- const linkExit = linkSelection
- .exit()
- .transition()
- .duration(attrs.duration)
- .attr("d", (d) => {
- const xo = attrs.layoutBindings[attrs.layout].linkJoinX({ x, y, width, height });
- const yo = attrs.layoutBindings[attrs.layout].linkJoinY({ x, y, width, height });
- const o = { x: xo, y: yo };
- return attrs.layoutBindings[attrs.layout].diagonal(o, o);
- })
- .remove();
-
-
- // -------------------------- CONNECTIONS ----------------------
-
- const connectionsSel = attrs.connectionsWrapper
- .selectAll("path.connection")
- .data(visibleConnections)
-
- // Enter any new connections at the parent's previous position.
- const connEnter = connectionsSel
- .enter()
- .insert("path", "g")
- .attr("class", "connection")
- .attr("d", (d) => {
- const xo = attrs.layoutBindings[attrs.layout].linkJoinX({ x: x0, y: y0, width, height });
- const yo = attrs.layoutBindings[attrs.layout].linkJoinY({ x: x0, y: y0, width, height });
- const o = { x: xo, y: yo };
- return attrs.layoutBindings[attrs.layout].diagonal(o, o);
- });
-
-
- // Get connections update selection
- const connUpdate = connEnter.merge(connectionsSel);
-
- // Styling connections
- connUpdate.attr("fill", "none")
-
- // Transition back to the parent element position
- connUpdate
- .transition()
- .duration(attrs.duration)
- .attr('d', (d) => {
- const xs = attrs.layoutBindings[attrs.layout].linkX({ x: d._source.x, y: d._source.y, width: d._source.width, height: d._source.height });
- const ys = attrs.layoutBindings[attrs.layout].linkY({ x: d._source.x, y: d._source.y, width: d._source.width, height: d._source.height });
- const xt = attrs.layoutBindings[attrs.layout].linkJoinX({ x: d._target.x, y: d._target.y, width: d._target.width, height: d._target.height });
- const yt = attrs.layoutBindings[attrs.layout].linkJoinY({ x: d._target.x, y: d._target.y, width: d._target.width, height: d._target.height });
- return attrs.linkGroupArc({ source: { x: xs, y: ys }, target: { x: xt, y: yt } })
- })
-
- // Allow external modifications
- connUpdate.each(attrs.connectionsUpdate);
-
- // Remove any links which is exiting after animation
- const connExit = connectionsSel
- .exit()
- .transition()
- .duration(attrs.duration)
- .attr('opacity', 0)
- .remove();
-
- // -------------------------- NODES ----------------------
- // Get nodes selection
- const nodesSelection = attrs.nodesWrapper
- .selectAll("g.node")
- .data(nodes, ({ data }) => attrs.nodeId(data));
-
- // Enter any new nodes at the parent's previous position.
- const nodeEnter = nodesSelection
- .enter()
- .append("g")
- .attr("class", "node")
- .attr("transform", (d) => {
- if (d == attrs.root) return `translate(${x0},${y0})`
- const xj = attrs.layoutBindings[attrs.layout].nodeJoinX({ x: x0, y: y0, width, height });
- const yj = attrs.layoutBindings[attrs.layout].nodeJoinY({ x: x0, y: y0, width, height });
- return `translate(${xj},${yj})`
- })
- .attr("cursor", "pointer")
- .on("click", (event, { data }) => {
- if ([...event.srcElement.classList].includes("node-button-foreign-object")) {
- return;
- }
- attrs.onNodeClick(attrs.nodeId(data));
- });
-
- // Add background rectangle for the nodes
- nodeEnter
- .patternify({
- tag: "rect",
- selector: "node-rect",
- data: (d) => [d]
- })
-
- // Node update styles
- const nodeUpdate = nodeEnter
- .merge(nodesSelection)
- .style("font", "12px sans-serif");
-
- // Add foreignObject element inside rectangle
- const fo = nodeUpdate.patternify({
- tag: "foreignObject",
- selector: "node-foreign-object",
- data: (d) => [d]
- })
- .style('overflow', 'visible')
-
- // Add foreign object
- fo.patternify({
- tag: "xhtml:div",
- selector: "node-foreign-object-div",
- data: (d) => [d]
- })
-
- this.restyleForeignObjectElements();
-
- // Add Node button circle's group (expand-collapse button)
- const nodeButtonGroups = nodeEnter
- .patternify({
- tag: "g",
- selector: "node-button-g",
- data: (d) => [d]
- })
- .on("click", (event, d) => this.onButtonClick(event, d));
-
- nodeButtonGroups.patternify({
- tag: 'rect',
- selector: 'node-button-rect',
- data: (d) => [d]
- })
- .attr('opacity', 0)
- .attr('pointer-events', 'all')
- .attr('width', 40)
- .attr('height', 40)
- .attr('x', -20)
- .attr('y', -20)
-
- // Add expand collapse button content
- const nodeFo = nodeButtonGroups
- .patternify({
- tag: "foreignObject",
- selector: "node-button-foreign-object",
- data: (d) => [d]
- })
- .attr('width', 40)
- .attr('height', 40)
- .attr('x', -20)
- .attr('y', -20)
- .style('overflow', 'visible')
- .patternify({
- tag: "xhtml:div",
- selector: "node-button-div",
- data: (d) => [d]
- })
- .style('pointer-events', 'none')
- .style('display', 'flex')
- .style('width', '100%')
- .style('height', '100%')
-
-
-
- // Transition to the proper position for the node
- nodeUpdate
- .transition()
- .attr("opacity", 0)
- .duration(attrs.duration)
- .attr("transform", ({ x, y, width, height }) => {
- return attrs.layoutBindings[attrs.layout].nodeUpdateTransform({ x, y, width, height });
-
- })
- .attr("opacity", 1);
-
- // Style node rectangles
- nodeUpdate
- .select(".node-rect")
- .attr("width", ({ width }) => width)
- .attr("height", ({ height }) => height)
- .attr("x", ({ width }) => 0)
- .attr("y", ({ height }) => 0)
- .attr("cursor", "pointer")
- .attr('rx', 3)
- .attr("fill", attrs.nodeDefaultBackground)
-
- // Move node button group to the desired position
- nodeUpdate
- .select(".node-button-g")
- .attr("transform", ({ data, width, height }) => {
- const x = attrs.layoutBindings[attrs.layout].buttonX({ width, height });
- const y = attrs.layoutBindings[attrs.layout].buttonY({ width, height });
- return `translate(${x},${y})`
- })
- .attr("display", ({ data }) => {
- return data._directSubordinates > 0 ? null : 'none';
- })
- .attr("opacity", ({ children, _children }) => {
- if (children || _children) {
- return 1;
- }
- return 0;
- });
-
- // Restyle node button circle
- nodeUpdate
- .select(".node-button-foreign-object .node-button-div")
- .html((node) => {
- return attrs.buttonContent({ node, state: attrs })
- })
-
- // Restyle button texts
- nodeUpdate
- .select(".node-button-text")
- .attr("text-anchor", "middle")
- .attr("alignment-baseline", "middle")
- .attr("fill", attrs.defaultTextFill)
- .attr("font-size", ({ children }) => {
- if (children) return 40;
- return 26;
- })
- .text(({ children }) => {
- if (children) return "-";
- return "+";
- })
- .attr("y", this.isEdge() ? 10 : 0);
-
- nodeUpdate.each(attrs.nodeUpdate)
-
- // Remove any exiting nodes after transition
- const nodeExitTransition = nodesSelection
- .exit()
- .attr("opacity", 1)
- .transition()
- .duration(attrs.duration)
- .attr("transform", (d) => {
- const ex = attrs.layoutBindings[attrs.layout].nodeJoinX({ x, y, width, height });
- const ey = attrs.layoutBindings[attrs.layout].nodeJoinY({ x, y, width, height });
- return `translate(${ex},${ey})`
- })
- .on("end", function () {
- d3.select(this).remove();
- })
- .attr("opacity", 0);
-
- // Store the old positions for transition.
- nodes.forEach((d) => {
- d.x0 = d.x;
- d.y0 = d.y;
+ const o = { x: xo, y: yo };
+ return attrs.layoutBindings[attrs.layout].diagonal(o, o);
+ })
+ .remove();
+
+ // -------------------------- CONNECTIONS ----------------------
+
+ const connectionsSel = attrs.connectionsWrapper
+ .selectAll('path.connection')
+ .data(visibleConnections);
+
+ // Enter any new connections at the parent's previous position.
+ const connEnter = connectionsSel
+ .enter()
+ .insert('path', 'g')
+ .attr('class', 'connection')
+ .attr('d', (d) => {
+ const xo = attrs.layoutBindings[attrs.layout].linkJoinX({
+ x: x0,
+ y: y0,
+ width,
+ height,
});
+ const yo = attrs.layoutBindings[attrs.layout].linkJoinY({
+ x: x0,
+ y: y0,
+ width,
+ height,
+ });
+ const o = { x: xo, y: yo };
+ return attrs.layoutBindings[attrs.layout].diagonal(o, o);
+ });
- // CHECK FOR CENTERING
- const centeredNode = attrs.allNodes.filter(d => d.data._centered)[0]
- if (centeredNode) {
- const centeredNodes = centeredNode.data._centeredWithDescendants ? centeredNode.descendants().filter((d, i) => i < 7) : [centeredNode]
- centeredNode.data._centeredWithDescendants = null;
- centeredNode.data._centered = null;
- this.fit({
- animate: true,
- scale: false,
- nodes: centeredNodes
- })
+ // Get connections update selection
+ const connUpdate = connEnter.merge(connectionsSel);
+
+ // Styling connections
+ connUpdate.attr('fill', 'none');
+
+ // Transition back to the parent element position
+ connUpdate
+ .transition()
+ .duration(attrs.duration)
+ .attr('d', (d) => {
+ const xs = attrs.layoutBindings[attrs.layout].linkX({
+ x: d._source.x,
+ y: d._source.y,
+ width: d._source.width,
+ height: d._source.height,
+ });
+ const ys = attrs.layoutBindings[attrs.layout].linkY({
+ x: d._source.x,
+ y: d._source.y,
+ width: d._source.width,
+ height: d._source.height,
+ });
+ const xt = attrs.layoutBindings[attrs.layout].linkJoinX({
+ x: d._target.x,
+ y: d._target.y,
+ width: d._target.width,
+ height: d._target.height,
+ });
+ const yt = attrs.layoutBindings[attrs.layout].linkJoinY({
+ x: d._target.x,
+ y: d._target.y,
+ width: d._target.width,
+ height: d._target.height,
+ });
+ return attrs.linkGroupArc({
+ source: { x: xs, y: ys },
+ target: { x: xt, y: yt },
+ });
+ });
+
+ // Allow external modifications
+ connUpdate.each(attrs.connectionsUpdate);
+
+ // Remove any links which is exiting after animation
+ const connExit = connectionsSel
+ .exit()
+ .transition()
+ .duration(attrs.duration)
+ .attr('opacity', 0)
+ .remove();
+
+ // -------------------------- NODES ----------------------
+ // Get nodes selection
+ const nodesSelection = attrs.nodesWrapper
+ .selectAll('g.node')
+ .data(nodes, ({ data }) => attrs.nodeId(data));
+
+ // Enter any new nodes at the parent's previous position.
+ const nodeEnter = nodesSelection
+ .enter()
+ .append('g')
+ .attr('class', 'node')
+ .attr('transform', (d) => {
+ if (d == attrs.root) return `translate(${x0},${y0})`;
+ const xj = attrs.layoutBindings[attrs.layout].nodeJoinX({
+ x: x0,
+ y: y0,
+ width,
+ height,
+ });
+ const yj = attrs.layoutBindings[attrs.layout].nodeJoinY({
+ x: x0,
+ y: y0,
+ width,
+ height,
+ });
+ return `translate(${xj},${yj})`;
+ })
+ .attr('cursor', 'pointer')
+ .on('click', (event, { data }) => {
+ if (
+ [...event.srcElement.classList].includes('node-button-foreign-object')
+ ) {
+ return;
}
+ attrs.onNodeClick(attrs.nodeId(data));
+ });
+ // Add background rectangle for the nodes
+ nodeEnter.patternify({
+ tag: 'rect',
+ selector: 'node-rect',
+ data: (d) => [d],
+ });
+
+ // Node update styles
+ const nodeUpdate = nodeEnter
+ .merge(nodesSelection)
+ .style('font', '12px sans-serif');
+
+ // Add foreignObject element inside rectangle
+ const fo = nodeUpdate
+ .patternify({
+ tag: 'foreignObject',
+ selector: 'node-foreign-object',
+ data: (d) => [d],
+ })
+ .style('overflow', 'visible');
+
+ // Add foreign object
+ fo.patternify({
+ tag: 'xhtml:div',
+ selector: 'node-foreign-object-div',
+ data: (d) => [d],
+ });
+
+ this.restyleForeignObjectElements();
+
+ // Add Node button circle's group (expand-collapse button)
+ const nodeButtonGroups = nodeEnter
+ .patternify({
+ tag: 'g',
+ selector: 'node-button-g',
+ data: (d) => [d],
+ })
+ .on('click', (event, d) => this.onButtonClick(event, d));
+
+ nodeButtonGroups
+ .patternify({
+ tag: 'rect',
+ selector: 'node-button-rect',
+ data: (d) => [d],
+ })
+ .attr('opacity', 0)
+ .attr('pointer-events', 'all')
+ .attr('width', 40)
+ .attr('height', 40)
+ .attr('x', -20)
+ .attr('y', -20);
+
+ // Add expand collapse button content
+ const nodeFo = nodeButtonGroups
+ .patternify({
+ tag: 'foreignObject',
+ selector: 'node-button-foreign-object',
+ data: (d) => [d],
+ })
+ .attr('width', 40)
+ .attr('height', 40)
+ .attr('x', -20)
+ .attr('y', -20)
+ .style('overflow', 'visible')
+ .patternify({
+ tag: 'xhtml:div',
+ selector: 'node-button-div',
+ data: (d) => [d],
+ })
+ .style('pointer-events', 'none')
+ .style('display', 'flex')
+ .style('width', '100%')
+ .style('height', '100%');
+
+ // Transition to the proper position for the node
+ nodeUpdate
+ .transition()
+ .attr('opacity', 0)
+ .duration(attrs.duration)
+ .attr('transform', ({ x, y, width, height }) => {
+ return attrs.layoutBindings[attrs.layout].nodeUpdateTransform({
+ x,
+ y,
+ width,
+ height,
+ });
+ })
+ .attr('opacity', 1);
+
+ // Style node rectangles
+ nodeUpdate
+ .select('.node-rect')
+ .attr('width', ({ width }) => width)
+ .attr('height', ({ height }) => height)
+ .attr('x', ({ width }) => 0)
+ .attr('y', ({ height }) => 0)
+ .attr('cursor', 'pointer')
+ .attr('rx', 3)
+ .attr('fill', attrs.nodeDefaultBackground);
+
+ // Move node button group to the desired position
+ nodeUpdate
+ .select('.node-button-g')
+ .attr('transform', ({ data, width, height }) => {
+ const x = attrs.layoutBindings[attrs.layout].buttonX({ width, height });
+ const y = attrs.layoutBindings[attrs.layout].buttonY({ width, height });
+ return `translate(${x},${y})`;
+ })
+ .attr('display', ({ data }) => {
+ return data._directSubordinates > 0 ? null : 'none';
+ })
+ .attr('opacity', ({ children, _children }) => {
+ if (children || _children) {
+ return 1;
+ }
+ return 0;
+ });
+
+ // Restyle node button circle
+ nodeUpdate
+ .select('.node-button-foreign-object .node-button-div')
+ .html((node) => {
+ return attrs.buttonContent({ node, state: attrs });
+ });
+
+ // Restyle button texts
+ nodeUpdate
+ .select('.node-button-text')
+ .attr('text-anchor', 'middle')
+ .attr('alignment-baseline', 'middle')
+ .attr('fill', attrs.defaultTextFill)
+ .attr('font-size', ({ children }) => {
+ if (children) return 40;
+ return 26;
+ })
+ .text(({ children }) => {
+ if (children) return '-';
+ return '+';
+ })
+ .attr('y', this.isEdge() ? 10 : 0);
+
+ nodeUpdate.each(attrs.nodeUpdate);
+
+ // Remove any exiting nodes after transition
+ const nodeExitTransition = nodesSelection
+ .exit()
+ .attr('opacity', 1)
+ .transition()
+ .duration(attrs.duration)
+ .attr('transform', (d) => {
+ const ex = attrs.layoutBindings[attrs.layout].nodeJoinX({
+ x,
+ y,
+ width,
+ height,
+ });
+ const ey = attrs.layoutBindings[attrs.layout].nodeJoinY({
+ x,
+ y,
+ width,
+ height,
+ });
+ return `translate(${ex},${ey})`;
+ })
+ .on('end', function () {
+ d3.select(this).remove();
+ })
+ .attr('opacity', 0);
+
+ // Store the old positions for transition.
+ nodes.forEach((d) => {
+ d.x0 = d.x;
+ d.y0 = d.y;
+ });
+
+ // CHECK FOR CENTERING
+
+ // const centeredNode = attrs.allNodes.filter(d=> d.data._centered === null)[0];
+ const centeredNode = attrs.allNodes.filter((d) => d.data._centered)[0];
+ if (centeredNode) {
+ const centeredNodes = centeredNode.data._centeredWithDescendants
+ ? centeredNode.descendants().filter((d, i) => i < 7)
+ : [centeredNode];
+ centeredNode.data._centeredWithDescendants = null;
+ centeredNode.data._centered = null;
+ // if (type == 'buttonClick') {
+ // this.fit({
+ // animate: true,
+ // scale: false,
+ // nodes: centeredNodes,
+ // transform: false,
+ // });
+ // } else {
+ // this.fit();
+ // }
+ console.log(44444, centeredNodes);
+ this.fit({
+ animate: true,
+ scale: false,
+ nodes: centeredNodes,
+ transform: false,
+ });
}
+ }
- // This function detects whether current browser is edge
- isEdge() {
- return window.navigator.userAgent.includes("Edge");
- }
+ // This function detects whether current browser is edge
+ isEdge() {
+ return window.navigator.userAgent.includes('Edge');
+ }
- // Generate horizontal diagonal - play with it here - https://observablehq.com/@bumbeishvili/curved-edges-horizontal-d3-v3-v4-v5-v6
- hdiagonal(s, t, m) {
- // Define source and target x,y coordinates
- const x = s.x;
- const y = s.y;
- const ex = t.x;
- const ey = t.y;
+ // Generate horizontal diagonal - play with it here - https://observablehq.com/@bumbeishvili/curved-edges-horizontal-d3-v3-v4-v5-v6
+ hdiagonal(s, t, m) {
+ // Define source and target x,y coordinates
+ const x = s.x;
+ const y = s.y;
+ const ex = t.x;
+ const ey = t.y;
- let mx = m && m.x || x;
- let my = m && m.y || y;
+ let mx = (m && m.x) || x;
+ let my = (m && m.y) || y;
- // Values in case of top reversed and left reversed diagonals
- let xrvs = ex - x < 0 ? -1 : 1;
- let yrvs = ey - y < 0 ? -1 : 1;
+ // Values in case of top reversed and left reversed diagonals
+ let xrvs = ex - x < 0 ? -1 : 1;
+ let yrvs = ey - y < 0 ? -1 : 1;
- // Define preferred curve radius
- let rdef = 35;
+ // Define preferred curve radius
+ let rdef = 35;
- // Reduce curve radius, if source-target x space is smaller
- let r = Math.abs(ex - x) / 2 < rdef ? Math.abs(ex - x) / 2 : rdef;
+ // Reduce curve radius, if source-target x space is smaller
+ let r = Math.abs(ex - x) / 2 < rdef ? Math.abs(ex - x) / 2 : rdef;
- // Further reduce curve radius, is y space is more small
- r = Math.abs(ey - y) / 2 < r ? Math.abs(ey - y) / 2 : r;
+ // Further reduce curve radius, is y space is more small
+ r = Math.abs(ey - y) / 2 < r ? Math.abs(ey - y) / 2 : r;
- // Defin width and height of link, excluding radius
- let h = Math.abs(ey - y) / 2 - r;
- let w = Math.abs(ex - x) / 2 - r;
+ // Defin width and height of link, excluding radius
+ let h = Math.abs(ey - y) / 2 - r;
+ let w = Math.abs(ex - x) / 2 - r;
- // Build and return custom arc command
- return `
+ // Build and return custom arc command
+ return `
M ${mx} ${my}
L ${mx} ${y}
L ${x} ${y}
L ${x + w * xrvs} ${y}
- C ${x + w * xrvs + r * xrvs} ${y}
- ${x + w * xrvs + r * xrvs} ${y}
+ C ${x + w * xrvs + r * xrvs} ${y}
+ ${x + w * xrvs + r * xrvs} ${y}
${x + w * xrvs + r * xrvs} ${y + r * yrvs}
- L ${x + w * xrvs + r * xrvs} ${ey - r * yrvs}
- C ${x + w * xrvs + r * xrvs} ${ey}
- ${x + w * xrvs + r * xrvs} ${ey}
+ L ${x + w * xrvs + r * xrvs} ${ey - r * yrvs}
+ C ${x + w * xrvs + r * xrvs} ${ey}
+ ${x + w * xrvs + r * xrvs} ${ey}
${ex - w * xrvs} ${ey}
L ${ex} ${ey}
`;
- }
+ }
- // Generate custom diagonal - play with it here - https://observablehq.com/@bumbeishvili/curved-edges
- diagonal(s, t, m) {
- const x = s.x;
- const y = s.y;
- const ex = t.x;
- const ey = t.y;
+ // Generate custom diagonal - play with it here - https://observablehq.com/@bumbeishvili/curved-edges
+ diagonal(s, t, m) {
+ const x = s.x;
+ const y = s.y;
+ const ex = t.x;
+ const ey = t.y;
- let mx = m && m.x || x;
- let my = m && m.y || y;
+ let mx = (m && m.x) || x;
+ let my = (m && m.y) || y;
- let xrvs = ex - x < 0 ? -1 : 1;
- let yrvs = ey - y < 0 ? -1 : 1;
+ let xrvs = ex - x < 0 ? -1 : 1;
+ let yrvs = ey - y < 0 ? -1 : 1;
- let rdef = 35;
- let r = Math.abs(ex - x) / 2 < rdef ? Math.abs(ex - x) / 2 : rdef;
+ let rdef = 35;
+ let r = Math.abs(ex - x) / 2 < rdef ? Math.abs(ex - x) / 2 : rdef;
- r = Math.abs(ey - y) / 2 < r ? Math.abs(ey - y) / 2 : r;
+ r = Math.abs(ey - y) / 2 < r ? Math.abs(ey - y) / 2 : r;
- let h = Math.abs(ey - y) / 2 - r;
- let w = Math.abs(ex - x) - r * 2;
- //w=0;
- const path = `
+ let h = Math.abs(ey - y) / 2 - r;
+ let w = Math.abs(ex - x) - r * 2;
+ //w=0;
+ const path = `
M ${mx} ${my}
L ${x} ${my}
L ${x} ${y}
L ${x} ${y + h * yrvs}
- C ${x} ${y + h * yrvs + r * yrvs} ${x} ${y + h * yrvs + r * yrvs
- } ${x + r * xrvs} ${y + h * yrvs + r * yrvs}
+ C ${x} ${y + h * yrvs + r * yrvs} ${x} ${
+ y + h * yrvs + r * yrvs
+ } ${x + r * xrvs} ${y + h * yrvs + r * yrvs}
L ${x + w * xrvs + r * xrvs} ${y + h * yrvs + r * yrvs}
- C ${ex} ${y + h * yrvs + r * yrvs} ${ex} ${y + h * yrvs + r * yrvs
- } ${ex} ${ey - h * yrvs}
+ C ${ex} ${y + h * yrvs + r * yrvs} ${ex} ${
+ y + h * yrvs + r * yrvs
+ } ${ex} ${ey - h * yrvs}
L ${ex} ${ey}
`;
- return path;
+ return path;
+ }
+
+ restyleForeignObjectElements() {
+ const attrs = this.getChartState();
+
+ attrs.svg
+ .selectAll('.node-foreign-object')
+ .attr('width', ({ width }) => width)
+ .attr('height', ({ height }) => height)
+ .attr('x', ({ width }) => 0)
+ .attr('y', ({ height }) => 0);
+ attrs.svg
+ .selectAll('.node-foreign-object-div')
+ .style('width', ({ width }) => `${width}px`)
+ .style('height', ({ height }) => `${height}px`)
+ .html(function (d, i, arr) {
+ return d.id ? attrs.nodeContent.bind(this)(d, i, arr, attrs) : '';
+ });
+ }
+
+ // Toggle children on click.
+ onButtonClick(event, d) {
+ const attrs = this.getChartState();
+ if (attrs.setActiveNodeCentered) {
+ d.data._centered = true;
+ d.data._centeredWithDescendants = true;
}
- restyleForeignObjectElements() {
- const attrs = this.getChartState();
+ // If childrens are expanded
+ if (d.children) {
+ //Collapse them
+ d._children = d.children;
+ d.children = null;
- attrs.svg
- .selectAll(".node-foreign-object")
- .attr("width", ({ width }) => width)
- .attr("height", ({ height }) => height)
- .attr("x", ({ width }) => 0)
- .attr("y", ({ height }) => 0);
- attrs.svg
- .selectAll(".node-foreign-object-div")
- .style("width", ({ width }) => `${width}px`)
- .style("height", ({ height }) => `${height}px`)
- .html(function (d, i, arr) { return attrs.nodeContent.bind(this)(d, i, arr, attrs) })
+ // Set descendants expanded property to false
+ this.setExpansionFlagToChildren(d, false);
+ } else {
+ // Expand children
+ d.children = d._children;
+ d._children = null;
+
+ // Set each children as expanded
+ if (d.children) {
+ d.children.forEach(({ data }) => (data._expanded = true));
+ }
}
- // Toggle children on click.
- onButtonClick(event, d) {
- const attrs = this.getChartState();
- if (attrs.setActiveNodeCentered) {
- d.data._centered = true;
- d.data._centeredWithDescendants = true;
- }
+ // Redraw Graph
+ this.update(d, 'buttonClick');
+ }
- // If childrens are expanded
- if (d.children) {
- //Collapse them
- d._children = d.children;
- d.children = null;
+ // This function changes `expanded` property to descendants
+ setExpansionFlagToChildren({ data, children, _children }, flag) {
+ // Set flag to the current property
+ data._expanded = flag;
- // Set descendants expanded property to false
- this.setExpansionFlagToChildren(d, false);
- } else {
- // Expand children
- d.children = d._children;
- d._children = null;
-
- // Set each children as expanded
- if (d.children) {
- d.children.forEach(({ data }) => (data._expanded = true));
- }
- }
-
- // Redraw Graph
- this.update(d);
+ // Loop over and recursively update expanded children's descendants
+ if (children) {
+ children.forEach((d) => {
+ this.setExpansionFlagToChildren(d, flag);
+ });
}
- // This function changes `expanded` property to descendants
- setExpansionFlagToChildren({ data, children, _children }, flag) {
- // Set flag to the current property
- data._expanded = flag;
+ // Loop over and recursively update collapsed children's descendants
+ if (_children) {
+ _children.forEach((d) => {
+ this.setExpansionFlagToChildren(d, flag);
+ });
+ }
+ }
- // Loop over and recursively update expanded children's descendants
- if (children) {
- children.forEach((d) => {
- this.setExpansionFlagToChildren(d, flag);
- });
+ // Method which only expands nodes, which have property set "expanded=true"
+ expandSomeNodes(d) {
+ // If node has expanded property set
+ if (d.data._expanded) {
+ // Retrieve node's parent
+ let parent = d.parent;
+
+ // While we can go up
+ while (parent) {
+ // Expand all current parent's children
+ if (parent._children) {
+ parent.children = parent._children;
}
- // Loop over and recursively update collapsed children's descendants
- if (_children) {
- _children.forEach((d) => {
- this.setExpansionFlagToChildren(d, flag);
- });
- }
+ // Replace current parent holding object
+ parent = parent.parent;
+ }
}
-
- // Method which only expands nodes, which have property set "expanded=true"
- expandSomeNodes(d) {
- // If node has expanded property set
- if (d.data._expanded) {
- // Retrieve node's parent
- let parent = d.parent;
-
- // While we can go up
- while (parent) {
- // Expand all current parent's children
- if (parent._children) {
- parent.children = parent._children;
- }
-
- // Replace current parent holding object
- parent = parent.parent;
- }
- }
-
- // Recursivelly do the same for collapsed nodes
- if (d._children) {
- d._children.forEach((ch) => this.expandSomeNodes(ch));
- }
-
- // Recursivelly do the same for expanded nodes
- if (d.children) {
- d.children.forEach((ch) => this.expandSomeNodes(ch));
- }
+ // Recursivelly do the same for collapsed nodes
+ if (d._children) {
+ d._children.forEach((ch) => this.expandSomeNodes(ch));
}
- // This function updates nodes state and redraws graph, usually after data change
- updateNodesState() {
- const attrs = this.getChartState();
-
- this.setLayouts({ expandNodesFirst: true });
-
- // Redraw Graphs
- this.update(attrs.root);
+ // Recursivelly do the same for expanded nodes
+ if (d.children) {
+ d.children.forEach((ch) => this.expandSomeNodes(ch));
}
+ }
- setLayouts({ expandNodesFirst = true }) {
- const attrs = this.getChartState();
- // Store new root by converting flat data to hierarchy
- attrs.root = d3
- .stratify()
- .id((d) => attrs.nodeId(d))
- .parentId(d => attrs.parentNodeId(d))(attrs.data);
+ // This function updates nodes state and redraws graph, usually after data change
+ updateNodesState() {
+ const attrs = this.getChartState();
- attrs.root.each((node, i, arr) => {
- let width = attrs.nodeWidth(node);
- let height = attrs.nodeHeight(node);
- Object.assign(node, { width, height })
- })
+ this.setLayouts({ expandNodesFirst: true });
- // Store positions, where children appear during their enter animation
- attrs.root.x0 = 0;
- attrs.root.y0 = 0;
- attrs.allNodes = attrs.root.descendants();
+ // Redraw Graphs
+ this.update(attrs.root);
+ }
- // Store direct and total descendants count
- attrs.allNodes.forEach((d) => {
- Object.assign(d.data, {
- _directSubordinates: d.children ? d.children.length : 0,
- _totalSubordinates: d.descendants().length - 1
- });
+ setLayouts({ expandNodesFirst = true }) {
+ const attrs = this.getChartState();
+ // Store new root by converting flat data to hierarchy
+ attrs.root = d3
+ .stratify()
+ .id((d) => attrs.nodeId(d))
+ .parentId((d) => attrs.parentNodeId(d))(attrs.data);
+
+ attrs.root.each((node, i, arr) => {
+ let width = attrs.nodeWidth(node);
+ let height = attrs.nodeHeight(node);
+ Object.assign(node, { width, height });
+ });
+
+ // Store positions, where children appear during their enter animation
+ attrs.root.x0 = 0;
+ attrs.root.y0 = 0;
+ attrs.allNodes = attrs.root.descendants();
+
+ // Store direct and total descendants count
+ attrs.allNodes.forEach((d) => {
+ Object.assign(d.data, {
+ _directSubordinates: d.children ? d.children.length : 0,
+ _totalSubordinates: d.descendants().length - 1,
+ });
+ });
+
+ if (attrs.root.children) {
+ if (expandNodesFirst) {
+ // Expand all nodes first
+ attrs.root.children.forEach(this.expand);
+ attrs.root.children.forEach((item) => {
+ item?.children?.forEach(this.expand);
});
+ }
+ // Then collapse them all
+ attrs.root.children.forEach((d) => this.collapse(d));
- if (attrs.root.children) {
- if (expandNodesFirst) {
- // Expand all nodes first
- attrs.root.children.forEach(this.expand)
- attrs.root.children.forEach(item => {
- item.children.forEach(this.expand)
- })
- }
- // Then collapse them all
- attrs.root.children.forEach((d) => this.collapse(d));
+ // Collapse root if level is 0
+ if (attrs.expandLevel == 0) {
+ attrs.root._children = attrs.root.children;
+ attrs.root.children = null;
+ }
- // Collapse root if level is 0
- if (attrs.expandLevel == 0) {
- attrs.root._children = attrs.root.children;
- attrs.root.children = null;
- }
-
- // Then only expand nodes, which have expanded proprty set to true
- [attrs.root].forEach((ch) => this.expandSomeNodes(ch));
- }
+ // Then only expand nodes, which have expanded proprty set to true
+ [attrs.root].forEach((ch) => this.expandSomeNodes(ch));
}
+ }
- // Function which collapses passed node and it's descendants
- collapse(d) {
- if (d.children) {
- d._children = d.children;
- d._children.forEach((ch) => this.collapse(ch));
- d.children = null;
- }
+ // Function which collapses passed node and it's descendants
+ collapse(d) {
+ if (d.children) {
+ d._children = d.children;
+ d._children.forEach((ch) => this.collapse(ch));
+ d.children = null;
}
+ }
- // Function which expands passed node and it's descendants
- expand(d) {
- if (d._children) {
- d.children = d._children;
- d.children.forEach((ch) => this.expand(ch));
- d._children = null;
- }
+ // Function which expands passed node and it's descendants
+ expand(d) {
+ if (d._children) {
+ d.children = d._children;
+ d.children.forEach((ch) => this.expand(ch));
+ d._children = null;
}
+ }
- // Zoom handler function
- zoomed(event, d) {
- const attrs = this.getChartState();
- const chart = attrs.chart;
+ // Zoom handler function
+ zoomed(event, d) {
+ const attrs = this.getChartState();
+ const chart = attrs.chart;
- // Get d3 event's transform object
- const transform = event.transform;
+ // Get d3 event's transform object
+ const transform = event.transform;
- // Store it
- attrs.lastTransform = transform;
+ // Store it
+ attrs.lastTransform = transform;
- // Reposition and rescale chart accordingly
- chart.attr("transform", transform);
+ // Reposition and rescale chart accordingly
+ chart.attr('transform', transform);
- // Apply new styles to the foreign object element
- if (this.isEdge()) {
- this.restyleForeignObjectElements();
- }
+ // Apply new styles to the foreign object element
+ if (this.isEdge()) {
+ this.restyleForeignObjectElements();
}
+ }
- zoomTreeBounds({ x0, x1, y0, y1, params = { animate: true, scale: true } }) {
- const { centerG, svgWidth: w, svgHeight: h, svg, zoomBehavior, duration, lastTransform } = this.getChartState()
- let scaleVal = Math.min(8, 0.9 / Math.max((x1 - x0) / w, (y1 - y0) / h));
- let identity = d3.zoomIdentity.translate(w / 2, h / 2)
- identity = identity.scale(params.scale ? scaleVal : lastTransform.k)
+ zoomTreeBounds({ x0, x1, y0, y1, params = { animate: true, scale: true } }) {
+ const {
+ centerG,
+ svgWidth: w,
+ svgHeight: h,
+ svg,
+ zoomBehavior,
+ duration,
+ lastTransform,
+ } = this.getChartState();
+ let scaleVal = Math.min(8, 0.9 / Math.max((x1 - x0) / w, (y1 - y0) / h));
+ let identity = d3.zoomIdentity.translate(w / 2, h / 2);
+ identity = identity.scale(params.scale ? scaleVal : lastTransform.k);
- identity = identity.translate(-(x0 + x1) / 2, -(y0 + y1) / 2);
- // Transition zoom wrapper component into specified bounds
- svg.transition().duration(params.animate ? duration : 0).call(zoomBehavior.transform, identity);
- centerG.transition().duration(params.animate ? duration : 0).attr('transform', 'translate(0,0)')
+ identity = identity.translate(-(x0 + x1) / 2, -(y0 + y1) / 2);
+ // Transition zoom wrapper component into specified bounds
+ svg
+ .transition()
+ .duration(params.animate ? duration : 0)
+ .call(zoomBehavior.transform, identity);
+ centerG
+ .transition()
+ .duration(params.animate ? duration : 0)
+ .attr('transform', 'translate(0,0)');
+ }
+
+ fit({ animate = true, nodes, scale = true, transform = true } = {}) {
+ const attrs = this.getChartState();
+ const { root } = attrs;
+ let descendants = nodes ? nodes : root.descendants();
+ console.log(11111111111, nodes, descendants, scale);
+ const minX = d3.min(
+ descendants,
+ (d) => d.x + attrs.layoutBindings[attrs.layout].nodeLeftX(d),
+ );
+ const maxX = d3.max(
+ descendants,
+ (d) => d.x + attrs.layoutBindings[attrs.layout].nodeRightX(d),
+ );
+ const minY = d3.min(
+ descendants,
+ (d) => d.y + attrs.layoutBindings[attrs.layout].nodeTopY(d),
+ );
+ const maxY = d3.max(
+ descendants,
+ (d) => d.y + attrs.layoutBindings[attrs.layout].nodeBottomY(d),
+ );
+ console.log('333,minY', minX, maxX, minY, maxY);
+ this.zoomTreeBounds({
+ params: { animate: animate, scale },
+ x0: transform == 'export' ? minX - 700 : minX - 50,
+ x1: transform == 'export' ? maxX + 0 : maxX - 50,
+ y0: transform == false ? minY - 250 : minY - 0,
+ y1: transform == false ? maxY + 350 : maxY + 250,
+ });
+ return this;
+ }
+
+ // This function can be invoked via chart.setExpanded API, it expands or collapses particular node
+ setExpanded(id, expandedFlag = true) {
+ const attrs = this.getChartState();
+ // Retrieve node by node Id
+ const node = attrs.allNodes.filter(
+ ({ data }) => attrs.nodeId(data) == id,
+ )[0];
+
+ if (!node) {
+ console.log(
+ `ORG CHART - ${
+ expandedFlag ? 'EXPAND' : 'COLLAPSE'
+ } - Node with id (${id}) not found in the tree`,
+ );
+ return this;
}
+ node.data._expanded = expandedFlag;
+ return this;
+ }
- fit({ animate = true, nodes, scale = true } = {}) {
- const attrs = this.getChartState();
- const { root } = attrs;
- let descendants = nodes ? nodes : root.descendants();
- const minX = d3.min(descendants, d => d.x + attrs.layoutBindings[attrs.layout].nodeLeftX(d))
- const maxX = d3.max(descendants, d => d.x + attrs.layoutBindings[attrs.layout].nodeRightX(d))
- const minY = d3.min(descendants, d => d.y + attrs.layoutBindings[attrs.layout].nodeTopY(d))
- const maxY = d3.max(descendants, d => d.y + attrs.layoutBindings[attrs.layout].nodeBottomY(d))
-
- this.zoomTreeBounds({
- params: { animate: animate, scale },
- x0: minX - 50,
- x1: maxX + 50,
- y0: minY - 50,
- y1: maxY + 50,
- });
- return this;
+ setCentered(nodeId) {
+ const attrs = this.getChartState();
+ // this.setExpanded(nodeId)
+ const node = attrs.allNodes?.filter(
+ (d) => attrs.nodeId(d.data) === nodeId,
+ )[0];
+ if (!node) {
+ console.log(
+ `ORG CHART - CENTER - Node with id (${nodeId}) not found in the tree`,
+ );
+ return this;
}
+ node.data._centered = true;
+ node.data._expanded = true;
+ return this;
+ }
- // This function can be invoked via chart.setExpanded API, it expands or collapses particular node
- setExpanded(id, expandedFlag = true) {
-
- const attrs = this.getChartState();
- // Retrieve node by node Id
- const node = attrs.allNodes.filter(({ data }) => attrs.nodeId(data) == id)[0];
-
- if (!node) {
- console.log(`ORG CHART - ${expandedFlag ? "EXPAND" : "COLLAPSE"} - Node with id (${id}) not found in the tree`)
- return this;
- }
- node.data._expanded = expandedFlag;
- return this;
+ setHighlighted(nodeId) {
+ const attrs = this.getChartState();
+ const node = attrs.allNodes.filter(
+ (d) => attrs.nodeId(d.data) === nodeId,
+ )[0];
+ if (!node) {
+ console.log(
+ `ORG CHART - HIGHLIGHT - Node with id (${nodeId}) not found in the tree`,
+ );
+ return this;
}
+ node.data._highlighted = true;
+ node.data._expanded = true;
+ node.data._centered = true;
+ return this;
+ }
- setCentered(nodeId) {
- const attrs = this.getChartState();
- // this.setExpanded(nodeId)
- const node = attrs.allNodes.filter(d => attrs.nodeId(d.data) === nodeId)[0];
- if (!node) {
- console.log(`ORG CHART - CENTER - Node with id (${nodeId}) not found in the tree`)
- return this;
- }
- node.data._centered = true;
- node.data._expanded = true;
- return this;
+ setUpToTheRootHighlighted(nodeId) {
+ const attrs = this.getChartState();
+ const node = attrs.allNodes.filter(
+ (d) => attrs.nodeId(d.data) === nodeId,
+ )[0];
+ if (!node) {
+ console.log(
+ `ORG CHART - HIGHLIGHTROOT - Node with id (${nodeId}) not found in the tree`,
+ );
+ return this;
}
+ node.data._upToTheRootHighlighted = true;
+ node.data._expanded = true;
+ node.ancestors().forEach((d) => (d.data._upToTheRootHighlighted = true));
+ return this;
+ }
- setHighlighted(nodeId) {
- const attrs = this.getChartState();
- const node = attrs.allNodes.filter(d => attrs.nodeId(d.data) === nodeId)[0];
- if (!node) {
- console.log(`ORG CHART - HIGHLIGHT - Node with id (${nodeId}) not found in the tree`);
- return this
- }
- node.data._highlighted = true;
- node.data._expanded = true;
- node.data._centered = true;
- return this;
+ clearHighlighting() {
+ const attrs = this.getChartState();
+ attrs.allNodes.forEach((d) => {
+ d.data._highlighted = false;
+ d.data._upToTheRootHighlighted = false;
+ });
+ this.update(attrs.root);
+ }
+
+ // It can take selector which would go fullscreen
+ fullscreen(elem) {
+ const attrs = this.getChartState();
+ const el = d3.select(elem || attrs.container).node();
+
+ d3.select(document).on('fullscreenchange.' + attrs.id, function (d) {
+ const fsElement =
+ document.fullscreenElement ||
+ document.mozFullscreenElement ||
+ document.webkitFullscreenElement;
+ if (fsElement == el) {
+ setTimeout((d) => {
+ attrs.svg.attr('height', window.innerHeight - 40);
+ }, 500);
+ } else {
+ attrs.svg.attr('height', attrs.svgHeight);
+ }
+ });
+
+ if (el.requestFullscreen) {
+ el.requestFullscreen();
+ } else if (el.mozRequestFullScreen) {
+ el.mozRequestFullScreen();
+ } else if (el.webkitRequestFullscreen) {
+ el.webkitRequestFullscreen();
+ } else if (el.msRequestFullscreen) {
+ el.msRequestFullscreen();
}
+ }
- setUpToTheRootHighlighted(nodeId) {
- const attrs = this.getChartState();
- const node = attrs.allNodes.filter(d => attrs.nodeId(d.data) === nodeId)[0];
- if (!node) {
- console.log(`ORG CHART - HIGHLIGHTROOT - Node with id (${nodeId}) not found in the tree`)
- return this;
- }
- node.data._upToTheRootHighlighted = true;
- node.data._expanded = true;
- node.ancestors().forEach(d => d.data._upToTheRootHighlighted = true)
- return this;
- }
+ // Zoom in exposed method
+ zoomIn() {
+ const { svg, zoomBehavior } = this.getChartState();
+ svg.transition().call(zoomBehavior.scaleBy, 1.3);
+ }
- clearHighlighting() {
- const attrs = this.getChartState();
- attrs.allNodes.forEach(d => {
- d.data._highlighted = false;
- d.data._upToTheRootHighlighted = false;
- })
- this.update(attrs.root)
- }
+ // Zoom out exposed method
+ zoomOut() {
+ const { svg, zoomBehavior } = this.getChartState();
+ svg.transition().call(zoomBehavior.scaleBy, 0.78);
+ }
- // It can take selector which would go fullscreen
- fullscreen(elem) {
- const attrs = this.getChartState();
- const el = d3.select(elem || attrs.container).node();
+ toDataURL(url, callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function () {
+ var reader = new FileReader();
+ reader.onloadend = function () {
+ callback(reader.result);
+ };
+ reader.readAsDataURL(xhr.response);
+ };
+ xhr.open('GET', url);
+ xhr.responseType = 'blob';
+ xhr.send();
+ }
- d3.select(document).on('fullscreenchange.' + attrs.id, function (d) {
- const fsElement = document.fullscreenElement || document.mozFullscreenElement || document.webkitFullscreenElement;
- if (fsElement == el) {
- setTimeout(d => {
- attrs.svg.attr('height', window.innerHeight - 40);
- }, 500)
- } else {
- attrs.svg.attr('height', attrs.svgHeight)
- }
- })
+ exportImg({ full = false, scale = 12, onLoad = (d) => d, save = true } = {}) {
+ const that = this;
+ const attrs = this.getChartState();
+ const { svg: svgImg, root } = attrs;
+ let count = 0;
+ const selection = svgImg.selectAll('img');
+ let total = selection.size();
- if (el.requestFullscreen) {
- el.requestFullscreen();
- } else if (el.mozRequestFullScreen) {
- el.mozRequestFullScreen();
- } else if (el.webkitRequestFullscreen) {
- el.webkitRequestFullscreen();
- } else if (el.msRequestFullscreen) {
- el.msRequestFullscreen();
- }
- }
+ const exportImage = () => {
+ const transform = JSON.parse(JSON.stringify(that.lastTransform()));
+ const duration = that.duration();
+ if (full) {
+ that.fit({ transform: 'export' });
+ }
+ const { svg } = that.getChartState();
- // Zoom in exposed method
- zoomIn() {
- const { svg, zoomBehavior } = this.getChartState();
- svg.transition().call(zoomBehavior.scaleBy, 1.3);
- }
+ setTimeout(
+ (d) => {
+ that.downloadImage({
+ node: svg.node(),
+ scale,
+ isSvg: false,
+ onAlreadySerialized: (d) => {
+ that.update(root);
+ },
+ onLoad: onLoad,
+ save,
+ });
+ },
+ full ? duration + 10 : 0,
+ );
+ };
- // Zoom out exposed method
- zoomOut() {
- const { svg, zoomBehavior } = this.getChartState();
- svg.transition().call(zoomBehavior.scaleBy, 0.78);
- }
-
- toDataURL(url, callback) {
- var xhr = new XMLHttpRequest();
- xhr.onload = function () {
- var reader = new FileReader();
- reader.onloadend = function () {
- callback(reader.result);
- }
- reader.readAsDataURL(xhr.response);
- };
- xhr.open('GET', url);
- xhr.responseType = 'blob';
- xhr.send();
- }
-
- exportImg({ full = false, scale = 3, onLoad = d => d, save = true } = {}) {
- const that = this;
- const attrs = this.getChartState();
- const { svg: svgImg, root } = attrs
- let count = 0;
- const selection = svgImg.selectAll('img')
- let total = selection.size()
-
- const exportImage = () => {
- const transform = JSON.parse(JSON.stringify(that.lastTransform()));
- const duration = that.duration();
- if (full) {
- that.fit();
- }
- const { svg } = that.getChartState()
-
- setTimeout(d => {
- that.downloadImage({
- node: svg.node(), scale, isSvg: false,
- onAlreadySerialized: d => {
- that.update(root)
- },
- onLoad: onLoad,
- save
- })
- }, full ? duration + 10 : 0)
- }
-
- if (total > 0) {
- selection
- .each(function () {
- that.toDataURL(this.src, (dataUrl) => {
- this.src = dataUrl;
- if (++count == total) {
- exportImage();
- }
- })
- })
- } else {
+ if (total > 0) {
+ selection.each(function () {
+ that.toDataURL(this.src, (dataUrl) => {
+ this.src = dataUrl;
+ if (++count == total) {
exportImage();
+ }
+ });
+ });
+ } else {
+ exportImage();
+ }
+ }
+
+ exportSvg() {
+ const { svg } = this.getChartState();
+ this.downloadImage({ node: svg.node(), scale: 3, isSvg: true });
+ return this;
+ }
+
+ expandAll() {
+ const { allNodes, root } = this.getChartState();
+ allNodes.forEach((d) => (d.data._expanded = true));
+ this.render();
+ return this;
+ }
+
+ expandExpandNodes() {
+ const { allNodes, root } = this.getChartState();
+ // allNodes.forEach(d => d.data._expanded = true);
+ allNodes.forEach(
+ (d) => (d.data._expanded = d.data.expand === '1' ? true : false),
+ );
+ this.render();
+ return this;
+ }
+
+ collapseAll() {
+ const { allNodes, root } = this.getChartState();
+ allNodes.forEach((d) => (d.data._expanded = false));
+ this.expandLevel(0);
+ this.render();
+ return this;
+ }
+
+ downloadImage({
+ node,
+ scale = 2,
+ isSvg = false,
+ save = true,
+ onAlreadySerialized = (d) => {},
+ onLoad = (d) => {},
+ }) {
+ // Retrieve svg node
+ const svgNode = node;
+
+ if (isSvg) {
+ let source = serializeString(svgNode);
+ //add xml declaration
+ source = '\r\n' + source;
+ //convert svg source to URI data scheme.
+ var url =
+ 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source);
+ saveAs(url, 'graph.svg');
+ onAlreadySerialized();
+ return;
+ }
+ // Get image quality index (basically, index you can zoom in)
+ const quality = scale;
+ // Create image
+ const image = document.createElement('img');
+ image.onload = function () {
+ // Create image canvas
+ const canvas = document.createElement('canvas');
+ // Set width and height based on SVG node
+ const rect = svgNode.getBoundingClientRect();
+ canvas.width = rect.width * quality;
+ canvas.height = rect.height * quality;
+ // Draw background
+ const context = canvas.getContext('2d');
+ context.fillStyle = '#FAFAFA';
+ context.fillRect(0, 0, rect.width * quality, rect.height * quality);
+ context.drawImage(
+ image,
+ 0,
+ 0,
+ rect.width * quality,
+ rect.height * quality,
+ );
+ // Set some image metadata
+ let dt = canvas.toDataURL('image/png');
+ if (onLoad) {
+ onLoad(dt);
+ }
+ if (save) {
+ // Invoke saving function
+ saveAs(dt, 'graph.png');
+ }
+ };
+
+ var url =
+ 'data:image/svg+xml; charset=utf8, ' +
+ encodeURIComponent(serializeString(svgNode));
+
+ onAlreadySerialized();
+
+ image.src = url; // URL.createObjectURL(blob);
+ // This function invokes save window
+ function saveAs(uri, filename) {
+ // create link
+ var link = document.createElement('a');
+ if (typeof link.download === 'string') {
+ document.body.appendChild(link); // Firefox requires the link to be in the body
+ link.download = filename;
+ link.href = uri;
+ link.click();
+ document.body.removeChild(link); // remove the link when done
+ } else {
+ location.replace(uri);
+ }
+ }
+ // This function serializes SVG and sets all necessary attributes
+ function serializeString(svg) {
+ const xmlns = 'http://www.w3.org/2000/xmlns/';
+ const xlinkns = 'http://www.w3.org/1999/xlink';
+ const svgns = 'http://www.w3.org/2000/svg';
+ svg = svg.cloneNode(true);
+ const fragment = window.location.href + '#';
+ const walker = document.createTreeWalker(
+ svg,
+ NodeFilter.SHOW_ELEMENT,
+ null,
+ false,
+ );
+ while (walker.nextNode()) {
+ for (const attr of walker.currentNode.attributes) {
+ if (attr.value.includes(fragment)) {
+ attr.value = attr.value.replace(fragment, '#');
+ }
}
-
-
+ }
+ svg.setAttributeNS(xmlns, 'xmlns', svgns);
+ svg.setAttributeNS(xmlns, 'xmlns:xlink', xlinkns);
+ const serializer = new XMLSerializer();
+ const string = serializer.serializeToString(svg);
+ return string;
}
+ }
-
-
- exportSvg() {
- const { svg } = this.getChartState();
- this.downloadImage({ node: svg.node(), scale: 3, isSvg: true })
- return this;
- }
-
- expandAll() {
- const { allNodes, root } = this.getChartState();
- allNodes.forEach(d => d.data._expanded = true);
- this.render()
- return this;
- }
-
- expandExpandNodes() {
- const { allNodes, root } = this.getChartState();
- // allNodes.forEach(d => d.data._expanded = true);
- allNodes.forEach(d => d.data._expanded = d.data.expand === "1" ? true : false);
- this.render()
- return this;
- }
-
- collapseAll() {
- const { allNodes, root } = this.getChartState();
- allNodes.forEach(d => d.data._expanded = false);
- this.expandLevel(0)
- this.render();
- return this;
- }
-
- downloadImage({ node, scale = 2, isSvg = false, save = true, onAlreadySerialized = d => { }, onLoad = d => { } }) {
- // Retrieve svg node
- const svgNode = node;
-
- if (isSvg) {
- let source = serializeString(svgNode);
- //add xml declaration
- source = '\r\n' + source;
- //convert svg source to URI data scheme.
- var url = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
- saveAs(url, "graph.svg");
- onAlreadySerialized()
- return;
- }
- // Get image quality index (basically, index you can zoom in)
- const quality = scale
- // Create image
- const image = document.createElement('img');
- image.onload = function () {
- // Create image canvas
- const canvas = document.createElement('canvas');
- // Set width and height based on SVG node
- const rect = svgNode.getBoundingClientRect();
- canvas.width = rect.width * quality;
- canvas.height = rect.height * quality;
- // Draw background
- const context = canvas.getContext('2d');
- context.fillStyle = '#FAFAFA';
- context.fillRect(0, 0, rect.width * quality, rect.height * quality);
- context.drawImage(image, 0, 0, rect.width * quality, rect.height * quality);
- // Set some image metadata
- let dt = canvas.toDataURL('image/png');
- if (onLoad) {
- onLoad(dt)
- }
- if (save) {
- // Invoke saving function
- saveAs(dt, 'graph.png');
- }
-
- };
-
- var url = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(serializeString(svgNode));
-
- onAlreadySerialized()
-
- image.src = url// URL.createObjectURL(blob);
- // This function invokes save window
- function saveAs(uri, filename) {
- // create link
- var link = document.createElement('a');
- if (typeof link.download === 'string') {
- document.body.appendChild(link); // Firefox requires the link to be in the body
- link.download = filename;
- link.href = uri;
- link.click();
- document.body.removeChild(link); // remove the link when done
- } else {
- location.replace(uri);
- }
- }
- // This function serializes SVG and sets all necessary attributes
- function serializeString(svg) {
- const xmlns = 'http://www.w3.org/2000/xmlns/';
- const xlinkns = 'http://www.w3.org/1999/xlink';
- const svgns = 'http://www.w3.org/2000/svg';
- svg = svg.cloneNode(true);
- const fragment = window.location.href + '#';
- const walker = document.createTreeWalker(svg, NodeFilter.SHOW_ELEMENT, null, false);
- while (walker.nextNode()) {
- for (const attr of walker.currentNode.attributes) {
- if (attr.value.includes(fragment)) {
- attr.value = attr.value.replace(fragment, '#');
- }
- }
- }
- svg.setAttributeNS(xmlns, 'xmlns', svgns);
- svg.setAttributeNS(xmlns, 'xmlns:xlink', xlinkns);
- const serializer = new XMLSerializer();
- const string = serializer.serializeToString(svg);
- return string;
- }
- }
-
- // Calculate what size text will take
- getTextWidth(text, {
- fontSize = 14,
- fontWeight = 400,
- defaultFont = "Helvetice",
- ctx
- } = {}) {
- ctx.font = `${fontWeight || ''} ${fontSize}px ${defaultFont} `
- const measurement = ctx.measureText(text);
- return measurement.width;
- }
+ // Calculate what size text will take
+ getTextWidth(
+ text,
+ { fontSize = 14, fontWeight = 400, defaultFont = 'Helvetice', ctx } = {},
+ ) {
+ ctx.font = `${fontWeight || ''} ${fontSize}px ${defaultFont} `;
+ const measurement = ctx.measureText(text);
+ return measurement.width;
+ }
}
diff --git a/src/pages/company.jsx b/src/pages/company.jsx
index a47abd9..c360017 100644
--- a/src/pages/company.jsx
+++ b/src/pages/company.jsx
@@ -9,6 +9,7 @@ import moment from 'moment';
import qs from 'qs';
import { message } from 'antd';
+let active = 'top';
export default function companyPage() {
const [data, setData] = useState(null);
const [sliderProgress, setSliderProgress] = useState(50);
@@ -70,13 +71,15 @@ export default function companyPage() {
// 获取部门图片
function getDepartmentImage() {
let index = Math.floor(Math.random() * 8) + 1;
- return `./img/department/${index}.png`;
+ // return `./img/department/${index}.png`;
+ return `./img/department/1.png`;
}
// 获取部门图片
function getSubcompanyImage() {
let index = Math.floor(Math.random() * 3) + 1;
- return `./img/subcompany/${index}.png`;
+ // return `./img/subcompany/${index}.png`;
+ return `./img/subcompany/2.png`;
}
// 获取数据
@@ -94,9 +97,9 @@ export default function companyPage() {
// ButtonContent渲染
const buttonContentRender = ({ node, state }) => {
if (node.children) {
- return `
`;
+ return `
`;
} else {
- return `
`;
+ return `
`;
}
};
@@ -147,22 +150,14 @@ export default function companyPage() {
letter-spacing: 1px;
margin-top: 10px;
">${d.data.fname}
-