Compare commits

..

34 Commits

Author SHA1 Message Date
Purwa Shrivastava
d95c0f572e Merge pull request #24 from qlik-oss/QLIK-98564/sourceMaps
Removing source maps from production mode.
2020-02-12 14:22:31 +01:00
Purwa Shrivastava
fd2f9fa277 Removing source maps from production mode. 2020-02-12 13:39:48 +01:00
Shiben Dutta
e2aac7a294 Merge pull request #23 from qlik-oss/QB886-fix-noInteraction-options
fix: fix noInteraction option in network chart QB-885
2020-02-07 15:28:20 +05:30
Shiben Dutta
b6bcbe7f75 fix: fix noInteraction option in embeded chart 2020-01-30 13:58:48 +05:30
sauravqlik
f4441ef683 Merge pull request #22 from qlik-oss/bugfix/QB-296-take-snapshot
fix: enabling take snapshot flag for Network and Radar chart
2019-12-16 12:28:31 +05:30
SAURAV
3a832e7d6a fix: enabling take snapshot flag for Network and Radar chart 2019-12-11 16:21:49 +05:30
Philip Olsén
22da42de9f Merge pull request #21 from qlik-oss/pol/bd
Update black duck link
2019-09-20 16:04:08 +02:00
Philip Olsén
fddf286a8e Update black duck link 2019-09-20 14:20:10 +02:00
Purwa Shrivastava
e6692b8779 Merge pull request #20 from qlik-oss/atq/AboutInfo
Atq/about info
2019-07-16 16:24:35 +02:00
Purwa Shrivastava
4341fdb5db Typos in About Info. 2019-07-16 10:13:39 +02:00
Purwa Shrivastava
d02852b2ed Added an About Info section to the properties panel. 2019-07-16 10:05:00 +02:00
Albert Backenhof
25f6593f35 Merge pull request #19 from qlik-oss/DEB-136/readme
Updated github readme
2019-05-20 09:09:09 +02:00
Albert Backenhof
76a22121a9 Updated github readme
Issue: DEB-136
2019-05-20 07:29:18 +02:00
Albert Backenhof
d4154fde09 Merge pull request #18 from qlik-oss/DEB-133
Aligned build to Dashboard bundle extension builds
2019-05-10 09:11:55 +02:00
Albert Backenhof
d65b619546 Aligned build to Dashboard bundle extension builds
-Part of the work to streamline how the extensions
are handled, irregardless of what bundle.

Issue: DEB-130, DEB-133
2019-03-27 13:05:38 +01:00
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
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
15 changed files with 388 additions and 379 deletions

View File

@@ -19,8 +19,13 @@ jobs:
name: Install dependencies
command: npm install
- run:
name: Run tests
command: npm run test-once
name: BlackDuck scan
command: curl -s https://detect.synopsys.com/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"
bump-version:
<<: *defaults
@@ -48,16 +53,18 @@ jobs:
command: |
export VERSION=$(scripts/get-bumped-version.sh)
echo "Version: ${VERSION}"
npm run build
npm run build:zip
sudo chmod +x scripts/verify-files.sh
scripts/verify-files.sh
environment:
NODE_ENV: production
- persist_to_workspace:
root: ~/qlik-network-chart
paths:
- build
- dist
- store_artifacts:
path: build
destination: build
path: dist
destination: dist
deploy:
<<: *defaults
steps:

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
node_modules/
build/
dist/
BUMPED_VERSION

View File

