UNPKG

gojs

Version:

Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams

443 lines (404 loc) 19.1 kB
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Custom Animation Demo</title> <meta name="description" content="Custom animation examples for GoJS." /> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Copyright 1998-2020 by Northwoods Software Corporation. --> <script src="../release/go.js"></script> <script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework --> <style> .flex-container { display: flex; flex-wrap: nowrap; flex-direction: column; } .flex-container>div { margin-bottom: 10px; } </style> <script id="code"> function init() { if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this var $ = go.GraphObject.make; // for conciseness in defining templates myDiagram = $(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element { "clickCreatingTool.archetypeNodeData": { color: "palegreen", key: "node" }, "undoManager.isEnabled": true, "animationManager.isInitial": false, // To use custom initial animation instead "InitialLayoutCompleted": animateFadeIn // animate with this function }); function animateFadeIn(e) { var diagram = e.diagram; var animation = new go.Animation(); animation.isViewportUnconstrained = true; animation.easing = go.Animation.EaseOutExpo; // Looks better for a fade in animation animation.duration = 900; animation.add(diagram, 'position', diagram.position.copy().offset(0, diagram instanceof go.Palette ? 200 : -200), diagram.position); animation.add(diagram, 'opacity', 0, 1); animation.start(); } var addNodeAdornment = $(go.Adornment, "Spot", $(go.Panel, "Auto", $(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 3 }), $(go.Placeholder)), // the button to create a "next" node, at the top-right corner $("Button", { alignment: go.Spot.TopRight, click: addNode // this function is defined below }, $(go.Shape, "PlusLine", { desiredSize: new go.Size(6, 6) }) ) ); // define a simple Node template myDiagram.nodeTemplate = $(go.Node, "Auto", { selectionAdornmentTemplate: addNodeAdornment, locationSpot: go.Spot.Center }, new go.Binding("location", "loc").makeTwoWay(), $(go.Shape, "RoundedRectangle", { strokeWidth: 2, portId: "", // this Shape is the Node's port, not the whole Node fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true, toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true, cursor: "pointer" }, // Shape.fill is bound to Node.data.color new go.Binding("fill", "color")), $(go.TextBlock, { margin: 10, font: '14px sans-serif' }, // some room around the text // TextBlock.text is bound to Node.data.key new go.Binding("text", "key")) ); // create the model data that will be represented by Nodes and Links myDiagram.model = new go.GraphLinksModel( [ { key: "Alpha", loc: new go.Point(0, 0), color: "lightblue" }, { key: "Beta", loc: new go.Point(150, 0), color: "orange" }, { key: "Gamma", loc: new go.Point(0, 150), color: "lightgreen" }, { key: "Delta", loc: new go.Point(150, 150), color: "pink" } ], [ // No links to start ]); // This animation can be used in LinkDrawn Diagram listeners to animate // from a straight temporary link to a Bezier finished link // Custom animation for the curviness of a bezier link go.AnimationManager.defineAnimationEffect('curviness', function (obj, startValue, endValue, easing, currentTime, duration, animationState) { if (isNaN(startValue)) startValue = 0; if (isNaN(endValue)) endValue = 20; obj.curviness = easing(currentTime, startValue, endValue - startValue, duration); } ); // This animation can be used in LinkDrawn Diagram listeners to animate // from a straight temporary link to an Orthogonal finished link go.AnimationManager.defineAnimationEffect('orthogonalLinkanim', function (link, initPoints, tempPoints, easing, currentTime, duration, animation) { var animationState = animation.getTemporaryState(link); if (animationState.initial === undefined) { // On the first animaiton tick, save the initial points animationState.initial = true; var pts = link.points.copy(); tempPoints.points = pts; animationState.startPt = pts.first(); animationState.toPt1 = pts.elt(2); animationState.toPt2 = pts.elt(3); animationState.endPt = pts.last(); } var newpt1 = new go.Point( easing(currentTime, animationState.startPt.x, animationState.toPt1.x - animationState.startPt.x, duration), easing(currentTime, animationState.startPt.y, animationState.toPt1.y - animationState.startPt.y, duration)); var newpt2 = new go.Point( easing(currentTime, animationState.endPt.x, -(animationState.endPt.x - animationState.toPt2.x), duration), easing(currentTime, animationState.endPt.y, -(animationState.endPt.y - animationState.toPt2.y), duration)); // Setting the array of points will automatically call makeGeometry which will redraw the segments of the line link.points = [animationState.startPt, tempPoints.points.elt(1), newpt1, newpt2, tempPoints.points.elt(4), animationState.endPt]; } ); go.AnimationManager.defineAnimationEffect('corner', function (obj, startValue, endValue, easing, currentTime, duration, animation) { // If no corner set, default to 0 -> 20 startValue = startValue || 0; endValue = endValue || 20; obj.corner = easing(currentTime, startValue, endValue - startValue, duration); } ); myDiagram.addDiagramListener('LinkDrawn', function (e) { var link = e.subject; var animation = new go.Animation(); var linkChoice = document.getElementById("links").value if (linkChoice == "bezier") { link.curve = go.Link.Bezier; animation.easing = elasticEase; animation.add(link, "curviness", 0, link.curviness); animation.duration = 500; } else if (linkChoice == "orthogonal") { // The orthogonal animation is two animations, chained together. One to modify the points, // and then another to modify the link.corner value. // Store the initial link.corner value, // then set it to 0 so that in between animations it does not revert back to the original state var initCorner = link.corner; link.corner = 0; // Store the original points to this object var tempPoints = {}; animation.add(link, "orthogonalLinkanim", link.points, tempPoints); animation.duration = 300; // Chain animation after first one is completed animation.finished = function () { // Set points back to what they were before the animation myDiagram.startTransaction("Fix Points"); link.points = tempPoints.points; myDiagram.commitTransaction("Fix Points"); // Need to make a new animation object var animation2 = new go.Animation() animation2.add(link, "corner", 0, initCorner); animation2.duration = 250; animation2.start(); } animation.start(); console.log(link.points.copy()); link.routing = go.Link.Orthogonal; // NYI ortho animation link.ensureBounds(); console.log(link.points.copy()); } animation.start(); }); go.AnimationManager.defineAnimationEffect('bounceDelete', function (obj, startValue, endValue, easing, currentTime, duration, animation) { var animationState = animation.getTemporaryState(obj); if (animationState.initial === undefined) { // Set the initial positions as part of the animationState data animationState.yPos = obj.location.y; animationState.xPos = obj.location.x; animationState.yVelo = 0; animationState.xVelo = 0; animationState.newTime = 0; animationState.oldTime = 0; animationState.initial = true; } obj.location = getPointBounceDelete(currentTime, obj, animationState, obj.diagram); } ); // Get the point the object should be at based upon the time and original point function getPointBounceDelete(currentTime, obj, animationState, diagram) { if (diagram === null) return new go.Point(animationState.xPos, animationState.yPos); let height = obj.actualBounds.height; animationState.newTime = currentTime; // Animation uses a change in time in order to be more consistant let delTime = (animationState.newTime - animationState.oldTime) / 3; animationState.yVelo += .05 * delTime; // Add a slight easing to the x movement at the beginning of the animation if (currentTime < 200) { animationState.xVelo = currentTime / 300; } // Adjust positions based on the velocities and the change in time animationState.yPos += animationState.yVelo * delTime; animationState.xPos += animationState.xVelo * delTime; // Check to see if the Y position will be less than the bottom of the diagram, if so, // change the direction and scale down the velocity and set the position to the bottom of the diagram if (animationState.yPos > diagram.viewportBounds.height / 2 - height) { animationState.yVelo = -.75 * animationState.yVelo; animationState.yPos = diagram.viewportBounds.height / 2 - height; } let myPoint = new go.Point(animationState.xPos, animationState.yPos) // Get the new old time for use in the next iteration animationState.oldTime = animationState.newTime; return myPoint; } myDiagram.addDiagramListener('SelectionDeleting', function (e) { var deletionSelection = document.getElementById('deletion'); var animation = new go.Animation(); var diagram = e.diagram; e.subject.each(function (p) { // Because we are deleting this part, we cannot animate it, instead we must animate a temporary copy // The animation handles this via addTemporaryPart, which must be passed a copy var part = p.copy(); animation.addTemporaryPart(part, diagram); var initPosition = part.position.copy() part.locationSpot = go.Spot.Center; part.position = initPosition; switch (deletionSelection.value) { case "spinOut": animation.add(part, "angle", part.angle, part.angle + 1000); animation.add(part, "scale", part.scale, 0.01); break; case "fadeOut": animation.add(part, "opacity", part.opacity, 0); break; case "zoomOut": animation.add(part, "scale", part.scale, 0.01); break; case "floatOut": animation.add(part, "opacity", part.opacity, 0); animation.add(part, "position", part.position, part.position.copy().add(new go.Point(0, -80))); break; case "bounceOut": animation.add(part, "bounceDelete", part.location); // does't need an end value, bounceDelete determines one animation.add(part, "scale", part.scale, 0.01); animation.duration = 1500; animation.isViewportUnconstrained = true; break; default: // nothing animates break; } }); animation.start(); }); myDiagram.addDiagramListener('ClipboardPasted', function (e) { var creationSelection = document.getElementById('creation'); // For best performance, be sure to use only one Animation for the entire selection // instead of creating one animation for each object in the selection var animation = new go.Animation(); e.subject.each(function (part) { addCreatedPart(part, animation, creationSelection.value) }); animation.start(); }); myDiagram.addDiagramListener('PartCreated', function (e) { // From ClickCreatingTool var creationSelection = document.getElementById('creation'); var animation = new go.Animation(); addCreatedPart(e.subject, animation, creationSelection.value) animation.start(); }); function addCreatedPart(part, animation, creationSelection) { switch (creationSelection) { case "spinIn": animation.add(part, "angle", part.angle + 1000, part.angle); animation.add(part, "scale", 0.01, part.scale); break; case "fadeIn": animation.add(part, "opacity", 0, part.opacity); break; case "zoomIn": animation.add(part, "scale", 0.01, part.scale); break; case "floatIn": animation.add(part, "opacity", 0, part.opacity); animation.add(part, "location", part.location.copy().add(new go.Point(0, -80)), part.location); break; default: // nothing animates break; } } document.getElementById('addNode').addEventListener('click', function (e) { addNode(); }); function addNode() { var diagram = myDiagram; var tempNodes = new go.List(); diagram.startTransaction("Add States"); diagram.nodes.each(function (node) { if (node.isSelected) { tempNodes.push(node); } }) var animation = new go.Animation(); // Set the easing to a custom easing function animation.easing = elasticEase; // Add a new node to the right of each node tempNodes.each(function (part) { // get the node data for which the user clicked the button var fromNode = part; var fromData = part.data; // create a new "State" data object, positioned off to the right of the adorned Node var toData = { key: "new" }; toData.color = "purple"; var p = fromNode.location.copy(); // Place the new node randomly somewhere along a circular 200px radius var angle = Math.random() * Math.PI * 2; p.x += Math.cos(angle) * 200; p.y += Math.sin(angle) * 200; toData.loc = p; // add the new node data to the model var model = diagram.model; model.addNodeData(toData); // create a link data from the old node data to the new node data var linkdata = { from: model.getKeyForNodeData(fromData), // or just: fromData.key to: model.getKeyForNodeData(toData), }; // and add the link data to the model model.addLinkData(linkdata); var newnode = diagram.findNodeForData(toData); // Animate each newly created node animation.add(newnode, "position", part.location, newnode.location); }); animation.start(); diagram.commitTransaction("Add States"); }; document.getElementById('reloadModel').addEventListener('click', function (e) { myDiagram.model = go.Model.fromJSON(myDiagram.model.toJSON()) }); // Custom easing function used in some animations function elasticEase(currentTime, startValue, byValue, duration) { var ts = (currentTime /= duration) * currentTime; var tc = ts * currentTime; return startValue + byValue * (56 * tc * ts + -175 * ts * ts + 200 * tc + -100 * ts + 20 * currentTime); } } </script> </head> <body onload="init()"> <div id="sample"> <div id="myDiagramDiv" style="border: solid 1px black; width: 700px; height: 600px;"></div> <div class="flex-container" style="width:700px"> <p style="margin-bottom: 0;"> This extension implements several custom animations within GoJS. It may be useful to copy some of them into your own project. </p> <ul> <li>Double click in the Diagram background to create a node, or copy and paste nodes, to view creation animations. <li>Delete a node to view deletion animations. <li>Draw links to see new link creation animations. <li>Select a node and press the + button or the button below to see a link-and-node creation animation. <li>Reload the model using the button below to see the custom load animation </ul> </div> <div class="flex-container"> <p><strong>Options:</strong></p> <div> Creation Animation <select id="creation"> <option value="spinIn">Spin In</option> <option value="fadeIn">Fade In</option> <option value="floatIn">Float In</option> <option value="zoomIn">Zoom In</option> <option>--None--</option> </select> </div> <div> Deletion Animation <select id="deletion"> <option value="spinOut">Spin Out</option> <option value="fadeOut">Fade Out</option> <option value="floatOut">Float Out</option> <option value="zoomOut">Zoom Out</option> <option value="bounceOut">Bounce Out</option> <option>--None--</option> </select> </div> <div> Drawn Link Style <select id="links"> <option value="bezier">Bezier Curve</option> <option value="orthogonal">Orthogonal Curve</option> <option>Linear (no animation)</option> </select> </div> </div> <button id="addNode">Add Node + Link from selected Node</button> <button id="reloadModel">Reload model</button> </div> </body> </html>