maxGraph/packages/html/stories/OrgChart.stories.js

390 lines
11 KiB
JavaScript

/*
Copyright 2021-present The maxGraph project Contributors
Copyright (c) 2006-2020, JGraph Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {
Graph,
constants,
InternalEvent,
Client,
Point,
Outline,
EdgeStyle,
KeyHandler,
CompactTreeLayout,
LayoutManager,
CellOverlay,
ImageBox,
utils,
MaxToolbar,
} from '@maxgraph/core';
import { globalTypes } from '../.storybook/preview';
export default {
title: 'Layouts/OrgChart',
argTypes: {
...globalTypes,
contextMenu: {
type: 'boolean',
defaultValue: false,
},
},
};
const Template = ({ label, ...args }) => {
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);
// Should we allow overriding constants?
// Makes the shadow brighter
//constants.SHADOWCOLOR = '#C0C0C0';
const outline = document.getElementById('outlineContainer');
if (!args.contextMenu) InternalEvent.disableContextMenu(container);
// Sets a gradient background
if (Client.IS_GC || Client.IS_SF) {
container.style.background =
'-webkit-gradient(linear, 0% 0%, 0% 100%, from(#FFFFFF), to(#E7E7E7))';
} else if (Client.IS_NS) {
container.style.background = '-moz-linear-gradient(top, #FFFFFF, #E7E7E7)';
}
// Creates the graph inside the given container
const graph = new Graph(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;
const panningHandler = graph.getPlugin('PanningHandler');
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
panningHandler.popupMenuHandler = false;
// Creates the outline (navigator, overview) for moving
// around the graph in the top, right corner of the window.
const outln = new Outline(graph, outline);
// Disables tooltips on touch devices
graph.setTooltips(!Client.IS_TOUCH);
// Set some stylesheet options for the visual appearance of vertices
let style = graph.getStylesheet().getDefaultVertexStyle();
style.shape = 'label';
style.verticalAlign = constants.ALIGN.MIDDLE;
style.align = constants.ALIGN.LEFT;
style.spacingLeft = 54;
style.gradientColor = '#7d85df';
style.strokeColor = '#5d65df';
style.fillColor = '#adc5ff';
style.fontColor = '#1d258f';
style.fontFamily = 'Verdana';
style.fontSize = '12';
style.fontStyle = '1';
style.shadow = '1';
style.rounded = '1';
style.glass = '1';
style.image = '/images/dude3.png';
style.imageWidth = '48';
style.imageHeight = '48';
style.spacing = 8;
// Sets the default style for edges
style = graph.getStylesheet().getDefaultEdgeStyle();
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
// Disable the following for straight lines
style.edge = EdgeStyle.TopToBottom;
// Stops editing on enter or escape keypress
const keyHandler = new KeyHandler(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 CompactTreeLayout(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 LayoutManager(graph);
layoutMgr.getLayout = function (cell) {
if (cell.getChildCount() > 0) {
return layout;
}
};
const popupMenuHandler = graph.getPlugin('PopupMenuHandler');
// Installs a popupmenu handler using local function (see below).
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;
if (state.cell.isVertex()) {
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.batchUpdate(() => {
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);
});
const content = document.createElement('div');
content.style.padding = '4px';
div.appendChild(content);
const tb = new MaxToolbar(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) {
const preview = new PrintPreview(graph, 1);
preview.open();
});
tb.addItem('Poster Print', 'images/press32.png', function (evt) {
const pageCount = utils.prompt('Enter maximum page count', '1');
if (pageCount != null) {
const scale = utils.getScaleForPageCount(pageCount, graph);
const preview = new PrintPreview(graph, scale);
preview.open();
}
});
// Function to create the entries in the popupmenu
function createPopupMenu(graph, menu, cell, evt) {
const model = graph.getDataModel();
if (cell != null) {
if (cell.isVertex()) {
menu.addItem('Add child', '/images/overlays/check.png', function () {
addChild(graph, cell);
});
}
menu.addItem('Edit label', '/images/text.gif', function () {
graph.startEditingAtCell(cell);
});
if (cell.id != 'treeRoot' && cell.isVertex()) {
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 () {
const preview = new PrintPreview(graph, 1);
preview.open();
});
menu.addItem('Poster Print', '/images/print.gif', function () {
const pageCount = utils.prompt('Enter maximum page count', '1');
if (pageCount != null) {
const scale = utils.getScaleForPageCount(pageCount, graph);
const preview = new PrintPreview(graph, scale);
preview.open();
}
});
}
function addOverlays(graph, cell, addDeleteIcon) {
let overlay = new CellOverlay(new ImageBox('images/add.png', 24, 24), 'Add child');
overlay.cursor = 'hand';
overlay.align = constants.ALIGN.CENTER;
overlay.addListener(InternalEvent.CLICK, (sender, evt) => {
addChild(graph, cell);
});
graph.addCellOverlay(cell, overlay);
if (addDeleteIcon) {
overlay = new CellOverlay(new ImageBox('images/close.png', 30, 30), 'Delete');
overlay.cursor = 'hand';
overlay.offset = new Point(-4, 8);
overlay.align = constants.ALIGN.RIGHT;
overlay.verticalAlign = constants.ALIGN.TOP;
overlay.addListener(InternalEvent.CLICK, (sender, evt) => {
deleteSubtree(graph, cell);
});
graph.addCellOverlay(cell, overlay);
}
}
function addChild(graph, cell) {
const model = graph.getDataModel();
const parent = graph.getDefaultParent();
let vertex;
model.beginUpdate();
try {
vertex = graph.insertVertex(parent, null, 'Double click to set name');
const geometry = vertex.getGeometry();
// 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 Point(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({});