Compare commits

..

34 Commits

Author SHA1 Message Date
Martin Walter
ad63832d18 Merge pull request #13 from qlik-oss/feature/QPE-416
[QPE-416] Removed delete/replace flag
2018-12-05 13:53:24 +01:00
Martin Walter
28b3aeb676 [QPE-416] Removed delete/replace flag 2018-12-05 13:37:39 +01:00
Tobias Åström
321c71825e disable snapshot 2018-11-30 13:35:23 +01:00
Tobias Åström
6433daee95 Merge branch 'master' of https://github.com/qlik-oss/network-vis-chart 2018-11-30 13:31:24 +01:00
Tobias Åström
d210ad3908 disable snapshot 2018-11-30 13:31:05 +01:00
Tobias Åström
f461493b0f Added data clarification and example 2018-11-29 11:10:36 +01:00
Tobias Åström
4312078951 Added data sample 2018-11-29 11:06:49 +01:00
Tobias Åström
7c53f8811b Update qlik-network-chart.qext 2018-11-28 09:47:37 +01:00
Tobias Åström
51ea042580 Update qlik-network-chart.qext 2018-11-27 14:55:39 +01:00
Martin Walter
f9242dff27 Merge pull request #12 from qlik-oss/Caele-qext-update
Update qlik-network-chart.qext
2018-11-23 10:42:37 +01:00
Martin Walter
50fc4289e0 [QPE-331] Updated preview 2018-11-23 10:39:52 +01:00
Tobias Åström
71944b4a9e Update qlik-network-chart.qext 2018-11-23 09:10:25 +01:00
Christopher Lebond
401944e837 Merge pull request #11 from qlik-oss/feature/QPE-332
Correction of spelling
2018-11-19 16:11:36 +01:00
Christopher Lebond
67b1e97951 Corection of spelling 2018-11-19 15:18:14 +01:00
John Lunde
8079887f10 Merge pull request #8 from qlik-oss/standardize-object-properties
[QPE-236] Standardize object properties
2018-11-19 13:39:32 +01:00
Martin Walter
43c5856986 Merge pull request #4 from qlik-oss/fix-tooltip-xss-vulnerability
[QPE-261] Fix tooltip xss vulnerability
2018-11-19 13:01:02 +01:00
John Lunde
ec98314793 Merge pull request #10 from qlik-oss/revert-the-reverted
Revert
2018-11-19 11:00:06 +01:00
Martin Walter
0953911571 Revert 2018-11-19 10:58:50 +01:00
Piotr Nestorow
0a673631b2 Merge pull request #9 from qlik-oss/revert-5-data-limit
Revert "[QPE-233] Data limit"
2018-11-16 15:43:01 +01:00
John Lunde
b1ade90e8b Revert "[QPE-233] Data limit" 2018-11-16 15:40:30 +01:00
Kristoffer Lind
4fa54e3fb2 fix broken tooltip and edge label 2018-11-16 14:31:35 +01:00
Kristoffer Lind
ba89c2108f responsive: scale to fit 2018-11-16 14:26:16 +01:00
Kristoffer Lind
e9a28e4f0b patch tooltip xss vulnerability 2018-11-16 14:26:16 +01:00
John Lunde
c088774e75 Merge pull request #5 from qlik-oss/data-limit
[QPE-233] Data limit
2018-11-16 14:05:37 +01:00
John Lunde
7d6bd5696a Merge pull request #7 from qlik-oss/disable-edit-mode-interactions
[QPE-303] Disable edit mode interactions
2018-11-16 14:04:56 +01:00
John Lunde
dd30f94b34 Merge pull request #6 from qlik-oss/use-qlik-theme-colors
[QPE-237] Use qlik theme colors
2018-11-16 13:01:08 +01:00
ahmed-Bazzara
2eef3679c7 -pulling sorting and addons out from data property
- add "disableRef" to Data property to delete "adding alternatives"
in Data panel
2018-11-16 11:28:33 +01:00
Kristoffer Lind
7518b6c9ce standardize object properties 2018-11-15 09:36:25 +01:00
Kristoffer Lind
11f2bad2bd disable pointer events when is-edit-mode 2018-11-15 08:10:01 +01:00
Kristoffer Lind
06a28a2ae7 set is-edit-mode class when in edit mode 2018-11-15 08:09:13 +01:00
Kristoffer Lind
5c84aeb8ee update karma webpack config 2018-11-14 16:48:38 +01:00
Kristoffer Lind
fe1c3d90e2 use colors from qlik theme color scale 2018-11-14 16:42:09 +01:00
Kristoffer Lind
78a441036c expose qlik as available import 2018-11-14 16:41:02 +01:00
Kristoffer Lind
15be850423 document data limitation 2018-11-13 12:54:56 +01:00
13 changed files with 186 additions and 51 deletions

