2021-04-24 12:30:27 +00:00
|
|
|
import mxgraph from '@mxgraph/core';
|
|
|
|
|
|
|
|
import { globalTypes } from '../.storybook/preview';
|
|
|
|
|
|
|
|
export default {
|
2021-04-25 03:39:40 +00:00
|
|
|
title: 'Layouts/OrgChart',
|
2021-04-24 12:30:27 +00:00
|
|
|
argTypes: {
|
|
|
|
...globalTypes,
|
|
|
|
contextMenu: {
|
|
|
|
type: 'boolean',
|
|
|
|
defaultValue: false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const Template = ({ label, ...args }) => {
|
|
|
|
const {
|
|
|
|
mxGraph,
|
|
|
|
mxConstants,
|
|
|
|
mxEvent,
|
|
|
|
mxClient,
|
|
|
|
mxPoint,
|
|
|
|
mxOutline,
|
|
|
|
mxEdgeStyle,
|
|
|
|
mxKeyHandler,
|
|
|
|
mxCompactTreeLayout,
|
|
|
|
mxLayoutManager,
|
|
|
|
mxCellOverlay,
|
|
|
|
mxImage,
|
|
|
|
mxUtils,
|
|
|
|
mxToolbar
|
|
|
|
} = mxgraph;
|
|
|
|
|
|
|
|
const div = document.createElement('div');
|
|
|
|
|
|
|
|
const container = document.createElement('div');
|
|
|
|
container.style.position = 'relative';
|
|
|
|
container.style.overflow = 'hidden';
|
|
|
|
container.style.width = `${args.width}px`;
|
|
|
|
container.style.height = `${args.height}px`;
|
|
|
|
container.style.background = 'url(/images/grid.gif)';
|
|
|
|
container.style.cursor = 'default';
|
|
|
|
div.appendChild(container);
|
|
|
|
|
|
|
|
// Makes the shadow brighter
|
|
|
|
mxConstants.SHADOWCOLOR = '#C0C0C0';
|
|
|
|
|
|
|
|
const outline = document.getElementById('outlineContainer');
|
|
|
|
|
|
|
|
if (!args.contextMenu)
|
|
|
|
mxEvent.disableContextMenu(container);
|
|
|
|
|
|
|
|
// Sets a gradient background
|
|
|
|
if (mxClient.IS_GC || mxClient.IS_SF) {
|
|
|
|
container.style.background =
|
|
|
|
'-webkit-gradient(linear, 0% 0%, 0% 100%, from(#FFFFFF), to(#E7E7E7))';
|
|
|
|
} else if (mxClient.IS_NS) {
|
|
|
|
container.style.background =
|
|
|
|
'-moz-linear-gradient(top, #FFFFFF, #E7E7E7)';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates the graph inside the given container
|
|
|
|
const graph = new mxGraph(container);
|
|
|
|
|
|
|
|
// Enables automatic sizing for vertices after editing and
|
|
|
|
// panning by using the left mouse button.
|
|
|
|
graph.setCellsMovable(false);
|
|
|
|
graph.setAutoSizeCells(true);
|
|
|
|
graph.setPanning(true);
|
|
|
|
graph.centerZoom = false;
|
|
|
|
graph.panningHandler.useLeftButtonForPanning = true;
|
|
|
|
|
|
|
|
// Displays a popupmenu when the user clicks
|
|
|
|
// on a cell (using the left mouse button) but
|
|
|
|
// do not select the cell when the popup menu
|
|
|
|
// is displayed
|
|
|
|
graph.panningHandler.popupMenuHandler = false;
|
|
|
|
|
|
|
|
// Creates the outline (navigator, overview) for moving
|
|
|
|
// around the graph in the top, right corner of the window.
|
|
|
|
const outln = new mxOutline(graph, outline);
|
|
|
|
|
|
|
|
// Disables tooltips on touch devices
|
|
|
|
graph.setTooltips(!mxClient.IS_TOUCH);
|
|
|
|
|
|
|
|
// Set some stylesheet options for the visual appearance of vertices
|
|
|
|
let style = graph.getStylesheet().getDefaultVertexStyle();
|
2021-05-02 06:04:34 +00:00
|
|
|
style.shape = 'label';
|
2021-04-24 12:30:27 +00:00
|
|
|
|
2021-05-02 06:04:34 +00:00
|
|
|
style.verticalAlign = mxConstants.ALIGN_MIDDLE;
|
|
|
|
style.align = mxConstants.ALIGN_LEFT;
|
|
|
|
style.spacingLeft = 54;
|
2021-04-24 12:30:27 +00:00
|
|
|
|
2021-05-02 06:04:34 +00:00
|
|
|
style.gradientColor = '#7d85df';
|
|
|
|
style.strokeColor = '#5d65df';
|
|
|
|
style.fillColor = '#adc5ff';
|
2021-04-24 12:30:27 +00:00
|
|
|
|
2021-05-02 13:56:17 +00:00
|
|
|
style.fontColor = '#1d258f';
|
2021-05-02 06:04:34 +00:00
|
|
|
style.fontFamily = 'Verdana';
|
|
|
|
style.fontSize = '12';
|
|
|
|
style.fontStyle = '1';
|
2021-04-24 12:30:27 +00:00
|
|
|
|
2021-05-02 06:04:34 +00:00
|
|
|
style.shadow = '1';
|
|
|
|
style.rounded = '1';
|
|
|
|
style.glass = '1';
|
2021-04-24 12:30:27 +00:00
|
|
|
|
2021-05-02 06:04:34 +00:00
|
|
|
style.image = '/images/dude3.png';
|
|
|
|
style.imageWidth = '48';
|
|
|
|
style.imageHeight = '48';
|
|
|
|
style.spacing = 8;
|
2021-04-24 12:30:27 +00:00
|
|
|
|
|
|
|
// Sets the default style for edges
|
|
|
|
style = graph.getStylesheet().getDefaultEdgeStyle();
|
2021-05-02 06:04:34 +00:00
|
|
|
style.rounded = true;
|
|
|
|
style.strokeWidth = 3;
|
|
|
|
style.exitX = 0.5; // center
|
|
|
|
style.exitY = 1.0; // bottom
|
|
|
|
style.exitPerimeter = 0; // disabled
|
|
|
|
style.entryX = 0.5; // center
|
|
|
|
style.entryY = 0; // top
|
|
|
|
style.entryPerimeter = 0; // disabled
|
2021-04-24 12:30:27 +00:00
|
|
|
|
|
|
|
// Disable the following for straight lines
|
2021-05-02 06:04:34 +00:00
|
|
|
style.edge = mxEdgeStyle.TopToBottom;
|
2021-04-24 12:30:27 +00:00
|
|
|
|
|
|
|
// Stops editing on enter or escape keypress
|
|
|
|
const keyHandler = new mxKeyHandler(graph);
|
|
|
|
|
|
|
|
// Enables automatic layout on the graph and installs
|
|
|
|
// a tree layout for all groups who's children are
|
|
|
|
// being changed, added or removed.
|
|
|
|
const layout = new mxCompactTreeLayout(graph, false);
|
|
|
|
layout.useBoundingBox = false;
|
|
|
|
layout.edgeRouting = false;
|
|
|
|
layout.levelDistance = 60;
|
|
|
|
layout.nodeDistance = 16;
|
|
|
|
|
|
|
|
// Allows the layout to move cells even though cells
|
|
|
|
// aren't movable in the graph
|
|
|
|
layout.isVertexMovable = function(cell) {
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const layoutMgr = new mxLayoutManager(graph);
|
|
|
|
|
|
|
|
layoutMgr.getLayout = function(cell) {
|
|
|
|
if (cell.getChildCount() > 0) {
|
|
|
|
return layout;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Installs a popupmenu handler using local function (see below).
|
|
|
|
graph.popupMenuHandler.factoryMethod = function(menu, cell, evt) {
|
|
|
|
return createPopupMenu(graph, menu, cell, evt);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Fix for wrong preferred size
|
|
|
|
const oldGetPreferredSizeForCell = graph.getPreferredSizeForCell;
|
|
|
|
graph.getPreferredSizeForCell = function(cell) {
|
|
|
|
const result = oldGetPreferredSizeForCell.apply(this, arguments);
|
|
|
|
|
|
|
|
if (result != null) {
|
|
|
|
result.width = Math.max(120, result.width - 40);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Sets the maximum text scale to 1
|
|
|
|
graph.cellRenderer.getTextScale = function(state) {
|
|
|
|
return Math.min(1, state.view.scale);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Dynamically adds text to the label as we zoom in
|
|
|
|
// (without affecting the preferred size for new cells)
|
|
|
|
graph.cellRenderer.getLabelValue = function(state) {
|
|
|
|
let result = state.cell.value;
|
|
|
|
|
2021-04-25 03:39:40 +00:00
|
|
|
if (state.cell.isVertex()) {
|
2021-04-24 12:30:27 +00:00
|
|
|
if (state.view.scale > 1) {
|
|
|
|
result += '\nDetails 1';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state.view.scale > 1.3) {
|
|
|
|
result += '\nDetails 2';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Gets the default parent for inserting new cells. This
|
|
|
|
// is normally the first child of the root (ie. layer 0).
|
|
|
|
const parent = graph.getDefaultParent();
|
|
|
|
|
|
|
|
// Adds the root vertex of the tree
|
|
|
|
graph.getModel().beginUpdate();
|
|
|
|
try {
|
|
|
|
const w = graph.container.offsetWidth;
|
|
|
|
const v1 = graph.insertVertex(
|
|
|
|
parent,
|
|
|
|
'treeRoot',
|
|
|
|
'Organization',
|
|
|
|
w / 2 - 30,
|
|
|
|
20,
|
|
|
|
140,
|
|
|
|
60,
|
|
|
|
'image=/images/house.png'
|
|
|
|
);
|
|
|
|
graph.updateCellSize(v1);
|
|
|
|
addOverlays(graph, v1, false);
|
|
|
|
} finally {
|
|
|
|
// Updates the display
|
|
|
|
graph.getModel().endUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
const content = document.createElement('div');
|
|
|
|
content.style.padding = '4px';
|
|
|
|
div.appendChild(content);
|
|
|
|
const tb = new mxToolbar(content);
|
|
|
|
|
|
|
|
tb.addItem('Zoom In', 'images/zoom_in32.png', function(evt) {
|
|
|
|
graph.zoomIn();
|
|
|
|
});
|
|
|
|
|
|
|
|
tb.addItem('Zoom Out', 'images/zoom_out32.png', function(evt) {
|
|
|
|
graph.zoomOut();
|
|
|
|
});
|
|
|
|
|
|
|
|
tb.addItem('Actual Size', 'images/view_1_132.png', function(evt) {
|
|
|
|
graph.zoomActual();
|
|
|
|
});
|
|
|
|
|
|
|
|
tb.addItem('Print', 'images/print32.png', function(evt) {
|
2021-06-06 13:04:44 +00:00
|
|
|
const preview = new PrintPreview(graph, 1);
|
2021-04-24 12:30:27 +00:00
|
|
|
preview.open();
|
|
|
|
});
|
|
|
|
|
|
|
|
tb.addItem('Poster Print', 'images/press32.png', function(evt) {
|
|
|
|
const pageCount = mxUtils.prompt('Enter maximum page count', '1');
|
|
|
|
|
|
|
|
if (pageCount != null) {
|
|
|
|
const scale = mxUtils.getScaleForPageCount(pageCount, graph);
|
2021-06-06 13:04:44 +00:00
|
|
|
const preview = new PrintPreview(graph, scale);
|
2021-04-24 12:30:27 +00:00
|
|
|
preview.open();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Function to create the entries in the popupmenu
|
|
|
|
function createPopupMenu(graph, menu, cell, evt) {
|
|
|
|
const model = graph.getModel();
|
|
|
|
|
|
|
|
if (cell != null) {
|
2021-04-25 03:39:40 +00:00
|
|
|
if (cell.isVertex()) {
|
2021-04-24 12:30:27 +00:00
|
|
|
menu.addItem(
|
|
|
|
'Add child',
|
|
|
|
'/images/overlays/check.png',
|
|
|
|
function() {
|
|
|
|
addChild(graph, cell);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
menu.addItem('Edit label', '/images/text.gif', function() {
|
|
|
|
graph.startEditingAtCell(cell);
|
|
|
|
});
|
|
|
|
|
2021-04-25 03:39:40 +00:00
|
|
|
if (cell.id != 'treeRoot' && cell.isVertex()) {
|
2021-04-24 12:30:27 +00:00
|
|
|
menu.addItem('Delete', '/images/delete.gif', function() {
|
|
|
|
deleteSubtree(graph, cell);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
menu.addSeparator();
|
|
|
|
}
|
|
|
|
|
|
|
|
menu.addItem('Fit', '/images/zoom.gif', function() {
|
|
|
|
graph.fit();
|
|
|
|
});
|
|
|
|
|
|
|
|
menu.addItem('Actual', '/images/zoomactual.gif', function() {
|
|
|
|
graph.zoomActual();
|
|
|
|
});
|
|
|
|
|
|
|
|
menu.addSeparator();
|
|
|
|
|
|
|
|
menu.addItem('Print', '/images/print.gif', function() {
|
2021-06-06 13:04:44 +00:00
|
|
|
const preview = new PrintPreview(graph, 1);
|
2021-04-24 12:30:27 +00:00
|
|
|
preview.open();
|
|
|
|
});
|
|
|
|
|
|
|
|
menu.addItem('Poster Print', '/images/print.gif', function() {
|
|
|
|
const pageCount = mxUtils.prompt('Enter maximum page count', '1');
|
|
|
|
|
|
|
|
if (pageCount != null) {
|
|
|
|
const scale = mxUtils.getScaleForPageCount(pageCount, graph);
|
2021-06-06 13:04:44 +00:00
|
|
|
const preview = new PrintPreview(graph, scale);
|
2021-04-24 12:30:27 +00:00
|
|
|
preview.open();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function addOverlays(graph, cell, addDeleteIcon) {
|
|
|
|
let overlay = new mxCellOverlay(
|
|
|
|
new mxImage('images/add.png', 24, 24),
|
|
|
|
'Add child'
|
|
|
|
);
|
|
|
|
overlay.cursor = 'hand';
|
|
|
|
overlay.align = mxConstants.ALIGN_CENTER;
|
|
|
|
overlay.addListener(
|
|
|
|
mxEvent.CLICK,
|
2021-04-27 12:23:17 +00:00
|
|
|
(sender, evt) => {
|
2021-04-24 12:30:27 +00:00
|
|
|
addChild(graph, cell);
|
2021-04-27 12:23:17 +00:00
|
|
|
}
|
2021-04-24 12:30:27 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
graph.addCellOverlay(cell, overlay);
|
|
|
|
|
|
|
|
if (addDeleteIcon) {
|
|
|
|
overlay = new mxCellOverlay(
|
|
|
|
new mxImage('images/close.png', 30, 30),
|
|
|
|
'Delete'
|
|
|
|
);
|
|
|
|
overlay.cursor = 'hand';
|
|
|
|
overlay.offset = new mxPoint(-4, 8);
|
|
|
|
overlay.align = mxConstants.ALIGN_RIGHT;
|
|
|
|
overlay.verticalAlign = mxConstants.ALIGN_TOP;
|
|
|
|
overlay.addListener(
|
|
|
|
mxEvent.CLICK,
|
2021-04-27 12:23:17 +00:00
|
|
|
(sender, evt) => {
|
2021-04-24 12:30:27 +00:00
|
|
|
deleteSubtree(graph, cell);
|
2021-04-27 12:23:17 +00:00
|
|
|
}
|
2021-04-24 12:30:27 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
graph.addCellOverlay(cell, overlay);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function addChild(graph, cell) {
|
|
|
|
const model = graph.getModel();
|
|
|
|
const parent = graph.getDefaultParent();
|
|
|
|
let vertex;
|
|
|
|
|
|
|
|
model.beginUpdate();
|
|
|
|
try {
|
|
|
|
vertex = graph.insertVertex(parent, null, 'Double click to set name');
|
2021-04-25 03:39:40 +00:00
|
|
|
const geometry = vertex.getGeometry();
|
2021-04-24 12:30:27 +00:00
|
|
|
|
|
|
|
// Updates the geometry of the vertex with the
|
|
|
|
// preferred size computed in the graph
|
|
|
|
const size = graph.getPreferredSizeForCell(vertex);
|
|
|
|
geometry.width = size.width;
|
|
|
|
geometry.height = size.height;
|
|
|
|
|
|
|
|
// Adds the edge between the existing cell
|
|
|
|
// and the new vertex and executes the
|
|
|
|
// automatic layout on the parent
|
|
|
|
const edge = graph.insertEdge(parent, null, '', cell, vertex);
|
|
|
|
|
|
|
|
// Configures the edge label "in-place" to reside
|
|
|
|
// at the end of the edge (x = 1) and with an offset
|
|
|
|
// of 20 pixels in negative, vertical direction.
|
|
|
|
edge.geometry.x = 1;
|
|
|
|
edge.geometry.y = 0;
|
|
|
|
edge.geometry.offset = new mxPoint(0, -20);
|
|
|
|
|
|
|
|
addOverlays(graph, vertex, true);
|
|
|
|
} finally {
|
|
|
|
model.endUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
return vertex;
|
|
|
|
}
|
|
|
|
|
|
|
|
function deleteSubtree(graph, cell) {
|
|
|
|
// Gets the subtree from cell downwards
|
|
|
|
const cells = [];
|
|
|
|
graph.traverse(cell, true, function(vertex) {
|
|
|
|
cells.push(vertex);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
graph.removeCells(cells);
|
|
|
|
}
|
|
|
|
|
|
|
|
return div;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const Default = Template.bind({});
|