Compare commits

..

48 Commits

Author SHA1 Message Date
caele
1f116d38a2 0.1.0 2021-10-04 09:55:03 +02:00
caele
291e2bd05f chore: set package name 2021-09-14 16:49:30 +02:00
caele
7fa1ee419f chore: correctly set constraints 2021-09-14 16:40:44 +02:00
caele
e26d77cb5d chore: console log 2021-09-14 16:28:56 +02:00
caele
f6facc8d21 chore: allow multi select 2021-09-14 16:28:36 +02:00
caele
e646bd242b chore: basic selections 2021-09-14 10:28:21 +02:00
caele
047b248c01 chore: properly replace version string 2021-09-14 08:31:39 +02:00
caele
39cacf167f chore: add pre-publish step 2021-09-13 16:53:00 +02:00
caele
d140c89e5a chore: update readme 2021-09-13 16:24:46 +02:00
caele
d5ed44c62c chore: add core package 2021-09-13 15:46:31 +02:00
Tobias Åström
4249dfa8cb Merge pull request #27 from qlik-oss/tsm/nebularize
feat: transform to nebula
2021-09-13 15:05:00 +02:00
caele
7198257f40 chore: rename package to sn-network-chart 2021-09-09 16:51:14 +02:00
caele
e74991445a chore: small readme update 2021-09-08 14:09:43 +02:00
caele
2914f13f6e store artifacts 2021-09-08 13:48:52 +02:00
caele
bd12b6519b chore: remove workspace 2021-09-08 13:46:20 +02:00
caele
ec4af50cff chore: merge workflow 2021-09-08 13:43:32 +02:00
caele
43f32a470a chore: merge workflow 2021-09-08 13:42:09 +02:00
caele
a2209e160f chore: merge workflow 2021-09-08 13:38:14 +02:00
caele
63ba3a9362 chore: remove script call 2021-09-08 13:37:09 +02:00
caele
dd0af64115 fix: lint 2021-09-08 13:32:50 +02:00
caele
aa2ef96469 chore: fix config 2021-09-08 13:31:31 +02:00
caele
e9b087bde2 chore: fix config 2021-09-08 13:31:09 +02:00
caele
25d5f4e4da chore: verify build 2021-09-08 13:30:00 +02:00
caele
f02e0c17e5 chore: clean out build system 2021-09-08 13:27:04 +02:00
caele
e406acef3a chore: update seed 2021-09-08 12:59:07 +02:00
caele
9669ccfdaf chore: set back original name 2021-09-08 09:02:14 +02:00
caele
a99a14c541 chore: remove old deps 2021-09-08 08:56:44 +02:00
caele
b9c7d2b0f1 chore: clean out all the old 2021-09-08 08:47:41 +02:00
caele
955d954b6f feat: add conversion to nebula 2021-09-08 08:33:23 +02:00
caele
4baadc1157 chore: update lockfile 2021-09-01 13:05:12 +02:00
Tobias Åström
e467da6b46 Merge pull request #26 from mountaindude/master
Updated dependencies
2021-09-01 13:03:21 +02:00
Göran Sander
7f4c6af61d Reverting to previous version number 2021-08-26 13:03:04 +02:00
Göran Sander
3e199979fd Add contributors section to README 2021-08-22 21:23:24 +02:00
Göran Sander
3e1384c900 Fix linting errors 2021-08-22 21:17:33 +02:00
Göran Sander
3ecaf3de42 Updated dependencies to latest versions. 2021-08-22 21:14:58 +02:00
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
30 changed files with 7869 additions and 12409 deletions

View File

@@ -1,9 +0,0 @@
{
"presets": [
["env", {
"targets": {
"chrome": "47"
}
}]
]
}

View File