BIN
assets/network.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -17,6 +17,10 @@ gulp.task('zip-build', function(){
.pipe(gulp.dest(settings.buildDestination));
});
gulp.task('add-assets', function(){
return gulp.src("./assets/**/*").pipe(gulp.dest(settings.buildDestination));
});
gulp.task('webpack-build', done => {
webpack(webpackConfig, (error, statistics) => {
const compilationErrors = statistics && statistics.compilation.errors;
@@ -42,7 +46,7 @@ gulp.task('update-qext-version', function () {
});
gulp.task('build',
gulp.series('remove-build-folder', 'webpack-build', 'update-qext-version', 'zip-build')
gulp.series('remove-build-folder', 'webpack-build', 'update-qext-version', 'add-assets', 'zip-build')
);
gulp.task('watch', () => new Promise((resolve, reject) => {

View File

@@ -19,6 +19,14 @@ module.exports = (config) => {
webpack: {
devtool: 'source-map',
mode: settings.mode,
externals: {
qlik: {
amd: 'qlik',
commonjs: 'qlik',
commonjs2: 'qlik',
root: '_'
}
},
module: {
rules: [
{

View File

@@ -8,9 +8,9 @@ Tested with Qlik Sense 2.2.3.
### Dimensions
4 dimensions are mandatory :
1. node identifier
2. node label
3. node parent identifier
1. node identifier (consecutive numbers starting at 0)
2. node label (Any text)
3. node parent identifier (Refers to node indentifier)
4. node group
### Measures
@@ -28,9 +28,14 @@ The measures are optional
* Display Shadow : switch to enable shadow effects behind edge and nodes
### Sample
A data sample can be found here: https://github.com/qlik-oss/network-vis-chart/blob/master/resources/Network%20data.xlsx
QVF based on characters from Victor Hugo's novel , Les Misérables.
![Network chart](resources/network_chart_v1.png)
### Data Limit
Starts having issues stabilizing(transforming into untangled view) at around 100-200 nodes depending on dataset.
# Getting Started

BIN
resources/Network data.xlsx Normal file

Binary file not shown.

View File

@@ -3,7 +3,7 @@ set -o errexit
echo "Creating release for version: $VERSION"
echo "Artifact name: ./build/${3}_${VERSION}.zip"
$HOME/bin/ghr -t ${ghoauth} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION} "./build/${3}_${4}.zip"
$HOME/bin/ghr -t ${ghoauth} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${VERSION} "./build/${3}_${4}.zip"
# Usage

View File

@@ -7,6 +7,7 @@ Use at your own risk.
*/
import "@babel/polyfill";
import paint from './paint';
import './styles/main.less';
const component = {
initialProperties: {
@@ -21,30 +22,41 @@ const component = {
}
},
//property panel
data: {
dimensions: {
min: 4,
max: 4
/*
1. Dimension: Node ID, numeric (Event ID or else) or String
2. Dimension: Node Label
3. Dimension: Node Parent ID, numeric (Event ID or else) or String
4. Dimension: Node Cluster
*/
},
measures: {
min: 0,
max: 3
/*
1. Measure: title text for tooltip (optional)
2. Measure: node value
3. Measure: edge value
*/
}
},
definition: {
type: "items",
component: "accordion",
items: {
dimensions: {
uses: "dimensions",
min: 4,
max: 4
/*
1. Dimension: Node ID, numeric (Event ID or else) or String
2. Dimension: Node Label
3. Dimension: Node Parent ID, numeric (Event ID or else) or String
4. Dimension: Node Cluster
*/
},
measures: {
uses: "measures",
min: 0,
max: 3
/*
1. Measure: title text for tooltip (optional)
2. Measure: node value
3. Measure: edge value
*/
data: {
uses: "data",
items:{
dimensions:{
disabledRef: ""
},
measures: {
disabledRef: ""
}
}
},
sorting: {
uses: "sorting"
@@ -58,13 +70,14 @@ const component = {
}
},
settings: {
uses: "settings",
type: "items",
label: "Settings",
items: {
edgeType: {
ref: "edgeType",
type: "string",
component: "dropdown",
label: "Egde Type",
label: "Edge Type",
options: [
{ value: 'dynamic' },
{ value: 'continuous' },
@@ -137,10 +150,11 @@ const component = {
}
},
support: {
export: true
export: true,
snapshot: false
},
snapshot: {
canTakeSnapshot: true
canTakeSnapshot: false
},
paint: paint
};

View File

@@ -1,19 +1,31 @@
import { Network } from 'vis/index-network';
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 paint ( $element, layout ) {
var _this = this,
qData = layout.qHyperCube.qDataPages[0],
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];
}
var qData = layout.qHyperCube.qDataPages[0],
id = layout.qInfo.qId,
containerId = 'network-container_' + id;
if(qData && qData.qMatrix) {
$element.empty().append($('<div />')
.attr({ id: containerId })
.toggleClass('is-edit-mode', component.inEditState())
.css({
height: $element.height(),
width: $element.width(),
@@ -21,20 +33,30 @@ function paint ( $element, layout ) {
}));
var dataSet = qData.qMatrix.map(function(e){
var dataItem = {
const nodeName = e[1].qText;
const groupNumber = e[3].qText;
const dataItem = {
id: e[0].qNum,
label: e[1].qText,
group: e[3].qText,
label: nodeName,
group: groupNumber,
parentid : e[2].qNum
};
// optional measures set
if (e.length > 4) {
// tooltip
if (isTextCellNotEmpty(e[4])) {
dataItem.title = e[4].qText;
const tooltip = e[4];
if (isTextCellNotEmpty(tooltip)) {
const tooltipText = tooltip.qText;
dataItem.title = escapeHTML(tooltipText);
} else {
dataItem.title = "*** Default Tooltip ***" + "<BR/>" + "Name:" + e[1].qText + "<BR/>" + "Group:" + e[3].qText;
const nodeMeasure = e[5].qText;
dataItem.title = createTooltipHTML({
name: nodeName,
groupNumber,
nodeMeasure
});
}
}
@@ -59,12 +81,22 @@ function paint ( $element, layout ) {
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
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
edges.push({
"from":dataSet[i].id,
"to":dataSet[i].parentid,
"value":dataSet[i].edgeValue
}); // create edges
}
// process uniqueness
@@ -72,13 +104,16 @@ function paint ( $element, layout ) {
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
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)
};
}
}
@@ -92,6 +127,7 @@ function paint ( $element, layout ) {
var container = document.getElementById(containerId);
var options = {
groups: groups,
layout: {
randomSeed: 1
},
@@ -126,6 +162,7 @@ function paint ( $element, layout ) {
}
};
var network = new Network(container, data, options);
network.fit();
// Handle Selection on 1-node
$("#"+containerId).css('cursor','default');
@@ -139,11 +176,24 @@ function paint ( $element, layout ) {
connectedNodes.push(properties.nodes[0]);
//Make the selections
_this.backendApi.selectValues(0,connectedNodes,false);
component.backendApi.selectValues(0,connectedNodes,false);
}
}
});
}
}
export default paint;
function themePaint ($element, layout) {
const component = this;
try {
const app = qlik.currApp(this);
app.theme.getApplied().then( function( qTheme ) {
paint($element, layout, qTheme, component);
});
} catch (exception) {
console.error(exception); // eslint-disable-line no-console
}
}
export default themePaint;

View File

@@ -1,7 +1,7 @@
{
"name" : "Network chart",
"description" : "Network chart",
"icon" : "extension",
"description" : "Displays hierarchical or relational dimensions as nodes and edges´, with measures to show the significance of its links.",
"icon" : "bubbles",
"type" : "visualization",
"version": "X.Y.Z",
"preview" : "network.png",

13
src/styles/main.less Normal file
View File

@@ -0,0 +1,13 @@
.is-edit-mode > div {
pointer-events: none;
}
.vis-tooltip {
position: absolute;
background-color: #333;
opacity: 0.95;
border-radius: 5px;
color: #eee;
padding: 10px;
max-width: 200px;
}

28
src/tooltip.js Normal file
View File

@@ -0,0 +1,28 @@
function createEntry(header, value) {
const entry = document.createElement('div');
const nameHeader = document.createElement('span');
const nameHeaderValue = document.createTextNode(header);
const nameValueContainer = document.createElement('b');
const nameValue = document.createTextNode(value);
nameHeader.appendChild(nameHeaderValue);
nameValueContainer.appendChild(nameValue);
entry.appendChild(nameHeader);
entry.appendChild(nameValueContainer);
return entry;
}
export function createTooltipHTML({ name, groupNumber, nodeMeasure }) {
const tooltip = document.createElement('div');
const nameEntry = createEntry('Name: ', name);
const groupNumberEntry = createEntry('Group number: ', groupNumber);
const nodeMeasureEntry = createEntry('Node measure: ', nodeMeasure);
tooltip.appendChild(nameEntry);
tooltip.appendChild(groupNumberEntry);
tooltip.appendChild(nodeMeasureEntry);
return tooltip.innerHTML;
}

5
src/utilities.js Normal file
View File

@@ -0,0 +1,5 @@
export function escapeHTML(str){
var span = document.createElement('span');
span.appendChild(document.createTextNode(str));
return span.innerHTML;
}

View File

@@ -16,6 +16,14 @@ const config = {
filename: settings.name + '.js',
libraryTarget: 'umd'
},
externals: {
qlik: {
amd: 'qlik',
commonjs: 'qlik',
commonjs2: 'qlik',
root: '_'
}
},
module: {
rules: [
{