@@ -1,24 +1,61 @@
var gulp = require('gulp');
var gutil = require('gulp-util');
var zip = require('gulp-zip');
var del = require('del');
var settings = require('./settings');
var webpackConfig = require('./webpack.config');
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var jeditor = require("gulp-json-editor");
var pkg = require('./package.json');
gulp.task('remove-build-folder', function(){
return del([settings.buildDestination], { force: true });
var DIST = './dist';
var VERSION = process.env.VERSION || 'local-dev';
gulp.task('qext', function () {
var qext = {
name: 'Network chart',
type: 'visualization',
description: pkg.description + '\nVersion: ' + VERSION,
version: VERSION,
icon: 'bubbles',
preview: 'network.png',
keywords: 'qlik-sense, visualization',
author: pkg.author,
homepage: pkg.homepage,
license: pkg.license,
repository: pkg.repository,
dependencies: {
'qlik-sense': '>=5.5.x'
}
};
if (pkg.contributors) {
qext.contributors = pkg.contributors;
}
var src = require('stream').Readable({
objectMode: true
});
src._read = function () {
this.push(new gutil.File({
cwd: '',
base: '',
path: pkg.name + '.qext',
contents: Buffer.from(JSON.stringify(qext, null, 4))
}));
this.push(null);
};
return src.pipe(gulp.dest(DIST));
});
gulp.task('clean', function(){
return del([DIST], { force: true });
});
gulp.task('zip-build', function(){
return gulp.src(settings.buildDestination + '/**/*')
.pipe(zip(`${settings.name}_${settings.version}.zip`))
.pipe(gulp.dest(settings.buildDestination));
return gulp.src(DIST + '/**/*')
.pipe(zip(`${pkg.name}_${VERSION}.zip`))
.pipe(gulp.dest(DIST));
});
gulp.task('add-assets', function(){
return gulp.src("./assets/**/*").pipe(gulp.dest(settings.buildDestination));
return gulp.src('./assets/**/*').pipe(gulp.dest(DIST));
});
gulp.task('webpack-build', done => {
@@ -37,39 +74,13 @@ gulp.task('webpack-build', done => {
});
});
gulp.task('update-qext-version', function () {
return gulp.src(`${settings.buildDestination}/${settings.name}.qext`)
.pipe(jeditor({
'version': settings.version
}))
.pipe(gulp.dest(settings.buildDestination));
});
gulp.task('build',
gulp.series('remove-build-folder', 'webpack-build', 'update-qext-version', 'add-assets', 'zip-build')
gulp.series('clean', 'webpack-build', 'qext', 'add-assets')
);
gulp.task('watch', () => new Promise((resolve, reject) => {
webpackConfig.entry.unshift('webpack-dev-server/client?http://localhost:' + settings.port);
const compiler = webpack(webpackConfig);
const originalOutputFileSystem = compiler.outputFileSystem;
const devServer = new WebpackDevServer(compiler, {
headers: {
"Access-Control-Allow-Origin": "*"
},
}).listen(settings.port, 'localhost', error => {
compiler.outputFileSystem = originalOutputFileSystem;
if (error) {
console.error(error); // eslint-disable-line no-console
return reject(error);
}
// eslint-disable-next-line no-console
console.log('Listening at localhost:' + settings.port);
resolve(null, devServer);
});
}));
gulp.task('zip',
gulp.series('build', 'zip-build')
);
gulp.task('default',
gulp.series('build')

View File

@@ -1,43 +0,0 @@
const settings = require('./settings');
module.exports = (config) => {
config.set({
browsers: ['SlimChromeHeadless'],
customLaunchers: {
SlimChromeHeadless: {
base: 'ChromeHeadless',
flags: ['--headless', '--disable-gpu', '--disable-translate', '--disable-extensions']
}
},
files: [
{ pattern: 'src/*.spec.js', watched: false }
],
frameworks: ['jasmine'],
preprocessors: {
'src/*.spec.js': ['webpack', 'sourcemap']
},
webpack: {
devtool: 'source-map',
mode: settings.mode,
externals: {
qlik: {
amd: 'qlik',
commonjs: 'qlik',
commonjs2: 'qlik',
root: '_'
}
},
module: {
rules: [
{
test: /\.js$/,
exclude: [/node_modules/],
loaders: ['babel-loader']
},
{ test: /\.less$/, loader: 'ignore-loader' },
{ test: /\.json$/, loader: 'ignore-loader' }
]
}
}
});
};

View File

@@ -1,18 +1,15 @@
{
"name": "qlik-network-chart",
"version": "0.0.1",
"description": "Network chart",
"keywords": "network chart qliksense extension",
"description": "Displays hierarchical or relational dimensions as nodes and edges´, with measures to show the significance of its links.",
"homepage": "",
"repository": "https://github.com/qlik-oss/network-vis-chart",
"author": "Michael Laenen (miclae76) <m.laenen@contactoffice.net>",
"license": "MIT",
"scripts": {
"build": "gulp build",
"eslint": "eslint src",
"eslint:fix": "eslint --fix src",
"test": "karma start karma.conf.js",
"test-once": "karma start karma.conf.js --single-run",
"watch": "gulp watch"
"build:zip": "gulp zip",
"eslint": "eslint src"
},
"devDependencies": {
"@babel/core": "7.1.5",
@@ -20,29 +17,20 @@
"@babel/preset-env": "7.1.5",
"babel-eslint": "10.0.1",
"babel-loader": "8.0.4",
"copy-webpack-plugin": "4.6.0",
"css-loader": "1.0.1",
"del": "3.0.0",
"eslint": "5.8.0",
"eslint-loader": "2.1.1",
"file-loader": "2.0.0",
"gulp": "4.0.0",
"gulp-json-editor": "2.4.3",
"gulp-util": "^3.0.7",
"gulp-zip": "4.2.0",
"jasmine-core": "3.3.0",
"karma": "3.1.1",
"karma-chrome-launcher": "2.2.0",
"karma-jasmine": "1.1.2",
"karma-sourcemap-loader": "0.3.7",
"karma-webpack": "3.0.5",
"less": "3.8.1",
"less-loader": "4.1.0",
"style-loader": "0.23.1",
"stylelint": "9.7.1",
"stylelint-webpack-plugin": "0.10.5",
"webpack": "4.25.1",
"webpack-cli": "3.1.2",
"webpack-dev-server": "3.1.10"
"webpack": "4.25.1"
},
"dependencies": {
"vis": "4.21.0"

View File

@@ -1,69 +1,20 @@
# Qlik Network Chart
This extension is part of the extension bundles for Qlik Sense. The repository is maintained and moderated by Qlik RD.
[![CircleCI](https://circleci.com/gh/qlik-oss/network-vis-chart.svg?style=shield)](https://circleci.com/gh/qlik-oss/network-vis-chart)
Qlik Sense extension to visualize networks data based on library vis.js (http://visjs.org).
Tested with Qlik Sense 2.2.3.
### Dimensions
4 dimensions are mandatory :
1. node identifier
2. node label
3. node parent identifier
4. node group
### Measures
The measures are optional
1. tooltip : expression that will be push in the tooltip when hover on a node
2. node value : used to scale the node size
3. edge value : used to scale the edge width
### Additional network settings
* Edge Type : select type of curve between nodes
* Node Shape : dot, square, diamond, triangle ...
* Display Edge Value : switch to display the measures on edge curves
* Position Edge Label : top, bottom, middle, horizontal
* Display Shadow : switch to enable shadow effects behind edge and nodes
### Sample
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
## Installation
1. Download the extension zip, `qlik-network-chart_<version>.zip`, from the latest release(https://github.com/qlik-oss/network-vis-chart/releases/latest)
2. Install the extension:
a. **Qlik Sense Desktop**: unzip to a directory under [My Documents]/Qlik/Sense/Extensions.
b. **Qlik Sense Server**: import the zip file in the QMC.
Feel free to fork and suggest pull requests for improvements and bug fixes. Changes will be moderated and reviewed before inclusion in future bundle versions. Please note that emphasis is on backward compatibility, i.e. breaking changes will most likely not be approved.
Usage documentation for the extension is available at https://help.qlik.com.
# Developing the extension
If you want to do code changes to the extension follow these simple steps to get going.
1. Get Qlik Sense Desktop
1. Create a new app and add the extension to a sheet.
1. Create a new app and add Network chart to a sheet.
2. Clone the repository
3. Run `npm install`
4. Set the environment variable `BUILD_PATH` to your extensions directory. It will be something like `C:/Users/<user>/Documents/Qlik/Sense/Extensions/<extension_name>`.
5. You now have two options. Either run the watch task or the build task. They are explained below. Both of them default to development mode but can be run in production by setting `NODE_ENV=production` before running the npm task.
a. **Watch**: `npm run watch`. This will start a watcher which will rebuild the extension and output all needed files to the `buildFolder` for each code change you make. See your changes directly in your Qlik Sense app.
b. **Build**: `npm run build`. If you want to build the extension package. The output zip-file can be found in the `buildFolder`.
4. Run `npm run build` - to build a dev-version to the /dist folder.
5. Move the content of the /dist folder to the extension directory. Usually in `C:/Users/<user>/Documents/Qlik/Sense/Extensions/qlik-network-chart`.
# Original Author
**Michael Laenen**
* [github.com/miclae76](https://github.com/miclae76)

View File

@@ -2,8 +2,8 @@
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"
echo "Artifact name: ./dist/${3}_${VERSION}.zip"
$HOME/bin/ghr -t ${ghoauth} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${VERSION} "./dist/${3}_${4}.zip"
# Usage

25
scripts/verify-files.sh Normal file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
# The build script has a known race-condition that sometimes causes it to not include all files
# in the built zip. This script verifies the that the zip contains the correct number of files.
set -o errexit
echo "Verifying built file count"
while read line; do
if [[ $line =~ ^\"name\": ]]; then
name=${line#*: \"}
name=${name%\"*}
fi
done < package.json
expected_file_count=$(($(find dist -type f | wc -l)-1))
zip_file_count=$(zipinfo dist/${name}_${VERSION}.zip | grep ^- | wc -l)
if [ "${expected_file_count}" -ne "${zip_file_count}" ]; then
# File count is incorrect
echo "Expected file count ${expected_file_count}, but was ${zip_file_count}"
exit 1
fi
echo "File count OK"
exit 0

View File

@@ -1,13 +0,0 @@
const path = require('path');
const packageJSON = require('./package.json');
const defaultBuildDestination = path.resolve("./build");
module.exports = {
buildDestination: process.env.BUILD_PATH || defaultBuildDestination,
mode: process.env.NODE_ENV || 'development',
name: packageJSON.name,
version: process.env.VERSION || 'local-dev',
url: process.env.BUILD_URL || defaultBuildDestination,
port: 8082
};

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: ""
@@ -146,11 +194,32 @@ const component = {
defaultValue: false
}
}
},
about: {
component: 'items',
label: 'About',
items: {
header: {
label: 'Network chart',
style: 'header',
component: 'text'
},
paragraph1: {
label: `Network chart is Qlik Sense chart which allows you to draw a network of connected nodes and edges from a data set to a sheet.`,
component: 'text'
},
paragraph2: {
label: 'Network chart is based upon an extension created by Michael Laenen.',
component: 'text'
}
}
}
}
},
support: {
export: true
export: true,
snapshot: true,
exportData: true
},
snapshot: {
canTakeSnapshot: true

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") && component.options.noInteraction !== true) {
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

@@ -1,7 +0,0 @@
import paint from './paint';
describe('paint', () => {
it('should be defined', () => {
expect(paint).toBeDefined();
});
});

View File

@@ -1,16 +0,0 @@
{
"name" : "Network chart",
"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",
"keywords": "qlik-sense, visualization",
"author": "Michael Laenen <michael.laenen@agilos.com>",
"homepage": "",
"license": "MIT",
"repository": "",
"dependencies": {
"qlik-sense": ">=5.5.x"
}
}

View File

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

View File

@@ -1,20 +1,25 @@
const CopyWebpackPlugin = require('copy-webpack-plugin');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const settings = require('./settings');
const packageJSON = require('./package.json');
const path = require('path');
const webpack = require('webpack');
const SOURCE_MAP = 'sourec-map';
const DEVTOOL = (process.env.NODE_ENV === 'development') ? SOURCE_MAP : false;
console.log('Webpack mode:', settings.mode); // eslint-disable-line no-console
const DIST = path.resolve("./dist");
const MODE = process.env.NODE_ENV || 'development';
console.log('Webpack mode:', MODE); // eslint-disable-line no-console
const config = {
devtool: 'source-map',
devtool: DEVTOOL,
entry: [
'./src/index.js'
],
mode: settings.mode,
mode: MODE,
output: {
path: settings.buildDestination,
filename: settings.name + '.js',
libraryTarget: 'umd'
filename: `${packageJSON.name}.js`,
libraryTarget: 'amd',
path: DIST
},
externals: {
qlik: {
@@ -71,9 +76,6 @@ const config = {
]
},
plugins: [
new CopyWebpackPlugin([
'src/' + settings.name + '.qext'
], {}),
new StyleLintPlugin(),
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/en$/),
]