Compare commits

..

49 Commits

Author SHA1 Message Date
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
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
John Lunde
7cba160828 Merge pull request #3 from qlik-oss/enable-printing
[QPE-235] enable printing
2018-11-13 14:54:41 +01:00
Kristoffer Lind
15be850423 document data limitation 2018-11-13 12:54:56 +01:00
John Lunde
6087cb5619 Merge pull request #2 from qlik-oss/replace-checked-in-vis-js
[QPE-246][QPE-248] Replace checked in vis.js
2018-11-13 10:10:17 +01:00
Kristoffer Lind
2af87e2d42 enable printing 2018-11-09 11:30:41 +01:00
Kristoffer Lind
925fcb8824 update renamed options in new version of vis 2018-11-09 11:00:06 +01:00
Kristoffer Lind
c7ee067b95 remove checked in vis and use updated version from npm 2018-11-09 10:59:47 +01:00
Kristoffer Lind
68bdaa7411 include vis from npm 2018-11-09 10:57:37 +01:00
Martin Walter
9bd45c2f9d Merge pull request #1 from qlik-oss/project-configuration
[QPE-138][QPE-244][QPE-245] Project configuration
2018-11-08 15:43:10 +01:00
Kristoffer Lind
83326a4c4c use buildDestination from settings for qext versioning 2018-11-08 14:29:53 +01:00
Martin Walter
54ed377394 [QPE-138] Updating LICENSE, README and qext-file 2018-11-08 10:24:30 +01:00
37 changed files with 445 additions and 186 deletions

View File

@@ -92,4 +92,3 @@ workflows:
branches:
only:
- master
- project-configuration

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
The MIT License
Copyright (c) 2018-present QlikTech International AB
Copyright (c) 2016 Michael Laenen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,24 +0,0 @@
**2 Dimensional Heatmap** Visualization Extension for Qlik Sense is licensed under the "MIT" license:
>
> The MIT License (MIT)
>
> Copyright (c) 2016 Michael Laenen
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.

BIN
assets/network.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -13,10 +13,14 @@ gulp.task('remove-build-folder', function(){
gulp.task('zip-build', function(){
return gulp.src(settings.buildDestination + '/**/*')
.pipe(zip(settings.name + '_' + settings.version + '.zip'))
.pipe(zip(`${settings.name}_${settings.version}.zip`))
.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;
@@ -34,15 +38,15 @@ gulp.task('webpack-build', done => {
});
gulp.task('update-qext-version', function () {
return gulp.src("./build/" + settings.name + ".qext")
return gulp.src(`${settings.buildDestination}/${settings.name}.qext`)
.pipe(jeditor({
'version': settings.version
}))
.pipe(gulp.dest("./build"));
})
.pipe(gulp.dest(settings.buildDestination));
});
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: [
{

81
package-lock.json generated
View File

@@ -3222,6 +3222,11 @@
"minimalistic-crypto-utils": "^1.0.0"
}
},
"emitter-component": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz",
"integrity": "sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY="
},
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@@ -4397,7 +4402,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@@ -4418,12 +4424,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -4438,17 +4446,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -4565,7 +4576,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@@ -4577,6 +4589,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -4591,6 +4604,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -4598,12 +4612,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -4622,6 +4638,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -4702,7 +4719,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -4714,6 +4732,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -4799,7 +4818,8 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4835,6 +4855,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -4854,6 +4875,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4897,12 +4919,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@@ -5480,6 +5504,11 @@
"glogg": "^1.0.0"
}
},
"hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
"integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE="
},
"handle-thing": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz",
@@ -6684,6 +6713,11 @@
}
}
},
"keycharm": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.2.0.tgz",
"integrity": "sha1-+m6i5DuQpoAohD0n8gddNajD5vk="
},
"killable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -7315,6 +7349,11 @@
"minimist": "0.0.8"
}
},
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -8566,6 +8605,14 @@
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
"dev": true
},
"propagating-hammerjs": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-1.4.6.tgz",
"integrity": "sha1-/tAOmwB2f/1C0U9bUxvEk+tnLjc=",
"requires": {
"hammerjs": "^2.0.6"
}
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@@ -11197,6 +11244,18 @@
"vinyl": "^2.0.0"
}
},
"vis": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/vis/-/vis-4.21.0.tgz",
"integrity": "sha1-3XFji/9/ZJXQC8n0DCU1JhM97Ws=",
"requires": {
"emitter-component": "^1.1.1",
"hammerjs": "^2.0.8",
"keycharm": "^0.2.0",
"moment": "^2.18.1",
"propagating-hammerjs": "^1.4.6"
}
},
"vm-browserify": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",