@@ -10,92 +10,36 @@ defaults: &defaults
PACKAGE_NAME: "qlik-network-chart"
jobs:
test:
build:
docker:
- image: circleci/node:stretch-browsers
steps:
- checkout
- run:
name: Install dependencies
command: npm install
command: yarn
- run:
name: Lint
command: yarn eslint
- run:
name: BlackDuck scan
command: curl -s https://blackducksoftware.github.io/hub-detect/hub-detect.sh | bash -s -- \
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
steps:
- checkout
- run:
name: Bump version
command: scripts/bump-version.sh $GITHUB_ORG $GITHUB_REPO
- persist_to_workspace:
root: ~/qlik-network-chart
paths:
- BUMPED_VERSION
build:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: ~/qlik-network-chart
- run:
name: Install dependencies
command: npm install
- run:
name: Build and package
command: |
export VERSION=$(scripts/get-bumped-version.sh)
echo "Version: ${VERSION}"
npm run build:zip
sudo chmod +x scripts/verify-files.sh
scripts/verify-files.sh
name: Build
command: yarn build && yarn sense
environment:
NODE_ENV: production
- persist_to_workspace:
root: ~/qlik-network-chart
paths:
- dist
- store_artifacts:
path: dist
destination: dist
deploy:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: ~/qlik-network-chart
- run:
name: Install ghr
command: scripts/install-ghr.sh
- run:
name: Create GitHub Release
command: |
export VERSION=$(scripts/get-bumped-version.sh)
echo "Version: ${VERSION}"
scripts/create-release.sh $GITHUB_ORG $GITHUB_REPO $PACKAGE_NAME $VERSION
workflows:
version: 2
master_flow:
jobs:
- test
- bump-version:
requires:
- test
- build:
requires:
- bump-version
- deploy:
requires:
- build
filters:
branches:
only:
- master
- build

View File

@@ -42,7 +42,7 @@ module.exports = {
"no-cond-assign": ["warn"],
"no-fallthrough": ["warn"],
"no-undef": ["warn"],
"no-unused-vars": ["warn"],
"no-unused-vars": ["error"],
"no-use-before-define": ["warn", { "functions": false, "classes": false, "variables": false }],
"no-useless-escape": ["warn"],
"no-useless-return": ["warn"],

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
node_modules/
dist/
BUMPED_VERSION
sn-network-chart-ext/
core/esm

1
.npmrc
View File

@@ -1 +0,0 @@
save-exact=true

View File

@@ -0,0 +1,136 @@
{
"scriptappy": "1.0.0",
"info": {
"name": "sn-network-chart:properties",
"description": "Network chart generic object definition",
"version": "0.0.1",
"license": "MIT",
"stability": "experimental",
"x-qlik-visibility": "public"
},
"entries": {},
"definitions": {
"module.exports.displayEdgeLabel": {
"optional": true,
"defaultValue": false,
"type": "boolean"
},
"module.exports.edgeType": {
"optional": true,
"defaultValue": "dynamic",
"kind": "union",
"items": [
{
"kind": "literal",
"value": "'dynamic'"
},
{
"kind": "literal",
"value": "'continuous'"
},
{
"kind": "literal",
"value": "'discrete'"
},
{
"kind": "literal",
"value": "'diagonalCross'"
},
{
"kind": "literal",
"value": "'straightCross'"
},
{
"kind": "literal",
"value": "'horizontal'"
},
{
"kind": "literal",
"value": "'vertical'"
},
{
"kind": "literal",
"value": "'curvedCW'"
},
{
"kind": "literal",
"value": "'curvedCCW'"
},
{
"kind": "literal",
"value": "'cubicBezier'"
}
],
"type": "string"
},
"module.exports.nodeShape": {
"optional": true,
"defaultValue": "dot",
"kind": "union",
"items": [
{
"kind": "literal",
"value": "'dot'"
},
{
"kind": "literal",
"value": "'square'"
},
{
"kind": "literal",
"value": "'star'"
},
{
"kind": "literal",
"value": "'triangle'"
},
{
"kind": "literal",
"value": "'triangleDown'"
},
{
"kind": "literal",
"value": "'diamond'"
}
],
"type": "string"
},
"module.exports.posEdgeLabel": {
"optional": true,
"defaultValue": "top",
"kind": "union",
"items": [
{
"kind": "literal",
"value": "'top'"
},
{
"kind": "literal",
"value": "'middle'"
},
{
"kind": "literal",
"value": "'bottom'"
},
{
"kind": "literal",
"value": "'horizontal'"
}
],
"type": "string"
},
"module.exports.qHyperCubeDef": {
"kind": "object",
"entries": {}
},
"module.exports.shadowMode": {
"optional": true,
"defaultValue": false,
"type": "boolean"
},
"module.exports.version": {
"description": "Current version of this generic object definition",
"type": "string"
}
}
}

8
core/package.json Normal file
View File

@@ -0,0 +1,8 @@
{
"module": "esm/index.js",
"peerDependencies": {
"@nebula.js/stardust": ">=1.1.1",
"vis-network": "9.1.0",
"vis-data": "^7.0.0"
}
}

View File

@@ -1,87 +0,0 @@
var gulp = require('gulp');
var gutil = require('gulp-util');
var zip = require('gulp-zip');
var del = require('del');
var webpackConfig = require('./webpack.config');
var webpack = require('webpack');
var pkg = require('./package.json');
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(DIST + '/**/*')
.pipe(zip(`${pkg.name}_${VERSION}.zip`))
.pipe(gulp.dest(DIST));
});
gulp.task('add-assets', function(){
return gulp.src('./assets/**/*').pipe(gulp.dest(DIST));
});
gulp.task('webpack-build', done => {
webpack(webpackConfig, (error, statistics) => {
const compilationErrors = statistics && statistics.compilation.errors;
const hasCompilationErrors = !statistics || (compilationErrors && compilationErrors.length > 0);
console.log(statistics && statistics.toString({ chunks: false, colors: true })); // eslint-disable-line no-console
if (error || hasCompilationErrors) {
console.log('Build has errors or eslint errors, fail it'); // eslint-disable-line no-console
process.exit(1);
}
done();
});
});
gulp.task('build',
gulp.series('clean', 'webpack-build', 'qext', 'add-assets')
);
gulp.task('zip',
gulp.series('build', 'zip-build')
);
gulp.task('default',
gulp.series('build')
);

