Compare commits

..

13 Commits

Author SHA1 Message Date
Tobias Åström
d15b246db4 Merge pull request #17 from qlik-oss/tsm/QLIK-94112-promise
Make sure promise resolves properly
2019-03-08 14:58:33 +01:00
Tobias Åström
fc8e9b0ba4 Make sure promise resolves properly 2019-03-08 11:17:35 +01:00
John Lunde
b443deca27 Merge pull request #16 from qlik-oss/feature/QPE-592
[QPE-592] only load babel if not already loaded
2019-02-14 13:01:43 +01:00
Kristoffer Lind
8a24c3ee92 only load babel if not already loaded 2019-02-13 15:02:12 +01:00
Tobias Åström
464d137095 Add blackduck 2019-02-04 16:51:08 +01:00
John Lunde
903a2caa1d Merge pull request #15 from qlik-oss/caele/QPE-524
Fix issue with coloring
2019-01-11 14:56:14 +01:00
Tobias Åström
d68b2ed863 Fix issue with coloring 2019-01-10 13:12:14 +01:00
Piotr Nestorow
f87bc3ea88 Merge pull request #14 from qlik-oss/caele-test
Simplification of Network chart data
2019-01-10 09:04:53 +01:00
Tobias Åström
b7753143fd Hide undefined edge labels 2018-12-19 08:46:49 +01:00
Tobias Åström
8f239687f3 Correct selections and add descriptions 2018-12-18 16:54:36 +01:00
Tobias Åström
d970d05711 Make the last dimension optional 2018-12-14 11:09:31 +01:00
Tobias Åström
192f4a8597 Update coloring 2018-12-12 16:47:35 +01:00
Tobias Åström
6eff5e1fd0 Change to allow strings instead of only numerics for IDs 2018-12-12 16:47:02 +01:00
4 changed files with 258 additions and 167 deletions

View File

@@ -18,6 +18,14 @@ jobs:
- run:
name: Install dependencies
command: npm install
- run:
name: BlackDuck scan
command: curl -s https://blackducksoftware.github.io/hub-detect/hub-detect.sh | bash -s -- \
--blackduck.url="https://qliktech.blackducksoftware.com" \
--blackduck.trust.cert=true \
--blackduck.username="svc-blackduck" \
--blackduck.password=${svc_blackduck} \
--detect.project.name="viz-bundle-qlik-network-chart"
- run:
name: Run tests
command: npm run test-once

View File