View File

@@ -43,5 +43,8 @@
"webpack": "4.25.1",
"webpack-cli": "3.1.2",
"webpack-dev-server": "3.1.10"
},
"dependencies": {
"vis": "4.21.0"
}
}

View File

@@ -1,42 +1,69 @@
# Qlik Network Chart
[![CircleCI](https://circleci.com/gh/qlik-oss/network-vis-chart.svg?style=svg)](https://circleci.com/gh/qlik-oss/network-vis-chart)
[![CircleCI](https://circleci.com/gh/qlik-oss/network-vis-chart.svg?style=shield)](https://circleci.com/gh/qlik-oss/network-vis-chart)
<h2>Qlik Sense extension to visualize networks data</h2>
<hr>
Based on library vis.js (http://visjs.org)
<br>Tested with Qlik Sense 2.2.3.
<hr>
<h3>Dimensions</h3>
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 :
<ol>
<li>node identifier</li>
<li>node label</li>
<li>node parent identifier</li>
<li>node group</li>
</ol>
<h3>Measures</h3>
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
The measures are optional
<ol>
<li>tooltip : expression that will be push in the tooltip when hover on a node</li>
<li>node value : used to scale the node size</li>
<li>edge value : used to scale the edge width</li>
</ol>
<h3>Additional network settings</h3>
<ul>
<li>Edge Type : select type of curve between nodes</li>
<li>Node Shape : dot, square, diamond, triangle ...</li>
<li>Display Edge Value : switch to display the measures on edge curves</li>
<li>Position Edge Label : top, bottom, middle, horizontal</li>
<li>Display Shadow : switch to enable shadow effects behind edge and nodes</li>
</ul>
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
A data sample can be found here: https://github.com/qlik-oss/network-vis-chart/blob/master/resources/Network%20data.xlsx
<h3>Sample</h3>
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.
# 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`.
# Original Author
**Michael Laenen**

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -7,7 +7,31 @@ Use at your own risk.
*/
import "@babel/polyfill";
import paint from './paint';
import './styles/vis.min.css';
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: {
@@ -22,30 +46,62 @@ const component = {
}
},
//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
*/
},
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: {
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: "",
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"
@@ -59,13 +115,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' },
@@ -74,8 +131,8 @@ const component = {
{ value: 'straightCross' },
{ value: 'horizontal' },
{ value: 'vertical' },
{ value: 'curveCW' },
{ value: 'curveCCW' },
{ value: 'curvedCW' },
{ value: 'curvedCCW' },
{ value: 'cubicBezier' }
],
defaultValue: "dynamic"
@@ -137,11 +194,13 @@ const component = {
}
}
},
snapshot: {
canTakeSnapshot: true
support: {
export: true,
snapshot: false
},
snapshot: {
canTakeSnapshot: false
},
paint: paint
};

View File

@@ -1,19 +1,29 @@
import vis from './scripts/vis.min';
import { Network } from 'vis/index-network';
import qlik from 'qlik';
import { createTooltipHTML } from './tooltip';
import { escapeHTML } from './utilities';
function isTextCellNotEmpty(c) {
return (c.qText && !(c.qIsNull || c.qText.trim() == ''));
}
function paint ( $element, layout ) {
var _this = this,
qData = layout.qHyperCube.qDataPages[0],
function getColor (index, colors) {
return colors[index % colors.length];
}
function paint ( $element, layout, qTheme, component ) {
const colorScale = qTheme.properties.palettes.data[0];
const numDimensions = layout.qHyperCube.qDimensionInfo.length;
const numMeasures = layout.qHyperCube.qMeasureInfo.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,34 +31,50 @@ function paint ( $element, layout ) {
}));
var dataSet = qData.qMatrix.map(function(e){
var dataItem = {
id: e[0].qNum,
label: e[1].qText,
group: e[3].qText,
parentid : e[2].qNum
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 (e.length > 4) {
// tooltip
if (isTextCellNotEmpty(e[4])) {
dataItem.title = e[4].qText;
} else {
dataItem.title = "*** Default Tooltip ***" + "<BR/>" + "Name:" + e[1].qText + "<BR/>" + "Group:" + e[3].qText;
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 (e.length > 5) {
if (e[5].qNum) {
if (numMeasures > 1) {
if (e[numDimensions+1].qNum) {
// node value - to scale node shape size
dataItem.nodeValue = e[5].qNum;
}
}
if (e.length > 6) {
if (e[6].qNum) {
if (numMeasures > 2) {
if (e[numDimensions+2].qNum) {
// edge value - to scale edge width
dataItem.edgeValue = e[6].qNum;
dataItem.edgeValue = e[numDimensions+2].qNum;
}
}
@@ -59,12 +85,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
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
edges.push({
"from":dataSet[i].id,
"to":dataSet[i].parentid,
"value":dataSet[i].edgeValue
}); // create edges
}
// process uniqueness
@@ -72,15 +108,21 @@ 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,
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
}
}
const colors = colorScale.scale[Math.max(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 = {
@@ -92,6 +134,10 @@ function paint ( $element, layout ) {
var container = document.getElementById(containerId);
var options = {
groups: groups,
layout: {
randomSeed: 1
},
nodes: {
shape:layout.nodeShape,
shadow:layout.shadowMode
@@ -122,7 +168,8 @@ function paint ( $element, layout ) {
stabilization: { iterations: 150 }
}
};
var network = new vis.Network(container, data, options);
var network = new Network(container, data, options);
network.fit();
// Handle Selection on 1-node
$("#"+containerId).css('cursor','default');
@@ -134,13 +181,39 @@ function paint ( $element, layout ) {
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
_this.backendApi.selectValues(0,connectedNodes,false);
component.backendApi.selectValues(0,toSelect,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,9 +1,16 @@
{
"name" : "Network Chart",
"description" : "NetWork Chart",
"icon" : "extension",
"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": "1.0",
"version": "X.Y.Z",
"preview" : "network.png",
"author": "Michael Laenen , agilos.com"
"keywords": "qlik-sense, visualization",
"author": "Michael Laenen <michael.laenen@agilos.com>",
"homepage": "",
"license": "MIT",
"repository": "",
"dependencies": {
"qlik-sense": ">=5.5.x"
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

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

@@ -0,0 +1,17 @@
.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;
}
.qlik-network-chart-italic-property {
font-style: italic;
}

File diff suppressed because one or more lines are too long

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

@@ -1,6 +1,7 @@
const CopyWebpackPlugin = require('copy-webpack-plugin');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const settings = require('./settings');
const webpack = require('webpack');
console.log('Webpack mode:', settings.mode); // eslint-disable-line no-console
@@ -15,6 +16,14 @@ const config = {
filename: settings.name + '.js',
libraryTarget: 'umd'
},
externals: {
qlik: {
amd: 'qlik',
commonjs: 'qlik',
commonjs2: 'qlik',
root: '_'
}
},
module: {
rules: [
{
@@ -26,6 +35,16 @@ const config = {
failOnError: true
}
},
{
test: /node_modules[\\\/]vis[\\\/].*\.js$/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: ['@babel/preset-env']
}
}
},
{
test: /.js$/,
exclude: /node_modules/,
@@ -55,7 +74,8 @@ const config = {
new CopyWebpackPlugin([
'src/' + settings.name + '.qext'
], {}),
new StyleLintPlugin()
new StyleLintPlugin(),
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/en$/),
]
};