Skip to content

Instantly share code, notes, and snippets.

@addshore
Created June 13, 2020 15:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save addshore/ef380319f97dc87adeebbd64d5f40ad8 to your computer and use it in GitHub Desktop.
Save addshore/ef380319f97dc87adeebbd64d5f40ad8 to your computer and use it in GitHub Desktop.
Wikidata & WIkibase, Vision Diagram
<script src=" https://gojs.net/latest/release/go-debug.js"></script>
<div id="sample">
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:800px;"></div>
<button id="SaveButton" onclick="save()">Save</button>
<button onclick="load()">Load</button>
<button onclick="layoutAll()">Layout</button>
Diagram Model saved in JSON format (to persist you must copy this back into the pen):
<br />
<textarea id="mySavedModel" style="width:100%;height:400px">
{ "class": "TreeModel",
"nodeDataArray": [
{"key":0, "text":"Wikidata & Wikibase", "brush":"gainsboro", "loc":"0 0", "font":"bold 13px sans-serif"},
{"key":3, "parent":0, "text":"For Wikimedia", "brush":"palevioletred", "dir":"left", "loc":"-20 -267.375", "font":"bold 13px sans-serif"},
{"key":32, "parent":3, "text":"Scaling", "brush":"palevioletred", "dir":"left", "loc":"-128.73388671875 -309.625"},
{"text":"UI", "brush":"palegreen", "dir":"right", "parent":0, "key":-56, "loc":"146.19140625 -222.35768543342272", "font":"bold 13px sans-serif"},
{"text":"APIs", "brush":"aquamarine", "dir":"right", "parent":0, "key":-57, "loc":"146.19140625 -98.85768543342272", "font":"bold 13px sans-serif"},
{"text":"For 3rd Parties", "brush":"darkblue", "dir":"left", "parent":0, "key":-54, "loc":"-20 -10.625", "font":"bold 13px sans-serif"},
{"text":"Vue only UI", "brush":"palegreen", "dir":"right", "parent":-56, "key":-60, "loc":"196.19140625 -254.85768543342272"},
{"text":"Component system", "brush":"palegreen", "dir":"right", "parent":-56, "key":-61, "loc":"196.19140625 -228.85768543342272"},
{"text":"Entities", "brush":"palegreen", "dir":"right", "parent":-56, "key":-62, "loc":"196.19140625 -189.85768543342272"},
{"text":"Compact UI", "brush":"palegreen", "dir":"right", "parent":-62, "key":-63, "loc":"258.822265625 -202.85768543342272"},
{"text":"Faster editing", "brush":"palegreen", "dir":"right", "parent":-62, "key":-64, "loc":"258.822265625 -176.85768543342272"},
{"text":"MediaWiki Extensions", "brush":"lightcoral", "parent":0, "key":-52, "loc":"146.19140625 5.357685433422716", "font":"bold 13px sans-serif"},
{"text":"Use modern MW Concepts", "brush":"lightcoral", "parent":-52, "key":-53, "loc":"302.60888671875 -40.142314566577284"},
{"text":"REST", "brush":"aquamarine", "dir":"right", "parent":-57, "key":-55, "loc":"196.19140625 -163.85768543342272"},
{"text":"GraphQL", "brush":"aquamarine", "dir":"right", "parent":-57, "key":-70, "loc":"196.19140625 -137.85768543342272"},
{"text":"Batch", "brush":"aquamarine", "dir":"right", "parent":-57, "key":-71, "loc":"196.19140625 -111.85768543342272"},
{"text":"Deferred", "brush":"aquamarine", "dir":"right", "parent":-57, "key":-72, "loc":"196.19140625 -85.85768543342272"},
{"text":"Extension Registration", "brush":"lightcoral", "parent":-53, "key":-46, "loc":"478.65966796875 -79.14231456657728"},
{"text":"Service Containers", "brush":"lightcoral", "parent":-53, "key":-47, "loc":"478.65966796875 -53.142314566577284"},
{"text":"Modern Services", "brush":"lightcoral", "parent":-53, "key":-50, "loc":"478.65966796875 -27.142314566577284"},
{"text":"Modern Values", "brush":"lightcoral", "parent":-53, "key":-58, "loc":"478.65966796875 -1.1423145665772836"},
{"text":"Consistent structure", "brush":"lightcoral", "parent":-52, "key":-59, "loc":"302.60888671875 24.857685433422716"},
{"text":"PSR4", "brush":"lightcoral", "parent":-59, "key":-73, "loc":"438.21240234375 24.857685433422716"},
{"text":"Libraries", "brush":"goldenrod", "parent":0, "key":-38, "loc":"146.19140625 128.85768543342272", "font":"bold 13px sans-serif"},
{"text":"Consistent tooling", "brush":"goldenrod", "parent":-38, "key":-39, "loc":"221.10498046875 76.85768543342272"},
{"text":"Easier releases", "brush":"goldenrod", "parent":-38, "key":-42, "loc":"221.10498046875 102.85768543342272"},
{"text":"Operation", "brush":"darkblue", "dir":"left", "parent":-54, "key":-44, "loc":"-131.75537109375 -160.125"},
{"text":"Docker images v2", "brush":"darkblue", "dir":"left", "parent":-44, "key":-51, "loc":"-208.84619140625 -199.125"},
{"text":"Documented releases", "brush":"darkblue", "dir":"left", "parent":-44, "key":-78, "loc":"-208.84619140625 -173.125"},
{"text":"Website home for the software", "brush":"darkblue", "dir":"left", "parent":-44, "key":-79, "loc":"-208.84619140625 -147.125"},
{"text":"Entity save resources", "brush":"palevioletred", "dir":"left", "parent":32, "key":-65, "loc":"-191.37109375 -342.125"},
{"text":"Visibility of sub graphs", "brush":"palevioletred", "dir":"left", "parent":32, "key":-43, "loc":"-191.37109375 -277.125"},
{"text":"AbuseFilter", "brush":"palevioletred", "dir":"left", "parent":-65, "key":-66, "loc":"-335.6455078125 -355.125"},
{"text":"Processes", "brush":"lightseagreen", "parent":0, "key":-67, "loc":"146.19140625 232.85768543342272", "font":"bold 13px sans-serif"},
{"text":"Dispatch using jobs", "brush":"lightseagreen", "parent":-67, "key":-68, "loc":"231.2421875 206.85768543342272"},
{"text":"Some processes disabled by default", "brush":"lightseagreen", "parent":-67, "key":-69, "loc":"231.2421875 232.85768543342272"},
{"text":"wb_changes", "brush":"lightseagreen", "parent":-69, "key":-80, "loc":"460.08642578125 232.85768543342272"},
{"text":"PropertySuggestor automation", "brush":"lightseagreen", "parent":-67, "key":-81, "loc":"231.2421875 258.8576854334227"},
{"text":"Recent Changes", "brush":"palevioletred", "dir":"left", "parent":-43, "key":-83, "loc":"-340.47607421875 -303.125"},
{"text":"Search", "brush":"palevioletred", "dir":"left", "parent":-43, "key":-84, "loc":"-340.47607421875 -277.125"},
{"text":"etc.", "brush":"palevioletred", "dir":"left", "parent":-43, "key":-85, "loc":"-340.47607421875 -251.125"},
{"text":"Abstract MediaWiki binding", "brush":"lightcoral", "parent":-52, "key":-86, "loc":"302.60888671875 50.857685433422716"},
{"text":"Better rate control", "brush":"aquamarine", "dir":"right", "parent":-57, "key":-87, "loc":"196.19140625 -59.857685433422716"},
{"text":"Wikibase Core", "brush":"darkblue", "dir":"left", "parent":-54, "key":-74, "loc":"-131.75537109375 -56.125"},
{"text":"Federation", "brush":"darkblue", "dir":"left", "parent":-74, "key":-75, "loc":"-236.28076171875 -95.125"},
{"text":"Include Gadgets", "brush":"darkblue", "dir":"left", "parent":-74, "key":-88, "loc":"-236.28076171875 -69.125"},
{"text":"More Data types", "brush":"darkblue", "dir":"left", "parent":-74, "key":-89, "loc":"-236.28076171875 -43.125"},
{"text":"Non Commons Media", "brush":"darkblue", "dir":"left", "parent":-89, "key":-90, "loc":"-351.654296875 -43.125"},
{"text":"Other Services", "brush":"darkblue", "dir":"left", "parent":-54, "key":-77, "loc":"-131.75537109375 47.875"},
{"text":"Generic Query Service UI", "brush":"darkblue", "dir":"left", "parent":-77, "key":-91, "loc":"-237.72802734375 8.875"},
{"text":"Well Defined", "brush":"goldenrod", "parent":-38, "key":-76, "loc":"221.10498046875 128.85768543342272"},
{"text":"More libraries extracted", "brush":"goldenrod", "parent":-38, "key":-82, "loc":"221.10498046875 154.85768543342272"},
{"text":"Automated management", "brush":"goldenrod", "parent":-38, "key":-92, "loc":"221.10498046875 180.85768543342272"},
{"text":"Wikibase Stack Sandbox", "brush":"darkblue", "dir":"left", "parent":-77, "key":-93, "loc":"-237.72802734375 34.875"},
{"text":"Better defaults", "brush":"darkblue", "dir":"left", "parent":-74, "key":-94, "loc":"-236.28076171875 -17.125"},
{"text":"For Developers", "brush":"gainsboro", "dir":"left", "parent":0, "key":-95, "loc":"-20 288.375", "font":"bold 13px sans-serif"},
{"text":"Development Environment", "brush":"gainsboro", "dir":"left", "parent":-95, "key":-96, "loc":"-134.6435546875 216.875"},
{"text":"Fast", "brush":"gainsboro", "dir":"left", "parent":-96, "key":-97, "loc":"-307.83154296875 190.875"},
{"text":"Featured", "brush":"gainsboro", "dir":"left", "parent":-96, "key":-98, "loc":"-307.83154296875 216.875"},
{"text":"Documentation", "brush":"gainsboro", "dir":"left", "parent":-95, "key":-99, "loc":"-134.6435546875 294.875"},
{"text":"Wikibase Hub", "brush":"gainsboro", "dir":"left", "parent":-99, "key":-100, "loc":"-242.08251953125 268.875"},
{"text":"Versioned", "brush":"gainsboro", "dir":"left", "parent":-99, "key":-101, "loc":"-242.08251953125 294.875"},
{"text":"Diagramed", "brush":"gainsboro", "dir":"left", "parent":-99, "key":-102, "loc":"-242.08251953125 320.875"},
{"text":"Documented optimized configurations", "brush":"darkblue", "dir":"left", "parent":-44, "key":-103, "loc":"-208.84619140625 -121.125"},
{"text":"Misc Triple store support", "brush":"darkblue", "dir":"left", "parent":-77, "key":-104, "loc":"-237.72802734375 60.875"},
{"text":"Granular entity alterations", "brush":"palevioletred", "dir":"left", "parent":-65, "key":-105, "loc":"-335.6455078125 -329.125"},
{"text":"Versioning", "brush":"aquamarine", "dir":"right", "parent":-57, "key":-106, "loc":"196.19140625 -33.857685433422716"},
{"text":"Wikidata developed \"tools\"", "brush":"darkblue", "dir":"left", "parent":-77, "key":-107, "loc":"-237.72802734375 86.875"},
{"text":"Wikibase as a service", "brush":"darkblue", "dir":"left", "parent":-93, "key":-108, "loc":"-401.521484375 34.875"},
{"text":"\"Wikimedia\" as a service", "brush":"darkblue", "dir":"left", "parent":-108, "key":-109, "loc":"-547.23046875 34.875"},
{"text":"Micro contributions", "brush":"palevioletred", "dir":"left", "parent":3, "key":-110, "loc":"-128.73388671875 -251.125"},
{"text":"MediaWiki Extension", "brush":"darkblue", "dir":"left", "parent":-54, "key":-111, "loc":"-131.75537109375 138.875"},
{"text":"Better Authentication", "brush":"darkblue", "dir":"left", "parent":-111, "key":-112, "loc":"-272.41162109375 112.875"},
{"text":"Spam Protection", "brush":"darkblue", "dir":"left", "parent":-111, "key":-113, "loc":"-272.41162109375 138.875"},
{"text":"Seeded data", "brush":"gainsboro", "dir":"left", "parent":-96, "key":-114, "loc":"-307.83154296875 242.875"},
{"text":"Advocacy", "brush":"gainsboro", "dir":"left", "parent":-95, "key":-115, "loc":"-134.6435546875 359.875"},
{"text":"Outreach (Blogs etc)", "brush":"gainsboro", "dir":"left", "parent":-115, "key":-116, "loc":"-211.00439453125 346.875"},
{"text":"SDK / Client libraries", "brush":"gainsboro", "dir":"left", "parent":-115, "key":-117, "loc":"-211.00439453125 372.875"},
{"text":"Inter Project Reconciliation", "brush":"palevioletred", "dir":"left", "parent":3, "key":-118, "loc":"-128.73388671875 -225.125"},
{"text":"Faster imports", "brush":"darkblue", "dir":"left", "parent":-111, "key":-119, "loc":"-272.41162109375 164.875"}
]}
</textarea>
</div>
var $ = go.GraphObject.make;
myDiagram = $(go.Diagram, "myDiagramDiv", {
// when the user drags a node, also move/copy/delete the whole subtree starting with that node
"commandHandler.copiesTree": true,
"commandHandler.copiesParentKey": true,
"commandHandler.deletesTree": true,
"draggingTool.dragsTree": true,
"undoManager.isEnabled": true
});
// when the document is modified, add a "*" to the title and enable the "Save" button
myDiagram.addDiagramListener("Modified", function (e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
} else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
// a node consists of some text with a line shape underneath
myDiagram.nodeTemplate = $(
go.Node,
"Vertical",
{ selectionObjectName: "TEXT" },
$(
go.TextBlock,
{
name: "TEXT",
minSize: new go.Size(30, 15),
editable: true
},
// remember not only the text string but the scale and the font in the node data
new go.Binding("text", "text").makeTwoWay(),
new go.Binding("scale", "scale").makeTwoWay(),
new go.Binding("font", "font").makeTwoWay()
),
$(
go.Shape,
"LineH",
{
stretch: go.GraphObject.Horizontal,
strokeWidth: 3,
height: 3,
// this line shape is the port -- what links connect with
portId: "",
fromSpot: go.Spot.LeftRightSides,
toSpot: go.Spot.LeftRightSides
},
new go.Binding("stroke", "brush"),
// make sure links come in from the proper direction and go out appropriately
new go.Binding("fromSpot", "dir", function (d) {
return spotConverter(d, true);
}),
new go.Binding("toSpot", "dir", function (d) {
return spotConverter(d, false);
})
),
// remember the locations of each node in the node data
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(
go.Point.stringify
),
// make sure text "grows" in the desired direction
new go.Binding("locationSpot", "dir", function (d) {
return spotConverter(d, false);
})
);
// selected nodes show a button for adding children
myDiagram.nodeTemplate.selectionAdornmentTemplate = $(
go.Adornment,
"Spot",
$(
go.Panel,
"Auto",
// this Adornment has a rectangular blue Shape around the selected node
$(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 3 }),
$(go.Placeholder, { margin: new go.Margin(4, 4, 0, 4) })
),
// and this Adornment has a Button to the right of the selected node
$(
"Button",
{
alignment: go.Spot.Right,
alignmentFocus: go.Spot.Left,
click: addNodeAndLink // define click behavior for this Button in the Adornment
},
$(
go.TextBlock,
"+", // the Button content
{ font: "bold 8pt sans-serif" }
)
)
);
// the context menu allows users to change the font size and weight,
// and to perform a limited tree layout starting at that node
myDiagram.nodeTemplate.contextMenu = $(
"ContextMenu",
$("ContextMenuButton", $(go.TextBlock, "Bigger"), {
click: function (e, obj) {
changeTextSize(obj, 1.1);
}
}),
$("ContextMenuButton", $(go.TextBlock, "Smaller"), {
click: function (e, obj) {
changeTextSize(obj, 1 / 1.1);
}
}),
$("ContextMenuButton", $(go.TextBlock, "Bold/Normal"), {
click: function (e, obj) {
toggleTextWeight(obj);
}
}),
$("ContextMenuButton", $(go.TextBlock, "Copy"), {
click: function (e, obj) {
e.diagram.commandHandler.copySelection();
}
}),
$("ContextMenuButton", $(go.TextBlock, "Delete"), {
click: function (e, obj) {
e.diagram.commandHandler.deleteSelection();
}
}),
$("ContextMenuButton", $(go.TextBlock, "Undo"), {
click: function (e, obj) {
e.diagram.commandHandler.undo();
}
}),
$("ContextMenuButton", $(go.TextBlock, "Redo"), {
click: function (e, obj) {
e.diagram.commandHandler.redo();
}
}),
$("ContextMenuButton", $(go.TextBlock, "Layout"), {
click: function (e, obj) {
var adorn = obj.part;
adorn.diagram.startTransaction("Subtree Layout");
layoutTree(adorn.adornedPart);
adorn.diagram.commitTransaction("Subtree Layout");
}
})
);
// a link is just a Bezier-curved line of the same color as the node to which it is connected
myDiagram.linkTemplate = $(
go.Link,
{
curve: go.Link.Bezier,
fromShortLength: -2,
toShortLength: -2,
selectable: false
},
$(
go.Shape,
{ strokeWidth: 3 },
new go.Binding("stroke", "toNode", function (n) {
if (n.data.brush) return n.data.brush;
return "black";
}).ofObject()
)
);
// the Diagram's context menu just displays commands for general functionality
myDiagram.contextMenu = $(
"ContextMenu",
$(
"ContextMenuButton",
$(go.TextBlock, "Paste"),
{
click: function (e, obj) {
e.diagram.commandHandler.pasteSelection(
e.diagram.toolManager.contextMenuTool.mouseDownPoint
);
}
},
new go.Binding("visible", "", function (o) {
return (
o.diagram &&
o.diagram.commandHandler.canPasteSelection(
o.diagram.toolManager.contextMenuTool.mouseDownPoint
)
);
}).ofObject()
),
$(
"ContextMenuButton",
$(go.TextBlock, "Undo"),
{
click: function (e, obj) {
e.diagram.commandHandler.undo();
}
},
new go.Binding("visible", "", function (o) {
return o.diagram && o.diagram.commandHandler.canUndo();
}).ofObject()
),
$(
"ContextMenuButton",
$(go.TextBlock, "Redo"),
{
click: function (e, obj) {
e.diagram.commandHandler.redo();
}
},
new go.Binding("visible", "", function (o) {
return o.diagram && o.diagram.commandHandler.canRedo();
}).ofObject()
),
$("ContextMenuButton", $(go.TextBlock, "Save"), {
click: function (e, obj) {
save();
}
}),
$("ContextMenuButton", $(go.TextBlock, "Load"), {
click: function (e, obj) {
load();
}
})
);
myDiagram.addDiagramListener("SelectionMoved", function (e) {
var rootX = myDiagram.findNodeForKey(0).location.x;
myDiagram.selection.each(function (node) {
if (node.data.parent !== 0) return; // Only consider nodes connected to the root
var nodeX = node.location.x;
if (rootX < nodeX && node.data.dir !== "right") {
updateNodeDirection(node, "right");
} else if (rootX > nodeX && node.data.dir !== "left") {
updateNodeDirection(node, "left");
}
layoutTree(node);
});
});
// read in the predefined graph using the JSON format data held in the "mySavedModel" textarea
load();
////}
function spotConverter(dir, from) {
if (dir === "left") {
return from ? go.Spot.Left : go.Spot.Right;
} else {
return from ? go.Spot.Right : go.Spot.Left;
}
}
function changeTextSize(obj, factor) {
var adorn = obj.part;
adorn.diagram.startTransaction("Change Text Size");
var node = adorn.adornedPart;
var tb = node.findObject("TEXT");
tb.scale *= factor;
adorn.diagram.commitTransaction("Change Text Size");
}
function toggleTextWeight(obj) {
var adorn = obj.part;
adorn.diagram.startTransaction("Change Text Weight");
var node = adorn.adornedPart;
var tb = node.findObject("TEXT");
// assume "bold" is at the start of the font specifier
var idx = tb.font.indexOf("bold");
if (idx < 0) {
tb.font = "bold " + tb.font;
} else {
tb.font = tb.font.substr(idx + 5);
}
adorn.diagram.commitTransaction("Change Text Weight");
}
function updateNodeDirection(node, dir) {
myDiagram.model.setDataProperty(node.data, "dir", dir);
// recursively update the direction of the child nodes
var chl = node.findTreeChildrenNodes(); // gives us an iterator of the child nodes related to this particular node
while (chl.next()) {
updateNodeDirection(chl.value, dir);
}
}
function addNodeAndLink(e, obj) {
var adorn = obj.part;
var diagram = adorn.diagram;
diagram.startTransaction("Add Node");
var oldnode = adorn.adornedPart;
var olddata = oldnode.data;
// copy the brush and direction to the new node data
var newdata = {
text: "idea",
brush: olddata.brush,
dir: olddata.dir,
parent: olddata.key
};
diagram.model.addNodeData(newdata);
layoutTree(oldnode);
diagram.commitTransaction("Add Node");
// if the new node is off-screen, scroll the diagram to show the new node
var newnode = diagram.findNodeForData(newdata);
if (newnode !== null) diagram.scrollToRect(newnode.actualBounds);
}
function layoutTree(node) {
if (node.data.key === 0) {
// adding to the root?
layoutAll(); // lay out everything
} else {
// otherwise lay out only the subtree starting at this parent node
var parts = node.findTreeParts();
layoutAngle(parts, node.data.dir === "left" ? 180 : 0);
}
}
function layoutAngle(parts, angle) {
var layout = go.GraphObject.make(go.TreeLayout, {
angle: angle,
arrangement: go.TreeLayout.ArrangementFixedRoots,
nodeSpacing: 5,
layerSpacing: 20,
setsPortSpot: false, // don't set port spots since we're managing them with our spotConverter function
setsChildPortSpot: false
});
layout.doLayout(parts);
}
function layoutAll() {
var root = myDiagram.findNodeForKey(0);
if (root === null) return;
myDiagram.startTransaction("Layout");
// split the nodes and links into two collections
var rightward = new go.Set(/*go.Part*/);
var leftward = new go.Set(/*go.Part*/);
root.findLinksConnected().each(function (link) {
var child = link.toNode;
if (child.data.dir === "left") {
leftward.add(root); // the root node is in both collections
leftward.add(link);
leftward.addAll(child.findTreeParts());
} else {
rightward.add(root); // the root node is in both collections
rightward.add(link);
rightward.addAll(child.findTreeParts());
}
});
// do one layout and then the other without moving the shared root node
layoutAngle(rightward, 0);
layoutAngle(leftward, 180);
myDiagram.commitTransaction("Layout");
}
// Show the diagram's model in JSON format
function save() {
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
myDiagram.isModified = false;
}
function load() {
myDiagram.model = go.Model.fromJson(
document.getElementById("mySavedModel").value
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment