Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97550cff60 | ||
|
|
164d6f4325 | ||
|
|
f9153aaed9 | ||
|
|
020290a1b7 | ||
|
|
155eac826d | ||
|
|
abc2f151aa | ||
|
|
1f116d38a2 | ||
|
|
291e2bd05f | ||
|
|
7fa1ee419f | ||
|
|
e26d77cb5d | ||
|
|
f6facc8d21 | ||
|
|
e646bd242b | ||
|
|
047b248c01 | ||
|
|
39cacf167f | ||
|
|
d140c89e5a | ||
|
|
d5ed44c62c | ||
|
|
4249dfa8cb | ||
|
|
7198257f40 | ||
|
|
e74991445a | ||
|
|
58e89efbb0 | ||
|
|
2914f13f6e | ||
|
|
bd12b6519b | ||
|
|
ec4af50cff | ||
|
|
43f32a470a | ||
|
|
a2209e160f | ||
|
|
63ba3a9362 | ||
|
|
dd0af64115 | ||
|
|
aa2ef96469 | ||
|
|
e9b087bde2 | ||
|
|
25d5f4e4da | ||
|
|
f02e0c17e5 | ||
|
|
e406acef3a | ||
|
|
9669ccfdaf | ||
|
|
a99a14c541 | ||
|
|
b9c7d2b0f1 | ||
|
|
955d954b6f | ||
|
|
4baadc1157 | ||
|
|
e467da6b46 | ||
|
|
7f4c6af61d | ||
|
|
3e199979fd | ||
|
|
3e1384c900 | ||
|
|
3ecaf3de42 | ||
|
|
d95c0f572e | ||
|
|
fd2f9fa277 | ||
|
|
e2aac7a294 | ||
|
|
b6bcbe7f75 | ||
|
|
f4441ef683 | ||
|
|
3a832e7d6a | ||
|
|
22da42de9f | ||
|
|
fddf286a8e | ||
|
|
e6692b8779 | ||
|
|
4341fdb5db | ||
|
|
d02852b2ed | ||
|
|
25f6593f35 | ||
|
|
76a22121a9 | ||
|
|
d4154fde09 | ||
|
|
d65b619546 | ||
|
|
d15b246db4 | ||
|
|
fc8e9b0ba4 | ||
|
|
b443deca27 | ||
|
|
8a24c3ee92 | ||
|
|
464d137095 | ||
|
|
903a2caa1d | ||
|
|
d68b2ed863 | ||
|
|
f87bc3ea88 | ||
|
|
b7753143fd | ||
|
|
8f239687f3 | ||
|
|
d970d05711 | ||
|
|
192f4a8597 | ||
|
|
6eff5e1fd0 | ||
|
|
ad63832d18 | ||
|
|
28b3aeb676 | ||
|
|
321c71825e | ||
|
|
6433daee95 | ||
|
|
d210ad3908 | ||
|
|
f461493b0f | ||
|
|
4312078951 | ||
|
|
7c53f8811b |
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"targets": {
|
||||
"chrome": "47"
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
@@ -10,85 +10,42 @@ defaults: &defaults
|
||||
PACKAGE_NAME: "qlik-network-chart"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
docker:
|
||||
- image: circleci/node:stretch-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: npm install
|
||||
- run:
|
||||
name: Run tests
|
||||
command: npm run test-once
|
||||
|
||||
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
|
||||
docker:
|
||||
- image: circleci/node:stretch-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: ~/qlik-network-chart
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: npm install
|
||||
command: yarn
|
||||
- run:
|
||||
name: Build and package
|
||||
command: |
|
||||
export VERSION=$(scripts/get-bumped-version.sh)
|
||||
echo "Version: ${VERSION}"
|
||||
npm run build
|
||||
name: Lint
|
||||
command: yarn eslint
|
||||
- run:
|
||||
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"
|
||||
- run:
|
||||
name: Build
|
||||
command: yarn build && yarn sense
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
- persist_to_workspace:
|
||||
root: ~/qlik-network-chart
|
||||
paths:
|
||||
- build
|
||||
- run:
|
||||
name: Update spec
|
||||
command: yarn run spec
|
||||
- run:
|
||||
name: Run unit tests
|
||||
command: yarn test:unit
|
||||
- store_artifacts:
|
||||
path: build
|
||||
destination: build
|
||||
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
|
||||
path: dist
|
||||
destination: dist
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
master_flow:
|
||||
jobs:
|
||||
- test
|
||||
- bump-version:
|
||||
requires:
|
||||
- test
|
||||
- build:
|
||||
requires:
|
||||
- bump-version
|
||||
- deploy:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- build
|
||||
|
||||
@@ -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"],
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
node_modules/
|
||||
build/
|
||||
dist/
|
||||
BUMPED_VERSION
|
||||
sn-network-chart-ext/
|
||||
core/esm
|
||||
coverage
|
||||
yarn-error.log
|
||||
|
||||
136
api-specifications/properties.json
Normal file
136
api-specifications/properties.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
3
babel.config.js
Normal file
3
babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-env'],
|
||||
};
|
||||
8
core/package.json
Normal file
8
core/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
76
gulpfile.js
76
gulpfile.js
@@ -1,76 +0,0 @@
|
||||
var gulp = require('gulp');
|
||||
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");
|
||||
|
||||
gulp.task('remove-build-folder', function(){
|
||||
return del([settings.buildDestination], { force: true });
|
||||
});
|
||||
|
||||
gulp.task('zip-build', function(){
|
||||
return gulp.src(settings.buildDestination + '/**/*')
|
||||
.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;
|
||||
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('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.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('default',
|
||||
gulp.series('build')
|
||||
);
|
||||
196
jest.config.js
Normal file
196
jest.config.js
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* For a detailed explanation regarding each configuration property and type check, visit:
|
||||
* https://jestjs.io/docs/configuration
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "/private/var/folders/dc/j98t7yvj11n21psdq_xy0y8snk5538/T/jest_tq54ko",
|
||||
|
||||
// Automatically clear mock calls, instances and results before every test
|
||||
// clearMocks: false,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
collectCoverage: true,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
// collectCoverageFrom: undefined,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
coverageDirectory: "coverage",
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
// coverageProvider: "babel",
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: undefined,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: undefined,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: undefined,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: undefined,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
// maxWorkers: "50%",
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
// "json",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: undefined,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state before every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: undefined,
|
||||
|
||||
// Automatically restore mock state and implementation before every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: undefined,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
// roots: [
|
||||
// "<rootDir>"
|
||||
// ],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
setupFilesAfterEnv: ["<rootDir>/jest/setup.js"],
|
||||
|
||||
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||
// slowTestThreshold: 5,
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: "jsdom",
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
//"**/src/**/__tests__/*.test.ts?(x)",
|
||||
testMatch: ["<rootDir>/src/_test/*.jest.test.js?(x)"],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: undefined,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jest-circus/runner",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: undefined,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "/node_modules/",
|
||||
// "\\.pnp\\.[^\\/]+$"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: undefined,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
|
||||
moduleNameMapper: {
|
||||
"\\.(css|less)$": "<rootDir>/src/tests/jest/mocks/css-mock.js"
|
||||
}
|
||||
};
|
||||
1
jest/setup.js
Normal file
1
jest/setup.js
Normal file
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -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' }
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
21
nebula.config.js
Normal file
21
nebula.config.js
Normal 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
11695
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
77
package.json
77
package.json
@@ -1,50 +1,47 @@
|
||||
{
|
||||
"name": "qlik-network-chart",
|
||||
"version": "0.0.1",
|
||||
"description": "Network chart",
|
||||
"keywords": "network chart qliksense extension",
|
||||
"name": "@nebula.js/sn-network-chart",
|
||||
"version": "0.2.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",
|
||||
"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",
|
||||
"eslint:fix": "eslint --fix src",
|
||||
"test": "karma start karma.conf.js",
|
||||
"test-once": "karma start karma.conf.js --single-run",
|
||||
"watch": "gulp watch"
|
||||
"spec": "scriptappy-from-jsdoc -c ./spec-configs/props.conf.js",
|
||||
"test:unit": "jest",
|
||||
"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",
|
||||
"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-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"
|
||||
"@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",
|
||||
"@testing-library/jest-dom": "5.16.4",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-jest": "28.1.3",
|
||||
"eslint": "7.32.0",
|
||||
"jest": "28.1.3",
|
||||
"jest-environment-jsdom": "28.1.3",
|
||||
"scriptappy-from-jsdoc": "0.7.0",
|
||||
"shx": "0.3.3",
|
||||
"vis-data": "7.1.4",
|
||||
"vis-network": "9.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"vis": "4.21.0"
|
||||
"peerDependencies": {
|
||||
"@nebula.js/stardust": ">=1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
108
readme.md
108
readme.md
@@ -1,69 +1,79 @@
|
||||
# Qlik Network Chart
|
||||
# @nebula.js/sn-network-chart
|
||||
|
||||
[](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.
|
||||
## Legacy build
|
||||
The chart before Nebula conversion and dependency updates can be found on the *release/legacy* branch.
|
||||
|
||||
### Dimensions
|
||||
4 dimensions are mandatory :
|
||||
## Requirements
|
||||
|
||||
1. node identifier
|
||||
2. node label
|
||||
3. node parent identifier
|
||||
4. node group
|
||||
Requires `@nebula.js/stardust` version `1.7.0` or later.
|
||||
|
||||
### Measures
|
||||
The measures are optional
|
||||
## Installing
|
||||
|
||||
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
|
||||
If you use npm: `npm install @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
|
||||
You can also load through the script tag directly from [https://unpkg.com](https://unpkg.com/@nebula.js/sn-network-chart).
|
||||
|
||||
### Sample
|
||||
QVF based on characters from Victor Hugo's novel , Les Misérables.
|
||||

|
||||
## Usage
|
||||
|
||||
### Data Limit
|
||||
Starts having issues stabilizing(transforming into untangled view) at around 100-200 nodes depending on dataset.
|
||||
```js
|
||||
import { embed } from '@nebula.js/stardust';
|
||||
import network from '@nebula.js/sn-network-chart';
|
||||
|
||||
// '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),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
# Getting Started
|
||||
// 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',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Installation
|
||||
## Data sample
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
a. **Qlik Sense Desktop**: unzip to a directory under [My Documents]/Qlik/Sense/Extensions.
|
||||
| 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 |
|
||||
|
||||
b. **Qlik Sense Server**: import the zip file in the QMC.
|
||||
Sense inline load script example:
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
BIN
resources/Network data.xlsx
Normal file
BIN
resources/Network data.xlsx
Normal file
Binary file not shown.
4
resources/meta.json
Normal file
4
resources/meta.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "Network chart",
|
||||
"icon": "bubbles"
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
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"
|
||||
|
||||
|
||||
# Usage
|
||||
# $ create-release.sh qlik-oss qsSimpleKPI qlik-multi-kpi 0.3.1
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -o errexit
|
||||
|
||||
echo "$(head -n 1 BUMPED_VERSION)"
|
||||
|
||||
# Usage
|
||||
# $ get-bumped-version.sh
|
||||
@@ -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
|
||||
@@ -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
|
||||
13
settings.js
13
settings.js
@@ -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
|
||||
};
|
||||
26
spec-configs/props.conf.js
Normal file
26
spec-configs/props.conf.js
Normal 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: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
11
src/_test/tooltip.jest.test.js
Normal file
11
src/_test/tooltip.jest.test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createTooltipHTML } from "../tooltip";
|
||||
|
||||
describe("createTooltipHTML", () => {
|
||||
it("Testing createTooltipHtml function - Name- GroupNumber- nodeMeasure", () => {
|
||||
expect(
|
||||
createTooltipHTML({ name: "Venice", groupNumber: 1, nodeMeasure: 2 })
|
||||
).toContainHTML(
|
||||
"<div><div><span>Name: </span><b>Venice</b></div><div><span>Group number: </span><b>1</b></div><div><span>Node measure: </span><b>2</b></div></div>"
|
||||
);
|
||||
});
|
||||
});
|
||||
34
src/extension/data.js
Normal file
34
src/extension/data.js
Normal 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
164
src/extension/ext.js
Normal 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
|
||||
},
|
||||
};
|
||||
}
|
||||
54
src/extension/properties.js
Normal file
54
src/extension/properties.js
Normal 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
23
src/extension/strings.js
Normal 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"
|
||||
];
|
||||
188
src/index.js
188
src/index.js
@@ -1,161 +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.
|
||||
*/
|
||||
import "@babel/polyfill";
|
||||
import paint from './paint';
|
||||
import './styles/main.less';
|
||||
|
||||
const component = {
|
||||
initialProperties: {
|
||||
version: 1.0,
|
||||
qHyperCubeDef: {
|
||||
qDimensions: [],
|
||||
qMeasures: [],
|
||||
qInitialDataFetch: [{
|
||||
qWidth: 7,
|
||||
qHeight: 1400
|
||||
}]
|
||||
}
|
||||
},
|
||||
//property panel
|
||||
data: {
|
||||
dimensions: {
|
||||
min: 4,
|
||||
max: 4
|
||||
/*
|
||||
1. Dimension: Node ID, numeric (Event ID or else) or String
|
||||
2. Dimension: Node Label
|
||||
3. Dimension: Node Parent ID, numeric (Event ID or else) or String
|
||||
4. Dimension: Node Cluster
|
||||
*/
|
||||
export default function supernova() {
|
||||
return {
|
||||
qae: {
|
||||
properties,
|
||||
data: data(),
|
||||
},
|
||||
measures: {
|
||||
min: 0,
|
||||
max: 3
|
||||
/*
|
||||
1. Measure: title text for tooltip (optional)
|
||||
2. Measure: node value
|
||||
3. Measure: edge value
|
||||
*/
|
||||
}
|
||||
},
|
||||
definition: {
|
||||
type: "items",
|
||||
component: "accordion",
|
||||
items: {
|
||||
data: {
|
||||
uses: "data",
|
||||
items:{
|
||||
dimensions:{
|
||||
disabledRef: ""
|
||||
},
|
||||
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: {
|
||||
canTakeSnapshot: true
|
||||
},
|
||||
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(),
|
||||
};
|
||||
}
|
||||
|
||||
199
src/paint.js
199
src/paint.js
@@ -1,199 +0,0 @@
|
||||
import { Network } from 'vis/index-network';
|
||||
import qlik from 'qlik';
|
||||
import { createTooltipHTML } from './tooltip';
|
||||
import { escapeHTML } from './utilities';
|
||||
|
||||
const colorScheme = 'Diverging Classes';
|
||||
|
||||
function isTextCellNotEmpty(c) {
|
||||
return (c.qText && !(c.qIsNull || c.qText.trim() == ''));
|
||||
}
|
||||
|
||||
function paint ( $element, layout, qTheme, component ) {
|
||||
const colorScale = qTheme.properties.scales
|
||||
.find(scale => scale.name === colorScheme).scale;
|
||||
const colors = colorScale[colorScale.length - 1];
|
||||
|
||||
function getColor (number) {
|
||||
return colors[number % colors.length];
|
||||
}
|
||||
|
||||
var qData = layout.qHyperCube.qDataPages[0],
|
||||
id = layout.qInfo.qId,
|
||||
containerId = 'network-container_' + id;
|
||||
|
||||
if(qData && qData.qMatrix) {
|
||||
$element.empty().append($('<div />')
|
||||
.attr({ id: containerId })
|
||||
.toggleClass('is-edit-mode', component.inEditState())
|
||||
.css({
|
||||
height: $element.height(),
|
||||
width: $element.width(),
|
||||
overflow: 'auto'
|
||||
}));
|
||||
|
||||
var dataSet = qData.qMatrix.map(function(e){
|
||||
const nodeName = e[1].qText;
|
||||
const groupNumber = e[3].qText;
|
||||
|
||||
const dataItem = {
|
||||
id: e[0].qNum,
|
||||
label: nodeName,
|
||||
group: groupNumber,
|
||||
parentid : e[2].qNum
|
||||
};
|
||||
|
||||
// optional measures set
|
||||
if (e.length > 4) {
|
||||
const tooltip = e[4];
|
||||
|
||||
if (isTextCellNotEmpty(tooltip)) {
|
||||
const tooltipText = tooltip.qText;
|
||||
dataItem.title = escapeHTML(tooltipText);
|
||||
} else {
|
||||
const nodeMeasure = e[5].qText;
|
||||
dataItem.title = createTooltipHTML({
|
||||
name: nodeName,
|
||||
groupNumber,
|
||||
nodeMeasure
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (e.length > 5) {
|
||||
if (e[5].qNum) {
|
||||
// node value - to scale node shape size
|
||||
dataItem.nodeValue = e[5].qNum;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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;
|
||||
@@ -1,7 +0,0 @@
|
||||
import paint from './paint';
|
||||
|
||||
describe('paint', () => {
|
||||
it('should be defined', () => {
|
||||
expect(paint).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name" : "Network chart",
|
||||
"description" : "Display hierarchical or relational dimensions as nodes and edges, adding measures to show the significance of the 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"
|
||||
}
|
||||
}
|
||||
223
src/sn-paint.js
Normal file
223
src/sn-paint.js
Normal file
@@ -0,0 +1,223 @@
|
||||
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() == ''));
|
||||
}
|
||||
|
||||
function getColor (index, colors) {
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
var qData = layout.qHyperCube.qDataPages[0],
|
||||
id = layout.qInfo.qId,
|
||||
containerId = 'network-container_' + id;
|
||||
|
||||
if(qData && qData.qMatrix) {
|
||||
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;
|
||||
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[numDimensions+1].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.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);
|
||||
});
|
||||
|
||||
// create dataset for \\
|
||||
var data = {
|
||||
nodes: nodes,
|
||||
edges: edges
|
||||
};
|
||||
|
||||
// create a network
|
||||
var container = document.getElementById(containerId);
|
||||
|
||||
var options = {
|
||||
groups: groups,
|
||||
layout: {
|
||||
randomSeed: 34545 //"0.6610209392878246:1631081903504"
|
||||
},
|
||||
nodes: {
|
||||
shape:layout.nodeShape,
|
||||
shadow:layout.shadowMode
|
||||
},
|
||||
edges: {
|
||||
shadow:layout.shadowMode,
|
||||
font: {
|
||||
align: layout.posEdgeLabel
|
||||
},
|
||||
smooth: {
|
||||
type: layout.edgeType
|
||||
}
|
||||
},
|
||||
interaction: {
|
||||
hideEdgesOnDrag: true,
|
||||
selectable: !constraints.active && !constraints.select,
|
||||
tooltipDelay: 100,
|
||||
multiselect: true,
|
||||
selectConnectedEdges: true
|
||||
},
|
||||
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();
|
||||
network.on('select', function (properties) {
|
||||
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 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;
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
//network.selectNodes(connectedNodes);
|
||||
|
||||
if (!selections.isActive()) {
|
||||
selections.begin('/qHyperCubeDef');
|
||||
}
|
||||
|
||||
//Make the selections
|
||||
selections.select({
|
||||
method: 'selectHyperCubeValues',
|
||||
params: ['/qHyperCubeDef', 0, toSelect, false],
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
network.on('stabilizationIterationsDone', function() {
|
||||
network.stopSimulation();
|
||||
resolve(network);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
@@ -11,3 +18,7 @@
|
||||
padding: 10px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.qlik-network-chart-italic-property {
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -24,5 +24,5 @@ export function createTooltipHTML({ name, groupNumber, nodeMeasure }) {
|
||||
tooltip.appendChild(groupNumberEntry);
|
||||
tooltip.appendChild(nodeMeasureEntry);
|
||||
|
||||
return tooltip.innerHTML;
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
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
|
||||
|
||||
const config = {
|
||||
devtool: 'source-map',
|
||||
entry: [
|
||||
'./src/index.js'
|
||||
],
|
||||
mode: settings.mode,
|
||||
output: {
|
||||
path: settings.buildDestination,
|
||||
filename: settings.name + '.js',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
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 CopyWebpackPlugin([
|
||||
'src/' + settings.name + '.qext'
|
||||
], {}),
|
||||
new StyleLintPlugin(),
|
||||
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/en$/),
|
||||
]
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
Reference in New Issue
Block a user