cytoscape-cose-bilkent
Advanced tools
Comparing version 1.6.7 to 1.6.8
{ | ||
"name": "cytoscape-cose-bilkent", | ||
"version": "1.6.7", | ||
"version": "1.6.8", | ||
"description": "The CoSE layout for Cytoscape.js by Bilkent", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
@@ -14,3 +14,6 @@ var FDLayoutConstants = require('./FDLayoutConstants'); | ||
CoSEConstants.DEFAULT_COMPONENT_SEPERATION = 60; | ||
CoSEConstants.TILE = true; | ||
CoSEConstants.TILING_PADDING_VERTICAL = 10; | ||
CoSEConstants.TILING_PADDING_HORIZONTAL = 10; | ||
module.exports = CoSEConstants; |
@@ -19,2 +19,4 @@ var FDLayout = require('./FDLayout'); | ||
FDLayout.call(this); | ||
this.toBeTiled = {}; // Memorize if a node is to be tiled or is tiled | ||
} | ||
@@ -445,2 +447,494 @@ | ||
// Tiling methods | ||
// Group zero degree members whose parents are not to be tiled, create dummy parents where needed and fill memberGroups by their dummp parent id's | ||
CoSELayout.prototype.groupZeroDegreeMembers = function () { | ||
var self = this; | ||
// array of [parent_id x oneDegreeNode_id] | ||
var tempMemberGroups = {}; // A temporary map of parent node and its zero degree members | ||
this.memberGroups = {}; // A map of dummy parent node and its zero degree members whose parents are not to be tiled | ||
this.idToDummyNode = {}; // A map of id to dummy node | ||
var zeroDegree = []; // List of zero degree nodes whose parents are not to be tiled | ||
var allNodes = this.graphManager.getAllNodes(); | ||
// Fill zero degree list | ||
for (var i = 0; i < allNodes.length; i++) { | ||
var node = allNodes[i]; | ||
var parent = node.getParent(); | ||
// If a node has zero degree and its parent is not to be tiled if exists add that node to zeroDegres list | ||
if (this.getNodeDegreeWithChildren(node) === 0 && ( parent.id == undefined || !this.getToBeTiled(parent) ) ) { | ||
zeroDegree.push(node); | ||
} | ||
} | ||
// Create a map of parent node and its zero degree members | ||
for (var i = 0; i < zeroDegree.length; i++) | ||
{ | ||
var node = zeroDegree[i]; // Zero degree node itself | ||
var p_id = node.getParent().id; // Parent id | ||
if (typeof tempMemberGroups[p_id] === "undefined") | ||
tempMemberGroups[p_id] = []; | ||
tempMemberGroups[p_id] = tempMemberGroups[p_id].concat(node); // Push node to the list belongs to its parent in tempMemberGroups | ||
} | ||
// If there are at least two nodes at a level, create a dummy compound for them | ||
Object.keys(tempMemberGroups).forEach(function(p_id) { | ||
if (tempMemberGroups[p_id].length > 1) { | ||
var dummyCompoundId = "DummyCompound_" + p_id; // The id of dummy compound which will be created soon | ||
self.memberGroups[dummyCompoundId] = tempMemberGroups[p_id]; // Add dummy compound to memberGroups | ||
var parent = tempMemberGroups[p_id][0].getParent(); // The parent of zero degree nodes will be the parent of new dummy compound | ||
// Create a dummy compound with calculated id | ||
var dummyCompound = new CoSENode(self.graphManager); | ||
dummyCompound.id = dummyCompoundId; | ||
dummyCompound.paddingLeft = parent.paddingLeft || 0; | ||
dummyCompound.paddingRight = parent.paddingRight || 0; | ||
dummyCompound.paddingBottom = parent.paddingBottom || 0; | ||
dummyCompound.paddingTop = parent.paddingTop || 0; | ||
self.idToDummyNode[dummyCompoundId] = dummyCompound; | ||
var dummyParentGraph = self.getGraphManager().add(self.newGraph(), dummyCompound); | ||
var parentGraph = parent.getChild(); | ||
// Add dummy compound to parent the graph | ||
parentGraph.add(dummyCompound); | ||
// For each zero degree node in this level remove it from its parent graph and add it to the graph of dummy parent | ||
for (var i = 0; i < tempMemberGroups[p_id].length; i++) { | ||
var node = tempMemberGroups[p_id][i]; | ||
parentGraph.remove(node); | ||
dummyParentGraph.add(node); | ||
} | ||
} | ||
}); | ||
}; | ||
CoSELayout.prototype.clearCompounds = function () { | ||
var childGraphMap = {}; | ||
var idToNode = {}; | ||
// Get compound ordering by finding the inner one first | ||
this.performDFSOnCompounds(); | ||
for (var i = 0; i < this.compoundOrder.length; i++) { | ||
idToNode[this.compoundOrder[i].id] = this.compoundOrder[i]; | ||
childGraphMap[this.compoundOrder[i].id] = [].concat(this.compoundOrder[i].getChild().getNodes()); | ||
// Remove children of compounds | ||
this.graphManager.remove(this.compoundOrder[i].getChild()); | ||
this.compoundOrder[i].child = null; | ||
} | ||
this.graphManager.resetAllNodes(); | ||
// Tile the removed children | ||
this.tileCompoundMembers(childGraphMap, idToNode); | ||
}; | ||
CoSELayout.prototype.clearZeroDegreeMembers = function () { | ||
var self = this; | ||
var tiledZeroDegreePack = this.tiledZeroDegreePack = []; | ||
Object.keys(this.memberGroups).forEach(function(id) { | ||
var compoundNode = self.idToDummyNode[id]; // Get the dummy compound | ||
tiledZeroDegreePack[id] = self.tileNodes(self.memberGroups[id], compoundNode.paddingLeft + compoundNode.paddingRight); | ||
// Set the width and height of the dummy compound as calculated | ||
compoundNode.rect.width = tiledZeroDegreePack[id].width; | ||
compoundNode.rect.height = tiledZeroDegreePack[id].height; | ||
}); | ||
}; | ||
CoSELayout.prototype.repopulateCompounds = function () { | ||
for (var i = this.compoundOrder.length - 1; i >= 0; i--) { | ||
var lCompoundNode = this.compoundOrder[i]; | ||
var id = lCompoundNode.id; | ||
var horizontalMargin = lCompoundNode.paddingLeft; | ||
var verticalMargin = lCompoundNode.paddingTop; | ||
this.adjustLocations(this.tiledMemberPack[id], lCompoundNode.rect.x, lCompoundNode.rect.y, horizontalMargin, verticalMargin); | ||
} | ||
}; | ||
CoSELayout.prototype.repopulateZeroDegreeMembers = function () { | ||
var self = this; | ||
var tiledPack = this.tiledZeroDegreePack; | ||
Object.keys(tiledPack).forEach(function(id) { | ||
var compoundNode = self.idToDummyNode[id]; // Get the dummy compound by its id | ||
var horizontalMargin = compoundNode.paddingLeft; | ||
var verticalMargin = compoundNode.paddingTop; | ||
// Adjust the positions of nodes wrt its compound | ||
self.adjustLocations(tiledPack[id], compoundNode.rect.x, compoundNode.rect.y, horizontalMargin, verticalMargin); | ||
}); | ||
}; | ||
CoSELayout.prototype.getToBeTiled = function (node) { | ||
var id = node.id; | ||
//firstly check the previous results | ||
if (this.toBeTiled[id] != null) { | ||
return this.toBeTiled[id]; | ||
} | ||
//only compound nodes are to be tiled | ||
var childGraph = node.getChild(); | ||
if (childGraph == null) { | ||
this.toBeTiled[id] = false; | ||
return false; | ||
} | ||
var children = childGraph.getNodes(); // Get the children nodes | ||
//a compound node is not to be tiled if all of its compound children are not to be tiled | ||
for (var i = 0; i < children.length; i++) { | ||
var theChild = children[i]; | ||
if (this.getNodeDegree(theChild) > 0) { | ||
this.toBeTiled[id] = false; | ||
return false; | ||
} | ||
//pass the children not having the compound structure | ||
if (theChild.getChild() == null) { | ||
this.toBeTiled[theChild.id] = false; | ||
continue; | ||
} | ||
if (!this.getToBeTiled(theChild)) { | ||
this.toBeTiled[id] = false; | ||
return false; | ||
} | ||
} | ||
this.toBeTiled[id] = true; | ||
return true; | ||
}; | ||
// Get degree of a node depending of its edges and independent of its children | ||
CoSELayout.prototype.getNodeDegree = function (node) { | ||
var id = node.id; | ||
var edges = node.getEdges(); | ||
var degree = 0; | ||
// For the edges connected | ||
for (var i = 0; i < edges.length; i++) { | ||
var edge = edges[i]; | ||
if (edge.getSource().id !== edge.getTarget().id) { | ||
degree = degree + 1; | ||
} | ||
} | ||
return degree; | ||
}; | ||
// Get degree of a node with its children | ||
CoSELayout.prototype.getNodeDegreeWithChildren = function (node) { | ||
var degree = this.getNodeDegree(node); | ||
if (node.getChild() == null) { | ||
return degree; | ||
} | ||
var children = node.getChild().getNodes(); | ||
for (var i = 0; i < children.length; i++) { | ||
var child = children[i]; | ||
degree += this.getNodeDegreeWithChildren(child); | ||
} | ||
return degree; | ||
}; | ||
CoSELayout.prototype.performDFSOnCompounds = function () { | ||
this.compoundOrder = []; | ||
this.fillCompexOrderByDFS(this.graphManager.getRoot().getNodes()); | ||
}; | ||
CoSELayout.prototype.fillCompexOrderByDFS = function (children) { | ||
for (var i = 0; i < children.length; i++) { | ||
var child = children[i]; | ||
if (child.getChild() != null) { | ||
this.fillCompexOrderByDFS(child.getChild().getNodes()); | ||
} | ||
if (this.getToBeTiled(child)) { | ||
this.compoundOrder.push(child); | ||
} | ||
} | ||
}; | ||
/** | ||
* This method places each zero degree member wrt given (x,y) coordinates (top left). | ||
*/ | ||
CoSELayout.prototype.adjustLocations = function (organization, x, y, compoundHorizontalMargin, compoundVerticalMargin) { | ||
x += compoundHorizontalMargin; | ||
y += compoundVerticalMargin; | ||
var left = x; | ||
for (var i = 0; i < organization.rows.length; i++) { | ||
var row = organization.rows[i]; | ||
x = left; | ||
var maxHeight = 0; | ||
for (var j = 0; j < row.length; j++) { | ||
var lnode = row[j]; | ||
lnode.rect.x = x;// + lnode.rect.width / 2; | ||
lnode.rect.y = y;// + lnode.rect.height / 2; | ||
x += lnode.rect.width + organization.horizontalPadding; | ||
if (lnode.rect.height > maxHeight) | ||
maxHeight = lnode.rect.height; | ||
} | ||
y += maxHeight + organization.verticalPadding; | ||
} | ||
}; | ||
CoSELayout.prototype.tileCompoundMembers = function (childGraphMap, idToNode) { | ||
var self = this; | ||
this.tiledMemberPack = []; | ||
Object.keys(childGraphMap).forEach(function(id) { | ||
// Get the compound node | ||
var compoundNode = idToNode[id]; | ||
self.tiledMemberPack[id] = self.tileNodes(childGraphMap[id], compoundNode.paddingLeft + compoundNode.paddingRight); | ||
compoundNode.rect.width = self.tiledMemberPack[id].width + 20; | ||
compoundNode.rect.height = self.tiledMemberPack[id].height + 20; | ||
}); | ||
}; | ||
CoSELayout.prototype.tileNodes = function (nodes, minWidth) { | ||
var verticalPadding = CoSEConstants.TILING_PADDING_VERTICAL; | ||
var horizontalPadding = CoSEConstants.TILING_PADDING_HORIZONTAL; | ||
var organization = { | ||
rows: [], | ||
rowWidth: [], | ||
rowHeight: [], | ||
width: 20, | ||
height: 20, | ||
verticalPadding: verticalPadding, | ||
horizontalPadding: horizontalPadding | ||
}; | ||
// Sort the nodes in ascending order of their areas | ||
nodes.sort(function (n1, n2) { | ||
if (n1.rect.width * n1.rect.height > n2.rect.width * n2.rect.height) | ||
return -1; | ||
if (n1.rect.width * n1.rect.height < n2.rect.width * n2.rect.height) | ||
return 1; | ||
return 0; | ||
}); | ||
// Create the organization -> tile members | ||
for (var i = 0; i < nodes.length; i++) { | ||
var lNode = nodes[i]; | ||
if (organization.rows.length == 0) { | ||
this.insertNodeToRow(organization, lNode, 0, minWidth); | ||
} | ||
else if (this.canAddHorizontal(organization, lNode.rect.width, lNode.rect.height)) { | ||
this.insertNodeToRow(organization, lNode, this.getShortestRowIndex(organization), minWidth); | ||
} | ||
else { | ||
this.insertNodeToRow(organization, lNode, organization.rows.length, minWidth); | ||
} | ||
this.shiftToLastRow(organization); | ||
} | ||
return organization; | ||
}; | ||
CoSELayout.prototype.insertNodeToRow = function (organization, node, rowIndex, minWidth) { | ||
var minCompoundSize = minWidth; | ||
// Add new row if needed | ||
if (rowIndex == organization.rows.length) { | ||
var secondDimension = []; | ||
organization.rows.push(secondDimension); | ||
organization.rowWidth.push(minCompoundSize); | ||
organization.rowHeight.push(0); | ||
} | ||
// Update row width | ||
var w = organization.rowWidth[rowIndex] + node.rect.width; | ||
if (organization.rows[rowIndex].length > 0) { | ||
w += organization.horizontalPadding; | ||
} | ||
organization.rowWidth[rowIndex] = w; | ||
// Update compound width | ||
if (organization.width < w) { | ||
organization.width = w; | ||
} | ||
// Update height | ||
var h = node.rect.height; | ||
if (rowIndex > 0) | ||
h += organization.verticalPadding; | ||
var extraHeight = 0; | ||
if (h > organization.rowHeight[rowIndex]) { | ||
extraHeight = organization.rowHeight[rowIndex]; | ||
organization.rowHeight[rowIndex] = h; | ||
extraHeight = organization.rowHeight[rowIndex] - extraHeight; | ||
} | ||
organization.height += extraHeight; | ||
// Insert node | ||
organization.rows[rowIndex].push(node); | ||
}; | ||
//Scans the rows of an organization and returns the one with the min width | ||
CoSELayout.prototype.getShortestRowIndex = function (organization) { | ||
var r = -1; | ||
var min = Number.MAX_VALUE; | ||
for (var i = 0; i < organization.rows.length; i++) { | ||
if (organization.rowWidth[i] < min) { | ||
r = i; | ||
min = organization.rowWidth[i]; | ||
} | ||
} | ||
return r; | ||
}; | ||
//Scans the rows of an organization and returns the one with the max width | ||
CoSELayout.prototype.getLongestRowIndex = function (organization) { | ||
var r = -1; | ||
var max = Number.MIN_VALUE; | ||
for (var i = 0; i < organization.rows.length; i++) { | ||
if (organization.rowWidth[i] > max) { | ||
r = i; | ||
max = organization.rowWidth[i]; | ||
} | ||
} | ||
return r; | ||
}; | ||
/** | ||
* This method checks whether adding extra width to the organization violates | ||
* the aspect ratio(1) or not. | ||
*/ | ||
CoSELayout.prototype.canAddHorizontal = function (organization, extraWidth, extraHeight) { | ||
var sri = this.getShortestRowIndex(organization); | ||
if (sri < 0) { | ||
return true; | ||
} | ||
var min = organization.rowWidth[sri]; | ||
if (min + organization.horizontalPadding + extraWidth <= organization.width) | ||
return true; | ||
var hDiff = 0; | ||
// Adding to an existing row | ||
if (organization.rowHeight[sri] < extraHeight) { | ||
if (sri > 0) | ||
hDiff = extraHeight + organization.verticalPadding - organization.rowHeight[sri]; | ||
} | ||
var add_to_row_ratio; | ||
if (organization.width - min >= extraWidth + organization.horizontalPadding) { | ||
add_to_row_ratio = (organization.height + hDiff) / (min + extraWidth + organization.horizontalPadding); | ||
} else { | ||
add_to_row_ratio = (organization.height + hDiff) / organization.width; | ||
} | ||
// Adding a new row for this node | ||
hDiff = extraHeight + organization.verticalPadding; | ||
var add_new_row_ratio; | ||
if (organization.width < extraWidth) { | ||
add_new_row_ratio = (organization.height + hDiff) / extraWidth; | ||
} else { | ||
add_new_row_ratio = (organization.height + hDiff) / organization.width; | ||
} | ||
if (add_new_row_ratio < 1) | ||
add_new_row_ratio = 1 / add_new_row_ratio; | ||
if (add_to_row_ratio < 1) | ||
add_to_row_ratio = 1 / add_to_row_ratio; | ||
return add_to_row_ratio < add_new_row_ratio; | ||
}; | ||
//If moving the last node from the longest row and adding it to the last | ||
//row makes the bounding box smaller, do it. | ||
CoSELayout.prototype.shiftToLastRow = function (organization) { | ||
var longest = this.getLongestRowIndex(organization); | ||
var last = organization.rowWidth.length - 1; | ||
var row = organization.rows[longest]; | ||
var node = row[row.length - 1]; | ||
var diff = node.width + organization.horizontalPadding; | ||
// Check if there is enough space on the last row | ||
if (organization.width - organization.rowWidth[last] > diff && longest != last) { | ||
// Remove the last element of the longest row | ||
row.splice(-1, 1); | ||
// Push it to the last row | ||
organization.rows[last].push(node); | ||
organization.rowWidth[longest] = organization.rowWidth[longest] - diff; | ||
organization.rowWidth[last] = organization.rowWidth[last] + diff; | ||
organization.width = organization.rowWidth[instance.getLongestRowIndex(organization)]; | ||
// Update heights of the organization | ||
var maxHeight = Number.MIN_VALUE; | ||
for (var i = 0; i < row.length; i++) { | ||
if (row[i].height > maxHeight) | ||
maxHeight = row[i].height; | ||
} | ||
if (longest > 0) | ||
maxHeight += organization.verticalPadding; | ||
var prevTotal = organization.rowHeight[longest] + organization.rowHeight[last]; | ||
organization.rowHeight[longest] = maxHeight; | ||
if (organization.rowHeight[last] < node.height + organization.verticalPadding) | ||
organization.rowHeight[last] = node.height + organization.verticalPadding; | ||
var finalTotal = organization.rowHeight[longest] + organization.rowHeight[last]; | ||
organization.height += (finalTotal - prevTotal); | ||
this.shiftToLastRow(organization); | ||
} | ||
}; | ||
CoSELayout.prototype.tilingPreLayout = function() { | ||
if (CoSEConstants.TILE) { | ||
// Find zero degree nodes and create a compound for each level | ||
this.groupZeroDegreeMembers(); | ||
// Tile and clear children of each compound | ||
this.clearCompounds(); | ||
// Separately tile and clear zero degree nodes for each level | ||
this.clearZeroDegreeMembers(); | ||
} | ||
}; | ||
CoSELayout.prototype.tilingPostLayout = function() { | ||
if (CoSEConstants.TILE) { | ||
this.repopulateZeroDegreeMembers(); | ||
this.repopulateCompounds(); | ||
} | ||
}; | ||
module.exports = CoSELayout; |
@@ -32,3 +32,2 @@ 'use strict'; | ||
var CoSENode = require('./CoSENode'); | ||
var TilingExtension = require('./TilingExtension'); | ||
@@ -97,3 +96,2 @@ var defaults = { | ||
function _CoSELayout(_options) { | ||
TilingExtension(this); // Extend this instance with tiling functions | ||
this.options = extend(defaults, _options); | ||
@@ -128,2 +126,7 @@ getUserOptions(this.options); | ||
CoSEConstants.ANIMATE = FDLayoutConstants.ANIMATE = options.animate; | ||
CoSEConstants.TILE = options.tile; | ||
CoSEConstants.TILING_PADDING_VERTICAL = | ||
typeof options.tilingPaddingVertical === 'function' ? options.tilingPaddingVertical.call() : options.tilingPaddingVertical; | ||
CoSEConstants.TILING_PADDING_HORIZONTAL = | ||
typeof options.tilingPaddingHorizontal === 'function' ? options.tilingPaddingHorizontal.call() : options.tilingPaddingHorizontal; | ||
}; | ||
@@ -150,11 +153,5 @@ | ||
this.root = gm.addRoot(); | ||
this.processChildrenList(this.root, this.getTopMostNodes(nodes), layout); | ||
if (!this.options.tile) { | ||
this.processChildrenList(this.root, this.getTopMostNodes(nodes), layout); | ||
} | ||
else { | ||
this.preLayout(); | ||
} | ||
for (var i = 0; i < edges.length; i++) { | ||
@@ -207,5 +204,2 @@ var edge = edges[i]; | ||
if (isDone) { | ||
if (self.options.tile) { | ||
self.postLayout(); | ||
} | ||
self.options.eles.nodes().positions(getPositions); | ||
@@ -230,3 +224,5 @@ | ||
var animationData = self.layout.getPositionsData(); // Get positions of layout nodes note that all nodes may not be layout nodes because of tiling | ||
// Position nodes, for the nodes who are not passed to layout because of tiling return the position of their dummy compound | ||
// Position nodes, for the nodes whose id does not included in data (because they are removed from their parents and included in dummy compounds) | ||
// use position of their ancestors or dummy ancestors | ||
options.eles.nodes().positions(function (ele, i) { | ||
@@ -236,16 +232,10 @@ if (typeof ele === "number") { | ||
} | ||
if (ele.scratch('coseBilkent') && ele.scratch('coseBilkent').dummy_parent_id) { | ||
var dummyParent = ele.scratch('coseBilkent').dummy_parent_id; | ||
return { | ||
x: dummyParent.x, | ||
y: dummyParent.y | ||
}; | ||
} | ||
var theId = ele.data('id'); | ||
var theId = ele.id(); | ||
var pNode = animationData[theId]; | ||
var temp = ele; | ||
// If pNode is undefined search until finding position data of its first ancestor (It may be dummy as well) | ||
while (pNode == null) { | ||
pNode = animationData[temp.data('parent')] || animationData['DummyCompound_' + temp.data('parent')]; | ||
animationData[theId] = pNode; | ||
temp = temp.parent()[0]; | ||
pNode = animationData[temp.id()]; | ||
animationData[theId] = pNode; | ||
} | ||
@@ -279,5 +269,2 @@ return { | ||
setTimeout(function() { | ||
if (self.options.tile) { | ||
self.postLayout(); | ||
} | ||
self.options.eles.nodes().not(":parent").layoutPositions(self, self.options, getPositions); // Use layout positions to reposition the nodes it considers the options parameter | ||
@@ -327,3 +314,3 @@ self.options.eles.nodes().removeScratch('coseBilkent'); | ||
theNode = parent.add(new CoSENode(layout.graphManager, | ||
new PointD(theChild.position('x'), theChild.position('y')), | ||
new PointD(theChild.position('x') - theChild.width() / 2, theChild.position('y') - theChild.height() / 2), | ||
new DimensionD(parseFloat(theChild.width()), | ||
@@ -335,3 +322,10 @@ parseFloat(theChild.height())))); | ||
} | ||
// Attach id to the layout node | ||
theNode.id = theChild.data("id"); | ||
// Attach the paddings of cy node to layout node | ||
theNode.paddingLeft = parseInt( theChild.css('padding-left') ); | ||
theNode.paddingTop = parseInt( theChild.css('padding-top') ); | ||
theNode.paddingRight = parseInt( theChild.css('padding-right') ); | ||
theNode.paddingBottom = parseInt( theChild.css('padding-bottom') ); | ||
// Map the layout node | ||
this.idToLNode[theChild.data("id")] = theNode; | ||
@@ -338,0 +332,0 @@ |
@@ -96,2 +96,6 @@ var LayoutConstants = require('./LayoutConstants'); | ||
this.isLayoutFinished = false; | ||
if (this.tilingPreLayout) { | ||
this.tilingPreLayout(); | ||
} | ||
@@ -134,2 +138,6 @@ this.initParameters(); | ||
if (this.tilingPostLayout) { | ||
this.tilingPostLayout(); | ||
} | ||
this.isLayoutFinished = true; | ||
@@ -136,0 +144,0 @@ |
@@ -0,2 +1,6 @@ | ||
var LGraph; | ||
var LEdge = require('./LEdge'); | ||
function LGraphManager(layout) { | ||
LGraph = require('./LGraph'); // It may be better to initilize this out of this function but it gives an error (Right-hand side of 'instanceof' is not callable) now. | ||
this.layout = layout; | ||
@@ -3,0 +7,0 @@ |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
591189
43
9721