javascript infovis toolkit: individal level distance for each level - javascript

how do i set individual levelDistance for each level in spacetree
when i set node.data.$width and label.style.width tree drawn with no equal edge
How to set levelDistance for each node level in the spacetree. For instance, I want to change the 'levelDistance' for node level 3. Thank you
function init(){
//init data
json = {
id: "node0",
name: "zvet",
data: {},
children: [{
id: "node0rrrrrr1",
name: "1.3",
data: {$orn:'left'},
children: [{
id: "fvwerr1",
name: "w33",
data: {$orn:'left'},
}]
},
{
id: "node02",
name: "qwe",
data: {$orn:'left'}
} ,{
id: "node03",
name: "bnm",
data: {$orn:'left'}
} ,{
id: "node04",
name: "1.3",
data: {$orn:"right",kk:"kk"}
},
{
id: "vwer",
name: "vfsewrg",
data: {$orn:"right",kk:"kk"}
},
{
id: "vweq33e",
name: "vvvserser",
data: {$orn:"right",kk:"kk"},
children: [{
id: "r345345",
name: "w33",
data: {$orn:'right'}
},
{
id: "u786786",
name: "w33",
data: {$orn:'right'}
},
{
id: "p809456",
name: "w33",
data: {$orn:'right'},
children: [{
id: "weqr232344",
name: "w33",
data: {$orn:'right',kk:"kk"},
children: [{
id: "weqoooooppp",
name: "w33",
data: {$orn:'right'}
}]
}]
}
]
}
]
};
//end
//init Spacetree
//Create a new ST instance
st = new $jit.ST({
//id of viz container element
injectInto: 'infovis',
Canvas:{
type: '2D'} ,
background:true,
//set duration for the animation
duration: 800,
//set animation transition type
transition: $jit.Trans.Quart.easeInOut,
//set distance between node and its children
levelDistance: 100,
levelsToShow:5,
multitree: true,
//enable panning
Navigation: {
enable:true,
panning:true
},
//set node and edge styles
//set overridable=true for styling individual
//nodes or edges
Node: {
height: 20,
width: 150,
type: 'rectangle',
color:'transparent',
overridable: true,
autoWidth:false ,
CanvasStyles: {
fillStyle: 'transparent'
}
},
Edge: {
type: 'bezier',
overridable: true
},
onBeforeCompute: function(node){
Log.write("loading " + node.name);
},
onAfterCompute: function(){
Log.write("done");
},
//This method is called on DOM label creation.
//Use this method to add event handlers and styles to
//your node.
onCreateLabel: function(label, node){
label.id = node.id;
label.innerHTML = node.name;
label.innerHTML='<div style="position:relative;">'+node.name+'</div>';
label.onclick = function(){
if(normal.checked) {
st.onClick(node.id);
//st.setRoot(node.id, 'animate');
st.selectedNodeId=node.id;
$("#"+node.id+" div").animate({"bottom":"+=10px"},"slow");
//st.addNodeInPath("1234");
} else {
st.setRoot(node.id, 'animate');
}
};
//set label styles
var style = label.style;
style.width = 150 + 'px';
style.height = 17 + 'px';
style.cursor = 'pointer';
style.color = '#fff';
style.backgroundColor = '#6257DD';
style.borderradius='14px';
style.boxshadow='0 0 16px #FFFFFF';
style.fontSize = '0.8em';
style.textAlign= 'center';
style.paddingTop = '3px';
if(node.data.kk=="kk")
{
style.width = 60+ 'px';
}
},
onPlaceLabel: function(label, node) {
} ,
//This method is called right before plotting
//a node. It's useful for changing an individual node
//style properties before plotting it.
//The data properties prefixed with a dollar
//sign will override the global node style properties.
onBeforePlotNode: function(node){
if(node.data.kk=="kk")
{
node.data.$width = 60;
}
//add some color to the nodes in the path between the
//root node and the selected node.
if (node.selected) {
node.data.$color = "#000000";
$("#"+node.id).css("background-color","red");
}
else {
delete node.data.$color;
$("#"+node.id).css("background-color","#6257DD");
//if the node belongs to the last plotted level
if(!node.anySubnode("exist")) {
//count children number
var count = 0;
node.eachSubnode(function(n) { count++; });
//assign a node color based on
//how many children it has
node.data.$color = ['#aaa', '#baa', '#caa', '#daa', '#eaa', '#faa'][count];
}
}
},
//This method is called right before plotting
//an edge. It's useful for changing an individual edge
//style properties before plotting it.
//Edge data proprties prefixed with a dollar sign will
//override the Edge global style properties.
onBeforePlotLine: function(adj){
if (adj.nodeFrom.selected && adj.nodeTo.selected) {
adj.data.$color = "#eed";
adj.data.$lineWidth = 3;
}
else {
delete adj.data.$color;
delete adj.data.$lineWidth;
}
}
});
//load json data
//end
//Add event handlers to switch spacetree orientation.
var top = $jit.id('r-top'),
left = $jit.id('r-left'),
bottom = $jit.id('r-bottom'),
right = $jit.id('r-right'),
normal = $jit.id('s-normal');
function changeHandler() {
if(this.checked) {
top.disabled = bottom.disabled = right.disabled = left.disabled = true;
st.switchPosition(this.value, "animate", {
onComplete: function(){
top.disabled = bottom.disabled = right.disabled = left.disabled = false;
}
});
}
};
st.loadJSON(json);
//compute node positions and layout
st.compute();
//optional: make a translation of the tree
st.geom.translate(new $jit.Complex(-200, 0), "current");
//emulate a click on the root node.
st.onClick(st.root);
st.select(st.root);
top.onchange = left.onchange = bottom.onchange = right.onchange = changeHandler;
//end
}

