UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

554 lines (500 loc) 20.4 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/> <meta name="description" content="Venn diagram showing common characters of two or three alphabets, using either CircularLayout or PackedLayout." /> <meta itemprop="description" content="Venn diagram showing common characters of two or three alphabets, using either CircularLayout or PackedLayout." /> <meta property="og:description" content="Venn diagram showing common characters of two or three alphabets, using either CircularLayout or PackedLayout." /> <meta name="twitter:description" content="Venn diagram showing common characters of two or three alphabets, using either CircularLayout or PackedLayout." /> <link rel="preconnect" href="https://rsms.me/"> <link rel="stylesheet" href="../assets/css/style.css"> <!-- Copyright 1998-2025 by Northwoods Software Corporation. --> <meta itemprop="name" content="Venn Diagram" /> <meta property="og:title" content="Venn Diagram" /> <meta name="twitter:title" content="Venn Diagram" /> <meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/venn.png" /> <meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/venn.png" /> <meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/venn.png" /> <meta property="og:url" content="https://gojs.net/latest/samples/venn.html" /> <meta property="twitter:url" content="https://gojs.net/latest/samples/venn.html" /> <meta name="twitter:card" content="summary_large_image" /> <meta property="og:type" content="website" /> <meta property="twitter:domain" content="gojs.net" /> <title> Venn Diagram | GoJS Diagramming Library </title> </head> <body> <!-- This top nav is not part of the sample code --> <nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200"> <div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4"> <a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo" href="../"> GoJS </a> <div class="relative"> <button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation"> <svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> <path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <div id="topnavList" class="hidden md:block"> <div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit"> <a href="../learn/">Learn</a> <a href="../samples/">Samples</a> <a href="../intro/">Intro</a> <a href="../api/">API</a> <a href="../download.html">Download</a> <a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a> <a id="tc" href="https://nwoods.com/contact.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a> <a id="tb" href="https://nwoods.com/sales/index.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a> </div> </div> </div> </div> </nav> <script> window.addEventListener("DOMContentLoaded", function () { // topnav var topButton = document.getElementById("topnavButton"); var topnavList = document.getElementById("topnavList"); if (topButton && topnavList) { topButton.addEventListener("click", function (e) { topnavList .classList .toggle("hidden"); e.stopPropagation(); }); document.addEventListener("click", function (e) { // if the clicked element isn't the list, close the list if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) { topButton.click(); } }); // set active <a> element var url = window .location .href .toLowerCase(); var aTags = topnavList.getElementsByTagName('a'); for (var i = 0; i < aTags.length; i++) { var lowerhref = aTags[i] .href .toLowerCase(); if (lowerhref.endsWith('.html')) lowerhref = lowerhref.slice(0, -5); if (url.startsWith(lowerhref)) { aTags[i] .classList .add('active'); break; } } } }); </script> <div class="flex flex-col prose"> <div class="w-full max-w-screen-xl mx-auto"> <!-- * * * * * * * * * * * * * --> <!-- Start of GoJS sample code --> <script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script> <div id="allSampleContent" class="p-4 w-full"> <script src="../extensions/Quadtree.js"></script> <script src="../extensions/PackedLayout.js"></script> <script id="code"> /** * * This Venn-style layout handles two or three sets of nodes, where each node.data object * has a "sets" property that is an Array listing which "Set"s the node belongs in. * The model should also hold a category: "Set" node data object for each collection * that is represented by a circular Shape. * * The name of the data property is given by the setsProperty property and defaults to "sets". * The value must be an Array of keys of the "Set"s. * * To arrange the collection of nodes in each "Set" intersection, you can either use * the default CircularLayout or you can use a different one such as PackedLayout. * Set the intersectionLayout property to determine the Layout to use. * * The spacing property, default zero, can provide extra space between each intersection collection. * * The circleName and labelName properties declare which GraphObject in the "Set" is a "Circle" Shape * and which GraphObject is the label to be aligned relative to the "Circle" Shape. * The names default to "SHAPE" and "LABEL" respectively. */ class VennLayout extends go.Layout { _sets = null; _bins = null; _setsProperty = "sets"; _circleName = "SHAPE"; _labelName = "LABEL"; _intersectionLayout = new go.CircularLayout({ arrangement: go.CircularArrangement.Packed, spacing: 0 }); _spacing = 0; constructor(init) { super(); if (init) Object.assign(this, init); } // Gets or sets the name of the node data property holding an Array of keys to the "Set"s of which the node is a member. // This defaults to "sets". get setsProperty() { return this._setsProperty; } set setsProperty(name) { if (name !== this.setsProperty) { this._setsProperty = name; this.invalidateLayout(); } } // Gets or sets the GraphObject.name of the "Circle" Shape in Nodes of "Set" category. // This defaults to "SHAPE". get circleName() { return this._circleName; } set circleName(name) { if (name !== this.circleName) { this._circleName = name; this.invalidateLayout(); } } // Gets or sets the GraphObject.name of the "Circle" Shape in Nodes of "Set" category. // This defaults to "LABEL". get labelName() { return this._labelName; } set labelName(name) { if (name !== this.labelName) { this._labelName = name; this.invalidateLayout(); } } // Gets or sets the Layout to use for each collection of nodes. // This defaults to CircularLayout. get intersectionLayout() { return this._intersectionLayout; } set intersectionLayout(lay) { if (lay !== this.intersectionLayout) { this._intersectionLayout = lay; this.invalidateLayout(); } } // Gets or sets the minimum distance between intersection collections. // This may want to be larger than zero when the intersections are laid out in a rectangular fashion, // since the circles of the "Set"s will tend to intersect with the corners of rectangles. // This defaults to 0. get spacing() { return this._spacing; } set spacing(sp) { if (sp !== this.spacing) { this._spacing = sp; this.invalidateLayout(); } } doLayout(coll) { if (coll === this.diagram) { coll = this.diagram.nodes.filter(n => n.isVisible()); } else if (coll instanceof go.Group) { coll = coll.memberParts.filter(p => p.isVisible() && !(p instanceof go.Link)); } else { coll = coll.filter(p => p.isVisible() && !(p instanceof go.Link)); } this._sets = new go.Map(); // first generate an array of the keys that name the "Sets" const setarr = []; coll.each(n => { if (n.category === "Set") { const key = n.key; this._sets.add(key, { members: [], key: key, node: n }); setarr.push(key); } }); // build tree structure with all combinations this._bins = this._buildBin(setarr, 0, []); // for each node that is not a "Set", add to bin corresponding to node.data.sets array of keys coll.each(n => { if (n.category === "Set") return; const a = n.data[this.setsProperty]; if (!Array.isArray(a)) return; a.forEach(k => { const setinfo = this._sets.get(k); if (setinfo) { setinfo.members.push(n); } else { // be tolerant of superfluous Set membership information -- ignore it // throw new Error("unknown set: " + k + " for node " + n.key); } }); this._walkBin(this._bins, intinfo => { const a = n.data[this.setsProperty]; return Array.isArray(a) && a.indexOf(intinfo.key) >= 0 }, intinfo => { const a = n.data[this.setsProperty]; return Array.isArray(a) && a.indexOf(intinfo.key) < 0 }, intinfo => intinfo.members.push(n)); }); // compute how big each laid-out collection of nodes in each intersection is let maxdia = 0.0; this._walkBin(this._bins, null, null, intinfo => { this._layoutIntersection(intinfo); const bounds = this.diagram.computePartsBounds(intinfo.members); intinfo.center = bounds.center; maxdia = Math.max(maxdia, Math.max(bounds.width + this.spacing/2, bounds.height + this.spacing/2)); }); // position the "Set"s // their diameters should depend on the maximum size of the intersection collections after their layout let i = 1; const dia = maxdia * this._sets.count; this._sets.iteratorValues.each(setinfo => { const shp = setinfo.node.findObject(this.circleName); if (shp) shp.desiredSize = new go.Size(dia, dia); const lab = setinfo.node.findObject(this.labelName); if (i === 1) { if (lab) { lab.alignment = new go.Spot(0.14, 0.14); lab.alignmentFocus = go.Spot.BottomRight; } setinfo.node.location = new go.Point(0, 0); i++; } else if (i === 2) { if (lab) { lab.alignment = new go.Spot(0.86, 0.14); lab.alignmentFocus = go.Spot.BottomLeft; } setinfo.node.location = new go.Point(dia / 2, 0); i++; } else if (i === 3) { if (lab) { lab.alignment = go.Spot.Bottom; lab.alignmentFocus = go.Spot.Top; } setinfo.node.location = new go.Point(dia / 4, 0.86 * dia / 2); i++; } else if (i > 3) { throw new Error("too many sets: " + i); } }); // position the intersection collections const center = this._average(setarr); this._walkBin(this._bins, null, null, intinfo => { const avg = this._average(intinfo.path); const c = intinfo.center; intinfo.members.forEach(n => { const dst = new go.Point(center.x + 2 * (avg.x - center.x), center.y + 2 * (avg.y - center.y)); n.moveTo(dst.x + n.location.x - c.x, dst.y + n.location.y - c.y, true); }); }); }; _buildBin(arr, idx, path) { if (idx < arr.length) { const k = arr[idx]; const t = { key: k }; t[false] = this._buildBin(arr, idx + 1, path); path.push(k); t[true] = this._buildBin(arr, idx + 1, path); path.pop(); return t; } else { return { members: [], path: path.slice(), size: new go.Size() }; } }; _walkBin(bins, present, absent, leaf) { if (Array.isArray(bins.members)) { if (leaf) leaf(bins); } else { const k = bins.key; if (!present || present(bins)) { this._walkBin(bins[true], present, absent, leaf); } if (!absent || absent(bins)) { this._walkBin(bins[false], present, absent, leaf); } } }; _layoutIntersection(intinfo) { const arr = intinfo.members; const coll = new go.List().addAll(arr); this._intersectionLayout.doLayout(coll); }; _average(keys) { const len = keys.length; let x = 0.0; let y = 0.0; for (let i = 0; i < len; i++) { const key = keys[i]; const node = this._sets.get(key).node; if (i === 0) { x = node.location.x; y = node.location.y; } else { x += node.location.x; y += node.location.y; } } if (len > 0) { x = x / len; y = y / len; } return new go.Point(x, y); } } // end VennLayout function init() { myDiagram = new go.Diagram("myDiagramDiv", { "animationManager.isEnabled": false, layout: new VennLayout() }); // this should have a "Circle" Shape named "SHAPE" and may have a label named "LABEL" myDiagram.nodeTemplateMap.add("Set", new go.Node("Spot", { locationSpot: go.Spot.Center, locationObjectName: "SHAPE", layerName: "Background", selectable: false }) .add( new go.Shape("Circle", { name: "SHAPE", fill: "transparent" }), new go.TextBlock({ name: "LABEL", font: "bold 12pt sans-serif" }) .bind("text") )); myDiagram.nodeTemplate = new go.Node({ locationSpot: go.Spot.Center }) .add( new go.TextBlock() .bind("text") ); // The model should have category "Set" data for each overall collection, // and each element node data should have a "sets" property that is an Array // holding the keys of those "Set"s. myDiagram.model = new go.GraphLinksModel([ { key: "G", text: "Greek", category: "Set" }, { key: "L", text: "Latin", category: "Set" }, { key: "R", text: "Russian", category: "Set" }, { text: "A", sets: ["G", "L", "R"] }, { text: "B", sets: ["G", "L", "R"] }, { text: "C", sets: ["L", "R"] }, { text: "D", sets: ["L"] }, { text: "E", sets: ["G", "L", "R"] }, { text: "F", sets: ["L"] }, { text: "G", sets: ["L"] }, { text: "H", sets: ["G", "L", "R"] }, { text: "I", sets: ["G", "L"] }, { text: "J", sets: ["L"] }, { text: "K", sets: ["G", "L", "R"] }, { text: "L", sets: ["L"] }, { text: "M", sets: ["G", "L", "R"] }, { text: "N", sets: ["G", "L"] }, { text: "O", sets: ["G", "L", "R"] }, { text: "P", sets: ["G", "L", "R"] }, { text: "Q", sets: ["L"] }, { text: "R", sets: ["L"] }, { text: "S", sets: ["L"] }, { text: "T", sets: ["G", "L", "R"] }, { text: "U", sets: ["L"] }, { text: "V", sets: ["L"] }, { text: "W", sets: ["L"] }, { text: "X", sets: ["G", "L", "R"] }, { text: "Y", sets: ["G", "L", "R"] }, { text: "Z", sets: ["G", "L"] }, { text: "Γ", sets: ["G", "R"] }, { text: "Δ", sets: ["G"] }, { text: "Θ", sets: ["G"] }, { text: "Λ", sets: ["G"] }, { text: "Ξ", sets: ["G"] }, { text: "Π", sets: ["G", "R"] }, { text: "Σ", sets: ["G"] }, { text: "Φ", sets: ["G", "R"] }, { text: "Ψ", sets: ["G"] }, { text: "Ω", sets: ["G"] }, { text: "Б", sets: ["R"] }, { text: "Д", sets: ["R"] }, { text: "Ж", sets: ["R"] }, { text: "З", sets: ["R"] }, { text: "И", sets: ["R"] }, { text: "Л", sets: ["R"] }, { text: "Ц", sets: ["R"] }, { text: "Ч", sets: ["R"] }, { text: "Ш", sets: ["R"] }, { text: "Щ", sets: ["R"] }, { text: "Ъ", sets: ["R"] }, { text: "Ы", sets: ["R"] }, { text: "Ь", sets: ["R"] }, { text: "Э", sets: ["R"] }, { text: "Ю", sets: ["R"] }, { text: "Я", sets: ["R"] } ]); } function lgr() { // make all Nodes visible myDiagram.commit(d => { d.nodes.each(n => n.visible = true); }); } function setVisibleIf(a, b) { myDiagram.commit(d => { d.nodes.each(n => { if (n.category === "Set") { n.visible = n.key === a || n.key === b; } else { n.visible = n.data.sets.indexOf(a) >= 0 || n.data.sets.indexOf(b) >= 0; } }); }); } function lg() { setVisibleIf("L", "G"); } function gr() { setVisibleIf("G", "R"); } function lr() { setVisibleIf("L", "R"); } function circular() { myDiagram.commit(d => { d.layout.intersectionLayout = new go.CircularLayout({ arrangement: go.CircularArrangement.Packed, spacing: 0 }) d.layout.spacing = 0; }); } function packed() { myDiagram.commit(d => { d.layout.intersectionLayout = new PackedLayout({ spacing: 0 }); d.layout.spacing = 20; }); } window.addEventListener('DOMContentLoaded', init); </script> <div id="sample"> <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div> <p> Venn Diagram of Greek, Latin, and Russian Glyphs </p> <p> <button onclick="lgr()">Greek, Latin, and Russian</button> <button onclick="lg()">Greek and Latin</button> <button onclick="gr()">Greek and Russian</button> <button onclick="lr()">Latin and Russian</button> </p> <p> <button onclick="circular()">Circular Layout</button> <button onclick="packed()">Packed Layout</button> </p> </div> </div> <!-- * * * * * * * * * * * * * --> <!-- End of GoJS sample code --> </div> <div id="allTagDescriptions" class="p-4 w-full max-w-screen-xl mx-auto"> <hr/> <h3 class="text-xl">GoJS Features in this sample</h3> <!-- blacklist tags that do not correspond to a specific GoJS feature --> <h4>Custom Layouts</h4> <p> GoJS allows for the creation of custom layouts to meet specific needs. </p> <p> There are also many layouts that are extensions -- not predefined in the <code>go.js</code> or <code>go-debug.js</code> library, but available as source code in one of the three extension directories, with some documentation and corresponding samples. More information can be found in the <a href="../intro/layouts.html#CustomLayouts">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#customlayout">Related samples</a> </p> <hr> <!-- blacklist tags that do not correspond to a specific GoJS feature --> <h4>Circular Layout</h4> <p> This predefined layout is used for placing Nodes in a cirular or elliptical arrangement. More information can be found in the <a href="../intro/layouts.html#CircularLayout">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#circularlayout">Related samples</a> </p> <hr> </div> </div> </body> <!-- This script is part of the gojs.net website, and is not needed to run the sample --> <script src="../assets/js/goSamples.js"></script> </html>