21
nebula.config.js Normal file
View File

@@ -0,0 +1,21 @@
const path = require('path');
const crypto = require('crypto');
const { name, version } = require(path.resolve(__dirname, './package.json')); // eslint-disable-line
const versionHash = crypto
.createHash('md5')
.update(`${name}@${version}`)
.digest('hex')
.slice(0, 4);
const replacementStrings = {
'process.env.VERSION_HASH': JSON.stringify(versionHash),
'process.env.PACKAGE_VERSION': JSON.stringify(version),
};
module.exports = {
build: {
replacementStrings,
},
};

11695
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +1,42 @@
{
"name": "qlik-network-chart",
"version": "0.0.1",
"name": "@nebula.js/sn-network-chart",
"version": "0.1.0",
"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>",
"author": "QLIK",
"license": "MIT",
"scripts": {
"build": "gulp build",
"build:zip": "gulp zip",
"eslint": "eslint src"
"start": "nebula serve --build false --type qlik-network-chart",
"watch": "nebula serve --type sn-network-chart",
"watch:legacy": "nebula serve --type qlik-network-chart",
"build": "nebula build --core core",
"sense": "nebula sense --meta resources/meta.json && shx cp resources/network_chart_v1.png sn-network-chart-ext",
"eslint": "eslint src",
"spec": "scriptappy-from-jsdoc -c ./spec-configs/props.conf.js",
"prepublishOnly": "shx rm -rf dist && shx rm -rf core/esm && shx rm -rf sn-network-chart-ext && yarn build && yarn sense"
},
"files": [
"api-specifications",
"dist",
"core",
"sn-network-chart-ext"
],
"main": "dist/sn-network-chart.js",
"devDependencies": {
"@babel/core": "7.1.5",
"@babel/polyfill": "7.0.0",
"@babel/preset-env": "7.1.5",
"babel-eslint": "10.0.1",
"babel-loader": "8.0.4",
"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-util": "^3.0.7",
"gulp-zip": "4.2.0",
"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"
"@nebula.js/cli": "1.7.0",
"@nebula.js/cli-build": "1.7.0",
"@nebula.js/cli-sense": "1.7.0",
"@nebula.js/cli-serve": "1.7.0",
"@nebula.js/stardust": "1.7.0",
"babel-eslint": "10.1.0",
"scriptappy-from-jsdoc": "^0.7.0",
"eslint": "7.32.0",
"shx": "^0.3.3",
"vis-network": "9.1.0",
"vis-data": "^7.0.0"
},
"dependencies": {
"vis": "4.21.0"
"peerDependencies": {
"@nebula.js/stardust": ">=1.0.0"
}
}

109
readme.md
View File

@@ -1,71 +1,76 @@
# Qlik Network Chart
# @nebula.js/sn-network-chart
[![CircleCI](https://circleci.com/gh/qlik-oss/network-vis-chart.svg?style=shield)](https://circleci.com/gh/qlik-oss/network-vis-chart)
The network chart is built using [visjs network visualization](https://github.com/visjs/vis-network) to display networks of nodes and edges. It was originally forked from [miclae76/network-vis-chart](miclae76/network-vis-chart) and has since been converted to use Nebula.
Qlik Sense extension to visualize networks data based on library vis.js (http://visjs.org).
Tested with Qlik Sense 2.2.3.
## Requirements
### Dimensions
4 dimensions are mandatory :
Requires `@nebula.js/stardust` version `1.7.0` or later.
1. node identifier (consecutive numbers starting at 0)
2. node label (Any text)
3. node parent identifier (Refers to node indentifier)
4. node group
## Installing
### Measures
The measures are optional
If you use npm: `npm install @nebula.js/sn-network-chart`.
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
You can also load through the script tag directly from [https://unpkg.com](https://unpkg.com/@nebula.js/sn-network-chart).
### 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
## Usage
### Sample
A data sample can be found here: https://github.com/qlik-oss/network-vis-chart/blob/master/resources/Network%20data.xlsx
```js
import { embed } from '@nebula.js/stardust';
import network from '@nebula.js/sn-network-chart';
QVF based on characters from Victor Hugo's novel , Les Misérables.
![Network chart](resources/network_chart_v1.png)
// 'app' is an enigma app model
const nuked = embed(app, {
types: [
{
// register grid chart - qlik-network-chart is the default name in sense
name: 'qlik-network-chart',
load: () => Promise.resolve(network),
},
],
});
### Data Limit
Starts having issues stabilizing(transforming into untangled view) at around 100-200 nodes depending on dataset.
// Rendering a simple network chart
nuked.render({
element: document.querySelector('.network'),
type: 'qlik-network-chart',
fields: ['Source', 'Target', '=Sum(Flow)'],
properties: {
title: 'Visualization of network flows',
},
});
```
## Data sample
# Getting Started
Check `resources/Network data.xlsx` for an example. The simplest data form is where each row represents an edge in the network. Take this example of airport connections:
## Installation
| AirportID | AirportName | LinkToId | Volume |
|-----------|----------------|----------|--------|
| 0 | Soekarno-Hatta | 3 | 23000 |
| 1 | Halim | 0 | 5460 |
| 2 | Changi | 0 | 10870 |
| 3 | KLCC | 1 | 2780 |
| 4 | Don Muang | 1 | 4800 |
| 4 | Don Muang | 2 | 7800 |
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:
Sense inline load script example:
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.
# 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.
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`.
```
Load * Inline [
AirportID, AirportName, LinktoID,Volume
0,Soekarno-Hatta,3,23000
1,Halim,0,5460
2,Changi,0,10870
3,KLCC,1,2780
4,Don Muang,1,4800
4,Don Muang,2,7800
];
```
# Original Author
**Michael Laenen**
* [github.com/miclae76](https://github.com/miclae76)
# Contributors
**Göran Sander**
* [github.com/mountaindude](https://github.com/mountaindude)

4
resources/meta.json Normal file
View File

@@ -0,0 +1,4 @@
{
"name": "Network chart",
"icon": "bubbles"
}

View File

@@ -1,32 +0,0 @@
#!/bin/bash
set -o errexit
join_by () {
local IFS="$1"; shift; echo "$*";
}
if [ "${CIRCLE_BRANCH}" == "master" ]; then
# get version from repo
OLD_VERSION="$(scripts/get-latest-version.sh $1 $2)"
echo "Latest GitHub release version: ${OLD_VERSION}"
# split into array
IFS='.' read -ra ARRAY_VERSION <<< "$OLD_VERSION"
# bump minor
ARRAY_VERSION[1]=$((ARRAY_VERSION[1]+1))
# join into string
NEW_VERSION=$(join_by . ${ARRAY_VERSION[@]})
elif [[ ! -z "${CIRCLE_BRANCH}" && ! -z "${CIRCLE_BUILD_NUM}" ]]; then
NEW_VERSION="$(echo ${CIRCLE_BRANCH} | sed -e 's/\//-/g')_${CIRCLE_BUILD_NUM}"
else
NEW_VERSION="dev"
fi
echo "Bumped version: ${NEW_VERSION}"
echo "${NEW_VERSION}" > BUMPED_VERSION
# Usage
# $ bump-version.sh qlik-oss qsSimpleKPI

View File

@@ -1,10 +0,0 @@
#!/bin/bash
set -o errexit
echo "Creating release for version: $VERSION"
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
# $ create-release.sh qlik-oss qsSimpleKPI qlik-multi-kpi 0.3.1

View File

@@ -1,7 +0,0 @@
#!/bin/bash
set -o errexit
echo "$(head -n 1 BUMPED_VERSION)"
# Usage
# $ get-bumped-version.sh

View File

@@ -1,17 +0,0 @@
#!/bin/bash
set -o errexit
VERSION=$(curl --silent "https://api.github.com/repos/$1/$2/releases/latest" | # Get latest release from GitHub api
grep '"tag_name":' | # Get tag line
sed -E 's/.*"([^"]+)".*/\1/') # Pluck JSON value
if [ -z "${VERSION}" ]; then
VERSION="0.1.0"
fi
echo "${VERSION}"
### Inspired by https://gist.github.com/lukechilds/a83e1d7127b78fef38c2914c4ececc3c
# Usage
# $ get-latest-version.sh qlik-oss qsSimpleKPI
# 0.12.0

View File

@@ -1,12 +0,0 @@
#!/bin/bash
set -o errexit -o verbose
URL="https://github.com/tcnksm/ghr/releases/download/v0.5.4/ghr_v0.5.4_linux_386.zip"
echo "Version to install: $URL"
echo "Installing ghr"
curl -L ${URL} > ghr.zip
mkdir -p "$HOME/bin"
export PATH="$HOME/bin:$PATH"
unzip ghr.zip -d "$HOME/bin"
rm ghr.zip

View File

@@ -1,25 +0,0 @@
#!/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

@@ -0,0 +1,26 @@
const path = require('path');
const pkg = require(path.resolve(__dirname, '../package.json')); // eslint-disable-line
module.exports = {
glob: ['./src/extension/properties.js'],
package: path.resolve(__dirname, '../package.json'),
api: {
stability: 'experimental',
properties: {
'x-qlik-visibility': 'public',
},
visibility: 'public',
name: `${pkg.name}:properties`,
version: pkg.version,
description: 'Network chart generic object definition',
},
output: {
file: path.resolve(__dirname, '../api-specifications/properties.json'),
},
parse: {
types: {
NxMeasure: {},
},
},
};

34
src/extension/data.js Normal file
View File

@@ -0,0 +1,34 @@
import { dimDesc, measureDesc } from './strings';
export default function data() {
return {
targets: [{
path: '/qHyperCubeDef',
dimensions: {
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
3. Dimension: Node Parent ID, numeric (Event ID or else) or String
4. Dimension: Node Cluster
*/
},
measures: {
min: 0,
max: 3,
description(properties, index) {
return measureDesc[index];
}
/*
1. Measure: title text for tooltip (optional)
2. Measure: node value
3. Measure: edge value
*/
}
}]
};
}

164
src/extension/ext.js Normal file
View File

@@ -0,0 +1,164 @@
import { dimLongDesc } from './strings';
export default function ext() {
return {
definition: {
type: "items",
component: "accordion",
items: {
data: {
uses: "data",
items:{
dimensions:{
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: ""
}
}
},
sorting: {
uses: "sorting"
},
addons: {
uses: "addons",
items: {
dataHandling: {
uses: "dataHandling"
}
}
},
settings: {
type: "items",
uses: 'settings',
items: {
presentation: {
type: 'items',
grouped: false,
translation: 'properties.presentation',
items: {
edgeType: {
ref: "edgeType",
type: "string",
component: "dropdown",
label: "Edge Type",
options: [
{ value: 'dynamic' },
{ value: 'continuous' },
{ value: 'discrete' },
{ value: 'diagonalCross' },
{ value: 'straightCross' },
{ value: 'horizontal' },
{ value: 'vertical' },
{ value: 'curvedCW' },
{ value: 'curvedCCW' },
{ value: 'cubicBezier' }
],
defaultValue: "dynamic"
},
displayEdgeLabel : {
ref: "displayEdgeLabel",
type: "boolean",
component: "switch",
label: "Display Edge Value",
options: [{
value: true,
label: "On"
}, {
value: false,
label: "Off"
}],
defaultValue: false
},
posEdgeLabel: {
ref: "posEdgeLabel",
type: "string",
component: "dropdown",
label: "Position Edge Label",
options: [
{ value: 'top' }, { value: 'middle' }, { value: 'bottom' }, { value: 'horizontal' }
],
defaultValue: "top"
},
nodeShape: {
ref: "nodeShape",
type: "string",
component: "dropdown",
label: "Node Shape",
options: [
{ value: 'dot' },
{ value: 'square' },
{ value: 'star' },
{ value: 'triangle' },
{ value: 'triangleDown' },
{ value: 'diamond' }
],
defaultValue: "dot"
},
shadowMode: {
ref: "shadowMode",
type: "boolean",
component: "switch",
label: "Display Shadow",
options: [{
value: true,
label: "On"
}, {
value: false,
label: "Off"
}],
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,
snapshot: true,
exportData: true
},
snapshot: {
canTakeSnapshot: true
},
};
}

View File

@@ -0,0 +1,54 @@
export default {
/**
* Current version of this generic object definition
* @type {string}
*/
version: process.env.PACKAGE_VERSION,
/**
* @typedef
*/
qHyperCubeDef: {
qDimensions: [],
qMeasures: [],
qInitialDataFetch: [{
qWidth: 7,
qHeight: 1400
}]
},
/**
* @type {boolean=}
*/
showTitles: false,
/**
* @type {string=}
*/
title: '',
/**
* @type {string=}
*/
subtitle: '',
/**
* @type {string=}
*/
footnote: '',
/**
* @type {('dynamic'|'continuous'|'discrete'|'diagonalCross'|'straightCross'|'horizontal'|'vertical'|'curvedCW'|'curvedCCW'|'cubicBezier')=}
*/
edgeType: "dynamic",
/**
* @type {boolean=}
*/
displayEdgeLabel: false,
/**
* @type {('top'|'middle'|'bottom'|'horizontal')=}
*/
posEdgeLabel: "top",
/**
* @type {('dot'|'square'|'star'|'triangle'|'triangleDown'|'diamond')=}
*/
nodeShape: "dot",
/**
* @type {boolean=}
*/
shadowMode: false,
};

23
src/extension/strings.js Normal file
View File

@@ -0,0 +1,23 @@
export const dimDesc = [
"Node Identifier",
"Node Label",
"Node Parent",
"Node Group"
];
export 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."
];
export const measureDesc = [
"Tooltip",
"Node size",
"Edge size"
];

View File

@@ -1,210 +1,33 @@
/*
Created by Michael Laenen - michael.laenen@agilos.com - (c) 2016
Tested on Qlik Sense 2.2.3
import { useElement, usePromise, useEffect, useStaleLayout, useTheme, useRect, useState, useConstraints, useSelections } from '@nebula.js/stardust';
import data from './extension/data';
import ext from './extension/ext';
import properties from './extension/properties';
import paint from './sn-paint';
Agilos.com takes no responsibility for any code.
Use at your own risk.
*/
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,
qHyperCubeDef: {
qDimensions: [],
qMeasures: [],
qInitialDataFetch: [{
qWidth: 7,
qHeight: 1400
}]
}
},
//property panel
data: {
dimensions: {
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
3. Dimension: Node Parent ID, numeric (Event ID or else) or String
4. Dimension: Node Cluster
*/
export default function supernova() {
return {
qae: {
properties,
data: data(),
},
measures: {
min: 0,
max: 3,
description(properties, index) {
return measureDesc[index];
}
/*
1. Measure: title text for tooltip (optional)
2. Measure: node value
3. Measure: edge value
*/
}
},
definition: {
type: "items",
component: "accordion",
items: {
data: {
uses: "data",
items:{
dimensions:{
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: ""
}
}
},
sorting: {
uses: "sorting"
},
addons: {
uses: "addons",
items: {
dataHandling: {
uses: "dataHandling"
}
}
},
settings: {
type: "items",
label: "Settings",
items: {
edgeType: {
ref: "edgeType",
type: "string",
component: "dropdown",
label: "Edge Type",
options: [
{ value: 'dynamic' },
{ value: 'continuous' },
{ value: 'discrete' },
{ value: 'diagonalCross' },
{ value: 'straightCross' },
{ value: 'horizontal' },
{ value: 'vertical' },
{ value: 'curvedCW' },
{ value: 'curvedCCW' },
{ value: 'cubicBezier' }
],
defaultValue: "dynamic"
},
displayEdgeLabel : {
ref: "displayEdgeLabel",
type: "boolean",
component: "switch",
label: "Display Edge Value",
options: [{
value: true,
label: "On"
}, {
value: false,
label: "Off"
}],
defaultValue: false
},
posEdgeLabel: {
ref: "posEdgeLabel",
type: "string",
component: "dropdown",
label: "Position Edge Label",
options: [
{ value: 'top' }, { value: 'middle' }, { value: 'bottom' }, { value: 'horizontal' }
],
defaultValue: "top"
},
nodeShape: {
ref: "nodeShape",
type: "string",
component: "dropdown",
label: "Node Shape",
options: [
{ value: 'dot' },
{ value: 'square' },
{ value: 'star' },
{ value: 'triangle' },
{ value: 'triangleDown' },
{ value: 'diamond' }
],
defaultValue: "dot"
},
shadowMode: {
ref: "shadowMode",
type: "boolean",
component: "switch",
label: "Display Shadow",
options: [{
value: true,
label: "On"
}, {
value: false,
label: "Off"
}],
defaultValue: false
}
}
}
}
},
support: {
export: true,
snapshot: false
},
snapshot: {
canTakeSnapshot: false
},
paint: paint
};
component() {
const layout = useStaleLayout();
const element = useElement();
const theme = useTheme();
const rect = useRect();
const constraints = useConstraints();
const selections = useSelections();
const [network, setNetwork] = useState();
export default component;
useEffect(()=> {
network && network.fit();
}, [rect.width, rect.height]);
usePromise(()=>
paint({ element,layout, theme, constraints, selections }).then((n)=>setNetwork(n)),
[layout, element, theme.name(), constraints ]);
},
ext: ext(),
};
}

View File

@@ -1,7 +1,7 @@
import { Network } from 'vis/index-network';
import qlik from 'qlik';
import { Network } from 'vis-network';
import { createTooltipHTML } from './tooltip';
import { escapeHTML } from './utilities';
import './styles/main.css';
function isTextCellNotEmpty(c) {
return (c.qText && !(c.qIsNull || c.qText.trim() == ''));
@@ -11,11 +11,9 @@ function getColor (index, colors) {
return colors[index % colors.length];
}
function paint ( $element, layout, qTheme, component ) {
return new qlik.Promise(function(resolve) {
const colorScale = qTheme.properties.palettes.data[0];
export default function paint ( { element,layout, theme, selections, constraints } ) {
return new Promise((resolve) => {
const colorScale = theme.getDataColorPalettes()[0];
const numDimensions = layout.qHyperCube.qDimensionInfo.length;
const numMeasures = layout.qHyperCube.qMeasureInfo.length;
@@ -24,14 +22,12 @@ function paint ( $element, layout, qTheme, component ) {
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(),
overflow: 'auto'
}));
element.textContent = '';
const topDiv = document.createElement("div");
topDiv.setAttribute('id', containerId);
topDiv.classList.add('sn-network-top');
constraints.passive && topDiv.classList.add('is-edit-mode');
element.append(topDiv);
var dataSet = qData.qMatrix.map(function(e){
const nodeName = e[1].qText;
@@ -70,7 +66,7 @@ function paint ( $element, layout, qTheme, component ) {
if (numMeasures > 1) {
if (e[numDimensions+1].qNum) {
// node value - to scale node shape size
dataItem.nodeValue = e[5].qNum;
dataItem.nodeValue = e[numDimensions+1].qNum;
}
}
@@ -122,7 +118,7 @@ function paint ( $element, layout, qTheme, component ) {
groups[nodeItem.group] = {};
}
}
const colors = colorScale.scale[Math.min(Object.keys(groups).length-1, colorScale.scale.length-1)];
const colors = colorScale.colors[Math.min(Object.keys(groups).length-1, colorScale.colors.length-1)];
Object.keys(groups).forEach(function(g,i) {
groups[g].color = getColor(i, colors);
@@ -140,7 +136,7 @@ function paint ( $element, layout, qTheme, component ) {
var options = {
groups: groups,
layout: {
randomSeed: 1
randomSeed: 34545 //"0.6610209392878246:1631081903504"
},
nodes: {
shape:layout.nodeShape,
@@ -157,7 +153,10 @@ function paint ( $element, layout, qTheme, component ) {
},
interaction: {
hideEdgesOnDrag: true,
tooltipDelay: 100
selectable: !constraints.active && !constraints.select,
tooltipDelay: 100,
multiselect: true,
selectConnectedEdges: true
},
physics: {
forceAtlas2Based: {
@@ -174,17 +173,15 @@ function paint ( $element, layout, qTheme, component ) {
};
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) {
if (Object.prototype.hasOwnProperty.call(properties, "nodes") && !constraints.active && !constraints.select) {
const nodes = network.getSelectedNodes();
if (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]);
var conNodes = nodes.map(n => network.getConnectedNodes(n));
// append nodes to the array
conNodes.push(nodes);
var connectedNodes = conNodes.flat();
const toSelect = [];
connectedNodes.forEach(function(node) {
var id;
@@ -199,32 +196,28 @@ function paint ( $element, layout, qTheme, component ) {
toSelect.indexOf(id) === -1 && toSelect.push(id);
}
});
//network.selectNodes(connectedNodes);
if (!selections.isActive()) {
selections.begin('/qHyperCubeDef');
}
//Make the selections
component.backendApi.selectValues(0,toSelect,false);
selections.select({
method: 'selectHyperCubeValues',
params: ['/qHyperCubeDef', 0, toSelect, false],
});
}
}
});
network.on('stabilizationIterationsDone', function() {
resolve();
network.stopSimulation();
resolve(network);
});
} else {
resolve();
}
});
}
function themePaint ($element, layout) {
const component = this;
try {
const app = qlik.currApp(this);
return app.theme.getApplied().then( function( qTheme ) {
return paint($element, layout, qTheme, component);
});
} catch (exception) {
console.error(exception); // eslint-disable-line no-console
}
}
export default themePaint;

View File

@@ -2,6 +2,13 @@
pointer-events: none;
}
.sn-network-top {
height: 100%;
width: 100%;
overflow: 'auto';
cursor: default;
}
.vis-tooltip {
position: absolute;
background-color: #333;

View File

@@ -1,35 +0,0 @@
"use strict";
module.exports = {
rules: {
"at-rule-no-unknown": true,
"block-no-empty": true,
"color-no-invalid-hex": true,
"comment-no-empty": true,
"declaration-block-no-duplicate-properties": [
true,
{
ignore: ["consecutive-duplicates-with-different-values"]
}
],
"declaration-block-no-shorthand-property-overrides": true,
"font-family-no-duplicate-names": true,
"font-family-no-missing-generic-family-keyword": true,
"function-calc-no-unspaced-operator": true,
"function-linear-gradient-no-nonstandard-direction": true,
"keyframe-declaration-no-important": true,
"media-feature-name-no-unknown": true,
"no-descending-specificity": true,
"no-duplicate-at-import-rules": true,
"no-duplicate-selectors": true,
"no-empty-source": true,
"no-extra-semicolons": true,
"no-invalid-double-slash-comments": true,
"property-no-unknown": true,
"selector-pseudo-class-no-unknown": true,
"selector-pseudo-element-no-unknown": true,
"selector-type-no-unknown": true,
"string-no-newline": true,
"unit-no-unknown": true
}
};

View File

@@ -1,82 +0,0 @@
const StyleLintPlugin = require('stylelint-webpack-plugin');
const packageJSON = require('./package.json');
const path = require('path');
const webpack = require('webpack');
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',
entry: [
'./src/index.js'
],
mode: MODE,
output: {
filename: `${packageJSON.name}.js`,
libraryTarget: 'amd',
path: DIST
},
externals: {
qlik: {
amd: 'qlik',
commonjs: 'qlik',
commonjs2: 'qlik',
root: '_'
}
},
module: {
rules: [
{
enforce: "pre",
test: /\.js$/,
exclude: /(node_modules)/,
loader: "eslint-loader",
options: {
failOnError: true
}
},
{
test: /node_modules[\\\/]vis[\\\/].*\.js$/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: ['@babel/preset-env']
}
}
},
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /.(less|css)$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {}
}
]
}
]
},
plugins: [
new StyleLintPlugin(),
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/en$/),
]
};
module.exports = config;

7224
yarn.lock Normal file

File diff suppressed because it is too large Load Diff