It is not possible to have different distances for different levels.
What you can do is to change the node.data.$size of the level, so it displays smaller or bigger than the other leaves.
Think the distance as an allocated space for the node to be placed in. If you create a node with a size smaller than the distance, you will get a gap, which can be seeing as a "border" (just visually) at the external part of it.

Related

How to display datasets correctly on a graph using Chart.js

I am developing a small dashboard in ASP.NET and with the help of C# and I am presenting a problem by generating a stacked horizontal bar graph properly.
I am using the chart.js library
Library chart.js
I enclose an example image of what I want to do I have the data of some technicians who were assigned a certain amount of tickets, which according to the image correspond to the Y axis of the graph A, B, C, D represent each technician and the datasets they represent the state in which it is, what I want to obtain in the graph is the amount of tickets that each technician has for his state.
Example image:
Error:
Kinda complicated to explain, I am doing the expected horizontal bar graph as I indicated in the previous example but I have not been able to see the datasets and their colors correctly.
What I have achieved so far is to see the technicians with the amount of tickets that each one has, but the dataset that I represent with the state at the bottom of the graph is repeated, in addition to ASIGNADO I have other states and each dataset that is each state has to be represented by a color.
The following is the table where I am getting the information I want to show, where TK_HD_TICKETS_ID are the tickets that are registered and where I want to get the amount, in TK_CT_STATUS_ID which the status of the ticket is stored and in TK_BT_EMPLOYEES_ID is the technician who has that ticket
TK_DT_RECORDS
Where TK_HD_TICKETS_ID are the tickets that are registered and where do I want to get the amount, in TK_CT_STATUS_ID which the state of the ticket is stored and in TK_BT_EMPLOYEES_ID is the technician who has that ticket
The following is the query with which I am obtaining the data so far
DashboardModel.cs
public class DashboardResult : Result
{
public List<TicketsDashboardAux> DashboardTicketList { set; get; }
/// <summary>
/// Object with the results for the reports
/// </summary>
public DashboardResult()
{
DashboardTicketList = new List<TicketsDashboardAux>();
}
}
/// <summary>
/// Auxiliary Object to obtain the consultation for the basic report
/// </summary>
public class TicketsDashboardAux
{
public string TicketsAsignedTo { set; get; }
public int TicketsCount { set; get; }
public string TicketsClasificationType { set; get; }
}
public class DashboardModel
{
/// <summary>
/// Query for ticket board by employee status
/// </summary>
/// <param name="refreshType"></param>
/// <param name="area"></param>
/// <returns></returns>
public static DashboardResult GetEmployeeByStatus(string refreshType, string area)
{
var result = new DashboardResult();
using (var db = new dbGoldenTicket())
{
var subQuery =
from tblTickets in db.TK_HD_TICKETS
join tblRecords in db.TK_DT_RECORDS on tblTickets.TK_HD_TICKETS_ID equals tblRecords
.TK_HD_TICKETS_ID
join tblStatus in db.TK_CT_STATUS on tblRecords.TK_CT_STATUS_ID equals tblStatus.
TK_CT_STATUS_ID
join tblEmployee in db.TK_BT_EMPLOYEES on tblRecords.TK_BT_EMPLOYEES_ID equals tblEmployee
.TK_BT_EMPLOYEES_ID into tempEmplo
from tblEmployee in tempEmplo.DefaultIfEmpty()
where crationDateTickets.Contains(tblTickets.TK_HD_TICKETS_ID)
&& tblRecords.TK_DT_RECORDS_ID == (
from tblTicketAux in db.TK_HD_TICKETS
join tblRecordAux in db.TK_DT_RECORDS on tblTicketAux.TK_HD_TICKETS_ID equals tblRecordAux
.TK_HD_TICKETS_ID
where tblTickets.TK_HD_TICKETS_ID == tblTicketAux.TK_HD_TICKETS_ID
select tblRecordAux.TK_DT_RECORDS_ID
).Max()
select new
{
tblRecords.TK_HD_TICKETS_ID,
tblEmployee.FULLNAME,
tblStatus.NAME,
};
var queryTicketsList = (from subquery in subQuery
group subquery by new { subquery.FULLNAME, subquery.NAME }
into grp
select new TicketsDashboardAux()
{
TicketsAsignedTo = grp.Key.FULLNAME,
TicketsClasificationType = grp.Key.NAME,
TicketsCount = grp.Count()
}).ToList();
foreach (TicketsDashboardAux rowAux in queryTicketsList)
{
rowAux.TicketsAsignedTo = rowAux.TicketsAsignedTo.IsEmpty() ? "Sin asignar" : rowAux.TicketsAsignedTo;
result.DashboardTicketList.Add(rowAux);
}
result.Success = true;
result.Message = "op_exitosa";
}
return result;
}
The following is the Javascript function with which I will load the data in the graph
function loadEmployeesChart() {
document.getElementById("chart-employee").remove();
let auxCanvasEmployee = document.createElement('canvas');
auxCanvasEmployee.setAttribute('id', 'chart-employee');
auxCanvasEmployee.setAttribute('style', 'width: 720px; height: 600px');
document.querySelector('#chartEmployeeContainer').appendChild(auxCanvasEmployee);
var canvas = document.getElementById("chart-employee");
var ctx = canvas.getContext("2d");
var dataEmployee;
var myNewChart;
$.ajax({
url: document.getElementById("employeeChart").value,
type: "POST",
dataType: "json",
data: {
refreshType: document.getElementById("dataOption").value
},
success: function (data) {
var dataChart = [];
var label = [];
var datalabels = [];
var stacks = []
for (let idx in data.DashboardTicketList) {
if (data.DashboardTicketList.hasOwnProperty(idx)) {
dataChart.push(data.DashboardTicketList[idx].TicketsCount);
label.push(data.DashboardTicketList[idx].TicketsAsignedTo);
datalabels.push(data.DashboardTicketList[idx].TicketsClasificationType);
}
}
var count = false;
for (let idx in dataChart) {
if (dataChart[idx] > 0) {
count = true;
break;
}
}
if (count) {
document.getElementById('noDataEmployee').style.display = 'none';
} else {
document.getElementById('noDataEmployee').style.display = 'block';
}
dataEmployee = {
labels: label,
datasets: [{
label: datalabels,
data: dataChart,
}],
};
myNewChart = new Chart(ctx, {
type: 'horizontalBar',
data: dataEmployee,
options: {
maintainAspectRatio: false,
scales: {
xAxes: [{
stacked: true // this should be set to make the bars stacked
}],
yAxes: [{
stacked: true // this also..
}]
},
legend: {
position: 'bottom',
padding: 5,
labels:
{
pointStyle: 'circle',
usePointStyle: true
}
}
},
});
}, error: function () {
}
});
}
I hope I understood your question and your data strucure (as I said in my comment to your question).
Bringing your data structure to the one chart.js is expecting is not so easy. That's why my code looks very difficult. That's why I put a few comments in there and I let all the console.logs in there so you can easily see what is happening.
Feel free to ask any questions you have. I'm sure it takes some time to understand all.
Complete code in the JSBin
let canvas = document.getElementById("chart");
let ctx = canvas.getContext("2d");
let data = {}
data.DashboardTicketList = {
0: {
TicketsAssignedTo: 'Tim',
TicketsClassificationType: 'TERMINADO',
TicketsCount: 1,
},
1: {
TicketsAssignedTo: 'Tim',
TicketsClassificationType: 'ASIGNADO',
TicketsCount: 7
},
2: {
TicketsAssignedTo: 'Tim',
TicketsClassificationType: 'CERRADO',
TicketsCount: 5
},
3: {
TicketsAssignedTo: 'Melanie',
TicketsClassificationType: 'ASIGNADO',
TicketsCount: 7
},
4: {
TicketsAssignedTo: 'Melanie',
TicketsClassificationType: 'CERRADO',
TicketsCount: 7
},
5: {
TicketsAssignedTo: 'Steffen',
TicketsClassificationType: 'TERMINADO',
TicketsCount: 0
},
6: {
TicketsAssignedTo: 'Steffen',
TicketsClassificationType: 'ASIGNADO',
TicketsCount: 10
},
7: {
TicketsAssignedTo: 'Steffen',
TicketsClassificationType: 'CERRADO',
TicketsCount: 7
},
8: {
TicketsAssignedTo: 'Talia',
TicketsClassificationType: 'TERMINADO',
TicketsCount: 5
},
9: {
TicketsAssignedTo: 'Talia',
TicketsClassificationType: 'ASIGNADO',
TicketsCount: 7
},
10: {
TicketsAssignedTo: 'Talia',
TicketsClassificationType: 'EN ESPERA USUARIO',
TicketsCount: 6
}
}
const status = [
'ABIERTO',
'ASIGNADO',
'EN PROCESO',
'EN ESPERA USUARIO',
'TERMINADO',
'CERRADO'
]
const colors = {
'ASIGNADO': '#F7A65C',
'ABIERTO': '#F76363',
'CERRADO': '#6CE5CE',
'TERMINADO': '#4285f4',
'EN PROCESO': '#F2CB5F',
'EN ESPERA USUARIO': '#B283ED'
}
let peopleData = {}
let classificationData = {}
let chartData = {
labels: [],
datasets: []
}
// loop through complete data
for (let idx in data.DashboardTicketList) {
let cData = data.DashboardTicketList[idx]
//console.log(cData)
// change data structure to all people
if (!peopleData.hasOwnProperty(cData.TicketsAssignedTo)) {
peopleData[cData.TicketsAssignedTo] = {}
}
peopleData[cData.TicketsAssignedTo][cData.TicketsClassificationType] = cData.TicketsCount
// Get all different TicketsClassificationTypes (object to eliminate duplicates)
if (!classificationData.hasOwnProperty(cData.TicketsClassificationType)) {
classificationData[cData.TicketsClassificationType] = true
}
}
// Get array of all different TicketsClassificationTypes
let classificationDataArray = Object.keys(classificationData)
//console.log('classData', classificationData)
//console.log('classDataArray', classificationDataArray)
//console.log('peopleData', peopleData)
// Assign 0 to all people with no specific TicketsClassificationType; may be improved
for (let idx in peopleData) {
let cPerson = peopleData[idx]
for (let i = 0; i < classificationDataArray.length; i++) {
if (!cPerson.hasOwnProperty(classificationDataArray[i])) {
cPerson[classificationDataArray[i]] = 0
}
}
}
// Get chart labels from peopleData
chartData.labels = Object.keys(peopleData)
// Sort TicketsClassificationType to order from status array; may be improved
for (let i = 0; i < status.length; i++) {
for (let j = 0; j < classificationDataArray.length; j++) {
if (status[i] === classificationDataArray[j]) {
let cClass = classificationDataArray[j]
//console.log('cClass', cClass)
let cData = []
for (let idx2 in peopleData) {
//console.log('peopleData[idx2]', peopleData[idx2])
cData.push(peopleData[idx2][cClass])
}
chartData.datasets.push({
label: cClass,
data: cData,
backgroundColor: colors[cClass]
})
break
}
}
}
/*
let count = false;
for (let idx in dataChart) {
if (dataChart[idx] > 0) {
count = true;
break;
}
}
if (count) {
document.getElementById('noDataEmployee').style.display = 'none';
} else {
document.getElementById('noDataEmployee').style.display = 'block';
}
*/
let options = {
responsive: true,
//maintainAspectRatio: false,
scales: {
xAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
},
legend: {
position: 'bottom',
padding: 5,
labels: {
pointStyle: 'circle',
usePointStyle: true
}
}
}
let myNewChart = new Chart(ctx, {
type: 'horizontalBar',
data: chartData,
options: options,
});

Kendo UI chart: adjust label amount dynamically

I have a zoomable area chart and an x-axis label for every datapoint. When the chart loads, there is way too many labels, therefor I set the step option:
categoryAxis: {
name: 'CatAxis',
categories: graphLabels,
step: 30
}
But when the user zooms in, I need to decrease the amount of steps, otherwise no labels will be shown at all.
Therefor I subscribed to the zoomEnd event and calculate the desired amount of step:
function onZoomEnd(e) {
var xRange = e.axisRanges.CatAxis;
if (xRange) {
var diff = xRange.max - xRange.min;
var step = 1;
while (diff / step > 6) {
step++;
}
e.sender.setOptions({ categoryAxis: { labels: { step: step } } });
}
But setting the options here causes the chart to refresh and thereby losing its zoom level. The ultimate goal is to show a reasonable amount of labels without them overlapping or disappearing when zooming in and out. Any ideas how to achieve this?
you can maintain the zoom level of the chart using the following example from documentation
https://docs.telerik.com/kendo-ui/knowledge-base/maintain-pan-zoom-state
<button id="rebind">Rebind Chart</button>
<div id="chart"></div>
<script>
// Sample data
var data = [];
for (var i = 0; i < 100; i++) {
var val = Math.round(Math.random() * 10);
data.push({
category: "C" + i,
value: val
});
}
function createChart() {
var axisMin = 0;
var axisMax = 10;
function updateRange(e) {
var axis = e.sender.getAxis('axis')
var range = axis.range()
axisMin = range.min;
axisMax = range.max;
}
function restoreRange(e) {
e.sender.options.categoryAxis.min = axisMin;
e.sender.options.categoryAxis.max = axisMax;
}
$("#chart").kendoChart({
renderAs: "canvas",
dataSource: {
data: data
},
categoryAxis: {
name: "axis",
min: axisMin,
max: axisMax,
labels: {
rotation: "auto"
}
},
series: [{
type: "column",
field: "value",
categoryField: "category"
}],
pannable: {
lock: "y"
},
zoomable: {
mousewheel: {
lock: "y"
},
selection: {
lock: "y"
}
},
zoom: updateRange,
drag: updateRange,
dataBound: restoreRange
});
}
$("#rebind").click(function() {
$("#chart").data("kendoChart").dataSource.read();
});
$(document).ready(createChart);
</script>

Cytoscape content style function not working when being generated dynamically

I'm using Cytoscape 2.5.0.
Here's the basic working code:
var elements = [
{ group: "nodes", data: { id: "n0", type: "foo"}, position: { x: 100, y: 100 } },
{ group: "nodes", data: { id: "n1", type: "bar"}, position: { x: 200, y: 200 } },
{ group: "nodes", data: { id: "n2", type: "foo"}, position: { x: 300, y: 300 } },
{ group: "nodes", data: { id: "n3", type: "biz"}, position: { x: 400, y: 400 } },
{ group: "edges", data: { id: "e0", source: "n0", target: "n1" } }
];
var style = cytoscape.stylesheet()
.selector('node[type = "foo"]').css({
'background-color': 'red',
'content': function(ele){
return "Is a foo: " + ele.data().id;
}
})
.selector('node[type = "bar"]').css({
'background-color': 'blue',
'content':function(ele){
return "Is a bar: " + ele.data().id;
}
})
.selector('node[type = "biz"]').css({
'background-color': 'green',
'content': function(ele){
return ele.data().id;
}
})
;
var dom = $('#graph');
var cy = cytoscape({
container: dom,
elements: elements,
style: style
});
As you can see we have nodes of three different types: foo, bar, and biz.
We've assigned selectors to color them differently, and display a custom label depending on the node type.
This code works fine:
But now I want to abstract the assignment of styling, to avoid that repeatition of code on assigning the selectors and styling.
To do this, I create a Config object.
var Config = function(type, color, message){
this.color = color;
this.fn = function(ele){
var eleMessage = (message)? message + ele.data().id : ele.data().id;
return eleMessage;
};
this.selector = 'node[type = "' + type + '"]';
}
var configs = [
new Config("foo", "red", "this is foo"),
new Config("bar", "blue", "this is bar"),
new Config("biz", "green")
];
for (var i in configs){
var config = configs[i];
style.selector(config.selector)
.css({'background-color': config.color, 'content': config.fn});
}
The code now stops working. The color styling works fine, but the label function is the same for all of them.
I thought this might be an issue with Javascript closures, so I wrapped the function in an IIFE, but that doesn't solve it either.
var Config = function(type, color, message){
this.color = color;
this.fn = (function(message) {return function(ele){
var eleMessage = (message)? message + ele.data().id : ele.data().id;
return eleMessage;
};})(message);
this.selector = 'node[type = "' + type + '"]';
}
Any suggestions for solving this?

JS object loop for dynamic div creation

I've got an object with some properties in which I want to display on a web page in a chart. Each object will have its own chart.
Currently receiving the objects and their properties but can't get them to display dynamically in a chart, this works with static data.
What I'm trying to achieve is to loop through the objects, create the chart and add the data to it dynamically, there isn't a constant of how many objects there will be.
IE - on page load I'll receive how many objects there are, 3 for example, as such, three boxes will be created, each with a chart inside. How do I achieve this?
I don't think there's an issue with the data I'm sending receiving, but more to do with how I'm creating an object and it's corresponding div with the pie chart inside.
Where am I going wrong?
Code:
<style type="text/css">
#box {
border: 1px solid;
width: 200px;
height: 200px;
}
#objectBox {
width: 100px;
height: 100px;
}
</style>
<script type="text/javascript">
function getData() {
$.ajax({
type: "GET",
url: "/Graphs/GetMyPie",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: drawChart
});
}
function drawChart(myObject) {
var dpoints = [];
// loop through array and create and display a pie for each object in array
for (var i = 0; i < myObject.length; i++) {
// each object
var prop1 = myObject[i].propertyOne;
var prop2 = myObject[i].propertyTwo;
var title = myObject[i].objectName;
// create container div and pie div
var outbox = document.createElement('div');
outbox.id = 'box';
document.body.appendChild('box');
var inbox = outbox.appendChild('objectBox' + i);
// fill pie div
dpoints[i] = [{ label: "Free", data: prop1, color: '#7DCC3D' }, { label: "Used", data: prop2, color: '#333366' }];
$.plot($('#objectBox' + [i]), dpoints[i], {
series: {
pie: {
show: true
}
}
});
}
$(document).ready(function () {
getData();
});
}
</script>
If you don't want to use jQuery, as it isn't much JavaScript, I have put together an example: https://jsfiddle.net/gg1kkqq7/2/
With the above example, put
var dpoints = [{ label: "Free", data: prop1, color: '#7DCC3D' }, { label: "Used", data: prop2, color: '#333366' }];
$.plot(objectBox, dpoints, {
series: {
pie: {
show: true
}
}
});`
below outbox.appendChild(inbox); which will then render whatever the data is.
On a side note, I believe your problem is with the var outbox = $('<div>', {id: 'box' + i});
var objectBox = $("<div>", { id: 'objectBox' + i }).appendTo(outbox);
$('body').append(outbox);
Try this:
function drawChart(myObject) {
// loop through array and create and display a pie for each object in array
for (var i = 0; i < myObject.length; i++) {
// each object
var prop1 = myObject[i].propertyOne;
var prop2 = myObject[i].propertyTwo;
// create container div and pie div
var outbox = $('<div>', {id: 'box' + i});
var objectBox = $("<div>", { id: 'objectBox' + i }).appendTo(outbox);
$('body').append(outbox);
// fill pie div
var dpoints = [{ label: "Free", data: prop1, color: '#7DCC3D' }, { label: "Used", data: prop2, color: '#333366' }];
$.plot(objectBox, dpoints, {
series: {
pie: {
show: true
}
}
});
}
}
I've changed it to use jQuery to create the containers. Your code there was all wrong -- the argument to appendChild is the element to append, not an ID. I couldn't see any reason for the dpoints array, so I just use a local dpoints variable.

Swimlane - GoJS library

I would like to ask, if anyone knows how to convert the orientation of the diagram of swimlanes in GOjs library from being top-level to a single row orientation, it is because I want to have a diagram that connects a node to another from a different group.
And also is it possible to add attributes to each node, like onmouseover, class, id, name, and etc.?
Currently I have this copied and paste code:
<!DOCTYPE html>
<html>
<head>
<title>Swimlane</title>
<!-- Copyright 1998-2014 by Northwoods Software Corporation. -->
<link href="goSamples.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="go.js"></script>
<script id="code">
// These parameters need to be set before defining the templates.
// this controls whether the swimlanes are horizontal stacked vertically, or the other way:
var HORIZONTAL = true;
// this controls the minimum length of any swimlane
var MINLENGTH = 200;
// this controls the minimum breadth of any swimlane
var MINBREADTH = 100;
// compute the minimum length needed to hold all of the subgraphs
function computeMinPlaceholderSize(diagram) {
var len = MINLENGTH;
for (var it = diagram.nodes; it.next(); ) {
var group = it.value;
if (!(group instanceof go.Group)) continue;
var holder = group.placeholder;
if (holder !== null) {
var sz = holder.actualBounds;
len = Math.max(len, (HORIZONTAL ? sz.width : sz.height));
}
}
return (HORIZONTAL ? new go.Size(len, NaN) : new go.Size(NaN, len));
}
// get the minimum placeholder size for a particular Group;
// when group is null, return the minimum size
function computePlaceholderSize(group) {
if (group instanceof go.Group) {
var holder = group.placeholder;
if (holder !== null) {
return holder.actualBounds.size;
}
}
return (HORIZONTAL ? new go.Size(MINLENGTH, MINBREADTH) : new go.Size(MINBREADTH, MINLENGTH));
}
// define a custom ResizingTool to limit how far one can shrink a Group
function GroupResizingTool() {
go.ResizingTool.call(this);
}
go.Diagram.inherit(GroupResizingTool, go.ResizingTool);
GroupResizingTool.prototype.isLengthening = function() {
return (this.handle.alignment === (HORIZONTAL ? go.Spot.Right : go.Spot.Bottom));
};
GroupResizingTool.prototype.computeMinSize = function() {
var msz = computePlaceholderSize(null); // get the minimum size
if (this.isLengthening()) { // compute the minimum length of all lanes
var sz = computeMinPlaceholderSize(this.diagram);
if (HORIZONTAL) {
msz.width = Math.max(msz.width, sz.width);
} else {
msz.height = Math.max(msz.height, sz.height);
}
} else { // find the minimum size of this single lane
var sz = computePlaceholderSize(this.adornedObject.part);
msz.width = Math.max(msz.width, sz.width);
msz.height = Math.max(msz.height, sz.height);
}
return msz;
};
GroupResizingTool.prototype.resize = function(newr) {
if (this.isLengthening()) { // changing the length of all of the lanes
for (var it = myDiagram.nodes; it.next(); ) {
var group = it.value;
if (!(group instanceof go.Group)) continue;
var shape = group.findObject("SHAPE");
if (shape !== null) { // set its desiredSize, but leave the other direction alone
if (HORIZONTAL) {
shape.width = newr.width;
} else {
shape.height = newr.height;
}
}
}
} else { // changing the breadth and length of a single lane
go.ResizingTool.prototype.resize.call(this, newr);
}
};
// end GroupResizingTool class
// define a custom grid layout that makes sure the length of each lane is the same
// and that each lane is broad enough to hold its subgraph
function StackLayout() {
go.GridLayout.call(this);
}
go.Diagram.inherit(StackLayout, go.GridLayout);
StackLayout.prototype.doLayout = function(coll) {
var diagram = this.diagram;
if (diagram === null) return;
diagram.startTransaction("StackLayout");
// make sure all of the Group Shapes are big enough
var minsize = computeMinPlaceholderSize(diagram);
for (var it = diagram.nodes; it.next(); ) {
var group = it.value;
if (!(group instanceof go.Group)) continue;
var shape = group.findObject("SHAPE");
if (shape !== null) { // change the desiredSize to be big enough in both directions
var sz = computePlaceholderSize(group);
if (HORIZONTAL) {
shape.width = (isNaN(shape.width) ? minsize.width : Math.max(shape.width, minsize.width));
if (!isNaN(shape.height)) shape.height = Math.max(shape.height, sz.height);
} else {
if (!isNaN(shape.width)) shape.width = Math.max(shape.width, sz.width);
shape.height = (isNaN(shape.height) ? minsize.height : Math.max(shape.height, minsize.height));
}
var cell = group.resizeCellSize;
if (!isNaN(shape.width) && !isNaN(cell.width) && cell.width > 0) shape.width = Math.ceil(shape.width / cell.width) * cell.width;
if (!isNaN(shape.height) && !isNaN(cell.height) && cell.height > 0) shape.height = Math.ceil(shape.height / cell.height) * cell.height;
}
}
// now do all of the usual stuff, according to whatever properties have been set on this GridLayout
go.GridLayout.prototype.doLayout.call(this, coll);
diagram.commitTransaction("StackLayout");
};
// end StackLayout class
function init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagram",
{
// use a custom ResizingTool (along with a custom ResizeAdornment on each Group)
resizingTool: new GroupResizingTool(),
// use a simple layout that ignores links to stack the top-level Groups on top of each other
layout:
$(StackLayout,
{
cellSize: new go.Size(1, 1),
spacing: new go.Size(0, 0),
wrappingColumn: (HORIZONTAL ? 1 : Infinity),
wrappingWidth: Infinity,
isViewportSized: false
}),
// don't allow dropping onto the diagram's background
mouseDrop: function(e) { e.diagram.currentTool.doCancel(); },
// a clipboard copied node is pasted into the original node's group (i.e. lane).
"commandHandler.copiesGroupKey": true
});
// When a Node has been moved, make sure all of the top-level Groups get laid out again in a stack
function relayoutDiagramStack(e) {
myDiagram.layout.invalidateLayout(); // but don't invalidate all Layouts that are in Groups
myDiagram.layoutDiagram();
}
myDiagram.addDiagramListener("SelectionMoved", relayoutDiagramStack);
myDiagram.addDiagramListener("SelectionCopied", relayoutDiagramStack);
// this is a Part.dragComputation function for limiting where a Node may be dragged
function stayInGroup(part, pt, gridpt) {
// don't constrain top-level nodes
var grp = part.containingGroup;
if (grp === null) return pt;
// try to stay within the background Shape of the Group
var back = grp.findObject("SHAPE");
if (back === null) return pt;
// allow dragging a Node out of a Group if the Shift key is down
if (part.diagram.lastInput.shift) return pt;
var b = part.actualBounds;
var p1 = back.getDocumentPoint(go.Spot.TopLeft);
var p2 = back.getDocumentPoint(go.Spot.BottomRight);
// find the padding inside the group's placeholder that is around the member parts
var m = grp.placeholder.padding;
// now limit the location appropriately
var x = Math.max(p1.x + m.left, Math.min(pt.x, p2.x - m.right - b.width - 1));
var y = Math.max(p1.y + m.top, Math.min(pt.y, p2.y - m.bottom - b.height - 1));
return new go.Point(x, y);
}
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle",
{ fill: "white", portId: "", cursor: "pointer", fromLinkable: true, toLinkable: true }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key")),
// limit dragging of Nodes to stay within the containing Group, defined above
{
dragComputation: stayInGroup,
mouseDrop: function (e, node) { // dropping a copy of some Nodes and Links onto this Node adds them to this Node's Group
if (!e.shift && !e.control) return; // cannot change groups with an unmodified drag-and-drop
var grp = node.containingGroup;
if (grp !== null) {
var ok = grp.addMembers(node.diagram.selection, true);
if (!ok) grp.diagram.currentTool.doCancel();
}
},
layoutConditions: go.Part.LayoutAdded | go.Part.LayoutNodeSized
}
);
// each Group is a "swimlane" with a header on the left and a resizable lane on the right
myDiagram.groupTemplate =
$(go.Group, HORIZONTAL ? "Horizontal" : "Vertical",
{
movable: false, copyable: false, deletable: false, // can't move or copy or delete lanes
avoidable: false,
selectionObjectName: "SHAPE", // selecting a lane causes the body of the lane to be highlit, not the label
resizable: true, resizeObjectName: "SHAPE", // allow lanes to be resized, but the custom resizeAdornmentTemplate only permits one kind of resizing
layout: $(go.LayeredDigraphLayout, // automatically lay out the lane's subgraph
{ direction: HORIZONTAL ? 0 : 90, columnSpacing: 10, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource }),
computesBoundsAfterDrag: true, // needed to prevent recomputing Group.placeholder bounds too soon
computesBoundsIncludingLinks: false,
computesBoundsIncludingLocation: true,
mouseDrop: function (e, grp) { // dropping a copy of some Nodes and Links onto this Group adds them to this Group
if (!e.shift && !e.control) return; // cannot change groups with an unmodified drag-and-drop
var ok = grp.addMembers(grp.diagram.selection, true);
if (!ok) grp.diagram.currentTool.doCancel();
}
},
// the lane header consisting of a Shape and a TextBlock
$(go.Panel, "Horizontal",
{ angle: HORIZONTAL ? 270 : 0, // maybe rotate the header to read sideways going up
alignment: go.Spot.Center },
$(go.Shape, "Diamond",
{ width: 8, height: 8 },
new go.Binding("fill", "color")),
$(go.TextBlock, // the lane label
{ font: "bold 16pt sans-serif" },
new go.Binding("text", "key"))
), // end Horizontal Panel
$(go.Panel, "Auto", // the lane consisting of a background Shape and a Placeholder representing the subgraph
$(go.Shape, "Rectangle",
{ name: "SHAPE", fill: "white", minSize: computePlaceholderSize(null) },
new go.Binding("fill", "color")),
$(go.Placeholder,
{ padding: 10, alignment: go.Spot.TopLeft })
) // end Auto Panel
); // end Group
// define a custom resize adornment that only has a single resize handle
myDiagram.groupTemplate.resizeAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Placeholder),
$(go.Shape, // for changing the length of a lane
{
alignment: HORIZONTAL ? go.Spot.Right: go.Spot.Bottom,
desiredSize: HORIZONTAL ? new go.Size(7, 50) : new go.Size(50, 7),
fill: "lightblue", stroke: "dodgerblue",
cursor: HORIZONTAL ? "col-resize" : "row-resize"
}),
$(go.Shape, // for changing the breadth of a lane
{
alignment: HORIZONTAL ? go.Spot.Bottom : go.Spot.Right,
desiredSize: HORIZONTAL ? new go.Size(50, 7) : new go.Size(7, 50),
fill: "lightblue", stroke: "dodgerblue",
cursor: HORIZONTAL ? "row-resize" : "col-resize"
})
);
myDiagram.linkTemplate =
$(go.Link,
{ routing: go.Link.AvoidsNodes, corner: 5 },
{ relinkableFrom: true, relinkableTo: true },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
{
mouseDrop: function (e, link) { // dropping a copy of some Nodes and Links onto this Link adds them to this Link's Group
if (!e.shift && !e.control) return; // cannot change groups with an unmodified drag-and-drop
var grp = link.containingGroup;
if (grp !== null) {
var ok = grp.addMembers(link.diagram.selection, true);
if (!ok) grp.diagram.currentTool.doCancel();
}
},
layoutConditions: go.Part.LayoutAdded
}
);
// define some sample graphs in some of the lanes
myDiagram.model = new go.GraphLinksModel(
[ // node data
{ key: "Lane1", isGroup: true, color: "lightblue" },
{ key: "Lane2", isGroup: true, color: "lightgreen" },
{ key: "Lane3", isGroup: true, color: "lightyellow" },
{ key: "Lane4", isGroup: true, color: "orange" },
{ key: "oneA", group: "Lane1" },
{ key: "oneB", group: "Lane1" },
{ key: "oneC", group: "Lane1" },
{ key: "oneD", group: "Lane1" },
{ key: "twoA", group: "Lane2" },
{ key: "twoB", group: "Lane2" },
{ key: "twoC", group: "Lane2" },
{ key: "twoD", group: "Lane2" },
{ key: "twoE", group: "Lane2" },
{ key: "twoF", group: "Lane2" },
{ key: "twoG", group: "Lane2" },
{ key: "fourA", group: "Lane4" },
{ key: "fourB", group: "Lane4" },
{ key: "fourC", group: "Lane4" },
{ key: "fourD", group: "Lane4" },
],
[ // link data
{ from: "oneA", to: "oneB" },
{ from: "oneA", to: "oneC" },
{ from: "oneB", to: "oneD" },
{ from: "oneC", to: "oneD" },
{ from: "twoA", to: "twoB" },
{ from: "twoA", to: "twoC" },
{ from: "twoA", to: "twoF" },
{ from: "twoB", to: "twoD" },
{ from: "twoC", to: "twoD" },
{ from: "twoD", to: "twoG" },
{ from: "twoE", to: "twoG" },
{ from: "twoF", to: "twoG" },
{ from: "fourA", to: "fourB" },
{ from: "fourB", to: "fourC" },
{ from: "fourC", to: "fourD" }
]);
myDiagram.model.undoManager.isEnabled = true;
}
</script>
</head>
<body onload="init()">
<div id="sample">
<div id="myDiagram" style="border: solid 1px blue; width:100%; height:600px;"></div>
</div>
</body>
</html>
I've tried setting
var HORIZONTAL = true;
to
var HORIZONTAL = false;
I may have the correct orientation though, but the flow is changed. I wonder if the column could just be horizontal and the flow runs like the top-level view.
The "flow" is changed because setting HORIZONTAL = false also changes the Group's layout that it users for its members:
myDiagram.groupTemplate =
$(go.Group, HORIZONTAL ? "Horizontal" : "Vertical",
{
...
layout: $(go.LayeredDigraphLayout, // automatically lay out the lane's subgraph
{ direction: HORIZONTAL ? 0 : 90, columnSpacing: 10, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource }),
So you're going to have to modify that direction: HORIZONTAL ? 0 : 90 to your liking if you don't want it to change when HORIZONTAL does.
It is not possible to add HTML attributes to each node, but you can store any arbitrary data for each node in the node data itself.

Categories

Resources