I want to make custom choices dropdown for users to edit node and link text.
So far, I used this code to make user select nodes and links text selectable in dropdown list:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>State Chart</title>
<meta name="description" content="A finite state machine chart with editable and interactive features." />
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="{{url_for('static', filename='go.js')}}"></script>
<!-- custom text editors -->
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorRadioButtons.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='DataInspector.js')}}"></script>
<link href="https://gojs.net/latest/extensions/DataInspector.css" rel="stylesheet">
<link href="{{url_for('static', filename='DataInspector.css')}}" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<style>
body {
overflow-x: hidden;
}
#wrapper {
padding-left: 0;
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
}
#wrapper.toggled {
padding-left: 250px;
}
#sidebar-wrapper {
z-index: 1000;
position: fixed;
left: 250px;
width: 0;
height: 100%;
margin-left: -250px;
overflow-y: auto;
background: #000;
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
}
#wrapper.toggled #sidebar-wrapper {
width: 250px;
}
#page-content-wrapper {
width: 100%;
position: absolute;
padding: 15px;
}
#wrapper.toggled #page-content-wrapper {
position: absolute;
margin-right: -250px;
}
/* Sidebar Styles */
.sidebar-nav {
position: absolute;
top: 0;
width: 250px;
margin: 0;
padding: 0;
list-style: none;
}
.sidebar-nav li {
text-indent: 20px;
line-height: 40px;
}
.sidebar-nav li a {
display: block;
text-decoration: none;
color: #999999;
}
.sidebar-nav li a:hover {
text-decoration: none;
color: #fff;
background: rgba(255, 255, 255, 0.2);
}
.sidebar-nav li a:active, .sidebar-nav li a:focus {
text-decoration: none;
}
.sidebar-nav>.sidebar-brand {
height: 65px;
font-size: 18px;
line-height: 60px;
}
.sidebar-nav>.sidebar-brand a {
color: #999999;
}
.sidebar-nav>.sidebar-brand a:hover {
color: #fff;
background: none;
}
#media(min-width:768px) {
#wrapper {
padding-left: 0;
}
#wrapper.toggled {
padding-left: 250px;
}
#sidebar-wrapper {
width: 0;
}
#wrapper.toggled #sidebar-wrapper {
width: 250px;
}
#page-content-wrapper {
padding: 20px;
position: relative;
}
#wrapper.toggled #page-content-wrapper {
position: relative;
margin-right: 0;
}
}
</style>
<script id="code">
var nodeChoices = ['choice 1', 'choice 2', 'choice 3', 'choice 4', 'choice 5'];
const originalNodeChoices = ['choice 1', 'choice 2', 'choice 3', 'choice 4', 'choice 5'];
var linkChoices = ['link choice 1', 'link choice 2', 'link choice 3', 'link choice 4', 'link choice 5'];
const originalLinkChoices = ['link choice 1', 'link choice 2', 'link choice 3', 'link choice 4', 'link choice 5'];
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
// start everything in the middle of the viewport
initialContentAlignment: go.Spot.Center,
// have mouse wheel events zoom in and out instead of scroll up and down
"toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
// support double-click in background creating a new node
"clickCreatingTool.archetypeNodeData": { text: "new node" },
// enable undo & redo
"textEditingTool.defaultTextEditor": window.TextEditorSelectBox,
"undoManager.isEnabled": true,
"layout": new go.ForceDirectedLayout(),
"ModelChanged": function(e) {
//console.log("Diagram model changed!");
if (e.change === go.ChangedEvent.Remove && e.modelChange === "linkDataArray") {
console.log("eee its linkDataArray");
console.log(e);
var linkdata = e.oldValue;
console.log("linkdata");
console.log(linkdata);
var oldstr = linkdata.text;
console.log("oldstr");
console.log(oldstr);
if (!oldstr) return;
var customModelData = e.model.modelData.choices;
var choices = e.model.modelData.choices.linkChoices;
console.log("choices");
console.log(choices);
var idx = choices.indexOf(oldstr);
if (idx < 0) {
console.log("adding choice: " + oldstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.push(oldstr);
e.model.set(e.model.modelData, "choices", newchoices);
}
}else if(e.change === go.ChangedEvent.Remove && e.modelChange === "nodeDataArray"){
console.log("eee its nodeDataArray");
}
/*
else{
console.log("elseeeeeee111!");
console.log("e.change");
console.log(e.change);
console.log("e.modelChange");
console.log(e.modelChange);
}*/
}
});
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
console.log("myDiagram.model.modelData");
console.log(myDiagram.model.modelData);
console.log("myDiagram.model.modelData.choices");
console.log(myDiagram.model.modelData.choices);
// 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);
}
});
myDiagram.addDiagramListener("textEdited", function(e) {
console.log("Text is edited");
console.log(e);
//CHECK IF LINK,
//IF YES REMOVE THAT OPTION FROM LIST
});
myDiagram.addDiagramListener("SelectionDeleting", function(e) {
console.log("inside SelectionDeleting");
console.log(e);
//CHECK IF LINK,
//IF YES PUT THAT OPTION BACK IN OPTION LIST
});
// define the Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// define the node's outer shape, which will surround the TextBlock
$(go.Shape, "RoundedRectangle",
{
parameter1: 20, // the corner has a large radius
fill: $(go.Brush, "Linear", { 0: "rgb(254, 201, 0)", 1: "rgb(254, 162, 0)" }),
stroke: null,
portId: "", // this Shape is the Node's port, not the whole Node
fromLinkable: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableDuplicates: true,
cursor: "pointer"
}),
$(go.TextBlock,
{
font: "bold 11pt helvetica, bold arial, sans-serif",
editable: true, // editing the text automatically updates the model data
//textEditor: window.TextEditorRadioButtons, // defined in textEditorRadioButtons.js
// this specific TextBlock has its own choices:
textEditor: window.TextEditorRadioButtons,
textEdited: function(tb, oldstr, newstr) {
console.log("textEdited nodeeeeeeeeeeeeeeeeeeeeeeeee!!!");
var choices = tb.diagram.model.modelData.choices.nodeChoices;
console.log("choices");
console.log(choices);
console.log("newstr");
console.log(newstr);
console.log("oldstr");
console.log(oldstr);
var idx = choices.indexOf(newstr);
if (idx >= 0 && oldstr !== newstr ) {
console.log(choices);
console.log("choices");
console.log(choices);
console.log("removing choice " + idx + ": " + newstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.splice(idx, 1);
console.log("after remove");
console.log(newchoices);
var currentChoicesFromModel = tb.diagram.model.modelData.choices;
console.log("currentChoicesFromModel");
console.log(currentChoicesFromModel);
currentChoicesFromModel.nodeChoices = newchoices;
console.log("currentChoicesFromModel after update: ");
console.log(currentChoicesFromModel);
tb.diagram.model.set(tb.diagram.model.modelData, "choices", currentChoicesFromModel);
//tb.editable = false; // don't allow choice again
}else{
console.log("elseeeeee");
}
},
//choices: JSON.parse('{{ choices | tojson | safe}}')
choices: nodeChoices
},
new go.Binding("text").makeTwoWay())
);
myDiagram.nodeTemplate.selectionAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Panel, "Auto",
$(go.Shape, { stroke: "dodgerblue", strokeWidth: 2, fill: null }),
$(go.Placeholder)
),
$(go.Panel, "Horizontal",
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom },
$("Button",
{ click: editText }, // defined below, to support editing the text of the node
$(go.TextBlock, "t",
{ font: "bold 10pt sans-serif", desiredSize: new go.Size(15, 15), textAlign: "center" })
),
$("Button",
{ // drawLink is defined below, to support interactively drawing new links
click: drawLink, // click on Button and then click on target node
actionMove: drawLink // drag from Button to the target node
},
$(go.Shape,
{ geometryString: "M0 0 L8 0 8 12 14 12 M12 10 L14 12 12 14" })
),
$("Button",
{
actionMove: dragNewNode, // defined below, to support dragging from the button
_dragData: { text: "?????", color: "lightgray" }, // node data to copy
click: clickNewNode // defined below, to support a click on the button
},
$(go.Shape,
{ geometryString: "M0 0 L3 0 3 10 6 10 x F1 M6 6 L14 6 14 14 6 14z", fill: "gray" })
)
)
);
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
console.log("myDiagram.model.modelData");
console.log(myDiagram.model.modelData);
console.log(myDiagram.model.modelData.choices);
function editText(e, button) {
//console.log(e);
var node = button.part.adornedPart;
console.log("node");
//console.log(node);
e.diagram.commandHandler.editTextBlock(node.findObject("TEXTBLOCK"));
//$("#nodeText").val(node.findObject("TEXTBLOCK"));
}
function drawLink(e, button) {
var node = button.part.adornedPart;
var tool = e.diagram.toolManager.linkingTool;
tool.startObject = node.port;
e.diagram.currentTool = tool;
tool.doActivate();
}
// used by both clickNewNode and dragNewNode to create a node and a link
// from a given node to the new node
function createNodeAndLink(data, fromnode) {
var diagram = fromnode.diagram;
var model = diagram.model;
var nodedata = model.copyNodeData(data);
model.addNodeData(nodedata);
var newnode = diagram.findNodeForData(nodedata);
var linkdata = model.copyLinkData({});
model.setFromKeyForLinkData(linkdata, model.getKeyForNodeData(fromnode.data));
model.setToKeyForLinkData(linkdata, model.getKeyForNodeData(newnode.data));
model.addLinkData(linkdata);
diagram.select(newnode);
return newnode;
}
// the Button.click event handler, called when the user clicks the "N" button
function clickNewNode(e, button) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("Create Node and Link");
var fromnode = button.part.adornedPart;
var newnode = createNodeAndLink(button._dragData, fromnode);
newnode.location = new go.Point(fromnode.location.x + 200, fromnode.location.y);
e.diagram.commitTransaction("Create Node and Link");
}
// the Button.actionMove event handler, called when the user drags within the "N" button
function dragNewNode(e, button) {
var tool = e.diagram.toolManager.draggingTool;
if (tool.isBeyondDragSize()) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("button drag"); // see doDeactivate, below
var newnode = createNodeAndLink(data, button.part.adornedPart);
newnode.location = e.diagram.lastInput.documentPoint;
// don't commitTransaction here, but in tool.doDeactivate, after drag operation finished
// set tool.currentPart to a selected movable Part and then activate the DraggingTool
tool.currentPart = newnode;
e.diagram.currentTool = tool;
tool.doActivate();
}
}
// using dragNewNode also requires modifying the standard DraggingTool so that it
// only calls commitTransaction when dragNewNode started a "button drag" transaction;
// do this by overriding DraggingTool.doDeactivate:
var tool = myDiagram.toolManager.draggingTool;
tool.doDeactivate = function() {
// commit "button drag" transaction, if it is ongoing; see dragNewNode, above
if (tool.diagram.undoManager.nestedTransactionNames.elt(0) === "button drag") {
tool.diagram.commitTransaction();
}
go.DraggingTool.prototype.doDeactivate.call(tool); // call the base method
};
// replace the default Link template in the linkTemplateMap
myDiagram.linkTemplate =
$(go.Link, // the whole link panel
{
curve: go.Link.Bezier,
adjusting: go.Link.Stretch,
reshapable: true,
relinkableFrom: true,
relinkableTo: true,
toShortLength: 3
},
new go.Binding("points").makeTwoWay(),
new go.Binding("curviness"),
$(go.Shape, // the link shape
{ strokeWidth: 1.5 }),
$(go.Shape, // the arrowhead
{ toArrow: "standard", stroke: null }),
$(go.Panel, "Auto",
$(go.Shape, // the label background, which becomes transparent around the edges
{
fill: $(go.Brush, "Radial", { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),
stroke: null
}),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
var choices = tb.diagram.model.modelData.choices.linkChoices;
console.log("choices");
console.log(choices);
console.log("newstr");
console.log(newstr);
console.log("oldstr");
console.log(oldstr);
var idx = choices.indexOf(newstr);
if (idx >= 0 && oldstr !== newstr ) {
console.log(choices);
console.log("choices");
console.log(choices);
console.log("removing choice " + idx + ": " + newstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.splice(idx, 1);
tb.diagram.model.set(tb.diagram.model.modelData.choices.linkChoices, "choices", newchoices);
//tb.editable = false; // don't allow choice again
}else{
console.log("elseeeeee");
}
}
},
// editing the text automatically updates the model data
//new go.Binding("text"),
new go.Binding("text").makeTwoWay(),
new go.Binding("choices").ofModel())
)
);
var inspector = new Inspector('myInspectorDiv', myDiagram,
{
// uncomment this line to only inspect the named properties below instead of all properties on each object:
// includesOwnProperties: false,
properties: {
"text": { },
// an example of specifying the type
"password": { show: Inspector.showIfPresent, type: 'password' },
// key would be automatically added for nodes, but we want to declare it read-only also:
"key": { readOnly: true, show: Inspector.showIfPresent },
// color would be automatically added for nodes, but we want to declare it a color also:
"color": { show: Inspector.showIfPresent, type: 'color' },
// Comments and LinkComments are not in any node or link data (yet), so we add them here:
"Comments": { show: Inspector.showIfNode },
"flag": { show: Inspector.showIfNode, type: 'checkbox' },
"LinkComments": { show: Inspector.showIfLink },
"isGroup": { readOnly: true, show: Inspector.showIfPresent }
}
});
// read in the JSON data from flask
loadGraphData();
}
function loadGraphData() {
var graphDataString = JSON.parse('{{ diagramData | tojson | safe}}');
//console.log("graphDataString");
//console.log(graphDataString);
var allChoices = JSON.parse('{{ allChoices | tojson | safe}}');
console.log("allChoices");
console.log(allChoices);
myDiagram.model = go.Model.fromJson(graphDataString);
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
var customModelData = {
"linkChoices": linkChoices, "nodeChoices": nodeChoices
};
myDiagram.model.set(myDiagram.model.modelData, "choices", allChoices);
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", customModelData);
console.log("whole model modelData");
console.log(myDiagram.model.modelData);
}
function saveGraphData(form, event) {
console.log("inside saveGraphData");
event.preventDefault();
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
form.submit();
}
function zoomToFit(){
console.log("inside zoomToFit");
myDiagram.zoomToRect(myDiagram.documentBounds);
}
function zoomIn(){
console.log("inside zoomIn");
myDiagram.commandHandler.increaseZoom();
}
function zoomOut(){
console.log("inside zoomOut");
myDiagram.commandHandler.decreaseZoom();
}
</script>
</head>
<body onload="init()">
<div id="wrapper">
<!-- Sidebar -->
<div id="sidebar-wrapper">
<ul class="sidebar-nav">
<li class="sidebar-brand"> CUSTOM CHOICES MODE </li>
<li> Free editing mode </li>
<li> Fill mode </li>
<li> Custom choices mode </li>
<li> Locked choices mode </li>
</ul>
</div>
<!-- /#sidebar-wrapper -->
<!-- Page Content -->
<div id="page-content-wrapper">
<div class="container-fluid">
<div id=formWrapper style="padding: 30px;">
Toggle Menu
<form method="POST" action="http://localhost:5000/updateResultFile" name="updateResultFileForm"
id="updateResultFileForm"
onsubmit="saveGraphData(this, event);">
<div id="graphWrapper" style="margin-bottom: 15px;">
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 800px;margin-bottom: 15px;"></div>
<div style="display: none;"><input id="mySavedModel" name="mySavedModel"></div>
<button class="btn btn-default" type="submit"> Save <i class="fa fa-save"> </i> </button>
</div>
</form>
<div id="myInspectorDiv">
</div>
<div>
<button class="btn btn-default" onclick="zoomToFit()"> Zoom to fit <i class="fa fa-search"> </i> </button>
<button class="btn btn-default" onclick="zoomIn()"> Zoom in <i class="fa fa-search-plus"> </i> </button>
<button class="btn btn-default" onclick="zoomOut()"> Zoom out <i class="fa fa-search-minus"> </i> </button>
</div>
</div>
</div>
</div>
</div>
<script src="{{url_for('static', filename='jquery.min.js')}}"></script>
<script src="{{url_for('static', filename='bootstrap.bundle.min.js')}}"></script>
<!-- Menu Toggle Script -->
<script>
var menuOpen = false;
$("#menu-toggle").click(function(e) {
e.preventDefault();
$("#wrapper").toggleClass("toggled");
if(menuOpen){
menuOpen = false;
$("#myDiagramDiv").css({border: "solid 1px black"});
}else{
menuOpen = true;
$("#myDiagramDiv").css({border: ""});
}
});
</script>
</body>
</html>
So basicly I have made custom choices for nodes and links. For example, for node I have these choices:
node choice 1
node choice 2
node choice 3
and for links I have these choices:
link choice 1
link choice 2
link choice 3
What I want to accomplish is that when I select "node choice 1" from dropdown list on one node, that that option "node choice 1" can not be selected again for another node. But if user deletes node, or sets empty selection on node text, "node choice 1" must be available again.
Same thing goes for links.
I think my code works fine for links, but I have problems with detecting node text changes and updating dataModel content.
I seem to have missed this before. Here's some code that answers your question by implementing it:
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
initialContentAlignment: go.Spot.Center,
"undoManager.isEnabled": true,
"ModelChanged": function(e) { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
if (e.change === go.ChangedEvent.Remove) {
if (e.modelChange === "linkDataArray") {
var linkdata = e.oldValue;
var oldstr = linkdata.text;
if (!oldstr) return;
var linkchoices = e.model.modelData.linkchoices;
var idx = linkchoices.indexOf(oldstr);
if (idx < 0) {
console.log("deletion adding link choice: " + oldstr);
var newlinkchoices = Array.prototype.slice.call(linkchoices);
newlinkchoices.push(oldstr);
e.model.set(e.model.modelData, "linkchoices", newlinkchoices);
}
} else if (e.modelChange === "nodeDataArray") {
var nodedata = e.oldValue;
var oldstr = nodedata.text;
if (!oldstr) return;
var nodechoices = e.model.modelData.nodechoices;
var idx = nodechoices.indexOf(oldstr);
if (idx < 0) {
console.log("deletion adding node choice: " + oldstr);
var newnodechoices = Array.prototype.slice.call(nodechoices);
newnodechoices.push(oldstr);
e.model.set(e.model.modelData, "nodechoices", newnodechoices);
}
}
}
}
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{
margin: 8,
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
if (oldstr !== newstr) {
console.log("changing from " + oldstr + " to " + newstr);
var model = tb.diagram.model;
var nodechoices = model.modelData.nodechoices;
if (!nodechoices) {
nodechoices = [];
model.set(model.modelData, "nodechoices", nodechoices);
}
var idx = nodechoices.indexOf(newstr);
if (idx >= 0) {
console.log("removing node choice " + idx + ": " + newstr);
model.removeArrayItem(nodechoices, idx);
}
if (oldstr) {
console.log(" adding node choice " + oldstr);
model.addArrayItem(nodechoices, oldstr);
}
}
}
},
new go.Binding("text").makeTwoWay(),
new go.Binding("choices", "nodechoices").ofModel())
);
myDiagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "OpenTriangle" }),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
if (oldstr !== newstr) {
console.log("changing from " + oldstr + " to " + newstr);
var model = tb.diagram.model;
var linkchoices = model.modelData.linkchoices;
if (!linkchoices) {
linkchoices = [];
model.set(model.modelData, "linkchoices", linkchoices);
}
var idx = linkchoices.indexOf(newstr);
if (idx >= 0) {
console.log("removing link choice " + idx + ": " + newstr);
model.removeArrayItem(linkchoices, idx);
}
if (oldstr) {
console.log(" adding link choice " + oldstr);
model.addArrayItem(linkchoices, oldstr);
}
}
}
},
new go.Binding("text").makeTwoWay(),
new go.Binding("choices", "linkchoices").ofModel())
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue" },
{ key: 2, text: "Beta", color: "orange" },
{ key: 3, text: "Gamma", color: "lightgreen" },
{ key: 4, text: "Delta", color: "pink" }
],
[
{ from: 1, to: 2, text: "one" },
{ from: 1, to: 3, text: "two" },
{ from: 3, to: 4, text: "three" }
]);
myDiagram.model.set(myDiagram.model.modelData, "nodechoices", ["Epsilon"]);
myDiagram.model.set(myDiagram.model.modelData, "linkchoices", ["four"]);
}
Related
I am testing the GoJS library for Javascript.
I'm looking to create ports on the sides of a group.
I attach an executable extract from the program.
When I create a new object from the palette, right-click on the object, and select "Add left port", the debugging message shows that the variable "arr" is declared "undefined" at execution. (program lines 111 and 112).
Thank you for your help.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dynamic Ports</title>
<meta name="description" content="Nodes with varying lists of ports on each of four sides." />
<!-- Copyright 1998-2017 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="../release/go-debug.js"></script>
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
<!-- LL <script>goCode("diagramEvents", 600, 200)</script>-->
<script src="/Users/leonlevy/gojs/site/extensionsTS/ResizeMultipleScript.js"></script>
<span id="diagramEventsMsg" style="color: red"></span>
<span id="changeMethodsMsg" style="color: red"></span>
<span id="BackgroundDoubleClicked" style="color: red"></span>
<p>
<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 node templates
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
allowDrop: true, // from Palette
// what to do when a drag-drop occurs in the Diagram's background
mouseDrop: function(e) { finishDrop(e, null); },
// layout: null, // Diagram has no layout
/* $(go.GridLayout,
{ wrappingWidth: Infinity, alignment: go.GridLayout.Position, cellSize: new go.Size(1, 1) }),*/
initialContentAlignment: go.Spot.Center,
"commandHandler.archetypeGroupData": { isGroup: true, category: "OfGroups" },
"undoManager.isEnabled": true
});
// LL
function showMessage(s) {
document.getElementById("diagramEventsMsg").textContent = s;
}
// 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);
}
});
myDiagram.addDiagramListener("ObjectSingleClicked",
function(e) {
var part = e.subject.part;
var heightPart = part.actualBounds.height;
var widthPart = part.actualBounds.width;
var xPart = part.actualBounds.position.x;
var yPart = part.actualBounds.position.y;
var xMouseClic = myDiagram.lastInput.documentPoint.x;
var yMouseClic = myDiagram.lastInput.documentPoint.y;
var xPartMiddle = xPart+ widthPart/2;
var side = xMouseClic<= xPartMiddle ? "Left" : "Right";
if (!(part instanceof go.Link)) {console.log("hello leon");}
});
myDiagram.addDiagramListener("BackgroundSingleClicked",
function(e) {
showMessage("Clicked on background " + myDiagram.lastInput.documentPoint);
//console.log("hello leon");}
});
myDiagram.addDiagramListener("BackgroundDoubleClicked", // LL KO
function(e) {
showMessage("Double-clicked at " );//+ e.myDiagram.lastInput.documentPoint);
//showMessage("Clicked on " + myDiagram.lastInput.documentPoint);
});
myDiagram.addDiagramListener("ClipboardPasted",
function(e) { showMessage("Pasted " + e.myDiagram.selection.count + " parts"); });
// LL end
// To simplify this code we define a function for creating a context menu button:
function makeButton(text, action, visiblePredicate) {
return $("ContextMenuButton",
$(go.TextBlock, text),
{ click: action },
//{showMessage( "button: " + myDiagram.lastInput.button);
// don't bother with binding GraphObject.visible if there's no predicate
visiblePredicate ? new go.Binding("visible", "", function(o, e) { return o.myDiagram ? visiblePredicate(o, e) : false; }).ofObject() : {});
//}
}
var nodeMenu = // context menu for each Node
$(go.Adornment, "Horizontal",
makeButton("Add left port",
function (e, obj) { addPort("left"); }),
makeButton("Add right port",
function (e, obj) { addPort("right"); }),
); //Leon end nodeM
// Add a port to the specified side of the selected nodes.
function addPort(side) {
myDiagram.startTransaction("addPort");
myDiagram.selection.each(function(node) {
// skip any selected Links
if (!(node instanceof go.Node)) return;
// compute the next available index number for the side
var i = 0;
while (node.findPort(side + i.toString()) !== node) i++;
// now this new port name is unique within the whole Node because of the side prefix
var name = side + i.toString();
// get the Array of port data to be modified
var arr = node.data[side + "Array"];
showMessage ("node: " + node + "; name: " + name + ";arr: " + arr + ";node.data: " + node.data);
if (arr) {
// create a new port data object
var newportdata = {
portId: name,
portColor: go.Brush.randomColor()
// if you add port data properties here, you should copy them in copyPortData above
};
// and add it to the Array of port data
myDiagram.model.insertArrayItem(arr, -1, newportdata);
}
});
myDiagram.commitTransaction("addPort");
}
var portSize = new go.Size(8, 8);
// this function is used to highlight a Group that the selection may be dropped into
function highlightGroup(e, grp, show) {
if (!grp) return; showMessage("ok")
e.handled = true;
if (show) {
// cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
// instead depend on the DraggingTool.draggedParts or .copiedParts
var tool = grp.diagram.toolManager.draggingTool;
var map = tool.draggedParts || tool.copiedParts; // this is a Map
// now we can check to see if the Group will accept membership of the dragged Parts
if (grp.canAddMembers(map.toKeySet())) {
grp.isHighlighted = true;
return;
}
}
grp.isHighlighted = false;
}
// Upon a drop onto a Group, we try to add the selection as members of the Group.
// Upon a drop onto the background, or onto a top-level Node, make selection top-level.
// If this is OK, we're done; otherwise we cancel the operation to rollback everything.
function finishDrop(e, grp) {
var ok = (grp !== null
? grp.addMembers(grp.diagram.selection, true)
: e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
if (!ok) e.diagram.currentTool.doCancel();
}
var portMenu = // context menu for each port
$(go.Adornment, "Vertical",
makeButton("Remove port",
// in the click event handler, the obj.part is the Adornment;
// its adornedObject is the port
function (e, obj) { removePort(obj.part.adornedObject); }),
makeButton("Change color",
function (e, obj) { changeColor(obj.part.adornedObject); }),
makeButton("Remove side ports",
function (e, obj) { removeAll(obj.part.adornedObject); })
);
// support double-clicking in the background to add a copy of this data as a node
myDiagram.toolManager.clickCreatingTool.archetypeNodeData = {
name: "Unit",
leftArray: [],
rightArray: [],
topArray: [],
bottomArray: []
};
myDiagram.groupTemplateMap.add("OfGroups",
$(go.Group, "Table",
{ locationObjectName: "BODY",
locationSpot: go.Spot.Center,
selectionObjectName: "BODY",
contextMenu: nodeMenu
},
{
background: "transparent",
resizable: true,
// highlight when dragging into the Group
mouseDragEnter: function(e, grp, prev) { highlightGroup(e, grp, true); },
mouseDragLeave: function(e, grp, next) { highlightGroup(e, grp, false); },
//computesBoundsAfterDrag: true,
mouseDrop: finishDrop,
resizable: true, resizeObjectName: "SHAPE"
//, minSize: new go.Size(36, 46)
},
new go.Binding("background", "isHighlighted", function(h) { return h ? "rgba(0,0,255,0.)" : "transparent"; }).ofObject(),
// save the modified size in the model node data
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "Rectangle",
{ fill: null, stroke: "rgba(0,50,255,1)", strokeWidth: 3, name: "SHAPE"}),
$(go.Panel, "Vertical", {alignment: go.Spot.Top},// title above Placeholder
$(go.Panel, "Horizontal", // button next to TextBlock
{ stretch: go.GraphObject.Horizontal, background: "rgba(0,0,255,0.1)" },
$("SubGraphExpanderButton",
{ alignment: go.Spot.Right, margin: 5 }),
$(go.TextBlock,
{ //text: "verticalAlignment: Top", verticalAlignment: go.Spot.Top,
alignment: go.Spot.Left,
editable: true,
margin: 5,
font: "bold 18px sans-serif",
opacity: 0.75,
stroke: "#404040"
},
new go.Binding("text", "text").makeTwoWay())
), // end Horizontal Panel
$(go.Placeholder,
{ padding: 5, alignment: go.Spot.TopLeft })
), // end Vertical Panel
// the Panel holding the left port elements, which are themselves Panels,
// created for each item in the itemArray, bound to data.leftArray
$(go.Panel, "Vertical",
new go.Binding("itemArray", "leftArray"),
{ row: 1, column: 0,
itemTemplate:
$(go.Panel,
{ _side: "left", // internal property to make it easier to tell which side it's on
fromSpot: go.Spot.Left, toSpot: go.Spot.Left,
fromLinkable: true, toLinkable: true, cursor: "pointer",
contextMenu: portMenu },
new go.Binding("portId", "portId"),
$(go.Shape, "Rectangle",
{ stroke: null, strokeWidth: 0,
desiredSize: portSize,
margin: new go.Margin(1,0) },
new go.Binding("fill", "portColor"))
) // end itemTemplate
}
), // end Vertical Panel
// the Panel holding the right port elements, which are themselves Panels,
// created for each item in the itemArray, bound to data.rightArray
$(go.Panel, "Vertical",
new go.Binding("itemArray", "rightArray"),
{ row: 1, column: 2,
itemTemplate:
$(go.Panel,
{ _side: "right",
fromSpot: go.Spot.Right, toSpot: go.Spot.Right,
fromLinkable: true, toLinkable: true, cursor: "pointer",
contextMenu: portMenu },
new go.Binding("portId", "portId"),
$(go.Shape, "Rectangle",
{ stroke: null, strokeWidth: 0,
desiredSize: portSize,
margin: new go.Margin(1, 0) },
new go.Binding("fill", "portColor"))
) // end itemTemplate
}
), // end Vertical Panel
)
); // end groupTemplateMap.add("OfGroups"
// initialize the Palette and its contents
myPalette =
$(go.Palette, "myPaletteDiv",
{
groupTemplateMap: myDiagram.groupTemplateMap,
layout: $(go.GridLayout, { wrappingColumn: 1, alignment: go.GridLayout.Position })
});
myPalette.nodeTemplate =
$(go.Node, "Horizontal",
$(go.Shape,
{ width: 10, height: 10, fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
new go.Binding("text", "color"))
);
myPalette.model = new go.GraphLinksModel([
{ key: 101, text: 'UNIT', isGroup: true, isSubProcess: true,
category: "OfGroups", isAdHoc: true, loc: '0 0' },
]);
}
</script>
</head>
<body onload="init()">
<!-- LL -->
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<div id="sample">
<div style="width: 100%; display: flex; justify-content: space-between">
<div id="myPaletteDiv" style="width: 100px; margin-right: 2px; background-color: whitesmoke; border: solid 1px black"></div>
<div id="myDiagramDiv" style="flex-grow: 1; height: 480px; border: solid 1px black"></div>
</div>
<p>
See the Ports Intro page for an explanation of GoJS ports.
</p>
<textarea id="mySavedModel" style="width:100%;height:250px">
</textarea>
</div>
</div>
</body>
</html>
You need to make sure that the node is bound to the correct data. In particular, the data object should have a leftArray property that is an Array holding port descriptor objects. (And it seems to expect a rightArray property too.) You can determine the expected properties for each Node by looking at the source property names of each of the Bindings that are in the node template.
You can determine the expected properties for each of the port descriptors in either data Array by looking at the properties that the Panel.itemTemplate uses in its data Bindings.
I'll need to use correct component to implement this https://imgur.com/a/P2VHX2i . I'm currently looking at TreeList Examples ( https://demos.telerik.com/kendo-ui/treelist/remote-data-binding ) using json data from server. Can you give some advice on creating this treelist?
I did a PoC once for a similar scenario like yours. I've simulated a TreeList with a Grid, and it worked:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Untitled</title>
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.3.917/styles/kendo.common.min.css">
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.3.917/styles/kendo.rtl.min.css">
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.3.917/styles/kendo.default.min.css">
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.3.917/styles/kendo.mobile.all.min.css">
<style type="text/css">
.item-level {
display: inline-block;
width: 10px;
}
.level-arrow-expanded:before {
content: "\00bb"
}
.level-arrow-collapsed:before {
content: "\00ab"
}
.item-cell {
cursor: pointer
}
td[role="gridcell"] {
padding: 0;
}
.cell-container {
display: inline-block;
padding-bottom: 6.4px;
padding-left: 9.6px;
padding-right: 9.6px;
padding-top: 6.4px;
box-sizing: border-box;
width: 100%;
}
.header-container {
padding: 0
}
.header-editable-cell {
color: #9cc3e5;
font-weight: bold
}
.k-grid tr:hover td .cell-container {
background-color: #bdb4af !important
}
</style>
<script src="https://code.jquery.com/jquery-1.12.3.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2019.3.917/js/angular.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2019.3.917/js/jszip.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2019.3.917/js/kendo.all.min.js"></script>
<script id="line-template" type="text/x-kendo-template">
<div class='cell-container'>
<div class='item-level #= (data.Level < 4 ? "level-arrow-" + (data.Collapsed ? "collapsed" : "expanded") : "") #'></div>
# for (let i = -1; i < (data.Level - 1); i++) { #
<div class='item-level level-space'></div>
# } #
#= data.Line #
</div>
</script>
<script>
$(function() {
const lineTemplate = $("#line-template").html();
let data = [];
let cols = [{
title: " ",
field: "Line",
locked: true,
width: 200,
template: lineTemplate,
attributes: { "class": "item-cell" }
}, {
title: "Customer Type",
field: "CustomerType",
width: 300
}];
for (var n = 0; n < 50000; n++) {
let level = (n % 5),
dataItem = {
Line: n,
CustomerType: (n % 2 == 0 ? "All" : "3rd Party"),
Level: level,
Show: true,
Index: n,
Collapsed: false
};
data.push(dataItem);
}
let grid = $("#grid").kendoGrid({
dataSource: {
data: data,
pageSize: 20,
filter: { field: "Show", operator: "eq", value: true }
},
height: 500,
columns: cols,
scrollable: {
virtual: true
}
}).data("kendoGrid");
grid.lockedTable.on("click", "td.item-cell", function() {
let data = grid.dataSource.data(),
dataItem = grid.dataItem($(this).closest("tr")),
item;
for (let i = 0, count = data.length; i < count; i++) {
item = data[i];
if (item.Index == dataItem.Index) {
dataItem.Collapsed = !dataItem.Collapsed;
}
else if (item.Index > dataItem.Index) {
if (dataItem.Index != i && item.Level == 0) {
break;
}
item.Show = !dataItem.Collapsed;
}
}
grid.dataSource.fetch();
});
});
</script>
</head>
<body>
<div id="grid"></div>
</body>
</html>
Demo in Dojo
Why I did that? To work with huge datasets which I don't know if is your case tho. As you can see, the above TreeList is displaying 50k rows, virtualized. The problem is that TreeList does not virtualizes(check out it's docs), but Grid does.
How that works:
Lock the first column to be the collpase/expand column. You can also lock more columns;
In your dataItems(row's data), you have to use some control properties, which are: Collapsed, Level, Show and Index. They control the collpase/expand behaviour and the hierarchical display. Add a console.log(data) before Grid's initialization to checkout how it's been filled;
The Show property handles the collpase/expand displaying status. Once a row is Show = false, it will be hidden. That works because of a filter in the dataSource: filter: { field: "Show", operator: "eq", value: true };
A click event on the item-cell class manages the row's children display status. That class is added in the first column as: attributes: { "class": "item-cell" }. The event changes the Show property for the children rows;
The template line-template will display the right level indetation and the arrows for the collpase/expand column;
I have simple python flask app where I send JSON data to my HTML and with goJS I display my graph which looks like this:
I made custom choices dropdown for users to edit node and link text. Those choice options are read from .txt file and sent to html via flask. Options in dropdown lists are made so that when option is selected once, it can not be selected again, until user delete node or link with that used option, and then he can use that option again. So far, I used this code to make nodes text selectable in dropdown list:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>State Chart</title>
<meta name="description" content="A finite state machine chart with editable and interactive features." />
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="{{url_for('static', filename='go.js')}}"></script>
<!-- custom text editors -->
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorRadioButtons.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='DataInspector.js')}}"></script>
<link href="https://gojs.net/latest/extensions/DataInspector.css" rel="stylesheet">
<link href="{{url_for('static', filename='DataInspector.css')}}" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script id="code">
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
// start everything in the middle of the viewport
initialContentAlignment: go.Spot.Center,
// have mouse wheel events zoom in and out instead of scroll up and down
"toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
// support double-click in background creating a new node
"clickCreatingTool.archetypeNodeData": { text: "new node" },
// enable undo & redo
"textEditingTool.defaultTextEditor": window.TextEditorSelectBox,
"undoManager.isEnabled": true,
"layout": new go.ForceDirectedLayout(),
"ModelChanged": function(e) {
console.log("Diagram model changed!");
if (e.change === go.ChangedEvent.Remove && e.modelChange === "linkDataArray") {
console.log("eee");
console.log(e);
var linkdata = e.oldValue;
console.log("linkdata");
console.log(linkdata);
var oldstr = linkdata.text;
console.log("oldstr");
console.log(oldstr);
if (!oldstr) return;
var choices = e.model.modelData.choices;
console.log("choices");
console.log(choices);
var idx = choices.indexOf(oldstr);
if (idx < 0) {
console.log("adding choice: " + oldstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.push(oldstr);
e.model.set(e.model.modelData, "choices", newchoices);
}
}
}
});
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
console.log("myDiagram.model.modelData");
console.log(myDiagram.model.modelData);
console.log("myDiagram.model.modelData.choices");
console.log(myDiagram.model.modelData.choices);
// 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);
}
});
myDiagram.addDiagramListener("textEdited", function(e) {
console.log("Text is edited");
console.log(e);
//CHECK IF LINK,
//IF YES REMOVE THAT OPTION FROM LIST
});
myDiagram.addDiagramListener("SelectionDeleting", function(e) {
console.log("inside SelectionDeleting");
console.log(e);
//CHECK IF LINK,
//IF YES PUT THAT OPTION BACK IN OPTION LIST
});
// define the Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// define the node's outer shape, which will surround the TextBlock
$(go.Shape, "RoundedRectangle",
{
parameter1: 20, // the corner has a large radius
fill: $(go.Brush, "Linear", { 0: "rgb(254, 201, 0)", 1: "rgb(254, 162, 0)" }),
stroke: null,
portId: "", // this Shape is the Node's port, not the whole Node
fromLinkable: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableDuplicates: true,
cursor: "pointer"
}),
$(go.TextBlock,
{
font: "bold 11pt helvetica, bold arial, sans-serif",
editable: true, // editing the text automatically updates the model data
//textEditor: window.TextEditorRadioButtons, // defined in textEditorRadioButtons.js
// this specific TextBlock has its own choices:
textEditor: window.TextEditorRadioButtons,
//choices: JSON.parse('{{ choices | tojson | safe}}')
choices: nodeChoices
},
new go.Binding("text").makeTwoWay())
);
myDiagram.nodeTemplate.selectionAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Panel, "Auto",
$(go.Shape, { stroke: "dodgerblue", strokeWidth: 2, fill: null }),
$(go.Placeholder)
),
$(go.Panel, "Horizontal",
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom },
$("Button",
{ click: editText }, // defined below, to support editing the text of the node
$(go.TextBlock, "t",
{ font: "bold 10pt sans-serif", desiredSize: new go.Size(15, 15), textAlign: "center" })
),
$("Button",
{ // drawLink is defined below, to support interactively drawing new links
click: drawLink, // click on Button and then click on target node
actionMove: drawLink // drag from Button to the target node
},
$(go.Shape,
{ geometryString: "M0 0 L8 0 8 12 14 12 M12 10 L14 12 12 14" })
),
$("Button",
{
actionMove: dragNewNode, // defined below, to support dragging from the button
_dragData: { text: "?????", color: "lightgray" }, // node data to copy
click: clickNewNode // defined below, to support a click on the button
},
$(go.Shape,
{ geometryString: "M0 0 L3 0 3 10 6 10 x F1 M6 6 L14 6 14 14 6 14z", fill: "gray" })
)
)
);
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
console.log("myDiagram.model.modelData");
console.log(myDiagram.model.modelData);
console.log(myDiagram.model.modelData.choices);
function editText(e, button) {
//console.log(e);
var node = button.part.adornedPart;
console.log("node");
//console.log(node);
e.diagram.commandHandler.editTextBlock(node.findObject("TEXTBLOCK"));
//$("#nodeText").val(node.findObject("TEXTBLOCK"));
}
function drawLink(e, button) {
var node = button.part.adornedPart;
var tool = e.diagram.toolManager.linkingTool;
tool.startObject = node.port;
e.diagram.currentTool = tool;
tool.doActivate();
}
// used by both clickNewNode and dragNewNode to create a node and a link
// from a given node to the new node
function createNodeAndLink(data, fromnode) {
var diagram = fromnode.diagram;
var model = diagram.model;
var nodedata = model.copyNodeData(data);
model.addNodeData(nodedata);
var newnode = diagram.findNodeForData(nodedata);
var linkdata = model.copyLinkData({});
model.setFromKeyForLinkData(linkdata, model.getKeyForNodeData(fromnode.data));
model.setToKeyForLinkData(linkdata, model.getKeyForNodeData(newnode.data));
model.addLinkData(linkdata);
diagram.select(newnode);
return newnode;
}
// the Button.click event handler, called when the user clicks the "N" button
function clickNewNode(e, button) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("Create Node and Link");
var fromnode = button.part.adornedPart;
var newnode = createNodeAndLink(button._dragData, fromnode);
newnode.location = new go.Point(fromnode.location.x + 200, fromnode.location.y);
e.diagram.commitTransaction("Create Node and Link");
}
// the Button.actionMove event handler, called when the user drags within the "N" button
function dragNewNode(e, button) {
var tool = e.diagram.toolManager.draggingTool;
if (tool.isBeyondDragSize()) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("button drag"); // see doDeactivate, below
var newnode = createNodeAndLink(data, button.part.adornedPart);
newnode.location = e.diagram.lastInput.documentPoint;
// don't commitTransaction here, but in tool.doDeactivate, after drag operation finished
// set tool.currentPart to a selected movable Part and then activate the DraggingTool
tool.currentPart = newnode;
e.diagram.currentTool = tool;
tool.doActivate();
}
}
// using dragNewNode also requires modifying the standard DraggingTool so that it
// only calls commitTransaction when dragNewNode started a "button drag" transaction;
// do this by overriding DraggingTool.doDeactivate:
var tool = myDiagram.toolManager.draggingTool;
tool.doDeactivate = function() {
// commit "button drag" transaction, if it is ongoing; see dragNewNode, above
if (tool.diagram.undoManager.nestedTransactionNames.elt(0) === "button drag") {
tool.diagram.commitTransaction();
}
go.DraggingTool.prototype.doDeactivate.call(tool); // call the base method
};
// replace the default Link template in the linkTemplateMap
myDiagram.linkTemplate =
$(go.Link, // the whole link panel
{
curve: go.Link.Bezier,
adjusting: go.Link.Stretch,
reshapable: true,
relinkableFrom: true,
relinkableTo: true,
toShortLength: 3
},
new go.Binding("points").makeTwoWay(),
new go.Binding("curviness"),
$(go.Shape, // the link shape
{ strokeWidth: 1.5 }),
$(go.Shape, // the arrowhead
{ toArrow: "standard", stroke: null }),
$(go.Panel, "Auto",
$(go.Shape, // the label background, which becomes transparent around the edges
{
fill: $(go.Brush, "Radial", { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),
stroke: null
}),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
var choices = tb.diagram.model.modelData.choices;
console.log("choices");
console.log(choices);
console.log("newstr");
console.log(newstr);
console.log("oldstr");
console.log(oldstr);
var idx = choices.indexOf(newstr);
if (idx >= 0 && oldstr !== newstr) {
console.log(choices);
console.log("choices");
console.log(choices);
console.log("removing choice " + idx + ": " + newstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.splice(idx, 1);
tb.diagram.model.set(tb.diagram.model.modelData, "choices", newchoices);
tb.editable = false; // don't allow choice again
}
}
},
// editing the text automatically updates the model data
//new go.Binding("text"),
new go.Binding("text").makeTwoWay(),
new go.Binding("choices").ofModel())
)
);
var inspector = new Inspector('myInspectorDiv', myDiagram,
{
// uncomment this line to only inspect the named properties below instead of all properties on each object:
// includesOwnProperties: false,
properties: {
"text": { },
// an example of specifying the type
"password": { show: Inspector.showIfPresent, type: 'password' },
// key would be automatically added for nodes, but we want to declare it read-only also:
"key": { readOnly: true, show: Inspector.showIfPresent },
// color would be automatically added for nodes, but we want to declare it a color also:
"color": { show: Inspector.showIfPresent, type: 'color' },
// Comments and LinkComments are not in any node or link data (yet), so we add them here:
"Comments": { show: Inspector.showIfNode },
"flag": { show: Inspector.showIfNode, type: 'checkbox' },
"LinkComments": { show: Inspector.showIfLink },
"isGroup": { readOnly: true, show: Inspector.showIfPresent }
}
});
// read in the JSON data from flask
loadGraphData();
}
function loadGraphData() {
var graphDataString = JSON.parse('{{ diagramData | tojson | safe}}');
//console.log("graphDataString");
//console.log(graphDataString);
myDiagram.model = go.Model.fromJson(graphDataString);
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
console.log("whole model");
console.log(myDiagram.model);
}
function saveGraphData(form, event) {
console.log("inside saveGraphData");
event.preventDefault();
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
form.submit();
}
function zoomToFit(){
console.log("inside zoomToFit");
myDiagram.zoomToRect(myDiagram.documentBounds);
}
function zoomIn(){
console.log("inside zoomIn");
myDiagram.commandHandler.increaseZoom();
}
function zoomOut(){
console.log("inside zoomOut");
myDiagram.commandHandler.decreaseZoom();
}
</script>
</head>
<body onload="init()">
<div id=formWrapper style="padding: 30px;">
<form method="POST" action="http://localhost:5000/updateResultFile" name="updateResultFileForm"
id="updateResultFileForm"
onsubmit="saveGraphData(this, event);">
<div id="graphWrapper" style="margin-bottom: 15px;">
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 800px;margin-bottom: 15px;"></div>
<div style="display: none;"><input id="mySavedModel" name="mySavedModel"></div>
<button class="btn btn-default" type="submit"> Save <i class="fa fa-save"> </i> </button>
</div>
</form>
<div id="myInspectorDiv">
</div>
<div>
<button class="btn btn-default" onclick="zoomToFit()"> Zoom to fit <i class="fa fa-search"> </i> </button>
<button class="btn btn-default" onclick="zoomIn()"> Zoom in <i class="fa fa-search-plus"> </i> </button>
<button class="btn btn-default" onclick="zoomOut()"> Zoom out <i class="fa fa-search-minus"> </i> </button>
</div>
</div>
</body>
</html>
Now I want to make when the user used all options for links and none of them is available to use, I would like to disable availability to add a new link. Also if he uses all options for node texts, I would like to disable the possibility to add new nodes. But, if the user deletes some node and that option for node text is free again, I would like to enable him the possibility to add new nodes with that option but I don't have any idea how to make diagram disable his core part to add new nodes or links?
Also, it is somehow hard to make double click on link text to make dropdown with options appear. Is there any trick or option to make it easier to use?
I didn't realize you had asked this so many times.
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
initialContentAlignment: go.Spot.Center,
"undoManager.isEnabled": true,
"ModelChanged": function(e) { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
if (e.change === go.ChangedEvent.Remove) {
if (e.modelChange === "linkDataArray") {
var linkdata = e.oldValue;
var oldstr = linkdata.text;
if (!oldstr) return;
var linkchoices = e.model.modelData.linkchoices;
var idx = linkchoices.indexOf(oldstr);
if (idx < 0) {
console.log("deletion adding link choice: " + oldstr);
var newlinkchoices = Array.prototype.slice.call(linkchoices);
newlinkchoices.push(oldstr);
e.model.set(e.model.modelData, "linkchoices", newlinkchoices);
}
} else if (e.modelChange === "nodeDataArray") {
var nodedata = e.oldValue;
var oldstr = nodedata.text;
if (!oldstr) return;
var nodechoices = e.model.modelData.nodechoices;
var idx = nodechoices.indexOf(oldstr);
if (idx < 0) {
console.log("deletion adding node choice: " + oldstr);
var newnodechoices = Array.prototype.slice.call(nodechoices);
newnodechoices.push(oldstr);
e.model.set(e.model.modelData, "nodechoices", newnodechoices);
}
}
}
}
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{
margin: 8,
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
if (oldstr !== newstr) {
console.log("changing from " + oldstr + " to " + newstr);
var model = tb.diagram.model;
var nodechoices = model.modelData.nodechoices;
if (!nodechoices) {
nodechoices = [];
model.set(model.modelData, "nodechoices", nodechoices);
}
var idx = nodechoices.indexOf(newstr);
if (idx >= 0) {
console.log("removing node choice " + idx + ": " + newstr);
model.removeArrayItem(nodechoices, idx);
}
if (oldstr) {
console.log(" adding node choice " + oldstr);
model.addArrayItem(nodechoices, oldstr);
}
}
}
},
new go.Binding("text").makeTwoWay(),
new go.Binding("choices", "nodechoices").ofModel())
);
myDiagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "OpenTriangle" }),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
if (oldstr !== newstr) {
console.log("changing from " + oldstr + " to " + newstr);
var model = tb.diagram.model;
var linkchoices = model.modelData.linkchoices;
if (!linkchoices) {
linkchoices = [];
model.set(model.modelData, "linkchoices", linkchoices);
}
var idx = linkchoices.indexOf(newstr);
if (idx >= 0) {
console.log("removing link choice " + idx + ": " + newstr);
model.removeArrayItem(linkchoices, idx);
}
if (oldstr) {
console.log(" adding link choice " + oldstr);
model.addArrayItem(linkchoices, oldstr);
}
}
}
},
new go.Binding("text").makeTwoWay(),
new go.Binding("choices", "linkchoices").ofModel())
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue" },
{ key: 2, text: "Beta", color: "orange" },
{ key: 3, text: "Gamma", color: "lightgreen" },
{ key: 4, text: "Delta", color: "pink" }
],
[
{ from: 1, to: 2, text: "one" },
{ from: 1, to: 3, text: "two" },
{ from: 3, to: 4, text: "three" }
]);
myDiagram.model.set(myDiagram.model.modelData, "nodechoices", ["Epsilon"]);
myDiagram.model.set(myDiagram.model.modelData, "linkchoices", ["four"]);
}
I've been trying to format the tooltips of my charts into percentages with no success. Here are the following features of my project reduced to bare minimum:
UI consists of 2 groups of radio buttons and 1 checkmark.
User can switch to view data sources displayed by different chart types.
Source is queried from 7 Google Sheets that the user can view.
The user can view these data sources with 4 chart types.
The initial data source is displayed by a chartWrapper() (chart called by drawChart())
The next 6 data sources share one chartWrapper() (main called by alterChart())
I've have commented within the M.C.V.E. details of the 3 failed attempts, they are as follows:
First attempt: Adding encoded query string to data source url.
Second attempt: Using setQuery()
Third attempt: Using formatter object.
Any help to resolve this by using any of the 3 ways previously mentioned is welcome. I'm open to anything I didn't cover, but I may need more details since I'm still learning this API. Thank you for your valuable time, I appreciate it.
[PLUNKER]
SNIPPET
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, user-scalable=no">
<title>MCVE - GVis Formatting Tooltips</title>
<link href='https://glpro.s3.amazonaws.com/_css/glb.css' rel='stylesheet'>
<style>
#import url('https://fonts.googleapis.com/css?family=Open+Sans');
*,
*:before,
*:after {
font-style: normal !important;
}
body {
position: relative;
}
form {
background-color: #333;
}
#ii {
margin-top: 80px
}
.panel {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
#chart {
height: 70vh;
width: 96vw;
}
.group.group:after,
.chart.chart:after,
.root.root:after {
color: #333;
}
div.google-visualization-tooltip {
background-color: rgba(0, 0, 0, .6);
border-radius: 6px;
min-width: 325px;
max-height: 75px;
}
div.google-visualization-tooltip > ul > li {
display: table-cell;
margin: 0 5px;
}
div.google-visualization-tooltip > ul > li > span {
color: gold;
}
#groupOpt {
display: none;
}
#groupOpt.on {
display: block;
}
</style>
</head>
<body class='sl'>
<header class='panel'>
<!--THIS PART REMOVED-->
</form>
</header>
<section id="ii">
<h1>Sources</h1>
<figure id='chart'></figure>
</section>
<footer>
</footer>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', {
packages: ['corechart']
});
google.charts.setOnLoadCallback(drawChart);
var options = {
backgroundColor: {
fill: 'transparent'
},
tooltip: {
textStyle: {
color: 'gold',
fontSize: 16,
fontName: 'Verdana'
},
trigger: 'focus',
isHtml: true
},
animation: {
startup: true,
duration: 1000,
easing: 'out'
},
title: 'Percentage of Americans in Favor of Same-sex Marriage (2001-16)',
titleTextStyle: {
color: 'gold',
fontName: 'Open Sans',
fontSize: 22
},
hAxis: {
textStyle: {
color: 'cyan'
},
title: 'Year',
titleTextStyle: {
color: 'gold',
fontName: 'Open Sans',
fontSize: 22
},
format: '####'
},
vAxis: {
maxValue: .85,
format: '#%',
textStyle: {
fontName: 'Open Sans',
color: 'cyan'
},
title: 'Percentage of Sub-Population that Approves of Same-sex Marriage',
titleTextStyle: {
color: 'gold',
fontName: 'Arial',
fontSize: 16
}
},
legend: {
textStyle: {
color: 'white',
fontName: 'Verdana'
},
position: 'bottom'
},
crosshair: {
trigger: 'both',
orientation: 'both',
focused: {
color: 'gold',
opacity: .7
},
selected: {
color: 'cyan',
opacity: .7
}
},
pointSize: 12,
theme: 'materials',
chartArea: {
left: 100,
top: 75,
width: '90%',
height: '60%'
}
}
var dataTable;
var chart;
var data;
var main;
var cArray = ['LineChart', 'AreaChart', 'ColumnChart', 'ScatterChart'];
var qArray = [THIS DATA REMOVED]
];
/* Attempt #1 - Using encoded query string with data query source url
// No Errors - QUERY_STRING = select * (# * 100)% -- Syntax is wrong, but I couldn't find any solid examples.
var qArray = [ 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=1104711743&select%20*%20(%23%20*%20100)%25','https://docs.google.com/spreadsheets/d/1EH9C-_OviFTLwC5Z30HTATZqtFnOx_JcOIDHYzF7-FY/gviz/tq?gid=1552974580&select%20*%20(%23%20*%20100)%25 ',
'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=2111420909&select%20*%20(%23%20*%20100)%25', 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=1379142381&select%20*%20(%23%20*%20100)%25', 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=1749299608&select%20*%20(%23%20*%20100)%25', 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=268750266&select%20*%20(%23%20*%20100)%25', 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=148086622&select%20*%20(%23%20*%20100)%25', 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=1474413858&select%20*%20(%23%20*%20100)%25'
];
*/ //
function drawChart() {
chart = new google.visualization.ChartWrapper();
chart.setDataSourceUrl(qArray[1]);
/* Attempt #2 - Using setQuery
// Error -- Syntax is wrong but as stated previously, I could not find a solid example.
INVALID_QUERY
Invalid query: PARSE_ERROR: Encountered "format" at line 1, column 8. Was expecting one of: "true" ... "false" ... "date" ... "timeofday" ... "datetime" ... "timestamp" ... "min" ... "max" ... "avg" ... "count" ... "sum" ... "no_values" ... "no_format" ... "is" ... "null" ... "year" ... "month" ... "day" ... "hour" ... "minute" ... "second" ... "millisecond" ... "with" ... "contains" ... "starts" ... "ends" ... "matches" ... "like" ... "now" ... "dateDiff" ... "quarter" ... "lower" ... "upper" ... "dayOfWeek" ... "toDate" ... <ID> ... <INTEGER_LITERAL> ... <DECIMAL_LITERAL> ... <STRING_LITERAL> ... <QUOTED_ID> ... "(" ... "*" ... "-" ...
*/
//chart.setQuery('select format #%');
dataTable = new google.visualization.DataTable();
/* Attempt #3 Using formatter object
// Uncaught Error: Table has no columns -- I believe the syntax is correct, just not it's location and/or specific requirements such as properly handling the data from the ResponseQuery?
formatter = new google.visualization.NumberFormat({pattern:'#%'});
formatter.format(dataTable, 1);
formatter.format(dataTable, 2);
formatter.format(dataTable, 3);
formatter.format(dataTable, 4);
formatter.format(dataTable, 5);
formatter.format(dataTable, 6);
formatter.format(dataTable, 7);
formatter.format(dataTable, 8);
formatter.format(dataTable, 9);
formatter.format(dataTable, 10);
formatter.format(dataTable, 11);
formatter.format(dataTable, 12);
formatter.format(dataTable, 13);
formatter.format(dataTable, 14);
formatter.format(dataTable, 15);
*/
chart.setChartType('LineChart');
chart.setContainerId('chart');
chart.setOptions(options);
chart.draw();
}
function alterChart(C, Q) {
C = Number(C);
Q = Number(Q);
var URL = qArray[Q];
var VIS = cArray[C];
main = new google.visualization.ChartWrapper();
main.setDataSourceUrl(URL);
// Attempt #2
// main.setQuery('select format #%');
data = new google.visualization.DataTable();
/* Attempt #3
// Uncaught Error: Table has no columns
pattern = new google.visualization.NumberFormat({pattern:'#%'});
pattern.format(data, 1);
pattern.format(data, 2);
pattern.format(data, 3);
pattern.format(data, 4);
pattern.format(data, 5);
pattern.format(data, 6);
pattern.format(data, 7);
pattern.format(data, 8);
pattern.format(data, 9);
pattern.format(data, 10);
pattern.format(data, 11);
pattern.format(data, 12);
pattern.format(data, 13);
pattern.format(data, 14);
pattern.format(data, 15);
*/ //
main.setChartType(VIS);
main.setContainerId('chart');
main.setOptions(options);
main.draw();
}
$('#chartOpt, #groupOpt, #rootOpt').on('change', function(e) {
var chartSel = $("input[name='chart']:checked").val();
var groupSel = $("input[name='group']:checked").val();
if (e.target !== e.currentTarget) {
var target = e.target.id;
var status = $(target).hasClass('on') ? true : false;
}
if (target === 'root0') {
$('#' + target).toggleClass('on');
if (status === true) {
$('#groupOpt').slideUp().removeClass('on');
return alterChart(chartSel, '1');
} else if (status === false) {
$('#groupOpt').slideDown().addClass('on');
return alterChart(chartSel, groupSel);
} else return false;
} else if (target === 'chart0' || target === 'chart1' || target === 'chart2' || target === 'chart3') {
if (status === true) {
return alterChart(chartSel, '1');
} else {
return alterChart(chartSel, groupSel);
}
} else {
if (status === true) {
return false;
} else {
return alterChart(chartSel, groupSel);
}
}
});
var group = document.getElementsByName('group');
var len = group.length;
var rad;
var i;
for (i = 0; i < len; i++) {
group[i].onclick = function() {
if (rad == this) {
this.checked = false;
rad = null;
} else {
rad = this;
}
}
}
</script>
<!--<script src='gvis-api.js'></script>-->
</body>
</html>
You were setting the chart's data source URL and it was rendering itself before you had a chance to format the data. The solution is to query the data yourself, format it, and then pass it into the chart.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, user-scalable=no">
<title>MCVE - GVis Formatting Tooltips</title>
<link href='https://glpro.s3.amazonaws.com/_css/glb.css' rel='stylesheet'>
<style>
#import url('https://fonts.googleapis.com/css?family=Open+Sans');
*, *:before, *:after { font-style: normal !important; }
body { position: relative; }
form { background-color: #333; }
#ii { margin-top: 80px }
.panel { display: flex; flex-wrap:wrap; justify-content: center; align-items: center; }
#chart { height: 70vh; width: 96vw; }
.group.group:after, .chart.chart:after, .root.root:after { color: #333; }
div.google-visualization-tooltip { background-color: rgba(0,0,0,.6); border-radius: 6px; min-width: 325px; max-height: 75px;}
div.google-visualization-tooltip > ul > li { display: table-cell; margin:0 5px; }
div.google-visualization-tooltip > ul > li > span { color: gold; }
#groupOpt { display:none; }
#groupOpt.on { display:block;}
</style>
</head>
<body class='sl'>
<header class='panel'>
<form id="rootOpt" class="sgc" style="width: 20%; color: #fffff">
<input type="checkbox" name="group" id="root0" value='1' checked>
<label for="root0" class="root" id="switch0" data-value="Results">Groups</label>
</form>
<form id="chartOpt" class="sgc" style="width: 80%; color: #ffcc00">
<input type="radio" name="chart" id="chart0" value='0' checked>
<input type="radio" name="chart" id="chart1" value='1'>
<input type="radio" name="chart" id="chart2" value='2'>
<input type="radio" name="chart" id="chart3" value='3'>
<label for="chart0" class="chart" data-value="Line Chart">Line Chart</label>
<label for="chart1" class="chart" data-value="Area Chart">Area Chart</label>
<label for="chart2" class="chart" data-value="Column Chart">Column Chart</label>
<label for="chart3" class="chart" data-value="Scatter Chart">Scatter Chart</label>
</form>
<form id="groupOpt" class='sgc' style="width:100%; color: #00ffff; display:none">
<input type="radio" name="group" id="group0" data-format='4' value='2' checked>
<input type="radio" name="group" id="group1" data-format='5' value='3'>
<input type="radio" name="group" id="group2" data-format='3' value='4'>
<input type="radio" name="group" id="group3" data-format='3' value='5'>
<input type="radio" name="group" id="group4" data-format='2' value='6'>
<input type="radio" name="group" id="group5" data-format='2' value='7'>
<label for="group0" class="group" data-value="Generation">Generation</label>
<label for="group1" class="group" data-value="Religion">Religion</label>
<label for="group2" class="group" data-value="Party Affiliation">Party Affiliation</label>
<label for="group3" class="group" data-value="Political Ideology">Political Ideology</label>
<label for="group4" class="group" data-value="Race">Race</label>
<label for="group5" class="group" data-value="Gender">Gender</label>
</form>
</header>
<section id="ii">
<h1>Sources</h1>
<figure id='chart'></figure>
</section>
<footer>
</footer>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.setOnLoadCallback(runQuery);
google.load('visualization', '1', {
packages: ['corechart', 'table', 'geomap']
});
var options = {
backgroundColor: {
fill: 'transparent'
},
tooltip: {
textStyle: {
color: 'gold',
fontSize: 16,
fontName: 'Verdana'
},
trigger: 'focus',
isHtml: true
},
animation: {
startup: true,
duration: 1000,
easing: 'out'
},
title: 'Percentage of Americans in Favor of Same-sex Marriage (2001-16)',
titleTextStyle: {
color: 'gold',
fontName: 'Open Sans',
fontSize: 22
},
hAxis: {
textStyle: {
color: 'cyan'
},
title: 'Year',
titleTextStyle: {
color: 'gold',
fontName: 'Open Sans',
fontSize: 22
},
format: '####'
},
vAxis: {
maxValue: .85,
format: '#%',
textStyle: {
fontName: 'Open Sans',
color: 'cyan'
},
title: 'Percentage of Sub-Population that Approves of Same-sex Marriage',
titleTextStyle: {
color: 'gold',
fontName: 'Arial',
fontSize: 16
}
},
legend: {
textStyle: {
color: 'white',
fontName: 'Verdana'
},
position: 'bottom'
},
crosshair: {
trigger: 'both',
orientation: 'both',
focused: {
color: 'gold',
opacity: .7
},
selected: {
color: 'cyan',
opacity: .7
}
},
pointSize: 12,
theme: 'materials',
chartArea: {
left: 100,
top: 75,
width: '90%',
height: '60%'
}
}
var dataTable;
var chart;
var data;
var main;
var cArray = ['LineChart', 'AreaChart', 'ColumnChart', 'ScatterChart'];
var qArray = ['https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=1104711743', 'https://docs.google.com/spreadsheets/d/1EH9C-_OviFTLwC5Z30HTATZqtFnOx_JcOIDHYzF7-FY/gviz/tq?gid=1552974580',
'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=2111420909', 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=1379142381', 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=1749299608', 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=268750266', 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=148086622', 'https://docs.google.com/spreadsheets/d/1EY13wZB9IUet4e5gVeMEFLQcHdNfr--S4j741XVAfxo/gviz/tq?gid=1474413858'
];
function runQuery() {
var opts = {
sendMethod: 'auto'
};
if (!google.visualization) return;
var query = new google.visualization.Query(qArray[1], opts);
query.send(handleQueryResponse);
}
function handleQueryResponse(response) {
if (!response) return;
if (!response.getDataTable()) return;
drawChart(response.getDataTable());
}
function drawChart(dataTable) {
console.debug(dataTable)
chart = new google.visualization.LineChart(document.getElementById('chart'));
if (dataTable && dataTable.getNumberOfColumns() > 0) {
var formatter = new google.visualization.NumberFormat({
pattern: '#%',
fractionDigits: 0
});
formatter.format(dataTable, 1);
formatter.format(dataTable, 2);
}
chart.draw(dataTable, options);
}
function alterChart(C, Q) {
C = Number(C);
Q = Number(Q);
var URL = qArray[Q];
var VIS = cArray[C];
main = new google.visualization.ChartWrapper();
main.setDataSourceUrl(URL);
if (dataTable.getNumberOfColumns() > 0) {
//formatter = new google.visualization.NumberFormat({pattern:'#\'%\''});
var formatter = new google.visualization.NumberFormat({
pattern: '#%',
fractionDigits: 0
});
formatter.format(dataTable, 1);
formatter.format(dataTable, 2);
}
main.setChartType(VIS);
main.setContainerId('chart');
main.setOptions(options);
main.draw();
}
runQuery();
$('#chartOpt, #groupOpt, #rootOpt').on('change', function(e) {
var chartSel = $("input[name='chart']:checked").val();
var groupSel = $("input[name='group']:checked").val();
if (e.target !== e.currentTarget) {
var target = e.target.id;
var status = $(target).hasClass('on') ? true : false;
}
if (target === 'root0') {
$('#' + target).toggleClass('on');
if (status === true) {
$('#groupOpt').slideUp().removeClass('on');
return alterChart(chartSel, '1');
} else if (status === false) {
$('#groupOpt').slideDown().addClass('on');
return alterChart(chartSel, groupSel);
} else return false;
} else if (target === 'chart0' || target === 'chart1' || target === 'chart2' || target === 'chart3') {
if (status === true) {
return alterChart(chartSel, '1');
} else {
return alterChart(chartSel, groupSel);
}
} else {
if (status === true) {
return false;
} else {
return alterChart(chartSel, groupSel);
}
}
});
var group = document.getElementsByName('group');
var len = group.length;
var rad;
var i;
for (i = 0; i < len; i++) {
group[i].onclick = function() {
if (rad == this) {
this.checked = false;
rad = null;
} else {
rad = this;
}
}
}
</script>
</body>
</html>
I just resolved my issue, I formatted the actual source in Google Sheets and now the tooltips are percentages. So simple it never occurred to me. The following array is the only thing changed. Each new url points to a preformatted table in Google Sheets:
var qArray = ['https://docs.google.com/spreadsheets/d/1c9xyhR1_BavywsAOvpkf65Nhf9o0j8KHqRrEYC25mus/gviz/tq?gid=1104711743&range=A:T', 'https://docs.google.com/spreadsheets/d/1c9xyhR1_BavywsAOvpkf65Nhf9o0j8KHqRrEYC25mus/gviz/tq?gid=1552974580&range=A:C', 'https://docs.google.com/spreadsheets/d/1c9xyhR1_BavywsAOvpkf65Nhf9o0j8KHqRrEYC25mus/gviz/tq?gid=2111420909&range=A:E', 'https://docs.google.com/spreadsheets/d/1c9xyhR1_BavywsAOvpkf65Nhf9o0j8KHqRrEYC25mus/gviz/tq?gid=1379142381&range=A:F', 'https://docs.google.com/spreadsheets/d/1c9xyhR1_BavywsAOvpkf65Nhf9o0j8KHqRrEYC25mus/gviz/tq?gid=1749299608&range=A:D', 'https://docs.google.com/spreadsheets/d/1c9xyhR1_BavywsAOvpkf65Nhf9o0j8KHqRrEYC25mus/gviz/tq?gid=268750266&range=A:D', 'https://docs.google.com/spreadsheets/d/1c9xyhR1_BavywsAOvpkf65Nhf9o0j8KHqRrEYC25mus/gviz/tq?gid=148086622&range=A:C', 'https://docs.google.com/spreadsheets/d/1c9xyhR1_BavywsAOvpkf65Nhf9o0j8KHqRrEYC25mus/gviz/tq?gid=1474413858&range=A:C'];
I'm trying to make an Excel-like editor with HandsOnTable but I haven't yet figured out how to change a cell's style dynamically, borders in this case.
I have tried to use
setCellMeta(row,col,"borders", My_borders_Object);
and then
MyHotInstance.render();
but this had no effect.
What could I do to solve this problem?
Any help will be very appreciated.
Not sure what my_borders_object is or why you're passing "borders" as a cell meta data argument, but here's a good way of doing it:
There is an initialization option called customBorders; see below for excerpt from documentation:
customBorders : Boolean (default false)
customBorders : Array [{ row: 2, col: 2, left: {width:2, color: 'red'}, right: {width:1, color: 'green'}, top: /*...*/, bottom: /*...*/ }]
customBorders : Array [{ range:{ from:{ row: 1, col: 1 }, to:{ row: 3, col: 4 } }, left: { /*...*/ }, right: { /*...*/ }, top: { /*...*/ }, bottom: { /*...*/ } }]
If true, enables Custom Borders plugin, which enables applying custom borders through the context menu (configurable with context menu key borders).
To initialize Handsontable with predefined custom borders, provide cell coordinates and border styles in form of an array.
See Custom Borders demo for examples.
Version added: 0.11.0
What this means is that at any given point, if you wanted to do a dynamic update of borders, you can use
hotInstance.updateSettings({
customBorders: new_borders_array
})
I'm trying to accomplish the same right now actually. I have tried the following:
ht is the handsontable instance
ht.updateSettings({
customBorders: [
{ range:
{
from: { row: 1, col: 15 },
to: { row: 1, col: 16 }
},
top: { width: 3, color: 'red' },
left: { width: 2, color: 'red' },
bottom: { width: 2, color: 'red' },
right: { width: 2, color: 'red' }
},
]
});
Without ht.init() it does not work:
ht.init();
In version 0.17 this worked fine, however after the update in version 0.18 ht.init(); it creates another instance of the table below the current one - very frustrating.
So now I'm again stuck, or I will downgrade to 0.17 until this is fixed in 0.18.
After going thought the handsontable.full.js I managed to do it by extracting some function from the code and building the borders objects:
var container = document.getElementById('ht_container');
var data = function () {
return Handsontable.helper.createSpreadsheetData(20, 12);
};
var hot = new Handsontable(container, {
data: data(),
height: 396,
colHeaders: true,
rowHeaders: true,
stretchH: 'all',
customBorders: true,
});
//get handsontable instance
var instance = hot;
//copy required functions from the JS.... not pretty, but easy enough
//instead of building the required objects manually
var getSettingIndex = function(className) {
for (var i = 0; i < instance.view.wt.selections.length; i++) {
if (instance.view.wt.selections[i].settings.className == className) {
return i;
}
}
return -1;
};
var insertBorderIntoSettings = function(border) {
var coordinates = {
row: border.row,
col: border.col
};
var selection = new WalkontableSelection(border, new WalkontableCellRange(coordinates, coordinates, coordinates));
var index = getSettingIndex(border.className);
if (index >= 0) {
instance.view.wt.selections[index] = selection;
} else {
instance.view.wt.selections.push(selection);
}
};
var createClassName = function(row, col) {
return "border_row" + row + "col" + col;
};
var createDefaultCustomBorder = function() {
return {
width: 1,
color: '#000'
};
};
var createSingleEmptyBorder = function() {
return {hide: true};
};
var createDefaultHtBorder = function() {
return {
width: 1,
color: '#000',
cornerVisible: false
};
};
var createEmptyBorders = function(row, col) {
return {
className: createClassName(row, col),
border: createDefaultHtBorder(),
row: row,
col: col,
top: createSingleEmptyBorder(),
right: createSingleEmptyBorder(),
bottom: createSingleEmptyBorder(),
left: createSingleEmptyBorder()
};
};
var prepareBorderFromCustomAddedRange = function(rowObj) {
var range = rowObj.range;
for (var row = range.from.row; row <= range.to.row; row++) {
for (var col = range.from.col; col <= range.to.col; col++) {
var border = createEmptyBorders(row, col);
var add = 0;
if (row == range.from.row) {
add++;
if (rowObj.hasOwnProperty('top')) {
border.top = rowObj.top;
}
}
if (row == range.to.row) {
add++;
if (rowObj.hasOwnProperty('bottom')) {
border.bottom = rowObj.bottom;
}
}
if (col == range.from.col) {
add++;
if (rowObj.hasOwnProperty('left')) {
border.left = rowObj.left;
}
}
if (col == range.to.col) {
add++;
if (rowObj.hasOwnProperty('right')) {
border.right = rowObj.right;
}
}
if (add > 0) {
this.setCellMeta(row, col, 'borders', border);
insertBorderIntoSettings(border);
}
}
}
};
$(document).ready(function () {
//create my borders object
var customBorders = [
{ range:
{
from: { row: 1, col: 2 },
to: { row: 4, col: 4 }
},
top: { width: 3, color: 'red' },
left: { width: 2, color: 'red' },
bottom: { width: 2, color: 'red' },
right: { width: 2, color: 'red' }
},
];
//used the 'stolen' functions to add them to the HT in
prepareBorderFromCustomAddedRange.call(instance, customBorders[0]);
instance.render();
instance.view.wt.draw(true);
instance.customBorders = customBorders;
});
</style>
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<link href="http://handsontable.com//styles/main.css" rel="stylesheet">
<link href="http://handsontable.com//bower_components/handsontable/dist/handsontable.full.min.css" rel="stylesheet">
<script src="http://handsontable.com//bower_components/handsontable/dist/handsontable.full.min.js"></script>
<style type="text/css">
body {background: white; margin: 20px;}
h2 {margin: 20px 0;}
<div id="ht_container"></div>
But if you are not lazy, you can build pretty much build your 'border' object and use, insertBorderIntoSettings to add them to your table or write custom code that does the same.