@@ -5,10 +5,37 @@ Tested on Qlik Sense 2.2.3
Agilos.com takes no responsibility for any code.
Use at your own risk.
*/
import "@babel/polyfill";
if (!window._babelPolyfill) { // eslint-disable-line no-underscore-dangle
require('@babel/polyfill'); // eslint-disable-line global-require
}
import paint from './paint';
import './styles/main.less';
const dimDesc = [
"Node Identifier",
"Node Label",
"Node Parent",
"Node Group"
];
const dimLongDesc = [
"Node Identifier - a field in the dataset which should be presented as a node in the network diagram."
+ " these control the actual elements presented in the network diagram.",
"Node Label - controls what field holds the data that described the nodes in the network"
+ " diagram. The field content will be presented as label.",
"Node Parent - is used to determine the ancestor node for the individual nodes."
+ " This field will be used for describing the relationships between network elements.",
"Node Group - is a field which describes groups of a node in the network."
+ " This is used to apply the same color to several nodes."
];
const measureDesc = [
"Tooltip",
"Node size",
"Edge size"
];
const component = {
initialProperties: {
version: 1.0,
@@ -24,8 +51,11 @@ const component = {
//property panel
data: {
dimensions: {
min: 4,
max: 4
min: 3,
max: 4,
description(properties, index) {
return dimDesc[index];
}
/*
1. Dimension: Node ID, numeric (Event ID or else) or String
2. Dimension: Node Label
@@ -35,7 +65,10 @@ const component = {
},
measures: {
min: 0,
max: 3
max: 3,
description(properties, index) {
return measureDesc[index];
}
/*
1. Measure: title text for tooltip (optional)
2. Measure: node value
@@ -51,7 +84,22 @@ const component = {
uses: "data",
items:{
dimensions:{
disabledRef: ""
disabledRef: "",
items: {
helpDesc: {
component: 'text',
style: 'qlik-network-chart-italic-property',
label: function(properties, handler) {
var index;
handler.getDimensions().forEach((element, i) => {
if(element.qDef.cId === properties.qDef.cId) {
index = i;
}
});
return dimLongDesc[index];
}
}
}
},
measures: {
disabledRef: ""

View File

@@ -3,184 +3,215 @@ import qlik from 'qlik';
import { createTooltipHTML } from './tooltip';
import { escapeHTML } from './utilities';
const colorScheme = 'Diverging Classes';
function isTextCellNotEmpty(c) {
return (c.qText && !(c.qIsNull || c.qText.trim() == ''));
}
function getColor (index, colors) {
return colors[index % colors.length];
}
function paint ( $element, layout, qTheme, component ) {
const colorScale = qTheme.properties.scales
.find(scale => scale.name === colorScheme).scale;
const colors = colorScale[colorScale.length - 1];
function getColor (number) {
return colors[number % colors.length];
}
return new qlik.Promise(function(resolve) {
var qData = layout.qHyperCube.qDataPages[0],
id = layout.qInfo.qId,
containerId = 'network-container_' + id;
const colorScale = qTheme.properties.palettes.data[0];
const numDimensions = layout.qHyperCube.qDimensionInfo.length;
const numMeasures = layout.qHyperCube.qMeasureInfo.length;
if(qData && qData.qMatrix) {
$element.empty().append($('<div />')
.attr({ id: containerId })
.toggleClass('is-edit-mode', component.inEditState())
.css({
height: $element.height(),
width: $element.width(),
overflow: 'auto'
}));
var qData = layout.qHyperCube.qDataPages[0],
id = layout.qInfo.qId,
containerId = 'network-container_' + id;
var dataSet = qData.qMatrix.map(function(e){
const nodeName = e[1].qText;
const groupNumber = e[3].qText;
if(qData && qData.qMatrix) {
$element.empty().append($('<div />')
.attr({ id: containerId })
.toggleClass('is-edit-mode', component.inEditState())
.css({
height: $element.height(),
width: $element.width(),
overflow: 'auto'
}));
const dataItem = {
id: e[0].qNum,
label: nodeName,
group: groupNumber,
parentid : e[2].qNum
var dataSet = qData.qMatrix.map(function(e){
const nodeName = e[1].qText;
let groupNumber;
const dataItem = {
id: e[0].qText,
eNum: e[0].qElemNumber,
label: nodeName,
parentid : e[2].qText
};
if(numDimensions === 4) {
groupNumber = e[3].qText;
dataItem.group = groupNumber;
}
// optional measures set
if (numMeasures > 0) {
const tooltip = e[numDimensions];
if (isTextCellNotEmpty(tooltip)) {
const tooltipText = tooltip.qText;
dataItem.title = escapeHTML(tooltipText);
} else if(numMeasures > 1) {
// This part is a bit fishy and should be tested
const nodeMeasure = e[numDimensions+1].qText;
dataItem.title = createTooltipHTML({
name: nodeName,
groupNumber,
nodeMeasure
});
}
}
if (numMeasures > 1) {
if (e[numDimensions+1].qNum) {
// node value - to scale node shape size
dataItem.nodeValue = e[5].qNum;
}
}
if (numMeasures > 2) {
if (e[numDimensions+2].qNum) {
// edge value - to scale edge width
dataItem.edgeValue = e[numDimensions+2].qNum;
}
}
return dataItem;
});
// Require 2 arrays : nodes and edges - nodes array must be unique values of IDs !
var uniqueId = [];
var nodes = [];
var edges = [];
const groups = {};
for(let i = 0; i< dataSet.length; i++){
if (layout.displayEdgeLabel && dataSet[i].edgeValue !== undefined) {
edges.push({
"from":dataSet[i].id,
"to":dataSet[i].parentid,
"value":dataSet[i].edgeValue,
"label": `${dataSet[i].edgeValue}`
}); // with labels
} else {
edges.push({
"from":dataSet[i].id,
"to":dataSet[i].parentid,
"value":dataSet[i].edgeValue
}); // create edges
}
// process uniqueness
if(uniqueId.indexOf(dataSet[i].id) === -1) {
uniqueId.push(dataSet[i].id);
var nodeItem = {
id: dataSet[i].id,
eNum: dataSet[i].eNum,
label: dataSet[i].label,
title: dataSet[i].title,
group: dataSet[i].group,
value: dataSet[i].nodeValue
};
nodes.push(nodeItem); // create node
groups[nodeItem.group] = {};
}
}
const colors = colorScale.scale[Math.min(Object.keys(groups).length-1, colorScale.scale.length-1)];
Object.keys(groups).forEach(function(g,i) {
groups[g].color = getColor(i, colors);
});
// create dataset for \\
var data = {
nodes: nodes,
edges: edges
};
// optional measures set
if (e.length > 4) {
const tooltip = e[4];
// create a network
var container = document.getElementById(containerId);
if (isTextCellNotEmpty(tooltip)) {
const tooltipText = tooltip.qText;
dataItem.title = escapeHTML(tooltipText);
} else {
const nodeMeasure = e[5].qText;
dataItem.title = createTooltipHTML({
name: nodeName,
groupNumber,
nodeMeasure
});
var options = {
groups: groups,
layout: {
randomSeed: 1
},
nodes: {
shape:layout.nodeShape,
shadow:layout.shadowMode
},
edges: {
shadow:layout.shadowMode,
font: {
align: layout.posEdgeLabel
},
smooth: {
type: layout.edgeType
}
},
interaction: {
hideEdgesOnDrag: true,
tooltipDelay: 100
},
physics: {
forceAtlas2Based: {
gravitationalConstant: -100,
centralGravity: 0.005,
springLength: 230,
springConstant: 0.18
},
maxVelocity: 146,
solver: 'forceAtlas2Based',
timestep: 0.35,
stabilization: { iterations: 150 }
}
}
};
var network = new Network(container, data, options);
network.fit();
if (e.length > 5) {
if (e[5].qNum) {
// node value - to scale node shape size
dataItem.nodeValue = e[5].qNum;
// Handle Selection on 1-node
$("#"+containerId).css('cursor','default');
network.on('select', function (properties) {
if (properties.hasOwnProperty("nodes")) {
if (properties.nodes.length > 0) {
// find connected nodes to selection
var connectedNodes = network.getConnectedNodes(properties.nodes[0]);
// append node to the array
connectedNodes.push(properties.nodes[0]);
const toSelect = [];
connectedNodes.forEach(function(node) {
var id;
data.nodes.forEach(function(dataNode) {
// Find match, ignore null
if(dataNode.id === node && node !== "-") {
id = dataNode.eNum;
}
});
if(id !== undefined) {
// Remove duplicates
toSelect.indexOf(id) === -1 && toSelect.push(id);
}
});
//Make the selections
component.backendApi.selectValues(0,toSelect,false);
}
}
}
});
if (e.length > 6) {
if (e[6].qNum) {
// edge value - to scale edge width
dataItem.edgeValue = e[6].qNum;
}
}
return dataItem;
});
// Require 2 arrays : nodes and edges - nodes array must be unique values of IDs !
var uniqueId = [];
var nodes = [];
var edges = [];
const groups = {};
for(let i = 0; i< dataSet.length; i++){
if (layout.displayEdgeLabel) {
edges.push({
"from":dataSet[i].id,
"to":dataSet[i].parentid,
"value":dataSet[i].edgeValue,
"label": `${dataSet[i].edgeValue}`
}); // with labels
} else {
edges.push({
"from":dataSet[i].id,
"to":dataSet[i].parentid,
"value":dataSet[i].edgeValue
}); // create edges
}
// process uniqueness
if(uniqueId.indexOf(dataSet[i].id) === -1) {
uniqueId.push(dataSet[i].id);
var nodeItem = {
id: dataSet[i].id,
label: dataSet[i].label,
title: dataSet[i].title,
group: dataSet[i].group,
value: dataSet[i].nodeValue
};
nodes.push(nodeItem); // create node
groups[nodeItem.group] = {
color: getColor(nodeItem.group)
};
}
network.on('stabilizationIterationsDone', function() {
resolve();
});
} else {
resolve();
}
// create dataset for \\
var data = {
nodes: nodes,
edges: edges
};
// create a network
var container = document.getElementById(containerId);
var options = {
groups: groups,
layout: {
randomSeed: 1
},
nodes: {
shape:layout.nodeShape,
shadow:layout.shadowMode
},
edges: {
shadow:layout.shadowMode,
font: {
align: layout.posEdgeLabel
},
smooth: {
type: layout.edgeType
}
},
interaction: {
hideEdgesOnDrag: true,
tooltipDelay: 100
},
physics: {
forceAtlas2Based: {
gravitationalConstant: -100,
centralGravity: 0.005,
springLength: 230,
springConstant: 0.18
},
maxVelocity: 146,
solver: 'forceAtlas2Based',
timestep: 0.35,
stabilization: { iterations: 150 }
}
};
var network = new Network(container, data, options);
network.fit();
// Handle Selection on 1-node
$("#"+containerId).css('cursor','default');
network.on('select', function (properties) {
if (properties.hasOwnProperty("nodes")) {
if (properties.nodes.length > 0) {
// find connected nodes to selection
var connectedNodes = network.getConnectedNodes(properties.nodes[0]);
// append node to the array
connectedNodes.push(properties.nodes[0]);
//Make the selections
component.backendApi.selectValues(0,connectedNodes,false);
}
}
});
}
});
}
function themePaint ($element, layout) {
@@ -188,8 +219,8 @@ function themePaint ($element, layout) {
try {
const app = qlik.currApp(this);
app.theme.getApplied().then( function( qTheme ) {
paint($element, layout, qTheme, component);
return app.theme.getApplied().then( function( qTheme ) {
return paint($element, layout, qTheme, component);
});
} catch (exception) {
console.error(exception); // eslint-disable-line no-console

View File

@@ -11,3 +11,7 @@
padding: 10px;
max-width: 200px;
}
.qlik-network-chart-italic-property {
font-style: italic;
}