├── .config ├── bundle-system.js ├── helpers.js ├── karma.conf.js ├── webpack.common.js ├── webpack.dev.js ├── webpack.prod.js └── webpack.test.js ├── .eslintrc.json ├── .gitignore ├── .ng2-config.js ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── components ├── index.ts ├── network │ ├── index.ts │ ├── vis-network.directive.ts │ ├── vis-network.service.spec.ts │ └── vis-network.service.ts └── timeline │ ├── index.ts │ ├── vis-timeline.directive.ts │ ├── vis-timeline.service.spec.ts │ └── vis-timeline.service.ts ├── demo ├── assets │ ├── css │ │ ├── example.css │ │ └── vis.min.css │ └── img │ │ ├── network │ │ ├── acceptDeleteIcon.png │ │ ├── addNodeIcon.png │ │ ├── backIcon.png │ │ ├── connectIcon.png │ │ ├── cross.png │ │ ├── cross2.png │ │ ├── deleteIcon.png │ │ ├── downArrow.png │ │ ├── editIcon.png │ │ ├── leftArrow.png │ │ ├── minus.png │ │ ├── plus.png │ │ ├── rightArrow.png │ │ ├── upArrow.png │ │ └── zoomExtends.png │ │ └── timeline │ │ └── delete.png ├── demo.component.ts ├── demo.module.ts ├── home │ └── home.component.ts ├── index.html ├── index.ts ├── network │ └── network-example.component.ts ├── polyfills.ts ├── timeline │ └── timeline-example.component.ts └── vendor.ts ├── gulp-tasks └── lint.js ├── gulpfile.js ├── karma.conf.js ├── ng2-vis.ts ├── package.json ├── spec-bundle.js ├── tsconfig.json ├── tsconfig.publish.json ├── tslint.json ├── typings.json └── webpack.config.js /.config/bundle-system.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | /*eslint no-console: 0, no-sync: 0*/ 6 | 7 | // System.js bundler 8 | // simple and yet reusable system.js bundler 9 | // bundles, minifies and gzips 10 | 11 | const fs = require('fs'); 12 | const del = require('del'); 13 | const path = require('path'); 14 | const zlib = require('zlib'); 15 | const async = require('async'); 16 | const Builder = require('systemjs-builder'); 17 | 18 | const pkg = require('../package.json'); 19 | const name = pkg.name; 20 | const targetFolder = path.resolve('./bundles'); 21 | 22 | async.waterfall([ 23 | cleanBundlesFolder, 24 | getSystemJsBundleConfig, 25 | buildSystemJs({minify: false, sourceMaps: true, mangle: false, noEmitHelpers: false, declaration: true}), 26 | getSystemJsBundleConfig, 27 | buildSystemJs({minify: true, sourceMaps: true, mangle: false, noEmitHelpers: false, declaration: true}), 28 | gzipSystemJsBundle 29 | ], err => { 30 | if (err) { 31 | throw err; 32 | } 33 | }); 34 | 35 | function getSystemJsBundleConfig(cb) { 36 | const config = { 37 | baseURL: '..', 38 | transpiler: 'typescript', 39 | typescriptOptions: { 40 | module: 'cjs' 41 | }, 42 | map: { 43 | typescript: path.resolve('node_modules/typescript/lib/typescript.js'), 44 | '@angular/core': path.resolve('node_modules/@angular/core/index.js'), 45 | '@angular/common': path.resolve('node_modules/@angular/common/index.js'), 46 | '@angular/compiler': path.resolve('node_modules/@angular/compiler/index.js'), 47 | '@angular/forms': path.resolve('node_modules/@angular/forms/index.js'), 48 | '@angular/platform-browser': path.resolve('node_modules/@angular/platform-browser/index.js'), 49 | '@angular/platform-browser-dynamic': path.resolve('node_modules/@angular/platform-browser-dynamic/'), 50 | rxjs: path.resolve('node_modules/rxjs'), 51 | vis: path.resolve('node_modules/vis/index.js') 52 | }, 53 | paths: { 54 | '*': '*.js' 55 | } 56 | }; 57 | 58 | config.meta = ['@angular/common','@angular/compiler','@angular/core', '@angular/forms', 59 | '@angular/platform-browser','@angular/platform-browser-dynamic', 'rxjs', 'vis'].reduce((memo, currentValue) => { 60 | memo[path.resolve(`node_modules/${currentValue}/*`)] = {build: false}; 61 | return memo; 62 | }, {}); 63 | config.meta.moment = {build: false}; 64 | return cb(null, config); 65 | } 66 | 67 | function cleanBundlesFolder(cb) { 68 | return del(targetFolder) 69 | .then(paths => { 70 | console.log('Deleted files and folders:\n', paths.join('\n')); 71 | cb(); 72 | }); 73 | } 74 | 75 | function buildSystemJs(options) { 76 | return (config, cb) => { 77 | const minPostFix = options && options.minify ? '.min' : ''; 78 | const fileName = `${name}${minPostFix}.js`; 79 | const dest = path.resolve(__dirname, targetFolder, fileName); 80 | const builder = new Builder(); 81 | 82 | console.log('Bundling system.js file:', fileName, options); 83 | builder.config(config); 84 | return builder 85 | .bundle([name, name].join('/'), dest, options) 86 | .then(() => cb()) 87 | .catch(cb); 88 | }; 89 | } 90 | 91 | function gzipSystemJsBundle(cb) { 92 | const files = fs 93 | .readdirSync(path.resolve(targetFolder)) 94 | .map(file => path.resolve(targetFolder, file)) 95 | .filter(file => fs.statSync(file).isFile()) 96 | .filter(file => path.extname(file) !== 'gz'); 97 | 98 | return async.eachSeries(files, (file, gzipcb) => { 99 | process.nextTick(() => { 100 | console.log('Gzipping ', file); 101 | const gzip = zlib.createGzip({level: 9}); 102 | const inp = fs.createReadStream(file); 103 | const out = fs.createWriteStream(`${file}.gz`); 104 | 105 | inp.on('end', () => gzipcb()); 106 | inp.on('error', err => gzipcb(err)); 107 | return inp.pipe(gzip).pipe(out); 108 | }); 109 | }, cb); 110 | } 111 | -------------------------------------------------------------------------------- /.config/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: @AngularClass 3 | */ 4 | 'use strict'; 5 | var path = require('path'); 6 | 7 | // Helper functions 8 | var ROOT = process.cwd(); 9 | 10 | console.log('root directory:', root() + '\n'); 11 | 12 | function hasProcessFlag(flag) { 13 | return process.argv.join('').indexOf(flag) > -1; 14 | } 15 | 16 | function root(args) { 17 | /* eslint-disable */ 18 | args = Array.prototype.slice.call(arguments, 0); 19 | return path.join.apply(path, [ROOT].concat(args)); 20 | } 21 | 22 | function excludeIndexHtml(src, htmlIndexes) { 23 | return htmlIndexes.map(function (str) { 24 | return root(path.join(src, str)); 25 | }); 26 | } 27 | 28 | exports.hasProcessFlag = hasProcessFlag; 29 | exports.root = root; 30 | exports.excludeIndexHtml = excludeIndexHtml; -------------------------------------------------------------------------------- /.config/karma.conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: @AngularClass 3 | */ 4 | 'use strict'; 5 | 6 | const testWebpackFn = require('./webpack.test.js'); 7 | 8 | module.exports = function localConf(conf) { 9 | const testWebpackConfig = testWebpackFn(conf); 10 | 11 | return karmaConf; 12 | 13 | function karmaConf(config) { 14 | config.set({ 15 | 16 | // base path that will be used to resolve all patterns (e.g. files, exclude) 17 | basePath: '', 18 | 19 | /* 20 | * Frameworks to use 21 | * 22 | * available frameworks: https://npmjs.org/browse/keyword/karma-adapter 23 | */ 24 | frameworks: ['jasmine'], 25 | 26 | // list of files to exclude 27 | exclude: [], 28 | 29 | /* 30 | * list of files / patterns to load in the browser 31 | * 32 | * we are building the test environment in ./spec-bundle.js 33 | */ 34 | files: [{pattern: conf.spec, watched: false}], 35 | 36 | /* 37 | * preprocess matching files before serving them to the browser 38 | * available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 39 | */ 40 | preprocessors: {[conf.spec]: ['coverage', 'webpack', 'sourcemap']}, 41 | 42 | // Webpack Config at ./webpack.test.js 43 | webpack: testWebpackConfig, 44 | 45 | coverageReporter: { 46 | dir: 'coverage/', 47 | reporters: [ 48 | {type: 'text-summary'}, 49 | {type: 'json'}, 50 | {type: 'html'}, 51 | {type: 'json', subdir: '.', file: 'coverage-final.json'} 52 | ] 53 | }, 54 | 55 | // Webpack please don't spam the console when running in karma! 56 | webpackServer: {noInfo: true}, 57 | 58 | /* 59 | * test results reporter to use 60 | * 61 | * possible values: 'dots', 'progress' 62 | * available reporters: https://npmjs.org/browse/keyword/karma-reporter 63 | */ 64 | reporters: ['mocha', 'coverage'], 65 | 66 | // web server port 67 | port: 9876, 68 | 69 | // enable / disable colors in the output (reporters and logs) 70 | colors: true, 71 | 72 | /* 73 | * level of logging 74 | * possible values: config.LOG_DISABLE || config.LOG_ERROR || 75 | * config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 76 | */ 77 | logLevel: config.LOG_INFO, 78 | 79 | // enable / disable watching file and executing tests whenever any file changes 80 | autoWatch: false, 81 | 82 | /* 83 | * start these browsers 84 | * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 85 | */ 86 | browsers: [ 87 | // 'Chrome', 88 | // 'PhantomJS', 89 | 'Firefox' 90 | ], 91 | 92 | /* 93 | * Continuous Integration mode 94 | * if true, Karma captures browsers, runs the tests and exits 95 | */ 96 | singleRun: true 97 | }); 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /.config/webpack.common.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | var helpers = require('./helpers'); 6 | 7 | module.exports = { 8 | entry: { 9 | 'polyfills': helpers.root('demo', 'polyfills.ts'), 10 | 'vendor': helpers.root('demo', 'vendor.ts'), 11 | 'app': helpers.root('demo', 'index.ts') 12 | }, 13 | 14 | resolve: { 15 | extensions: ['.ts', '.js'] 16 | }, 17 | 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.ts$/, 22 | loaders: [ 23 | { 24 | loader: 'awesome-typescript-loader', 25 | options: { configFileName: helpers.root('tsconfig.json') } 26 | } , 'angular2-template-loader' 27 | ] 28 | }, 29 | { 30 | test: /\.html$/, 31 | loader: 'html-loader' 32 | }, 33 | { 34 | test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/, 35 | loader: 'file-loader?name=assets/[name].[hash].[ext]' 36 | }, 37 | { 38 | test: /\.css$/, 39 | loader: ExtractTextPlugin.extract({ fallbackLoader: 'style-loader', loader: 'css-loader?sourceMap' }) 40 | } 41 | ] 42 | }, 43 | 44 | plugins: [ 45 | // Workaround for angular/angular#11580 46 | new webpack.ContextReplacementPlugin( 47 | // The (\\|\/) piece accounts for path separators in *nix and Windows 48 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, 49 | helpers.root('./demo'), // location of your src 50 | {} // a map of your routes 51 | ), 52 | 53 | new webpack.optimize.CommonsChunkPlugin({ 54 | name: ['app', 'vendor', 'polyfills'] 55 | }), 56 | 57 | new HtmlWebpackPlugin({ 58 | template: helpers.root('demo', 'index.html') 59 | }), 60 | 61 | new CopyWebpackPlugin([ 62 | { from: helpers.root('demo', 'assets'), to: 'assets' }, 63 | ] 64 | ) 65 | ] 66 | }; 67 | -------------------------------------------------------------------------------- /.config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | var webpackMerge = require('webpack-merge'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | var commonConfig = require('./webpack.common.js'); 4 | var helpers = require('./helpers'); 5 | 6 | module.exports = webpackMerge(commonConfig, { 7 | devtool: 'cheap-module-eval-source-map', 8 | 9 | output: { 10 | path: helpers.root('dist'), 11 | publicPath: 'http://localhost:8080/', 12 | filename: '[name].js', 13 | chunkFilename: '[id].chunk.js' 14 | }, 15 | 16 | plugins: [ 17 | new ExtractTextPlugin('[name].css') 18 | ], 19 | 20 | devServer: { 21 | historyApiFallback: true, 22 | stats: 'minimal' 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /.config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var webpackMerge = require('webpack-merge'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var commonConfig = require('./webpack.common.js'); 5 | var helpers = require('./helpers'); 6 | 7 | const ENV = process.env.NODE_ENV = process.env.ENV = 'production'; 8 | 9 | module.exports = webpackMerge(commonConfig, { 10 | devtool: 'source-map', 11 | 12 | output: { 13 | path: helpers.root('dist'), 14 | publicPath: '/', 15 | filename: '[name].[hash].js', 16 | chunkFilename: '[id].[hash].chunk.js' 17 | }, 18 | 19 | plugins: [ 20 | new webpack.NoEmitOnErrorsPlugin(), 21 | new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618 22 | mangle: { 23 | keep_fnames: true 24 | } 25 | }), 26 | new ExtractTextPlugin('[name].[hash].css'), 27 | new webpack.DefinePlugin({ 28 | 'process.env': { 29 | 'ENV': JSON.stringify(ENV) 30 | } 31 | }), 32 | new webpack.LoaderOptionsPlugin({ 33 | htmlLoader: { 34 | minimize: false // workaround for ng2 35 | } 36 | }) 37 | ] 38 | }); 39 | -------------------------------------------------------------------------------- /.config/webpack.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: @AngularClass 3 | */ 4 | 5 | const helpers = require('./helpers'); 6 | 7 | /** 8 | * Webpack Plugins 9 | */ 10 | const ProvidePlugin = require('webpack/lib/ProvidePlugin'); 11 | const DefinePlugin = require('webpack/lib/DefinePlugin'); 12 | const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'); 13 | const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin'); 14 | 15 | /** 16 | * Webpack Constants 17 | */ 18 | const ENV = process.env.ENV = process.env.NODE_ENV = 'test'; 19 | 20 | /** 21 | * Webpack configuration 22 | * 23 | * See: http://webpack.github.io/docs/configuration.html#cli 24 | */ 25 | module.exports = function (options) { 26 | return { 27 | 28 | /** 29 | * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack 30 | * 31 | * Do not change, leave as is or it wont work. 32 | * See: https://github.com/webpack/karma-webpack#source-maps 33 | */ 34 | devtool: 'inline-source-map', 35 | 36 | /** 37 | * Options affecting the resolving of modules. 38 | * 39 | * See: http://webpack.github.io/docs/configuration.html#resolve 40 | */ 41 | resolve: { 42 | 43 | /** 44 | * An array of extensions that should be used to resolve modules. 45 | * 46 | * See: http://webpack.github.io/docs/configuration.html#resolve-extensions 47 | */ 48 | extensions: ['.ts', '.js'], 49 | 50 | /** 51 | * Make sure root is src 52 | */ 53 | modules: ['node_modules'] 54 | 55 | }, 56 | 57 | /** 58 | * Options affecting the normal modules. 59 | * 60 | * See: http://webpack.github.io/docs/configuration.html#module 61 | * 62 | * 'use:' revered back to 'loader:' as a temp. workaround for #1188 63 | * See: https://github.com/AngularClass/angular2-webpack-starter/issues/1188#issuecomment-262872034 64 | */ 65 | module: { 66 | 67 | rules: [ 68 | 69 | /** 70 | * Source map loader support for *.js files 71 | * Extracts SourceMaps for source files that as added as sourceMappingURL comment. 72 | * 73 | * See: https://github.com/webpack/source-map-loader 74 | */ 75 | { 76 | enforce: 'pre', 77 | test: /\.js$/, 78 | loader: 'source-map-loader', 79 | exclude: [ 80 | // these packages have problems with their sourcemaps 81 | helpers.root('node_modules/rxjs'), 82 | helpers.root('node_modules/@angular') 83 | ] 84 | }, 85 | 86 | /** 87 | * Typescript loader support for .ts and Angular 2 async routes via .async.ts 88 | * 89 | * See: https://github.com/s-panferov/awesome-typescript-loader 90 | */ 91 | { 92 | test: /\.ts$/, 93 | use: [ 94 | { 95 | loader: 'awesome-typescript-loader', 96 | query: { 97 | // use inline sourcemaps for "karma-remap-coverage" reporter 98 | sourceMap: false, 99 | inlineSourceMap: true, 100 | compilerOptions: { 101 | 102 | // Remove TypeScript helpers to be injected 103 | // below by DefinePlugin 104 | removeComments: true 105 | 106 | } 107 | }, 108 | }, 109 | 'angular2-template-loader' 110 | ], 111 | exclude: [/\.e2e\.ts$/] 112 | }, 113 | 114 | 115 | /** 116 | * Raw loader support for *.css files 117 | * Returns file content as string 118 | * 119 | * See: https://github.com/webpack/raw-loader 120 | */ 121 | { 122 | test: /\.css$/, 123 | loader: ['to-string-loader', 'css-loader'], 124 | exclude: [helpers.root('demo/index.html')] 125 | }, 126 | 127 | /** 128 | * Raw loader support for *.html 129 | * Returns file content as string 130 | * 131 | * See: https://github.com/webpack/raw-loader 132 | */ 133 | { 134 | test: /\.html$/, 135 | loader: 'raw-loader', 136 | exclude: [helpers.root('demo/index.html')] 137 | }, 138 | 139 | /** 140 | * Instruments JS files with Istanbul for subsequent code coverage reporting. 141 | * Instrument only testing sources. 142 | * 143 | * See: https://github.com/deepsweet/istanbul-instrumenter-loader 144 | */ 145 | { 146 | enforce: 'post', 147 | test: /\.(js|ts)$/, 148 | loader: 'istanbul-instrumenter-loader', 149 | include: helpers.root('demo'), 150 | exclude: [ 151 | /\.(e2e|spec)\.ts$/, 152 | /node_modules/ 153 | ] 154 | } 155 | 156 | ] 157 | }, 158 | 159 | /** 160 | * Add additional plugins to the compiler. 161 | * 162 | * See: http://webpack.github.io/docs/configuration.html#plugins 163 | */ 164 | plugins: [ 165 | 166 | /** 167 | * Plugin: DefinePlugin 168 | * Description: Define free variables. 169 | * Useful for having development builds with debug logging or adding global constants. 170 | * 171 | * Environment helpers 172 | * 173 | * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin 174 | */ 175 | // NOTE: when adding more properties make sure you include them in custom-typings.d.ts 176 | new DefinePlugin({ 177 | 'ENV': JSON.stringify(ENV), 178 | 'HMR': false, 179 | 'process.env': { 180 | 'ENV': JSON.stringify(ENV), 181 | 'NODE_ENV': JSON.stringify(ENV), 182 | 'HMR': false, 183 | } 184 | }), 185 | 186 | /** 187 | * Plugin: ContextReplacementPlugin 188 | * Description: Provides context to Angular's use of System.import 189 | * 190 | * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin 191 | * See: https://github.com/angular/angular/issues/11580 192 | */ 193 | new ContextReplacementPlugin( 194 | // The (\\|\/) piece accounts for path separators in *nix and Windows 195 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, 196 | helpers.root('demo'), // location of your src 197 | { 198 | // your Angular Async Route paths relative to this root directory 199 | } 200 | ), 201 | 202 | /** 203 | * Plugin LoaderOptionsPlugin (experimental) 204 | * 205 | * See: https://gist.github.com/sokra/27b24881210b56bbaff7 206 | */ 207 | new LoaderOptionsPlugin({ 208 | debug: false, 209 | options: { 210 | // legacy options go here 211 | } 212 | }), 213 | 214 | ], 215 | 216 | /** 217 | * Disable performance hints 218 | * 219 | * See: https://github.com/a-tarasyuk/rr-boilerplate/blob/master/webpack/dev.config.babel.js#L41 220 | */ 221 | performance: { 222 | hints: false 223 | }, 224 | 225 | /** 226 | * Include polyfills or mocks for various node stuff 227 | * Description: Node configuration 228 | * 229 | * See: https://webpack.github.io/docs/configuration.html#node 230 | */ 231 | node: { 232 | global: true, 233 | process: false, 234 | crypto: 'empty', 235 | module: false, 236 | clearImmediate: false, 237 | setImmediate: false 238 | } 239 | 240 | }; 241 | }; -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | "rules": { 7 | "semi": 2 8 | }, 9 | "env": { 10 | "node": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | /node_modules 4 | npm-debug.log 5 | 6 | # type script artifacts 7 | /typings 8 | 9 | # WebStorm 10 | .idea 11 | .vscode 12 | 13 | # ignore build and dist for now 14 | /bundles 15 | /demo-build 16 | /dist 17 | /coverage 18 | /ts 19 | /doc 20 | 21 | # ignore inline compiling 22 | /demo/**/*.js 23 | /demo/**/*.js.map 24 | /demo/**/*.d.ts 25 | !/demo/custom-typings.d.ts 26 | /components/**/*.js 27 | /components/**/*.js.map 28 | /components/**/*.d.ts 29 | ng2-vis.js 30 | ng2-vis.d.ts 31 | ng2-vis.metadata.json 32 | ng2-vis.ngfactory.ts 33 | ng2-vis.js.map 34 | /logs 35 | 36 | # AoT generated files 37 | factories 38 | /**/*.metadata.json 39 | /**/*.ngfactory.ts -------------------------------------------------------------------------------- /.ng2-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var pkg = require('./package.json'); 3 | 4 | module.exports = { 5 | // metadata 6 | title: pkg.description, 7 | baseUrl: '/', 8 | // root folder name 9 | src: 'demo', 10 | dist: 'demo-build', 11 | htmlIndexes: ['index.html'], 12 | // karma bundle src 13 | spec: './spec-bundle.js', 14 | // webpack entry 15 | entry: { 16 | polyfills: './demo/polyfills.ts', 17 | vendor: './demo/vendor.ts', 18 | main: './demo/index.ts' 19 | }, 20 | commonChunks: { 21 | name: ['polyfills', 'vendor'].reverse() 22 | }, 23 | // webpack alias 24 | alias: {}, 25 | copy: [ 26 | {from: 'demo/favicon.ico', to: 'favicon.ico'}, 27 | {from: 'demo/assets', to: 'assets'} 28 | ] 29 | }; 30 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | .tsdrc 30 | 31 | #IntelliJ configuration files 32 | .idea 33 | 34 | dist 35 | dev 36 | docs 37 | test 38 | tmp 39 | 40 | Thumbs.db 41 | .DS_Store 42 | 43 | *.ts 44 | !*.d.ts 45 | 46 | src/app/example* 47 | src/public 48 | typings 49 | *_spec.* 50 | CONTRIBUTING.md 51 | gulpfile.ts 52 | favicon.ico 53 | karma-shim.js 54 | karma.conf.js 55 | make.js 56 | protractor.conf.js 57 | test-main.js 58 | tsconfig.json 59 | tslint.json 60 | typedoc.json 61 | typings.json 62 | webpack.config.js 63 | *.yml 64 | .jshintrc 65 | .editorconfig 66 | 67 | /factories 68 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | 5 | before_install: 6 | - npm i -g npm@latest 7 | - "export DISPLAY=:99.0" 8 | - "sh -e /etc/init.d/xvfb start" 9 | 10 | script: 11 | - npm test 12 | 13 | after_success: 14 | - ./node_modules/.bin/codecov -f coverage/coverage-final.json 15 | 16 | addons: 17 | firefox: "42.0" 18 | apt: 19 | sources: 20 | - ubuntu-toolchain-r-test 21 | # required by node-gyp to build some packages 22 | packages: 23 | - g++-4.8 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [0.0.6](https://github.com/seveves/ng2-vis/compare/v0.0.5...v0.0.6) (2016-12-13) 3 | * Using @types/vis now 4 | * Updated to webpack2 5 | * Updated to angular 2.4.10 6 | * Fixed build and test configuration 7 | * Fixed problems with AOT compilation #26 8 | 9 | 10 | ## [0.0.5](https://github.com/seveves/ng2-vis/compare/v0.0.4...v0.0.5) (2016-12-13) 11 | 12 | 13 | ## [0.0.4](https://github.com/seveves/ng2-vis/compare/v0.0.3...v0.0.4) (2016-12-04) 14 | * Updated to latest visjs version 4.17.0 15 | 16 | 17 | ## [0.0.3](https://github.com/seveves/ng2-vis/compare/v0.0.3-develop...v0.0.3) (2016-11-29) 18 | 19 | 20 | ## [0.0.2](https://github.com/seveves/ng2-vis/compare/v0.0.1-6...v0.0.2) (2016-11-08) 21 | 22 | 23 | ## [0.0.1-6](https://github.com/seveves/ng2-vis/compare/v0.0.1-5...v0.0.1-6) (2016-11-05) 24 | 25 | 26 | ## [0.0.3-develop](https://github.com/seveves/ng2-vis/compare/v0.0.1-5...v0.0.3-develop) (2016-11-18) 27 | 28 | 29 | ## [0.0.2](https://github.com/seveves/ng2-vis/compare/v0.0.1-6...v0.0.2) (2016-11-08) 30 | * added timeline directive and service 31 | * added basic timeline example to demo site 32 | 33 | 34 | ## [0.0.1-6](https://github.com/seveves/ng2-vis/compare/v0.0.1-5...v0.0.1-6) (2016-11-05) 35 | 36 | 37 | ## [0.0.1-5](https://github.com/seveves/ng2-vis/compare/v0.0.1-4...v0.0.1-5) (2016-11-05) 38 | 39 | 40 | ## [0.0.1-4](https://github.com/seveves/ng2-vis/compare/v0.0.1-3...v0.0.1-4) (2016-11-05) 41 | 42 | 43 | ## [0.0.1-3](https://github.com/seveves/ng2-vis/compare/v0.0.1-2...v0.0.1-3) (2016-11-04) 44 | 45 | 46 | ## [0.0.1-2](https://github.com/seveves/ng2-vis/compare/v0.0.1-1...v0.0.1-2) (2016-11-04) 47 | 48 | 49 | ## 0.0.1-1 (2016-11-04) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Severin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ng2-vis 2 | [![Build Status](https://travis-ci.org/seveves/ng2-vis.svg?branch=develop)](https://travis-ci.org/seveves/ng2-vis) 3 | [![npm version](https://badge.fury.io/js/ng2-vis.svg)](https://badge.fury.io/js/ng2-vis) 4 | [![codecov.io](https://codecov.io/github/seveves/ng2-vis/coverage.svg?branch=master)](https://codecov.io/gh/seveves/ng2-vis?branch=master) 5 | [![David](https://img.shields.io/david/seveves/ng2-vis.svg)]() 6 | [![David](https://img.shields.io/david/dev/seveves/ng2-vis.svg)]() 7 | [![David](https://img.shields.io/david/peer/seveves/ng2-vis.svg)]() 8 | 9 | An angular 2 vis.js project (in a very early development stage) 10 | 11 | ## Demo 12 | * [Demo Page](https://seveves.github.io/ng2-vis) 13 | 14 | ## Work in progress: 15 | Timeline and Network components are implemented and using DataSets for two-way binding. 16 | Currently they lack unit tests and the demo page is only showing basic examples but this will be fixed till the first minor release. 17 | I am trying to add the other components as soon as possible in the following order: 18 | * Graph2D 19 | * Graph3D 20 | 21 | Contributions of any kind are very welcome! Just check the issues and see what I am currently working on. 22 | 23 | ### Misc 24 | * testing npm package 25 | * writing more unit tests 26 | 27 | ## Credits 28 | * thanks to [almende](https://github.com/almende) for [vis.js](http://visjs.org/) 29 | * thanks to [MichaelBitard](https://github.com/agileek/typings-vis) for initiating the vis.js type definitions. 30 | * thanks to [valor-software](https://github.com/valor-software) for their nice examples on how to setup, build and publish a angular2 module. 31 | -------------------------------------------------------------------------------- /components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './timeline/index'; 2 | export * from './network/index'; 3 | -------------------------------------------------------------------------------- /components/network/index.ts: -------------------------------------------------------------------------------- 1 | import * as Vis from 'vis'; 2 | 3 | import { VisDataSetOptions, VisDataSetQueueOptions, VisId } from '../timeline/index'; 4 | 5 | export { VisId } from '../timeline/index'; 6 | export type VisNetworkEvents = Vis.NetworkEvents; 7 | 8 | export interface VisClusterOptions extends Vis.ClusterOptions {} 9 | export interface VisOpenClusterOptions extends Vis.OpenClusterOptions {} 10 | export interface VisNetworkData extends Vis.Data {} 11 | export interface VisNode extends Vis.Node { title?: string; } 12 | export interface VisEdge extends Vis.Edge {} 13 | export interface VisNodeSelectionOptions extends Vis.DataSelectionOptions {} 14 | export interface VisEdgeSelectionOptions extends Vis.DataSelectionOptions {} 15 | export interface VisFitOptions extends Vis.FitOptions {} 16 | export interface VisNetworkOptions extends Vis.Options {} 17 | export interface VisEdgeOptions extends Vis.EdgeOptions {} 18 | export class VisNetwork extends Vis.Network {} 19 | export interface VisNodeOptions extends Vis.NodeOptions {} 20 | export interface VisPosition extends Vis.Position {} 21 | 22 | export class VisNodes extends Vis.DataSet { 23 | public constructor(data?: VisNode[], options?: VisDataSetOptions) { 24 | super(data, options); 25 | } 26 | 27 | public getLength(): number { 28 | return this.length; 29 | } 30 | 31 | public add(data: VisNode | VisNode[], senderId?: VisId): VisId[] { 32 | return super.add(data, senderId); 33 | } 34 | 35 | public clear(senderId?: VisId): VisId[] { 36 | return super.clear(senderId); 37 | } 38 | 39 | public distinct(field: string): any[] { 40 | return super.distinct(field); 41 | } 42 | 43 | public flush(): void { 44 | super.flush(); 45 | } 46 | 47 | public forEach(callback: (item: VisNode, id: VisId) => void, options?: VisNodeSelectionOptions): void { 48 | super.forEach(callback, options); 49 | } 50 | 51 | public getAll(options?: VisNodeSelectionOptions): VisNode[] { 52 | return super.get(options); 53 | } 54 | 55 | public getById(id: VisId, options?: VisNodeSelectionOptions): VisNode { 56 | return super.get(id, options); 57 | } 58 | 59 | public getByIds(ids: VisId[], options?: VisNodeSelectionOptions): VisNode[] { 60 | return super.get(ids, options); 61 | } 62 | 63 | public getDataSet(): VisNodes { 64 | return super.getDataSet() as VisNodes; 65 | } 66 | 67 | public getIds(options?: VisNodeSelectionOptions): VisId[] { 68 | return super.getIds(options); 69 | } 70 | 71 | public map(callback: (item: VisNode, id: VisId) => any, options?: VisNodeSelectionOptions): any[] { 72 | return super.map(callback, options); 73 | } 74 | 75 | public max(field: string): VisNode { 76 | return super.max(field); 77 | } 78 | 79 | public min(field: string): VisNode { 80 | return super.min(field); 81 | } 82 | 83 | public on(event: string, callback: (event: string, properties: any, senderId: VisId) => void): void { 84 | super.on(event, callback); 85 | } 86 | 87 | public off(event: string, callback: (event: string, properties: any, senderId: VisId) => void): void { 88 | super.off(event, callback); 89 | } 90 | 91 | public removeItems(ids: VisId[], senderId?: VisId): VisId[] { 92 | return super.remove(ids, senderId); 93 | } 94 | 95 | public setOptions(options?: VisDataSetQueueOptions): void { 96 | super.setOptions(options); 97 | } 98 | 99 | public update(data: VisNode[], senderId?: VisId): VisId[] { 100 | return super.update(data, senderId); 101 | } 102 | } 103 | 104 | export class VisEdges extends Vis.DataSet { 105 | public constructor(data?: VisEdge[], options?: VisDataSetOptions) { 106 | super(data, options); 107 | } 108 | 109 | public getLength(): number { 110 | return this.length; 111 | } 112 | 113 | public add(data: VisEdge | VisEdge[], senderId?: VisId): VisId[] { 114 | return super.add(data, senderId); 115 | } 116 | 117 | public clear(senderId?: VisId): VisId[] { 118 | return super.clear(senderId); 119 | } 120 | 121 | public distinct(field: string): any[] { 122 | return super.distinct(field); 123 | } 124 | 125 | public flush(): void { 126 | super.flush(); 127 | } 128 | 129 | public forEach(callback: (item: VisEdge, id: VisId) => void, options?: VisEdgeSelectionOptions): void { 130 | super.forEach(callback, options); 131 | } 132 | 133 | public getAll(options?: VisEdgeSelectionOptions): VisEdge[] { 134 | return super.get(options); 135 | } 136 | 137 | public getById(id: VisId, options?: VisEdgeSelectionOptions): VisEdge { 138 | return super.get(id, options); 139 | } 140 | 141 | public getByIds(ids: VisId[], options?: VisEdgeSelectionOptions): VisEdge[] { 142 | return super.get(ids, options); 143 | } 144 | 145 | public getDataSet(): VisEdges { 146 | return super.getDataSet() as VisEdges; 147 | } 148 | 149 | public getIds(options?: VisEdgeSelectionOptions): VisId[] { 150 | return super.getIds(options); 151 | } 152 | 153 | public map(callback: (item: VisEdge, id: VisId) => any, options?: VisEdgeSelectionOptions): any[] { 154 | return super.map(callback, options); 155 | } 156 | 157 | public max(field: string): VisEdge { 158 | return super.max(field); 159 | } 160 | 161 | public min(field: string): VisEdge { 162 | return super.min(field); 163 | } 164 | 165 | public on(event: string, callback: (event: string, properties: any, senderId: VisId) => void): void { 166 | super.on(event, callback); 167 | } 168 | 169 | public off(event: string, callback: (event: string, properties: any, senderId: VisId) => void): void { 170 | super.off(event, callback); 171 | } 172 | 173 | public removeItems(ids: VisId[], senderId?: VisId): VisId[] { 174 | return super.remove(ids, senderId); 175 | } 176 | 177 | public setOptions(options?: VisDataSetQueueOptions): void { 178 | super.setOptions(options); 179 | } 180 | 181 | public update(data: VisEdge[], senderId?: VisId): VisId[] { 182 | return super.update(data, senderId); 183 | } 184 | } 185 | 186 | export * from './vis-network.directive'; 187 | export * from './vis-network.service'; 188 | -------------------------------------------------------------------------------- /components/network/vis-network.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, 4 | EventEmitter, 5 | Input, 6 | OnChanges, 7 | OnDestroy, 8 | OnInit, 9 | Output, 10 | SimpleChange } from '@angular/core'; 11 | 12 | import { VisNetworkService } from './vis-network.service'; 13 | 14 | import { 15 | VisNetworkData, 16 | VisNetworkOptions, 17 | } from './index'; 18 | 19 | /** 20 | * Use this directive with a div container to show network data. 21 | * 22 | * @export 23 | * @class VisNetworkDirective 24 | * @implements {OnInit} 25 | * @implements {OnDestroy} 26 | * @implements {OnChanges} 27 | */ 28 | @Directive({ 29 | selector: '[visNetwork]', 30 | }) 31 | export class VisNetworkDirective implements OnInit, OnDestroy, OnChanges { 32 | 33 | /** 34 | * The name or identifier of the network (must be unique in your application). 35 | * This property is used once on init and must not be changed. 36 | * 37 | * @type {string} 38 | * @memberOf VisNetworkDirective 39 | */ 40 | @Input('visNetwork') 41 | public visNetwork: string; 42 | 43 | /** 44 | * The data that will be used to create the network. 45 | * Changes to the nodes or edges property won't be detected but 46 | * changes to the reference of this object. 47 | * Changes lead to a call to setData of this network instance. 48 | * 49 | * @type {VisNetworkData} 50 | * @memberOf VisNetworkDirective 51 | */ 52 | @Input() 53 | public visNetworkData: VisNetworkData; 54 | 55 | /** 56 | * The options that will be used with this network instance. 57 | * Only reference changes to the whole options object will be detected 58 | * but not changes to properties. 59 | * Changes lead to a call to setOptions of the network instance. 60 | * 61 | * @type {VisNetworkOptions} 62 | * @memberOf VisNetworkDirective 63 | */ 64 | @Input() 65 | public visNetworkOptions: VisNetworkOptions; 66 | 67 | /** 68 | * This event will be raised when the network is initialized. 69 | * At this point of time the network is successfully registered 70 | * with the VisNetworkService and you can register to events. 71 | * The event data is the name of the network as a string. 72 | * 73 | * @type {EventEmitter} 74 | * @memberOf VisNetworkDirective 75 | */ 76 | @Output() 77 | public initialized: EventEmitter = new EventEmitter(); 78 | 79 | private visNetworkContainer: any; 80 | private isInitialized: boolean = false; 81 | 82 | /** 83 | * Creates an instance of VisNetworkDirective. 84 | * 85 | * @param {ElementRef} elementRef The HTML element reference. 86 | * @param {VisNetworkService} visNetworkService The VisNetworkService. 87 | * 88 | * @memberOf VisNetworkDirective 89 | */ 90 | public constructor(private elementRef: ElementRef, private visNetworkService: VisNetworkService) { 91 | this.visNetworkContainer = elementRef.nativeElement; 92 | } 93 | 94 | /** 95 | * Create the network when at least visNetwork and visNetworkData 96 | * are defined. 97 | * 98 | * @memberOf VisNetworkDirective 99 | */ 100 | public ngOnInit(): void { 101 | if (!this.isInitialized && this.visNetwork && this.visNetworkData) { 102 | this.createNetwork(); 103 | } 104 | } 105 | 106 | /** 107 | * Update the network data or options on reference changes to 108 | * the visNetworkData or visNetworkOptions properties. 109 | * 110 | * @param {{[propName: string]: SimpleChange}} changes 111 | * 112 | * @memberOf VisNetworkDirective 113 | */ 114 | public ngOnChanges(changes: {[propName: string]: SimpleChange}): void { 115 | 116 | if (!this.isInitialized && this.visNetwork && this.visNetworkData) { 117 | this.createNetwork(); 118 | } 119 | 120 | for (const propertyName in changes) { 121 | if (changes.hasOwnProperty(propertyName)) { 122 | const change = changes[propertyName]; 123 | if (!change.isFirstChange()) { 124 | if (propertyName === 'visNetworkData') { 125 | this.visNetworkService.setData(this.visNetwork, changes[propertyName].currentValue); 126 | } 127 | if (propertyName === 'visNetworkOptions') { 128 | this.visNetworkService.setOptions(this.visNetwork, changes[propertyName].currentValue); 129 | } 130 | } 131 | } 132 | } 133 | } 134 | 135 | /** 136 | * Calls the destroy function for this network instance. 137 | * 138 | * @memberOf VisNetworkDirective 139 | */ 140 | public ngOnDestroy(): void { 141 | this.isInitialized = false; 142 | this.visNetworkService.destroy(this.visNetwork); 143 | } 144 | 145 | private createNetwork(): void { 146 | this.visNetworkService.create( 147 | this.visNetwork, 148 | this.visNetworkContainer, 149 | this.visNetworkData, 150 | this.visNetworkOptions); 151 | this.isInitialized = true; 152 | this.initialized.emit(this.visNetwork); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /components/network/vis-network.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { VisNetworkService } from './vis-network.service'; 2 | 3 | describe('VisNetworkService Tests', () => { 4 | 5 | let visNetworkService: VisNetworkService; 6 | 7 | beforeEach(() => { 8 | visNetworkService = new VisNetworkService(); 9 | }); 10 | 11 | it('returns undefined when network id is unknown', () => { 12 | const selection = visNetworkService.getSelection('unknown'); 13 | expect(selection).toBeUndefined(); 14 | const selectedNodes = visNetworkService.getSelectedNodes('unknown'); 15 | expect(selectedNodes).toBeUndefined(); 16 | const selectedEdges = visNetworkService.getSelectedEdges('unknown'); 17 | expect(selectedEdges).toBeUndefined(); 18 | }); 19 | 20 | it('throws no error when the network does not exist', () => { 21 | visNetworkService.destroy('unknown'); 22 | }); 23 | 24 | it('throws error when network already exists', () => { 25 | const dummyElement = document.createElement('div'); 26 | visNetworkService.create('knownNetwork', dummyElement, { nodes: [], edges: [] }); 27 | expect(() => visNetworkService.create('knownNetwork', dummyElement, { nodes: [], edges: [] })).toThrowError(); 28 | }); 29 | 30 | it('returns false when network does not exist', () => { 31 | expect(visNetworkService.on('unknown', 'click')).toBe(false); 32 | expect(visNetworkService.once('unknown', 'click')).toBe(false); 33 | visNetworkService.off('unknown', 'click'); 34 | expect(visNetworkService.isCluster('unknown', 'nodeId')).toBe(false); 35 | }); 36 | 37 | it('throws error when network does not exist', () => { 38 | expect(() => visNetworkService.setData('unknown', { nodes: [], edges: [] })).toThrowError(); 39 | expect(() => visNetworkService.setOptions('unknown', { nodes: [], edges: [] })).toThrowError(); 40 | expect(() => visNetworkService.selectNodes('unknown', [])).toThrowError(); 41 | expect(() => visNetworkService.selectNodes('unknown', [], false)).toThrowError(); 42 | expect(() => visNetworkService.unselectAll('unknown')).toThrowError(); 43 | expect(() => visNetworkService.fit('unknown')).toThrowError(); 44 | expect(() => visNetworkService.fit('unknown', { animation: true })).toThrowError(); 45 | expect(() => visNetworkService.redraw('unknown')).toThrowError(); 46 | expect(() => visNetworkService.enableEditMode('unknown')).toThrowError(); 47 | expect(() => visNetworkService.addEdgeMode('unknown')).toThrowError(); 48 | expect(() => visNetworkService.disableEditMode('unknown')).toThrowError(); 49 | expect(() => visNetworkService.deleteSelected('unknown')).toThrowError(); 50 | expect(() => visNetworkService.cluster('unknown')).toThrowError(); 51 | expect(() => visNetworkService.cluster('unknown', {})).toThrowError(); 52 | expect(() => visNetworkService.clusterByConnection('unknown', 42, {})).toThrowError(); 53 | expect(() => visNetworkService.clusterByConnection('unknown', 42)).toThrowError(); 54 | expect(() => visNetworkService.clusterByHubsize('unknown', 42, {})).toThrowError(); 55 | expect(() => visNetworkService.clusterByHubsize('unknown', 42)).toThrowError(); 56 | expect(() => visNetworkService.clusterOutliers('unknown', {})).toThrowError(); 57 | expect(() => visNetworkService.clusterOutliers('unknown')).toThrowError(); 58 | expect(() => visNetworkService.findNode('unknown', 42)).toThrowError(); 59 | expect(() => visNetworkService.getClusteredEdges('unknown', 42)).toThrowError(); 60 | expect(() => visNetworkService.getBaseEdge('unknown', 42)).toThrowError(); 61 | expect(() => visNetworkService.updateEdge('unknown', 42)).toThrowError(); 62 | expect(() => visNetworkService.updateEdge('unknown', 42)).toThrowError(); 63 | expect(() => visNetworkService.updateClusteredNode('unknown', 42)).toThrowError(); 64 | expect(() => visNetworkService.updateClusteredNode('unknown', 42, {})).toThrowError(); 65 | expect(() => visNetworkService.getNodesInCluster('unknown', 42)).toThrowError(); 66 | expect(() => visNetworkService.openCluster('unknown', 'nodeId', { releaseFunction: null })).toThrowError(); 67 | expect(() => visNetworkService.openCluster('unknown', 'nodeId')).toThrowError(); 68 | expect(() => visNetworkService.canvasToDOM('unknown', { x: 1, y: 1 })).toThrowError(); 69 | expect(() => visNetworkService.DOMtoCanvas('unknown', { x: 1, y: 1 })).toThrowError(); 70 | }); 71 | 72 | it('returns -1 when network does not exist', () => { 73 | expect(visNetworkService.getSeed('unknown')).toBe(-1); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /components/network/vis-network.service.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter, Injectable } from '@angular/core'; 2 | 3 | import { 4 | VisClusterOptions, 5 | VisEdgeOptions, 6 | VisFitOptions, 7 | VisId, 8 | VisNetwork, 9 | VisNetworkData, 10 | VisNetworkEvents, 11 | VisNetworkOptions, 12 | VisNodeOptions, 13 | VisOpenClusterOptions, 14 | VisPosition } from './index'; 15 | 16 | /** 17 | * A service to create, manage and control VisNetwork instances. 18 | * 19 | * @export 20 | * @class VisNetworkService 21 | */ 22 | @Injectable() 23 | export class VisNetworkService { 24 | 25 | /** 26 | * Fired when the user clicks the mouse or taps on a touchscreen device. 27 | * 28 | * @type {EventEmitter} 29 | * @memberOf VisNetworkService 30 | */ 31 | public click: EventEmitter = new EventEmitter(); 32 | 33 | /** 34 | * Fired when the user double clicks the mouse or double taps on a touchscreen device. 35 | * Since a double click is in fact 2 clicks, 2 click events are fired, followed by a double click event. 36 | * If you do not want to use the click events if a double click event is fired, 37 | * just check the time between click events before processing them. 38 | * 39 | * @type {EventEmitter} 40 | * @memberOf VisNetworkService 41 | */ 42 | public doubleClick: EventEmitter = new EventEmitter(); 43 | 44 | /** 45 | * Fired when the user click on the canvas with the right mouse button. 46 | * The right mouse button does not select by default. 47 | * You can use the method getNodeAt to select the node if you want. 48 | * 49 | * @type {EventEmitter} 50 | * @memberOf VisNetworkService 51 | */ 52 | public oncontext: EventEmitter = new EventEmitter(); 53 | 54 | /** 55 | * Fired when the user clicks and holds the mouse or taps and holds on a touchscreen device. 56 | * A click event is also fired in this case. 57 | * 58 | * @type {EventEmitter} 59 | * @memberOf VisNetworkService 60 | */ 61 | public hold: EventEmitter = new EventEmitter(); 62 | 63 | /** 64 | * Fired after drawing on the canvas has been completed. 65 | * Can be used to draw on top of the network. 66 | * 67 | * @type {EventEmitter} 68 | * @memberOf VisNetworkService 69 | */ 70 | public release: EventEmitter = new EventEmitter(); 71 | 72 | /** 73 | * Fired when the selection has changed by user action. 74 | * This means a node or edge has been selected, added to the selection or deselected. 75 | * All select events are only triggered on click and hold. 76 | * 77 | * @type {EventEmitter} 78 | * @memberOf VisNetworkService 79 | */ 80 | public select: EventEmitter = new EventEmitter(); 81 | 82 | /** 83 | * Fired when a node has been selected by the user. 84 | * 85 | * @type {EventEmitter} 86 | * @memberOf VisNetworkService 87 | */ 88 | public selectNode: EventEmitter = new EventEmitter(); 89 | 90 | /** 91 | * Fired when a edge has been selected by the user. 92 | * 93 | * @type {EventEmitter} 94 | * @memberOf VisNetworkService 95 | */ 96 | public selectEdge: EventEmitter = new EventEmitter(); 97 | 98 | /** 99 | * Fired when a node (or nodes) has (or have) been deselected by the user. 100 | * The previous selection is the list of nodes and edges that were selected before the last user event. 101 | * 102 | * @type {EventEmitter} 103 | * @memberOf VisNetworkService 104 | */ 105 | public deselectNode: EventEmitter = new EventEmitter(); 106 | 107 | /** 108 | * Fired when a edge (or edges) has (or have) been deselected by the user. 109 | * The previous selection is the list of nodes and edges that were selected before the last user event. 110 | * 111 | * @type {EventEmitter} 112 | * @memberOf VisNetworkService 113 | */ 114 | public deselectEdge: EventEmitter = new EventEmitter(); 115 | 116 | /** 117 | * Fired when starting a drag. 118 | * 119 | * @type {EventEmitter} 120 | * @memberOf VisNetworkService 121 | */ 122 | public dragStart: EventEmitter = new EventEmitter(); 123 | 124 | /** 125 | * Fired when dragging node(s) or the view. 126 | * 127 | * @type {EventEmitter} 128 | * @memberOf VisNetworkService 129 | */ 130 | public dragging: EventEmitter = new EventEmitter(); 131 | 132 | /** 133 | * Fired when the drag has finished. 134 | * 135 | * @type {EventEmitter} 136 | * @memberOf VisNetworkService 137 | */ 138 | public dragEnd: EventEmitter = new EventEmitter(); 139 | 140 | /** 141 | * Fired if the option interaction:{hover:true} is enabled and the mouse hovers over a node. 142 | * 143 | * @type {EventEmitter} 144 | * @memberOf VisNetworkService 145 | */ 146 | public hoverNode: EventEmitter = new EventEmitter(); 147 | 148 | /** 149 | * Fired if the option interaction:{hover:true} is enabled and 150 | * the mouse moved away from a node it was hovering over before. 151 | * 152 | * @type {EventEmitter} 153 | * @memberOf VisNetworkService 154 | */ 155 | public blurNode: EventEmitter = new EventEmitter(); 156 | 157 | /** 158 | * Fired if the option interaction:{hover:true} is enabled and the mouse hovers over an edge. 159 | * 160 | * @type {EventEmitter} 161 | * @memberOf VisNetworkService 162 | */ 163 | public hoverEdge: EventEmitter = new EventEmitter(); 164 | 165 | /** 166 | * Fired if the option interaction:{hover:true} is enabled and 167 | * the mouse moved away from an edge it was hovering over before. 168 | * 169 | * @type {EventEmitter} 170 | * @memberOf VisNetworkService 171 | */ 172 | public blurEdge: EventEmitter = new EventEmitter(); 173 | 174 | /** 175 | * Fired when the user zooms in or out. 176 | * 177 | * @type {EventEmitter} 178 | * @memberOf VisNetworkService 179 | */ 180 | public zoom: EventEmitter = new EventEmitter(); 181 | 182 | /** 183 | * Fired when the popup (tooltip) is shown. 184 | * 185 | * @type {EventEmitter} 186 | * @memberOf VisNetworkService 187 | */ 188 | public showPopup: EventEmitter = new EventEmitter(); 189 | 190 | /** 191 | * Fired when the popup (tooltip) is hidden. 192 | * 193 | * @type {EventEmitter} 194 | * @memberOf VisNetworkService 195 | */ 196 | public hidePopup: EventEmitter = new EventEmitter(); 197 | 198 | /** 199 | * Fired when stabilization starts. 200 | * This is also the case when you drag a node and the physics 201 | * simulation restarts to stabilize again. 202 | * Stabilization does not neccesarily imply 'without showing'. 203 | * 204 | * @type {EventEmitter} 205 | * @memberOf VisNetworkService 206 | */ 207 | public startStabilizing: EventEmitter = new EventEmitter(); 208 | 209 | /** 210 | * Fired when a multiple of the updateInterval number of iterations is reached. 211 | * This only occurs in the 'hidden' stabilization. 212 | * 213 | * @type {EventEmitter} 214 | * @memberOf VisNetworkService 215 | */ 216 | public stabilizationProgress: EventEmitter = new EventEmitter(); 217 | 218 | /** 219 | * Fired when the 'hidden' stabilization finishes. 220 | * This does not necessarily mean the network is stabilized; 221 | * it could also mean that the amount of iterations defined in the options has been reached. 222 | * 223 | * @type {EventEmitter} 224 | * @memberOf VisNetworkService 225 | */ 226 | public stabilizationIterationsDone: EventEmitter = new EventEmitter(); 227 | 228 | /** 229 | * Fired when the 'hidden' stabilization finishes. 230 | * This does not necessarily mean the network is stabilized; 231 | * it could also mean that the amount of iterations defined in the options has been reached. 232 | * 233 | * @type {EventEmitter} 234 | * @memberOf VisNetworkService 235 | */ 236 | public stabilized: EventEmitter = new EventEmitter(); 237 | 238 | /** 239 | * Fired when the size of the canvas has been resized, 240 | * either by a redraw call when the container div has changed in size, 241 | * a setSize() call with new values or a setOptions() with new width and/or height values. 242 | * 243 | * @type {EventEmitter} 244 | * @memberOf VisNetworkService 245 | */ 246 | public resize: EventEmitter = new EventEmitter(); 247 | 248 | /** 249 | * Fired before the redrawing begins. 250 | * The simulation step has completed at this point. 251 | * Can be used to move custom elements before starting drawing the new frame. 252 | * 253 | * @type {EventEmitter} 254 | * @memberOf VisNetworkService 255 | */ 256 | public initRedraw: EventEmitter = new EventEmitter(); 257 | 258 | /** 259 | * Fired after the canvas has been cleared, scaled and translated to 260 | * the viewing position but before all edges and nodes are drawn. 261 | * Can be used to draw behind the network. 262 | * 263 | * @type {EventEmitter} 264 | * @memberOf VisNetworkService 265 | */ 266 | public beforeDrawing: EventEmitter = new EventEmitter(); 267 | 268 | /** 269 | * Fired after drawing on the canvas has been completed. 270 | * Can be used to draw on top of the network. 271 | * 272 | * @type {EventEmitter} 273 | * @memberOf VisNetworkService 274 | */ 275 | public afterDrawing: EventEmitter = new EventEmitter(); 276 | 277 | /** 278 | * Fired when an animation is finished. 279 | * 280 | * @type {EventEmitter} 281 | * @memberOf VisNetworkService 282 | */ 283 | public animationFinished: EventEmitter = new EventEmitter(); 284 | 285 | /** 286 | * Fired when a user changes any option in the configurator. 287 | * The options object can be used with the setOptions method or stringified using JSON.stringify(). 288 | * You do not have to manually put the options into the network: this is done automatically. 289 | * You can use the event to store user options in the database. 290 | * 291 | * @type {EventEmitter} 292 | * @memberOf VisNetworkService 293 | */ 294 | public configChange: EventEmitter = new EventEmitter(); 295 | 296 | private networks: {[id: string]: VisNetwork} = {}; 297 | 298 | /** 299 | * Creates a new network instance. 300 | * 301 | * @param {string} visNetwork The network name/identifier. 302 | * @param {HTMLElement} container The HTML element that contains the network view. 303 | * @param {VisNetworkData} data The initial network nodes and edges. 304 | * @param {VisNetworkOptions} [options] The network options. 305 | * 306 | * @throws {Error} Thrown when a network with the same name already exists. 307 | * 308 | * @memberOf VisNetworkService 309 | */ 310 | public create( 311 | visNetwork: string, 312 | container: HTMLElement, 313 | data: VisNetworkData, 314 | options?: VisNetworkOptions): void { 315 | if (this.networks[visNetwork]) { 316 | throw new Error(`Network with id ${visNetwork} already exists.`); 317 | } 318 | 319 | this.networks[visNetwork] = new VisNetwork(container, data, options); 320 | } 321 | 322 | /** 323 | * Remove the network from the DOM and remove all Hammer bindings and references. 324 | * 325 | * @param {string} visNetwork The network name/identifier. 326 | * 327 | * @memberOf VisNetworkService 328 | */ 329 | public destroy(visNetwork: string): void { 330 | if (this.networks[visNetwork]) { 331 | this.networks[visNetwork].destroy(); 332 | delete this.networks[visNetwork]; 333 | } 334 | } 335 | 336 | /** 337 | * Activates an event. 338 | * 339 | * @param {string} visNetwork The network name/identifier. 340 | * @param {VisNetworkEvents} eventName The event name. 341 | * @param {boolean} preventDefault Stops the default behavior of the event. 342 | * @returns {boolean} Returns true when the event was activated. 343 | * 344 | * @memberOf VisNetworkService 345 | */ 346 | public on(visNetwork: string, eventName: VisNetworkEvents, preventDefault?: boolean): boolean { 347 | if (this.networks[visNetwork]) { 348 | const that: {[index: string]: any} = this; 349 | this.networks[visNetwork].on(eventName, (params: any) => { 350 | const emitter = that[eventName] as EventEmitter; 351 | if (emitter) { 352 | emitter.emit(params ? [visNetwork].concat(params) : visNetwork); 353 | } 354 | if (preventDefault && params.event) { 355 | params.event.preventDefault(); 356 | } 357 | }); 358 | 359 | return true; 360 | } 361 | 362 | return false; 363 | } 364 | 365 | /** 366 | * Deactivates an event. 367 | * 368 | * @param {string} visNetwork The network name/identifier. 369 | * @param {VisNetworkEvents} eventName The event name. 370 | * 371 | * @memberOf VisNetworkService 372 | */ 373 | public off(visNetwork: string, eventName: VisNetworkEvents): void { 374 | if (this.networks[visNetwork]) { 375 | this.networks[visNetwork].off(eventName); 376 | } 377 | } 378 | 379 | /** 380 | * Activates an event listener only once. 381 | * After it has taken place, the event listener will be removed. 382 | * 383 | * @param {string} visNetwork The network name/identifier. 384 | * @param {VisNetworkEvents} eventName The event name. 385 | * @returns {boolean} Returns true when the event was activated. 386 | * 387 | * @memberOf VisNetworkService 388 | */ 389 | public once(visNetwork: string, eventName: VisNetworkEvents): boolean { 390 | if (this.networks[visNetwork]) { 391 | const that: {[index: string]: any} = this; 392 | this.networks[visNetwork].on(eventName, (params: any) => { 393 | const emitter = that[eventName] as EventEmitter; 394 | if (emitter) { 395 | emitter.emit(params ? [visNetwork].concat(params) : visNetwork); 396 | this.off(visNetwork, eventName); 397 | } 398 | }); 399 | 400 | return true; 401 | } 402 | 403 | return false; 404 | } 405 | 406 | /** 407 | * Override all the data in the network. 408 | * If stabilization is enabled in the physics module, 409 | * the network will stabilize again. 410 | * This method is also performed when first initializing the network. 411 | * 412 | * @param {string} visNetwork The network name/identifier. 413 | * @param {VisNetworkData} data The network data. 414 | * 415 | * @throws {Error} Thrown when the network does not exist. 416 | * 417 | * @memberOf VisNetworkService 418 | */ 419 | public setData(visNetwork: string, data: VisNetworkData): void { 420 | if (this.networks[visNetwork]) { 421 | this.networks[visNetwork].setData(data); 422 | } else { 423 | throw new Error(`Network with id ${visNetwork} not found.`); 424 | } 425 | } 426 | 427 | /** 428 | * Set the options. 429 | * 430 | * @param {string} visNetwork The network name/identifier. 431 | * @param {VisNetworkOptions} options The network options. 432 | * 433 | * @throws {Error} Thrown when the network does not exist. 434 | * 435 | * @memberOf VisNetworkService 436 | */ 437 | public setOptions(visNetwork: string, options: VisNetworkOptions): void { 438 | if (this.networks[visNetwork]) { 439 | this.networks[visNetwork].setOptions(options); 440 | } else { 441 | throw new Error(`Network with id ${visNetwork} not found.`); 442 | } 443 | } 444 | 445 | /** 446 | * Selects the nodes corresponding to the id's in the input array. 447 | * This method unselects all other objects before selecting its own objects. 448 | * Does not fire events. 449 | * 450 | * @param {string} visNetwork The network name/identifier. 451 | * @param {VisId[]} nodeIds The node ids that should be selected. 452 | * @param {boolean} [highlightEdges] If highlightEdges is true or undefined, 453 | * the neighbouring edges will also be selected. 454 | * 455 | * @throws {Error} Thrown when the network does not exist. 456 | * 457 | * @memberOf VisNetworkService 458 | */ 459 | public selectNodes(visNetwork: string, nodeIds: VisId[], highlightEdges?: boolean): void { 460 | if (this.networks[visNetwork]) { 461 | this.networks[visNetwork].selectNodes(nodeIds, highlightEdges); 462 | } else { 463 | throw new Error(`Network with id ${visNetwork} not found.`); 464 | } 465 | } 466 | 467 | /** 468 | * Returns an object with selected nodes and edges ids. 469 | * 470 | * @param {string} visNetwork The network name/identifier. 471 | * @returns {{ nodes: VisId[], edges: VisId[] }} 472 | * The selected node and edge ids or undefined when the network does not exist. 473 | * 474 | * @memberOf VisNetworkService 475 | */ 476 | public getSelection(visNetwork: string): { nodes: VisId[], edges: VisId[] } { 477 | if (this.networks[visNetwork]) { 478 | return this.networks[visNetwork].getSelection(); 479 | } 480 | return undefined; 481 | } 482 | 483 | /** 484 | * Returns an array of selected node ids. 485 | * 486 | * @param {string} visNetwork The network name/identifier. 487 | * @returns {VisId[]} The selected node ids or undefined when the network does not exist. 488 | * 489 | * @memberOf VisNetworkService 490 | */ 491 | public getSelectedNodes(visNetwork: string): VisId[] { 492 | if (this.networks[visNetwork]) { 493 | return this.networks[visNetwork].getSelectedNodes(); 494 | } 495 | return undefined; 496 | } 497 | 498 | /** 499 | * Returns an array of selected edge ids. 500 | * 501 | * @param {string} visNetwork The network name/identifier. 502 | * @returns {VisId[]} The selected edge ids or undefined when the network does not exist. 503 | * 504 | * @memberOf VisNetworkService 505 | */ 506 | public getSelectedEdges(visNetwork: string): VisId[] { 507 | if (this.networks[visNetwork]) { 508 | return this.networks[visNetwork].getSelectedEdges(); 509 | } 510 | return undefined; 511 | } 512 | 513 | /** 514 | * Unselect all objects. 515 | * Does not fire events. 516 | * 517 | * @param {string} visNetwork The network name/identifier. 518 | * 519 | * @throws {Error} Thrown when the network does not exist. 520 | * 521 | * @memberOf VisNetworkService 522 | */ 523 | public unselectAll(visNetwork: string): void { 524 | if (this.networks[visNetwork]) { 525 | this.networks[visNetwork].unselectAll(); 526 | } else { 527 | throw new Error(`Network with id ${visNetwork} not found.`); 528 | } 529 | } 530 | 531 | /** 532 | * Zooms out so all nodes fit on the canvas. 533 | * 534 | * @param {string} visNetwork The network name/identifier. 535 | * @param {VisFitOptions} [options] Options to customize. 536 | * 537 | * @throws {Error} Thrown when the network does not exist. 538 | * 539 | * @memberOf VisNetworkService 540 | */ 541 | public fit(visNetwork: string, options?: VisFitOptions): void { 542 | if (this.networks[visNetwork]) { 543 | this.networks[visNetwork].fit(options); 544 | } else { 545 | throw new Error(`Network with id ${visNetwork} not found.`); 546 | } 547 | } 548 | 549 | /** 550 | * Redraw the network. 551 | * 552 | * @param {string} visNetwork The network name/identifier. 553 | * 554 | * @throws {Error} Thrown when the network does not exist. 555 | * 556 | * @memberOf VisNetworkService 557 | */ 558 | public redraw(visNetwork: string): void { 559 | if (this.networks[visNetwork]) { 560 | this.networks[visNetwork].redraw(); 561 | } else { 562 | throw new Error(`Network with id ${visNetwork} not found.`); 563 | } 564 | } 565 | 566 | /** 567 | * Programatically enable the edit mode. 568 | * Similar effect to pressing the edit button. 569 | * 570 | * @param {string} visNetwork The network name/identifier. 571 | * 572 | * @throws {Error} Thrown when the network does not exist. 573 | * 574 | * @memberOf VisNetworkService 575 | */ 576 | public enableEditMode(visNetwork: string): void { 577 | if (this.networks[visNetwork]) { 578 | this.networks[visNetwork].enableEditMode(); 579 | } else { 580 | throw new Error(`Network with id ${visNetwork} not found.`); 581 | } 582 | } 583 | 584 | /** 585 | * Go into addEdge mode. 586 | * The explaination from addNodeMode applies here as well. 587 | * 588 | * @param {string} visNetwork The network name/identifier. 589 | * 590 | * @throws {Error} Thrown when the network does not exist. 591 | * 592 | * @memberOf VisNetworkService 593 | */ 594 | public addEdgeMode(visNetwork: string): void { 595 | if (this.networks[visNetwork]) { 596 | this.networks[visNetwork].addEdgeMode(); 597 | } else { 598 | throw new Error(`Network with id ${visNetwork} not found.`); 599 | } 600 | } 601 | 602 | /** 603 | * Programatically disable the edit mode. 604 | * Similar effect to pressing the close icon 605 | * (small cross in the corner of the toolbar). 606 | * 607 | * @param {string} visNetwork The network name/identifier. 608 | * 609 | * @throws {Error} Thrown when the network does not exist. 610 | * 611 | * @memberOf VisNetworkService 612 | */ 613 | public disableEditMode(visNetwork: string): void { 614 | if (this.networks[visNetwork]) { 615 | this.networks[visNetwork].disableEditMode(); 616 | } else { 617 | throw new Error(`Network with id ${visNetwork} not found.`); 618 | } 619 | } 620 | 621 | /** 622 | * Delete selected. 623 | * Having edit mode or manipulation enabled is not required. 624 | * 625 | * @param {string} visNetwork The network name/identifier. 626 | * 627 | * @throws {Error} Thrown when the network does not exist. 628 | * 629 | * @memberOf VisNetworkService 630 | */ 631 | public deleteSelected(visNetwork: string): void { 632 | if (this.networks[visNetwork]) { 633 | this.networks[visNetwork].deleteSelected(); 634 | } else { 635 | throw new Error(`Network with id ${visNetwork} not found.`); 636 | } 637 | } 638 | 639 | /** 640 | * Makes a cluster. 641 | * 642 | * @param {string} visNetwork The network name/identifier. 643 | * @param {VisClusterOptions} [options] The joinCondition function is presented with all nodes. 644 | * 645 | * @throws {Error} Thrown when the network does not exist. 646 | * 647 | * @memberOf VisNetworkService 648 | */ 649 | public cluster(visNetwork: string, options?: VisClusterOptions): void { 650 | if (this.networks[visNetwork]) { 651 | this.networks[visNetwork].cluster(options); 652 | } else { 653 | throw new Error(`Network with id ${visNetwork} not found.`); 654 | } 655 | } 656 | 657 | /** 658 | * This method looks at the provided node and makes a cluster of it and all it's connected nodes. 659 | * The behaviour can be customized by proving the options object. 660 | * All options of this object are explained below. 661 | * The joinCondition is only presented with the connected nodes. 662 | * 663 | * @param {string} visNetwork The network name/identifier. 664 | * @param {VisId} nodeId the id of the node 665 | * @param {VisClusterOptions} [options] the cluster options 666 | * 667 | * @memberOf VisNetworkService 668 | */ 669 | public clusterByConnection(visNetwork: string, nodeId: VisId, options?: VisClusterOptions): void { 670 | if (this.networks[visNetwork]) { 671 | this.networks[visNetwork].clusterByConnection(nodeId as any, options); 672 | } else { 673 | throw new Error(`Network with id ${visNetwork} not found.`); 674 | } 675 | } 676 | 677 | /** 678 | * This method checks all nodes in the network and those with a equal or higher 679 | * amount of edges than specified with the hubsize qualify. 680 | * If a hubsize is not defined, the hubsize will be determined as the average 681 | * value plus two standard deviations. 682 | * For all qualifying nodes, clusterByConnection is performed on each of them. 683 | * The options object is described for clusterByConnection and does the same here. 684 | * 685 | * @param {string} visNetwork The network name/identifier. 686 | * @param {number} [hubsize] optional hubsize 687 | * @param {VisClusterOptions} [options] optional cluster options 688 | * 689 | * @memberOf VisNetworkService 690 | */ 691 | public clusterByHubsize(visNetwork: string, hubsize?: number, options?: VisClusterOptions): void { 692 | if (this.networks[visNetwork]) { 693 | this.networks[visNetwork].clusterByHubsize(hubsize, options); 694 | } else { 695 | throw new Error(`Network with id ${visNetwork} not found.`); 696 | } 697 | } 698 | 699 | /** 700 | * This method will cluster all nodes with 1 edge with their respective connected node. 701 | * 702 | * @param {string} visNetwork The network name/identifier. 703 | * @param {VisClusterOptions} [options] optional cluster options 704 | * 705 | * @memberOf VisNetworkService 706 | */ 707 | public clusterOutliers(visNetwork: string, options?: VisClusterOptions): void { 708 | if (this.networks[visNetwork]) { 709 | this.networks[visNetwork].clusterOutliers(options); 710 | } else { 711 | throw new Error(`Network with id ${visNetwork} not found.`); 712 | } 713 | } 714 | 715 | /** 716 | * Nodes can be in clusters. 717 | * Clusters can also be in clusters. 718 | * This function returns an array of nodeIds showing where the node is. 719 | * 720 | * Example: 721 | * cluster 'A' contains cluster 'B', cluster 'B' contains cluster 'C', 722 | * cluster 'C' contains node 'fred'. 723 | * 724 | * network.clustering.findNode('fred') will return ['A','B','C','fred']. 725 | * 726 | * @param {string} visNetwork The network name/identifier. 727 | * @param {VisId} nodeId the node id. 728 | * @returns {VisId[]} an array of nodeIds showing where the node is 729 | * 730 | * @memberOf VisNetworkService 731 | */ 732 | public findNode(visNetwork: string, nodeId: VisId): VisId[] { 733 | if (this.networks[visNetwork]) { 734 | return this.networks[visNetwork].findNode(nodeId); 735 | } else { 736 | throw new Error(`Network with id ${visNetwork} not found.`); 737 | } 738 | } 739 | 740 | /** 741 | * Similar to findNode in that it returns all the edge ids that were 742 | * created from the provided edge during clustering. 743 | * 744 | * @param {string} visNetwork The network name/identifier. 745 | * @param {VisId} baseEdgeId the base edge id 746 | * @returns {VisId[]} an array of edgeIds 747 | * 748 | * @memberOf VisNetworkService 749 | */ 750 | public getClusteredEdges(visNetwork: string, baseEdgeId: VisId): VisId[] { 751 | if (this.networks[visNetwork]) { 752 | return this.networks[visNetwork].getClusteredEdges(baseEdgeId); 753 | } else { 754 | throw new Error(`Network with id ${visNetwork} not found.`); 755 | } 756 | } 757 | 758 | /** 759 | * When a clusteredEdgeId is available, this method will return the original 760 | * baseEdgeId provided in data.edges ie. 761 | * After clustering the 'SelectEdge' event is fired but provides only the clustered edge. 762 | * This method can then be used to return the baseEdgeId. 763 | * 764 | * @param {string} visNetwork The network name/identifier. 765 | * @param {VisId} clusteredEdgeId 766 | * @returns {VisId} 767 | * 768 | * @memberOf VisNetworkService 769 | * 770 | */ 771 | public getBaseEdge(visNetwork: string, clusteredEdgeId: VisId): VisId { 772 | if (this.networks[visNetwork]) { 773 | return this.networks[visNetwork].getBaseEdge(clusteredEdgeId); 774 | } else { 775 | throw new Error(`Network with id ${visNetwork} not found.`); 776 | } 777 | } 778 | 779 | /** 780 | * Visible edges between clustered nodes are not the same edge as the ones provided 781 | * in data.edges passed on network creation. With each layer of clustering, copies of 782 | * the edges between clusters are created and the previous edges are hidden, 783 | * until the cluster is opened. This method takes an edgeId (ie. a base edgeId from data.edges) 784 | * and applys the options to it and any edges that were created from it while clustering. 785 | * 786 | * @param {string} visNetwork The network name/identifier. 787 | * @param {VisId} startEdgeId 788 | * @param {VisEdgeOptions} [options] 789 | * 790 | * @memberOf VisNetworkService 791 | * 792 | */ 793 | public updateEdge(visNetwork: string, startEdgeId: VisId, options?: VisEdgeOptions): void { 794 | if (this.networks[visNetwork]) { 795 | this.networks[visNetwork].updateEdge(startEdgeId, options); 796 | } else { 797 | throw new Error(`Network with id ${visNetwork} not found.`); 798 | } 799 | } 800 | 801 | /** 802 | * Clustered Nodes when created are not contained in the original data.nodes 803 | * passed on network creation. This method updates the cluster node. 804 | * 805 | * @param {string} visNetwork The network name/identifier. 806 | * @param {VisId} clusteredNodeId 807 | * @param {VisNodeOptions} options 808 | * 809 | * @memberOf VisNetworkService 810 | */ 811 | public updateClusteredNode(visNetwork: string, clusteredNodeId: VisId, options?: VisNodeOptions): void { 812 | if (this.networks[visNetwork]) { 813 | this.networks[visNetwork].updateClusteredNode(clusteredNodeId, options); 814 | } else { 815 | throw new Error(`Network with id ${visNetwork} not found.`); 816 | } 817 | } 818 | 819 | /** 820 | * Returns an array of all nodeIds of the nodes that 821 | * would be released if you open the cluster. 822 | * 823 | * @param {string} visNetwork The network name/identifier. 824 | * @param {VisId} clusterNodeId the id of the cluster node 825 | * @returns {VisId[]} 826 | * 827 | * @memberOf VisNetworkService 828 | */ 829 | public getNodesInCluster(visNetwork: string, clusterNodeId: VisId): VisId[] { 830 | if (this.networks[visNetwork]) { 831 | return this.networks[visNetwork].getNodesInCluster(clusterNodeId); 832 | } else { 833 | throw new Error(`Network with id ${visNetwork} not found.`); 834 | } 835 | } 836 | 837 | /** 838 | * Opens the cluster, releases the contained nodes and edges, 839 | * removing the cluster node and cluster edges. 840 | * 841 | * @param {string} visNetwork The network name/identifier. 842 | * @param {VisId} nodeId The node id that represents the cluster. 843 | * @param {VisOpenClusterOptions} [options] Cluster options. 844 | * 845 | * @throws {Error} Thrown when the network does not exist. 846 | * 847 | * @memberOf VisNetworkService 848 | */ 849 | public openCluster(visNetwork: string, nodeId: VisId, options?: VisOpenClusterOptions): void { 850 | if (this.networks[visNetwork]) { 851 | this.networks[visNetwork].openCluster(nodeId, options); 852 | } else { 853 | throw new Error(`Network with id ${visNetwork} not found.`); 854 | } 855 | } 856 | 857 | /** 858 | * Returns true if the node whose ID has been supplied is a cluster. 859 | * 860 | * @param {string} visNetwork The network name/identifier. 861 | * @param {VisId} nodeId The associated node id. 862 | * @returns {boolean} True if the node whose ID has been supplied is a cluster. 863 | * 864 | * @memberOf VisNetworkService 865 | */ 866 | public isCluster(visNetwork: string, nodeId: VisId): boolean { 867 | if (this.networks[visNetwork]) { 868 | return this.networks[visNetwork].isCluster(nodeId); 869 | } 870 | 871 | return false; 872 | } 873 | 874 | /** 875 | * If you like the layout of your network and would like it to start in the same way next time, 876 | * ask for the seed using this method and put it in the layout.randomSeed option. 877 | * 878 | * @param {string} visNetwork The network name/identifier. 879 | * @returns {number} The seed of the current network or -1 when the network is not defined. 880 | * 881 | * @memberOf VisNetworkService 882 | */ 883 | public getSeed(visNetwork: string): number { 884 | if (this.networks[visNetwork]) { 885 | return this.networks[visNetwork].getSeed(); 886 | } 887 | 888 | return -1; 889 | } 890 | 891 | /** 892 | * This function converts canvas coordinates to coordinates on the DOM. 893 | * Input and output are in the form of {x:Number,y:Number}. 894 | * The DOM values are relative to the network container. 895 | * 896 | * @param {string} visNetwork The network name/identifier. 897 | * @param {Position} position The canvas position. 898 | * @returns {Position} The DOM position. 899 | * 900 | * @memberOf VisNetworkService 901 | */ 902 | public canvasToDOM(visNetwork: string, position: vis.Position) { 903 | return this.networks[visNetwork].canvasToDOM(position); 904 | } 905 | 906 | /** 907 | * This function converts DOM coordinates to coordinates on the canvas. 908 | * Input and output are in the form of {x:Number,y:Number}. 909 | * The DOM values are relative to the network container. 910 | * 911 | * @param {string} visNetwork The network name/identifier. 912 | * @param {Position} position The DOM position. 913 | * @returns {Position} The canvas position. 914 | * 915 | * @memberOf VisNetworkService 916 | */ 917 | public DOMtoCanvas(visNetwork: string, position: vis.Position) { 918 | return this.networks[visNetwork].DOMtoCanvas(position); 919 | } 920 | } 921 | -------------------------------------------------------------------------------- /components/timeline/index.ts: -------------------------------------------------------------------------------- 1 | import * as Vis from 'vis'; 2 | 3 | export type VisId = Vis.IdType; 4 | export interface VisTimelineItem extends Vis.DataItem {} 5 | export interface VisTimelineGroup extends Vis.DataGroup {} 6 | export interface VisDataSetOptions extends Vis.DataSetOptions {} 7 | export interface VisTimelineOptions extends Vis.TimelineOptions {} 8 | export class VisTimelineItems extends Vis.DataSet { 9 | public constructor(data?: VisTimelineItem[], options?: VisDataSetOptions) { 10 | super(data, options); 11 | } 12 | 13 | public getLength(): number { 14 | return this.length; 15 | } 16 | 17 | public add(data: VisTimelineItem | VisTimelineItem[], senderId?: VisId): VisId[] { 18 | return super.add(data, senderId); 19 | } 20 | 21 | public clear(senderId?: VisId): VisId[] { 22 | return super.clear(senderId); 23 | } 24 | 25 | public distinct(field: string): any[] { 26 | return super.distinct(field); 27 | } 28 | 29 | public flush(): void { 30 | super.flush(); 31 | } 32 | 33 | public forEach(callback: (item: VisTimelineItem, id: VisId) => void, options?: VisItemSelectionOptions): void { 34 | super.forEach(callback, options); 35 | } 36 | 37 | public getAll(options?: VisItemSelectionOptions): VisTimelineItem[] { 38 | return super.get(options); 39 | } 40 | 41 | public getById(id: VisId, options?: VisItemSelectionOptions): VisTimelineItem { 42 | return super.get(id, options); 43 | } 44 | 45 | public getByIds(ids: VisId[], options?: VisItemSelectionOptions): VisTimelineItem[] { 46 | return super.get(ids, options); 47 | } 48 | 49 | public getDataSet(): VisTimelineItems { 50 | return super.getDataSet() as VisTimelineItems; 51 | } 52 | 53 | public getIds(options?: VisItemSelectionOptions): VisId[] { 54 | return super.getIds(options); 55 | } 56 | 57 | public map(callback: (item: VisTimelineItem, id: VisId) => any, options?: VisItemSelectionOptions): any[] { 58 | return super.map(callback, options); 59 | } 60 | 61 | public max(field: string): VisTimelineItem { 62 | return super.max(field); 63 | } 64 | 65 | public min(field: string): VisTimelineItem { 66 | return super.min(field); 67 | } 68 | 69 | public on(event: string, callback: (event: string, properties: any, senderId: VisId) => void): void { 70 | super.on(event, callback); 71 | } 72 | 73 | public off(event: string, callback: (event: string, properties: any, senderId: VisId) => void): void { 74 | super.off(event, callback); 75 | } 76 | 77 | public removeItems(ids: VisId[], senderId?: VisId): VisId[] { 78 | return super.remove(ids, senderId); 79 | } 80 | 81 | public setOptions(options?: VisDataSetQueueOptions): void { 82 | super.setOptions(options); 83 | } 84 | 85 | public update(data: VisTimelineItem[], senderId?: VisId): VisId[] { 86 | return super.update(data, senderId); 87 | } 88 | } 89 | export class VisTimelineGroups extends Vis.DataSet { 90 | public constructor(data?: VisTimelineGroup[], options?: VisDataSetOptions) { 91 | super(data, options); 92 | } 93 | 94 | public getLength(): number { 95 | return this.length; 96 | } 97 | 98 | public add(data: VisTimelineGroup | VisTimelineGroup[], senderId?: VisId): VisId[] { 99 | return super.add(data, senderId); 100 | } 101 | 102 | public clear(senderId?: VisId): VisId[] { 103 | return super.clear(senderId); 104 | } 105 | 106 | public distinct(field: string): any[] { 107 | return super.distinct(field); 108 | } 109 | 110 | public flush(): void { 111 | super.flush(); 112 | } 113 | 114 | public forEach(callback: (item: VisTimelineGroup, id: VisId) => void, options?: VisGroupSelectionOptions): void { 115 | super.forEach(callback, options); 116 | } 117 | 118 | public getAll(options?: VisGroupSelectionOptions): VisTimelineGroup[] { 119 | return super.get(options); 120 | } 121 | 122 | public getById(id: VisId, options?: VisGroupSelectionOptions): VisTimelineGroup { 123 | return super.get(id, options); 124 | } 125 | 126 | public getByIds(ids: VisId[], options?: VisGroupSelectionOptions): VisTimelineGroup[] { 127 | return super.get(ids, options); 128 | } 129 | 130 | public getDataSet(): VisTimelineGroups { 131 | return super.getDataSet() as VisTimelineGroups; 132 | } 133 | 134 | public getIds(options?: VisGroupSelectionOptions): VisId[] { 135 | return super.getIds(options); 136 | } 137 | 138 | public map(callback: (item: VisTimelineGroup, id: VisId) => any, options?: VisGroupSelectionOptions): any[] { 139 | return super.map(callback, options); 140 | } 141 | 142 | public max(field: string): VisTimelineGroup { 143 | return super.max(field); 144 | } 145 | 146 | public min(field: string): VisTimelineGroup { 147 | return super.min(field); 148 | } 149 | 150 | public on(event: string, callback: (event: string, properties: any, senderId: VisId) => void): void { 151 | super.on(event, callback); 152 | } 153 | 154 | public off(event: string, callback: (event: string, properties: any, senderId: VisId) => void): void { 155 | super.off(event, callback); 156 | } 157 | 158 | public removeItems(ids: VisId[], senderId?: VisId): VisId[] { 159 | return super.remove(ids, senderId); 160 | } 161 | 162 | public setOptions(options?: VisDataSetQueueOptions): void { 163 | super.setOptions(options); 164 | } 165 | 166 | public update(data: VisTimelineGroup[], senderId?: VisId): VisId[] { 167 | return super.update(data, senderId); 168 | } 169 | } 170 | 171 | export interface VisDataSetQueueOptions extends Vis.DataSetQueueOptions {} 172 | export interface VisItemSelectionOptions extends Vis.DataSelectionOptions {} 173 | export interface VisGroupSelectionOptions extends Vis.DataSelectionOptions {} 174 | export type VisDate = Vis.DateType; 175 | export type VisTimelineEvents = Vis.TimelineEvents; 176 | export interface VisTimelineFitOptions extends Vis.TimelineFitOptions {} 177 | export interface VisTimelineEventPropertiesResult extends Vis.TimelineEventPropertiesResult {} 178 | 179 | export class VisTimeline extends Vis.Timeline {} 180 | 181 | export * from './vis-timeline.service'; 182 | export * from './vis-timeline.directive'; 183 | -------------------------------------------------------------------------------- /components/timeline/vis-timeline.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, 4 | EventEmitter, 5 | Input, 6 | OnChanges, 7 | OnDestroy, 8 | OnInit, 9 | Output, 10 | SimpleChange } from '@angular/core'; 11 | 12 | import { VisTimelineService } from './vis-timeline.service'; 13 | 14 | import { 15 | VisTimelineGroups, 16 | VisTimelineItems, 17 | VisTimelineOptions } from './index'; 18 | 19 | /** 20 | * Use this directive with a div container to show timeline data. 21 | * 22 | * @export 23 | * @class VisTimelineDirective 24 | * @implements {OnInit} 25 | * @implements {OnDestroy} 26 | * @implements {OnChanges} 27 | */ 28 | @Directive({ 29 | selector: '[visTimeline]', 30 | }) 31 | export class VisTimelineDirective implements OnInit, OnDestroy, OnChanges { 32 | 33 | /** 34 | * The name or identifier of the timeline (must be unique in your application). 35 | * This property is used once on init and must not be changed. 36 | * 37 | * @type {string} 38 | * @memberOf VisTimelineDirective 39 | */ 40 | @Input('visTimeline') 41 | public visTimeline: string; 42 | 43 | /** 44 | * The data that will be used to create the timeline. 45 | * Changes will be detected. If the reference changes then 46 | * setData will be called on this timeline instance. 47 | * 48 | * @type {VisTimelineItems} 49 | * @memberOf VisTimelineDirective 50 | */ 51 | @Input() 52 | public visTimelineItems: VisTimelineItems; 53 | 54 | /** 55 | * The groups that will be used to create the timeline. 56 | * Changes will be detected. If the reference changes then 57 | * setGroups will be called on this timeline instance. 58 | * 59 | * @type {VisTimelineGroups} 60 | * @memberOf VisTimelineDirective 61 | */ 62 | @Input() 63 | public visTimelineGroups: VisTimelineGroups; 64 | 65 | /** 66 | * The options that will be used with this timeline. 67 | * Changes will be detected. If the reference changes then 68 | * setOptions will be called on this timeline instance. 69 | * 70 | * @type {VisTimelineOptions} 71 | * @memberOf VisTimelineDirective 72 | */ 73 | @Input() 74 | public visTimelineOptions: VisTimelineOptions; 75 | 76 | /** 77 | * This event will be raised when the timline is initialized. 78 | * At this point of time the timeline is successfully registered 79 | * with the VisNetworkService and you can register to events. 80 | * The event data is the name of the timeline as a string. 81 | * 82 | * @type {EventEmitter} 83 | * @memberOf VisTimelineDirective 84 | */ 85 | @Output() 86 | public initialized: EventEmitter = new EventEmitter(); 87 | 88 | private visTimelineContainer: any; 89 | private isInitialized: boolean = false; 90 | 91 | /** 92 | * Creates an instance of VisTimelineDirective. 93 | * 94 | * @param {ElementRef} elementRef The HTML element reference. 95 | * @param {VisTimelineService} visTimelineService The VisTimelineService. 96 | * 97 | * @memberOf VisTimelineDirective 98 | */ 99 | public constructor(private elementRef: ElementRef, private visTimelineService: VisTimelineService) { 100 | this.visTimelineContainer = elementRef.nativeElement; 101 | } 102 | 103 | /** 104 | * Create the timeline when at least visNetwork and visNetworkData 105 | * are defined. 106 | * 107 | * @memberOf VisTimelineDirective 108 | */ 109 | public ngOnInit(): void { 110 | if (!this.isInitialized && this.visTimeline && this.visTimelineItems) { 111 | this.createTimeline(); 112 | } 113 | } 114 | 115 | /** 116 | * Update the timeline data, groups or options on reference changes to 117 | * the visTimelineItems, visTimelineGroups or visTimelineOptions properties. 118 | * 119 | * @param {{[propName: string]: SimpleChange}} changes 120 | * 121 | * @memberOf VisTimelineDirective 122 | */ 123 | public ngOnChanges(changes: {[propName: string]: SimpleChange}): void { 124 | if (!this.isInitialized && this.visTimeline && this.visTimelineItems) { 125 | this.createTimeline(); 126 | } 127 | 128 | for (const propertyName in changes) { 129 | if (changes.hasOwnProperty(propertyName)) { 130 | const change = changes[propertyName]; 131 | if (!change.isFirstChange()) { 132 | if (propertyName === 'visTimelineItems') { 133 | this.visTimelineService.setItems(this.visTimeline, changes[propertyName].currentValue); 134 | } 135 | if (propertyName === 'visTimelineOptions') { 136 | this.visTimelineService.setOptions(this.visTimeline, changes[propertyName].currentValue); 137 | } 138 | if (propertyName === 'visTimelineGroups') { 139 | this.visTimelineService.setGroups(this.visTimeline, changes[propertyName].currentValue); 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * Calls the destroy function for this timeline instance. 148 | * 149 | * 150 | * @memberOf VisTimelineDirective 151 | */ 152 | public ngOnDestroy(): void { 153 | this.isInitialized = false; 154 | this.visTimelineService.destroy(this.visTimeline); 155 | } 156 | 157 | private createTimeline(): void { 158 | if (this.visTimelineGroups) { 159 | this.visTimelineService.createWithItemsAndGroups( 160 | this.visTimeline, 161 | this.visTimelineContainer, 162 | this.visTimelineItems, 163 | this.visTimelineGroups, 164 | this.visTimelineOptions); 165 | } else { 166 | this.visTimelineService.createWithItems( 167 | this.visTimeline, 168 | this.visTimelineContainer, 169 | this.visTimelineItems, 170 | this.visTimelineOptions); 171 | } 172 | this.isInitialized = true; 173 | this.initialized.emit(this.visTimeline); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /components/timeline/vis-timeline.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | VisTimelineGroups, 3 | VisTimelineItems, 4 | VisTimelineService } from './index'; 5 | 6 | describe('VisTimelineService Tests', () => { 7 | 8 | let visTimelineService: VisTimelineService; 9 | 10 | beforeEach(() => { 11 | visTimelineService = new VisTimelineService(); 12 | }); 13 | 14 | it('throws no error when the network does not exist', () => { 15 | visTimelineService.destroy('unknown'); 16 | visTimelineService.off('unknown', 'click'); 17 | }); 18 | 19 | it('throws error when network already exists', () => { 20 | const dummyElement = document.createElement('div'); 21 | const items = new VisTimelineItems([ 22 | {id: 1, content: 'item 1', start: '2016-04-20'}, 23 | {id: 2, content: 'item 2', start: '2016-04-14'}, 24 | {id: 3, content: 'item 3', start: '2016-04-18'}, 25 | {id: 4, content: 'item 4', start: '2016-04-16', end: '2016-04-19'}, 26 | {id: 5, content: 'item 5', start: '2016-04-25'}, 27 | {id: 6, content: 'item 6', start: '2016-04-27', type: 'point'}, 28 | ]); 29 | const groups = new VisTimelineGroups(); 30 | visTimelineService.createWithItems('knownNetwork', dummyElement, items); 31 | expect(() => visTimelineService.createWithItems('knownNetwork', dummyElement, items)).toThrowError(); 32 | expect(() => visTimelineService.createWithItemsAndGroups( 33 | 'knownNetwork', dummyElement, items, groups)).toThrowError(); 34 | }); 35 | 36 | it('returns false when network does not exist', () => { 37 | expect(visTimelineService.on('unknown', 'click')).toBe(false); 38 | }); 39 | 40 | it('throws error when network does not exist', () => { 41 | const items = new VisTimelineItems([ 42 | {id: 1, content: 'item 1', start: '2016-04-20'}, 43 | {id: 2, content: 'item 2', start: '2016-04-14'}, 44 | {id: 3, content: 'item 3', start: '2016-04-18'}, 45 | {id: 4, content: 'item 4', start: '2016-04-16', end: '2016-04-19'}, 46 | {id: 5, content: 'item 5', start: '2016-04-25'}, 47 | {id: 6, content: 'item 6', start: '2016-04-27', type: 'point'}, 48 | ]); 49 | const groups = new VisTimelineGroups(); 50 | 51 | expect(() => visTimelineService.setData('unknown', { items, groups })).toThrowError(); 52 | expect(() => visTimelineService.setItems('unknown', items)).toThrowError(); 53 | expect(() => visTimelineService.setGroups('unknown', groups)).toThrowError(); 54 | expect(() => visTimelineService.setOptions('unknown', {} as any)).toThrowError(); 55 | expect(() => visTimelineService.addCustomTime('unknown', Date.now())).toThrowError(); 56 | expect(() => visTimelineService.fit('unknown', {})).toThrowError(); 57 | expect(() => visTimelineService.fit('unknown')).toThrowError(); 58 | expect(() => visTimelineService.focusOnId('unknown', 12, {})).toThrowError(); 59 | expect(() => visTimelineService.focusOnId('unknown', 12)).toThrowError(); 60 | expect(() => visTimelineService.focusOnIds('unknown', [12, 11], {})).toThrowError(); 61 | expect(() => visTimelineService.focusOnIds('unknown', [12, 11])).toThrowError(); 62 | expect(() => visTimelineService.getCurrentTime('unknown')).toThrowError(); 63 | expect(() => visTimelineService.getCustomTime('unknown')).toThrowError(); 64 | expect(() => visTimelineService.getCustomTime('unknown', 12)).toThrowError(); 65 | expect(() => visTimelineService.getEventProperties('unknown', new Event('click'))).toThrowError(); 66 | expect(() => visTimelineService.getItemRange('unknown')).toThrowError(); 67 | expect(() => visTimelineService.getSelection('unknown')).toThrowError(); 68 | expect(() => visTimelineService.getVisibleItems('unknown')).toThrowError(); 69 | expect(() => visTimelineService.getWindow('unknown')).toThrowError(); 70 | expect(() => visTimelineService.moveTo('unknown', Date.now())).toThrowError(); 71 | expect(() => visTimelineService.moveTo('unknown', Date.now(), {})).toThrowError(); 72 | expect(() => visTimelineService.redraw('unknown')).toThrowError(); 73 | expect(() => visTimelineService.removeCustomTime('unknown', 12)).toThrowError(); 74 | expect(() => visTimelineService.setCustomTime('unknown', Date.now())).toThrowError(); 75 | expect(() => visTimelineService.setCustomTime('unknown', Date.now(), 12)).toThrowError(); 76 | expect(() => visTimelineService.setCustomTimeTitle('unknown', 'title')).toThrowError(); 77 | expect(() => visTimelineService.setSelectionToId('unknown', 12)).toThrowError(); 78 | expect(() => visTimelineService.setSelectionToIds('unknown', [12, 11])).toThrowError(); 79 | expect(() => visTimelineService.setWindow('unknown', Date.now(), Date.now())).toThrowError(); 80 | expect(() => visTimelineService.setWindow('unknown', Date.now(), Date.now(), {})).toThrowError(); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /components/timeline/vis-timeline.service.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter, Injectable } from '@angular/core'; 2 | import { 3 | VisDate, 4 | VisId, 5 | VisTimeline, 6 | VisTimelineEventPropertiesResult, 7 | VisTimelineEvents, 8 | VisTimelineFitOptions, 9 | VisTimelineGroups, 10 | VisTimelineItems, 11 | VisTimelineOptions } from './index'; 12 | 13 | /** 14 | * A service to create, manage and control VisTimeline instances. 15 | * 16 | * @export 17 | * @class VisTimelineService 18 | */ 19 | @Injectable() 20 | export class VisTimelineService { 21 | /** 22 | * Fired when the current time bar redraws. 23 | * The rate depends on the zoom level. 24 | * 25 | * @type {EventEmitter} 26 | * @memberOf VisTimelineService 27 | */ 28 | public currentTimeTick: EventEmitter = new EventEmitter(); 29 | 30 | /** 31 | * Fired when clicked inside the Timeline. 32 | * 33 | * @type {EventEmitter} 34 | * @memberOf VisTimelineService 35 | */ 36 | public click: EventEmitter = new EventEmitter(); 37 | 38 | /** 39 | * Fired when right-clicked inside the Timeline. 40 | * Note that in order to prevent the context menu from showing up, 41 | * default behavior of the event must be stopped. 42 | * 43 | * @type {EventEmitter} 44 | * @memberOf VisTimelineService 45 | */ 46 | public contextmenu: EventEmitter = new EventEmitter(); 47 | 48 | /** 49 | * Fired when double clicked inside the Timeline. 50 | * 51 | * @type {EventEmitter} 52 | * @memberOf VisTimelineService 53 | */ 54 | public doubleClick: EventEmitter = new EventEmitter(); 55 | 56 | /** 57 | * Fired after the dragging of a group is finished. 58 | * 59 | * @type {EventEmitter} 60 | * @memberOf VisTimelineService 61 | */ 62 | public groupDragged: EventEmitter = new EventEmitter(); 63 | 64 | /** 65 | * Fired once after each graph redraw. 66 | * 67 | * @type {EventEmitter} 68 | * @memberOf VisTimelineService 69 | */ 70 | public changed: EventEmitter = new EventEmitter(); 71 | 72 | /** 73 | * Fired repeatedly when the timeline window is being changed. 74 | * 75 | * @type {EventEmitter} 76 | * @memberOf VisTimelineService 77 | */ 78 | public rangechange: EventEmitter = new EventEmitter(); 79 | 80 | /** 81 | * Fired once after the timeline window has been changed. 82 | * 83 | * @type {EventEmitter} 84 | * @memberOf VisTimelineService 85 | */ 86 | public rangechanged: EventEmitter = new EventEmitter(); 87 | 88 | /** 89 | * Fired after the user selects or deselects items by tapping or holding them. 90 | * When a use taps an already selected item, the select event is fired again. 91 | * Not fired when the method setSelectionis executed. 92 | * 93 | * @type {EventEmitter} 94 | * @memberOf VisTimelineService 95 | */ 96 | public select: EventEmitter = new EventEmitter(); 97 | 98 | /** 99 | * Fired when the user moves the mouse over an item. 100 | * 101 | * @type {EventEmitter} 102 | * @memberOf VisTimelineService 103 | */ 104 | public itemover: EventEmitter = new EventEmitter(); 105 | 106 | /** 107 | * Fired when the user moves the mouse out of an item. 108 | * 109 | * @type {EventEmitter} 110 | * @memberOf VisTimelineService 111 | */ 112 | public itemout: EventEmitter = new EventEmitter(); 113 | 114 | /** 115 | * Fired repeatedly when the user is dragging the custom time bar. 116 | * Only available when the custom time bar is enabled. 117 | * 118 | * @type {EventEmitter} 119 | * @memberOf VisTimelineService 120 | */ 121 | public timechange: EventEmitter = new EventEmitter(); 122 | 123 | /** 124 | * Fired once after the user has dragged the custom time bar. 125 | * Only available when the custom time bar is enabled. 126 | * 127 | * @type {EventEmitter} 128 | * @memberOf VisTimelineService 129 | */ 130 | public timechanged: EventEmitter = new EventEmitter(); 131 | 132 | private timelines: {[id: string]: VisTimeline} = {}; 133 | 134 | /** 135 | * Creates a new timeline instance. 136 | * 137 | * @param {string} visTimeline The timeline name/identifier. 138 | * @param {HTMLElement} container The HTML element that contains the timeline view. 139 | * @param {VisTimelineItems} items The initial timeline items. 140 | * @param {VisTimelineOptions} [options] The timeline options. 141 | * 142 | * @throws {Error} Thrown when timeline already exists. 143 | * 144 | * @memberOf VisTimelineService 145 | */ 146 | public createWithItems( 147 | visTimeline: string, 148 | container: HTMLElement, 149 | items: VisTimelineItems, 150 | options?: VisTimelineOptions): void { 151 | if (this.timelines[visTimeline]) { 152 | throw new Error(this.alreadyExistsError(visTimeline)); 153 | } 154 | 155 | this.timelines[visTimeline] = new VisTimeline(container, items, options); 156 | } 157 | 158 | /** 159 | * Creates a new timeline instance. 160 | * 161 | * @param {string} visTimeline The timeline name/identifier. 162 | * @param {HTMLElement} container The HTML element that contains the timeline view. 163 | * @param {VisTimelineItems} items The initial timeline items. 164 | * @param {VisTimelineGroups} groups The initial timeline groups. 165 | * @param {VisTimelineOptions} [options] The timeline options. 166 | * 167 | * @throws {Error} Thrown when timeline already exists. 168 | * 169 | * @memberOf VisTimelineService 170 | */ 171 | public createWithItemsAndGroups( 172 | visTimeline: string, 173 | container: HTMLElement, 174 | items: VisTimelineItems, 175 | groups: VisTimelineGroups, 176 | options?: VisTimelineOptions): void { 177 | if (this.timelines[visTimeline]) { 178 | throw new Error(this.alreadyExistsError(visTimeline)); 179 | } 180 | 181 | this.timelines[visTimeline] = new VisTimeline(container, items, groups, options); 182 | } 183 | 184 | /** 185 | * Add new vertical bar representing a custom time that can be dragged by the user. 186 | * The id is added as CSS class name of the custom time bar, 187 | * allowing to style multiple time bars differently. 188 | * 189 | * @param {string} visTimeline The timeline name/identifier. 190 | * @param {VisDate} time Parameter time can be a Date, Number, or String, and is new Date() by default. 191 | * @param {VisId} [id] Parameter id can be Number or String and is undefined by default. 192 | * @returns {VisId} The method returns id of the created bar. 193 | * 194 | * @throws {Error} Thrown when timeline does not exist. 195 | * 196 | * @memberOf VisTimelineService 197 | */ 198 | public addCustomTime(visTimeline: string, time: VisDate, id?: VisId): VisId { 199 | if (this.timelines[visTimeline]) { 200 | return this.timelines[visTimeline].addCustomTime(time, id); 201 | } else { 202 | throw new Error(this.doesNotExistError(visTimeline)); 203 | } 204 | } 205 | 206 | /** 207 | * Adjust the visible window such that it fits all items. 208 | * See also function focus(id). 209 | * 210 | * @param {string} visTimeline The timeline name/identifier. 211 | * @param {VisTimelineFitOptions} [options] Optional options. 212 | * 213 | * @throws {Error} Thrown when timeline does not exist. 214 | * 215 | * @memberOf VisTimelineService 216 | */ 217 | public fit(visTimeline: string, options?: VisTimelineFitOptions): void { 218 | if (this.timelines[visTimeline]) { 219 | this.timelines[visTimeline].fit(options); 220 | } else { 221 | throw new Error(this.doesNotExistError(visTimeline)); 222 | } 223 | } 224 | 225 | /** 226 | * Adjust the visible window such that the selected item is centered on screen. 227 | * 228 | * @param {string} visTimeline The timeline name/identifier. 229 | * @param {VisId} id The id of the item. 230 | * @param {VisTimelineFitOptions} [options] Options options. 231 | * 232 | * @throws {Error} Thrown when timeline does not exist. 233 | * 234 | * @memberOf VisTimelineService 235 | */ 236 | public focusOnId(visTimeline: string, id: VisId, options?: VisTimelineFitOptions): void { 237 | if (this.timelines[visTimeline]) { 238 | this.timelines[visTimeline].focus(id, options); 239 | } else { 240 | throw new Error(this.doesNotExistError(visTimeline)); 241 | } 242 | } 243 | 244 | /** 245 | * Adjust the visible window such that the selected items are centered on screen. 246 | * 247 | * @param {string} visTimeline The timeline name/identifier. 248 | * @param {VisId[]} ids The item ids. 249 | * @param {VisTimelineFitOptions} [options] Optional options. 250 | * 251 | * @throws {Error} Thrown when timeline does not exist. 252 | * 253 | * @memberOf VisTimelineService 254 | */ 255 | public focusOnIds(visTimeline: string, ids: VisId[], options?: VisTimelineFitOptions): void { 256 | if (this.timelines[visTimeline]) { 257 | this.timelines[visTimeline].focus(ids, options); 258 | } else { 259 | throw new Error(this.doesNotExistError(visTimeline)); 260 | } 261 | } 262 | 263 | /** 264 | * Get the current time. 265 | * Only applicable when option showCurrentTime is true. 266 | * 267 | * @param {string} visTimeline The timeline name/identifier. 268 | * @returns {Date} The current time. 269 | * 270 | * @throws {Error} Thrown when timeline does not exist. 271 | * 272 | * @memberOf VisTimelineService 273 | */ 274 | public getCurrentTime(visTimeline: string): Date { 275 | if (this.timelines[visTimeline]) { 276 | return this.timelines[visTimeline].getCurrentTime(); 277 | } else { 278 | throw new Error(this.doesNotExistError(visTimeline)); 279 | } 280 | } 281 | 282 | /** 283 | * Retrieve the custom time from the custom time bar with given id. 284 | * Id is undefined by default. 285 | * 286 | * @param {string} visTimeline The timeline name/identifier. 287 | * @param {VisId} [id] The time bar id. 288 | * @returns {Date} The custom time. 289 | * 290 | * @throws {Error} Thrown when timeline does not exist. 291 | * 292 | * @memberOf VisTimelineService 293 | */ 294 | public getCustomTime(visTimeline: string, id?: VisId): Date { 295 | if (this.timelines[visTimeline]) { 296 | return this.timelines[visTimeline].getCustomTime(id); 297 | } else { 298 | throw new Error(this.doesNotExistError(visTimeline)); 299 | } 300 | } 301 | 302 | /** 303 | * Returns an Object with relevant properties from an event. 304 | * 305 | * @param {string} visTimeline The timeline name/identifier. 306 | * @param {Event} event The event. 307 | * @returns {VisTimelineEventPropertiesResult} Properties of an event 308 | * 309 | * @throws {Error} Thrown when timeline does not exist. 310 | * 311 | * @memberOf VisTimelineService 312 | */ 313 | public getEventProperties(visTimeline: string, event: Event): VisTimelineEventPropertiesResult { 314 | if (this.timelines[visTimeline]) { 315 | return this.timelines[visTimeline].getEventProperties(event); 316 | } else { 317 | throw new Error(this.doesNotExistError(visTimeline)); 318 | } 319 | } 320 | 321 | /** 322 | * Get the range of all the items as an object containing min: Date and max: Date. 323 | * 324 | * @param {string} visTimeline The timeline name/identifier. 325 | * @returns {{ min: Date, max: Date }} The min and max dates. 326 | * 327 | * @throws {Error} Thrown when timeline does not exist. 328 | * 329 | * @memberOf VisTimelineService 330 | */ 331 | public getItemRange(visTimeline: string): { min: Date, max: Date } { 332 | if (this.timelines[visTimeline]) { 333 | return this.timelines[visTimeline].getItemRange(); 334 | } else { 335 | throw new Error(this.doesNotExistError(visTimeline)); 336 | } 337 | } 338 | 339 | /** 340 | * Get an array with the ids of the currently selected items. 341 | * 342 | * @param {string} visTimeline The timeline name/identifier. 343 | * @returns {VisId[]} The currently selected items. 344 | * 345 | * @throws {Error} Thrown when timeline does not exist. 346 | * 347 | * @memberOf VisTimelineService 348 | */ 349 | public getSelection(visTimeline: string): VisId[] { 350 | if (this.timelines[visTimeline]) { 351 | return this.timelines[visTimeline].getSelection(); 352 | } else { 353 | throw new Error(this.doesNotExistError(visTimeline)); 354 | } 355 | } 356 | 357 | /** 358 | * Get an array with the ids of the currently visible items. 359 | * 360 | * @param {string} visTimeline The timeline name/identifier. 361 | * @returns {VisId[]} The currently visible items. 362 | * 363 | * @throws {Error} Thrown when timeline does not exist. 364 | * 365 | * @memberOf VisTimelineService 366 | */ 367 | public getVisibleItems(visTimeline: string): VisId[] { 368 | if (this.timelines[visTimeline]) { 369 | return this.timelines[visTimeline].getVisibleItems(); 370 | } else { 371 | throw new Error(this.doesNotExistError(visTimeline)); 372 | } 373 | } 374 | 375 | /** 376 | * Get the current visible window. 377 | * 378 | * @param {string} visTimeline The timeline name/identifier. 379 | * @returns {{ start: Date, end: Date }} Returns an object with properties start: Date and end: Date. 380 | * 381 | * @throws {Error} Thrown when timeline does not exist. 382 | * 383 | * @memberOf VisTimelineService 384 | */ 385 | public getWindow(visTimeline: string): { start: Date, end: Date } { 386 | if (this.timelines[visTimeline]) { 387 | return this.timelines[visTimeline].getWindow(); 388 | } else { 389 | throw new Error(this.doesNotExistError(visTimeline)); 390 | } 391 | } 392 | 393 | /** 394 | * Move the window such that given time is centered on screen. 395 | * 396 | * @param {string} visTimeline The timeline name/identifier. 397 | * @param {VisDate} time Parameter time can be a Date, Number, or String. 398 | * @param {VisTimelineFitOptions} [options] Optional options. 399 | * 400 | * @throws {Error} Thrown when timeline does not exist. 401 | * 402 | * @memberOf VisTimelineService 403 | */ 404 | public moveTo(visTimeline: string, time: VisDate, options?: VisTimelineFitOptions): void { 405 | if (this.timelines[visTimeline]) { 406 | this.timelines[visTimeline].moveTo(time, options); 407 | } else { 408 | throw new Error(this.doesNotExistError(visTimeline)); 409 | } 410 | } 411 | 412 | /** 413 | * Force a redraw of the Timeline. 414 | * The size of all items will be recalculated. 415 | * Can be useful to manually redraw when option autoResize=false and the window has been resized, 416 | * or when the items CSS has been changed. 417 | * 418 | * @param {string} visTimeline The timeline name/identifier. 419 | * 420 | * @throws {Error} Thrown when timeline does not exist. 421 | * 422 | * @memberOf VisTimelineService 423 | */ 424 | public redraw(visTimeline: string): void { 425 | if (this.timelines[visTimeline]) { 426 | this.timelines[visTimeline].redraw(); 427 | } else { 428 | throw new Error(this.doesNotExistError(visTimeline)); 429 | } 430 | } 431 | 432 | /** 433 | * Remove vertical bars previously added to the timeline via addCustomTime method. 434 | * 435 | * @param {string} visTimeline The timeline name/identifier. 436 | * @param {VisId} id Parameter id is the ID of the custom vertical bar returned by addCustomTime method. 437 | * 438 | * @throws {Error} Thrown when timeline does not exist. 439 | * 440 | * @memberOf VisTimelineService 441 | */ 442 | public removeCustomTime(visTimeline: string, id: VisId): void { 443 | if (this.timelines[visTimeline]) { 444 | this.timelines[visTimeline].removeCustomTime(id); 445 | } else { 446 | throw new Error(this.doesNotExistError(visTimeline)); 447 | } 448 | } 449 | 450 | /** 451 | * Set a current time. 452 | * This can be used for example to ensure that a client's time is synchronized 453 | * with a shared server time. 454 | * Only applicable when option showCurrentTime is true. 455 | * 456 | * @param {string} visTimeline The timeline name/identifier. 457 | * @param {VisDate} time time can be a Date object, numeric timestamp, or ISO date string. 458 | * 459 | * @throws {Error} Thrown when timeline does not exist. 460 | * 461 | * @memberOf VisTimelineService 462 | */ 463 | public setCurrentTime(visTimeline: string, time: VisDate): void { 464 | if (this.timelines[visTimeline]) { 465 | this.timelines[visTimeline].setCurrentTime(time); 466 | } else { 467 | throw new Error(this.doesNotExistError(visTimeline)); 468 | } 469 | } 470 | 471 | /** 472 | * Adjust the time of a custom time bar. 473 | * 474 | * @param {string} visTimeline The timeline name/identifier. 475 | * @param {VisDate} time Parameter time can be a Date object, numeric timestamp, or ISO date string. 476 | * @param {VisId} [id] Parameter id is the id of the custom time bar, and is undefined by default. 477 | * 478 | * @throws {Error} Thrown when timeline does not exist. 479 | * 480 | * @memberOf VisTimelineService 481 | */ 482 | public setCustomTime(visTimeline: string, time: VisDate, id?: VisId): void { 483 | if (this.timelines[visTimeline]) { 484 | this.timelines[visTimeline].setCustomTime(time, id); 485 | } else { 486 | throw new Error(this.doesNotExistError(visTimeline)); 487 | } 488 | } 489 | 490 | /** 491 | * Adjust the title attribute of a custom time bar. 492 | * 493 | * @param {string} visTimeline The timeline name/identifier. 494 | * @param {string} title Parameter title is the string to be set as title. 495 | * Use empty string to hide the title completely. 496 | * @param {VisId} [id] Parameter id is the id of the custom time bar, and is undefined by default. 497 | * 498 | * @throws {Error} Thrown when timeline does not exist. 499 | * 500 | * @memberOf VisTimelineService 501 | */ 502 | public setCustomTimeTitle(visTimeline: string, title: string, id?: VisId): void { 503 | if (this.timelines[visTimeline]) { 504 | this.timelines[visTimeline].setCustomTimeTitle(title, id); 505 | } else { 506 | throw new Error(this.doesNotExistError(visTimeline)); 507 | } 508 | } 509 | 510 | /** 511 | * Set both groups and items at once. 512 | * Both properties are optional. 513 | * This is a convenience method for individually calling both setItems(items) and setGroups(groups). 514 | * Both items and groups can be an Array with Objects, a DataSet (offering 2 way data binding), 515 | * or a DataView (offering 1 way data binding). 516 | * For each of the groups, the items of the timeline are filtered on the property group, 517 | * which must correspond with the id of the group. 518 | * 519 | * @param {string} visTimeline The timeline name/identifier. 520 | * @param {{ groups?: VisTimelineGroups; items?: VisTimelineItems }} data The new timline data. 521 | * 522 | * @throws {Error} Thrown when timeline does not exist. 523 | * 524 | * @memberOf VisTimelineService 525 | */ 526 | public setData(visTimeline: string, data: { groups?: VisTimelineGroups; items?: VisTimelineItems }): void { 527 | if (this.timelines[visTimeline]) { 528 | this.timelines[visTimeline].setData(data); 529 | } else { 530 | throw new Error(this.doesNotExistError(visTimeline)); 531 | } 532 | } 533 | 534 | /** 535 | * Set a data set with groups for the Timeline. 536 | * For each of the groups, the items of the timeline are filtered on the property group, 537 | * which must correspond with the id of the group. 538 | * 539 | * @param {string} visTimeline The timeline name/identifier. 540 | * @param {VisTimelineGroups} groups a DataSet (offering 2 way data binding) 541 | * 542 | * @throws {Error} Thrown when timeline does not exist. 543 | * 544 | * @memberOf VisTimelineService 545 | */ 546 | public setGroups(visTimeline: string, groups: VisTimelineGroups): void { 547 | if (this.timelines[visTimeline]) { 548 | this.timelines[visTimeline].setGroups(groups); 549 | } else { 550 | throw new Error(this.doesNotExistError(visTimeline)); 551 | } 552 | } 553 | 554 | /** 555 | * Set a data set with items for the Timeline. 556 | * 557 | * @param {string} visTimeline The timeline name/identifier. 558 | * @param {VisTimelineItems} items can be an Array with Objects, a DataSet (offering 2 way data binding) 559 | * 560 | * @throws {Error} Thrown when timeline does not exist. 561 | * 562 | * @memberOf VisTimelineService 563 | */ 564 | public setItems(visTimeline: string, items: VisTimelineItems): void { 565 | if (this.timelines[visTimeline]) { 566 | this.timelines[visTimeline].setItems(items); 567 | } else { 568 | throw new Error(this.doesNotExistError(visTimeline)); 569 | } 570 | } 571 | 572 | /** 573 | * Set or update options. 574 | * It is possible to change any option of the timeline at any time. 575 | * You can for example switch orientation on the fly. 576 | * 577 | * @param {string} visTimeline The timeline name/identifier. 578 | * @param {VisTimelineOptions} options The new options of the timeline. 579 | * 580 | * @throws {Error} Thrown when timeline does not exist. 581 | * 582 | * @memberOf VisTimelineService 583 | */ 584 | public setOptions(visTimeline: string, options: VisTimelineOptions): void { 585 | if (this.timelines[visTimeline]) { 586 | this.timelines[visTimeline].setOptions(options); 587 | } else { 588 | throw new Error(this.doesNotExistError(visTimeline)); 589 | } 590 | } 591 | 592 | /** 593 | * Select one item by its id.# 594 | * The currently selected items will be unselected. 595 | * 596 | * @param {string} visTimeline The timeline name/identifier. 597 | * @param {VisId} id The id of the item that should be selected. 598 | * 599 | * @throws {Error} Thrown when timeline does not exist. 600 | * 601 | * @memberOf VisTimelineService 602 | */ 603 | public setSelectionToId(visTimeline: string, id: VisId): void { 604 | if (this.timelines[visTimeline]) { 605 | this.timelines[visTimeline].setSelection(id); 606 | } else { 607 | throw new Error(this.doesNotExistError(visTimeline)); 608 | } 609 | } 610 | 611 | /** 612 | * Select multiple items by their id. 613 | * The currently selected items will be unselected. 614 | * To unselect all selected items, call `setSelection([])`. 615 | * 616 | * @param {string} visTimeline The timeline name/identifier. 617 | * @param {VisId[]} ids The ids of the irems that should be selected. 618 | * 619 | * @throws {Error} Thrown when timeline does not exist. 620 | * 621 | * @memberOf VisTimelineService 622 | */ 623 | public setSelectionToIds(visTimeline: string, ids: VisId[]): void { 624 | if (this.timelines[visTimeline]) { 625 | this.timelines[visTimeline].setSelection(ids); 626 | } else { 627 | throw new Error(this.doesNotExistError(visTimeline)); 628 | } 629 | } 630 | 631 | /** 632 | * Set the current visible window. 633 | * 634 | * If the parameter value of start or end is null, the parameter will be left unchanged. 635 | * 636 | * @param {string} visTimeline The timeline name/identifier. 637 | * @param {VisDate} start The parameters start can be a Date, Number, or String. 638 | * @param {VisDate} end The parameters end can be a Date, Number, or String. 639 | * @param {VisTimelineFitOptions} [options] Optional options. 640 | * 641 | * @throws {Error} Thrown when timeline does not exist. 642 | * 643 | * @memberOf VisTimelineService 644 | */ 645 | public setWindow(visTimeline: string, start: VisDate, end: VisDate, options?: VisTimelineFitOptions): void { 646 | if (this.timelines[visTimeline]) { 647 | this.timelines[visTimeline].setWindow(start, end, options); 648 | } else { 649 | throw new Error(this.doesNotExistError(visTimeline)); 650 | } 651 | } 652 | 653 | /** 654 | * Destroy the Timeline. 655 | * The timeline is removed from memory. 656 | * All DOM elements and event listeners are cleaned up. 657 | * 658 | * @param {string} visTimeline The timeline name/identifier. 659 | * 660 | * @memberOf VisTimelineService 661 | */ 662 | public destroy(visTimeline: string): void { 663 | if (this.timelines[visTimeline]) { 664 | this.timelines[visTimeline].destroy(); 665 | delete this.timelines[visTimeline]; 666 | } 667 | } 668 | 669 | /** 670 | * Activates an event. 671 | * 672 | * @param {string} visTimeline The timeline name/identifier. 673 | * @param {VisTimelineEvents} eventName The event name. 674 | * @param {boolean} preventDefault Stops the default behavior of the event. 675 | * @returns {boolean} Returns true when the event was activated. 676 | * 677 | * @memberOf VisTimelineService 678 | */ 679 | public on(visTimeline: string, eventName: VisTimelineEvents, preventDefault?: boolean): boolean { 680 | if (this.timelines[visTimeline]) { 681 | const that: {[index: string]: any} = this; 682 | this.timelines[visTimeline].on(eventName, (params: any) => { 683 | const emitter = that[eventName] as EventEmitter; 684 | if (emitter) { 685 | emitter.emit(params ? [visTimeline].concat(params) : visTimeline); 686 | } 687 | if (preventDefault && params.event) { 688 | params.event.preventDefault(); 689 | } 690 | }); 691 | 692 | return true; 693 | } 694 | 695 | return false; 696 | } 697 | 698 | /** 699 | * Deactivates an event. 700 | * 701 | * @param {string} visTimeline The timeline name/identifier. 702 | * @param {VisTimelineEvents} eventName The event name. 703 | * 704 | * @memberOf VisTimelineService 705 | */ 706 | public off(visTimeline: string, eventName: VisTimelineEvents): void { 707 | if (this.timelines[visTimeline]) { 708 | this.timelines[visTimeline].off(eventName, undefined); 709 | } 710 | } 711 | 712 | private doesNotExistError(visTimeline: string): string { 713 | return `Timeline with id ${visTimeline} does not exist.`; 714 | } 715 | 716 | private alreadyExistsError(visTimeline: string): string { 717 | return `Timeline with id ${visTimeline} already exists.`; 718 | } 719 | } 720 | -------------------------------------------------------------------------------- /demo/assets/css/example.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 20px; 3 | padding-bottom: 20px; 4 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 5 | } 6 | 7 | /* Everything but the jumbotron gets side spacing for mobile first views */ 8 | .header, 9 | .marketing, 10 | .footer { 11 | padding-right: 15px; 12 | padding-left: 15px; 13 | } 14 | 15 | /* Custom page header */ 16 | .header { 17 | padding-bottom: 20px; 18 | border-bottom: 1px solid #e5e5e5; 19 | } 20 | /* Make the masthead heading the same height as the navigation */ 21 | .header h3 { 22 | margin-top: 0; 23 | margin-bottom: 0; 24 | line-height: 40px; 25 | } 26 | 27 | /* Custom page footer */ 28 | .footer { 29 | padding-top: 19px; 30 | color: #777; 31 | border-top: 1px solid #e5e5e5; 32 | } 33 | 34 | /* Main marketing message and sign up button */ 35 | .jumbotron { 36 | text-align: center; 37 | border-bottom: 1px solid #e5e5e5; 38 | } 39 | .jumbotron .btn { 40 | padding: 14px 24px; 41 | font-size: 21px; 42 | } 43 | 44 | /* Supporting marketing content */ 45 | .marketing { 46 | margin: 40px 0; 47 | } 48 | .marketing p + h4 { 49 | margin-top: 28px; 50 | } 51 | 52 | /* Responsive: Portrait tablets and up */ 53 | @media screen and (min-width: 768px) { 54 | /* Remove the padding we set earlier */ 55 | .header, 56 | .marketing, 57 | .footer { 58 | padding-right: 0; 59 | padding-left: 0; 60 | } 61 | /* Space out the masthead */ 62 | .header { 63 | margin-bottom: 30px; 64 | } 65 | /* Remove the bottom border on the jumbotron for visual effect */ 66 | .jumbotron { 67 | border-bottom: 0; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /demo/assets/css/vis.min.css: -------------------------------------------------------------------------------- 1 | .vis-background,.vis-labelset,.vis-timeline{overflow:hidden}.vis .overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis [class*=span]{min-height:0;width:auto}div.vis-configuration{position:relative;display:block;float:left;font-size:12px}div.vis-configuration-wrapper{display:block;width:700px}div.vis-configuration-wrapper::after{clear:both;content:"";display:block}div.vis-configuration.vis-config-option-container{display:block;width:495px;background-color:#fff;border:2px solid #f7f8fa;border-radius:4px;margin-top:20px;left:10px;padding-left:5px}div.vis-configuration.vis-config-button{display:block;width:495px;height:25px;vertical-align:middle;line-height:25px;background-color:#f7f8fa;border:2px solid #ceced0;border-radius:4px;margin-top:20px;left:10px;padding-left:5px;cursor:pointer;margin-bottom:30px}div.vis-configuration.vis-config-button.hover{background-color:#4588e6;border:2px solid #214373;color:#fff}div.vis-configuration.vis-config-item{display:block;float:left;width:495px;height:25px;vertical-align:middle;line-height:25px}div.vis-configuration.vis-config-item.vis-config-s2{left:10px;background-color:#f7f8fa;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s3{left:20px;background-color:#e4e9f0;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s4{left:30px;background-color:#cfd8e6;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-header{font-size:18px;font-weight:700}div.vis-configuration.vis-config-label{width:120px;height:25px;line-height:25px}div.vis-configuration.vis-config-label.vis-config-s3{width:110px}div.vis-configuration.vis-config-label.vis-config-s4{width:100px}div.vis-configuration.vis-config-colorBlock{top:1px;width:30px;height:19px;border:1px solid #444;border-radius:2px;padding:0;margin:0;cursor:pointer}input.vis-configuration.vis-config-checkbox{left:-5px}input.vis-configuration.vis-config-rangeinput{position:relative;top:-5px;width:60px;padding:1px;margin:0;pointer-events:none}.vis-panel,.vis-timeline{padding:0;box-sizing:border-box}input.vis-configuration.vis-config-range{-webkit-appearance:none;border:0 solid #fff;background-color:rgba(0,0,0,0);width:300px;height:20px}input.vis-configuration.vis-config-range::-webkit-slider-runnable-track{width:300px;height:5px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#dedede', endColorstr='#c8c8c8', GradientType=0 );border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-webkit-slider-thumb{-webkit-appearance:none;border:1px solid #14334b;height:17px;width:17px;border-radius:50%;background:#3876c2;background:-moz-linear-gradient(top,#3876c2 0,#385380 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3876c2),color-stop(100%,#385380));background:-webkit-linear-gradient(top,#3876c2 0,#385380 100%);background:-o-linear-gradient(top,#3876c2 0,#385380 100%);background:-ms-linear-gradient(top,#3876c2 0,#385380 100%);background:linear-gradient(to bottom,#3876c2 0,#385380 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#3876c2', endColorstr='#385380', GradientType=0 );box-shadow:#111927 0 0 1px 0;margin-top:-7px}input.vis-configuration.vis-config-range:focus{outline:0}input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track{background:#9d9d9d;background:-moz-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9d9d9d),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-o-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:linear-gradient(to bottom,#9d9d9d 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#9d9d9d', endColorstr='#c8c8c8', GradientType=0 )}input.vis-configuration.vis-config-range::-moz-range-track{width:300px;height:10px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#dedede', endColorstr='#c8c8c8', GradientType=0 );border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:-moz-focusring{outline:#fff solid 1px;outline-offset:-1px}input.vis-configuration.vis-config-range::-ms-track{width:300px;height:5px;background:0 0;border-color:transparent;border-width:6px 0;color:transparent}input.vis-configuration.vis-config-range::-ms-fill-lower{background:#777;border-radius:10px}input.vis-configuration.vis-config-range::-ms-fill-upper{background:#ddd;border-radius:10px}input.vis-configuration.vis-config-range::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:focus::-ms-fill-lower{background:#888}input.vis-configuration.vis-config-range:focus::-ms-fill-upper{background:#ccc}.vis-configuration-popup{position:absolute;background:rgba(57,76,89,.85);border:2px solid #f2faff;line-height:30px;height:30px;width:150px;text-align:center;color:#fff;font-size:14px;border-radius:4px;-webkit-transition:opacity .3s ease-in-out;-moz-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.vis-configuration-popup:after,.vis-configuration-popup:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.vis-configuration-popup:after{border-color:rgba(136,183,213,0);border-left-color:rgba(57,76,89,.85);border-width:8px;margin-top:-8px}.vis-configuration-popup:before{border-color:rgba(194,225,245,0);border-left-color:#f2faff;border-width:12px;margin-top:-12px}.vis-timeline{position:relative;border:1px solid #bfbfbf;margin:0}.vis-panel{position:absolute;margin:0}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right,.vis-panel.vis-top{border:1px #bfbfbf}.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right{border-top-style:solid;border-bottom-style:solid;overflow:hidden}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-top{border-left-style:solid;border-right-style:solid}.vis-panel>.vis-content{position:relative}.vis-panel .vis-shadow{position:absolute;width:100%;height:1px;box-shadow:0 0 10px rgba(0,0,0,.8)}.vis-itemset,.vis-labelset,.vis-labelset .vis-label{position:relative;box-sizing:border-box}.vis-panel .vis-shadow.vis-top{top:-1px;left:0}.vis-panel .vis-shadow.vis-bottom{bottom:-1px;left:0}.vis-labelset .vis-label{left:0;top:0;width:100%;color:#4d4d4d;border-bottom:1px solid #bfbfbf}.vis-labelset .vis-label.draggable{cursor:pointer}.vis-labelset .vis-label:last-child{border-bottom:none}.vis-labelset .vis-label .vis-inner{display:inline-block;padding:5px}.vis-labelset .vis-label .vis-inner.vis-hidden{padding:0}.vis-itemset{padding:0;margin:0}.vis-itemset .vis-background,.vis-itemset .vis-foreground{position:absolute;width:100%;height:100%;overflow:visible}.vis-axis{position:absolute;width:100%;height:0;left:0;z-index:1}.vis-foreground .vis-group{position:relative;box-sizing:border-box;border-bottom:1px solid #bfbfbf}.vis-foreground .vis-group:last-child{border-bottom:none}.vis-overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-item{position:absolute;color:#1A1A1A;border-color:#97B0F8;border-width:1px;background-color:#D5DDF6;display:inline-block}.vis-item.vis-point.vis-selected,.vis-item.vis-selected{background-color:#FFF785}.vis-item.vis-selected{border-color:#FFC200;z-index:2}.vis-editable.vis-selected{cursor:move}.vis-item.vis-box{text-align:center;border-style:solid;border-radius:2px}.vis-item.vis-point{background:0 0}.vis-item.vis-dot{position:absolute;padding:0;border-width:4px;border-style:solid;border-radius:4px}.vis-item.vis-range{border-style:solid;border-radius:2px;box-sizing:border-box}.vis-item.vis-background{border:none;background-color:rgba(213,221,246,.4);box-sizing:border-box;padding:0;margin:0}.vis-item .vis-item-overflow{position:relative;width:100%;height:100%;padding:0;margin:0;overflow:hidden}.vis-item .vis-delete,.vis-item .vis-delete-rtl{background:url(img/timeline/delete.png) center no-repeat;height:24px;top:-4px;cursor:pointer}.vis-item.vis-range .vis-item-content{position:relative;display:inline-block}.vis-item.vis-background .vis-item-content{position:absolute;display:inline-block}.vis-item.vis-line{padding:0;position:absolute;width:0;border-left-width:1px;border-left-style:solid}.vis-item .vis-item-content{white-space:nowrap;box-sizing:border-box;padding:5px}.vis-item .vis-delete{position:absolute;width:24px;right:-24px}.vis-item .vis-delete-rtl{position:absolute;width:24px;left:-24px}.vis-item.vis-range .vis-drag-left{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;left:-4px;cursor:w-resize}.vis-item.vis-range .vis-drag-right{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;right:-4px;cursor:e-resize}.vis-range.vis-item.vis-readonly .vis-drag-left,.vis-range.vis-item.vis-readonly .vis-drag-right{cursor:auto}.vis-time-axis{position:relative;overflow:hidden}.vis-time-axis.vis-foreground{top:0;left:0;width:100%}.vis-time-axis.vis-background{position:absolute;top:0;left:0;width:100%;height:100%}.vis-time-axis .vis-text{position:absolute;color:#4d4d4d;padding:3px;overflow:hidden;box-sizing:border-box;white-space:nowrap}.vis-time-axis .vis-text.vis-measure{position:absolute;padding-left:0;padding-right:0;margin-left:0;margin-right:0;visibility:hidden}.vis-time-axis .vis-grid.vis-vertical{position:absolute;border-left:1px solid}.vis-time-axis .vis-grid.vis-vertical-rtl{position:absolute;border-right:1px solid}.vis-time-axis .vis-grid.vis-minor{border-color:#e5e5e5}.vis-time-axis .vis-grid.vis-major{border-color:#bfbfbf}.vis-current-time{background-color:#FF7F6E;width:2px;z-index:1}.vis-custom-time{background-color:#6E94FF;width:2px;cursor:move;z-index:1}div.vis-network div.vis-close,div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-touch-callout:none;-khtml-user-select:none}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-horizontal{position:absolute;width:100%;height:0;border-bottom:1px solid}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-minor{border-color:#e5e5e5}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-major{border-color:#bfbfbf}.vis-data-axis .vis-y-axis.vis-major{width:100%;position:absolute;color:#4d4d4d;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-major.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-minor{position:absolute;width:100%;color:#bebebe;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-minor.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title{position:absolute;color:#4d4d4d;white-space:nowrap;bottom:20px;text-align:center}.vis-data-axis .vis-y-axis.vis-title.vis-measure{padding:0;margin:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title.vis-left{bottom:0;-webkit-transform-origin:left top;-moz-transform-origin:left top;-ms-transform-origin:left top;-o-transform-origin:left top;transform-origin:left bottom;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.vis-data-axis .vis-y-axis.vis-title.vis-right{bottom:0;-webkit-transform-origin:right bottom;-moz-transform-origin:right bottom;-ms-transform-origin:right bottom;-o-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.vis-legend{background-color:rgba(247,252,255,.65);padding:5px;border:1px solid #b3b3b3;box-shadow:2px 2px 10px rgba(154,154,154,.55)}.vis-legend-text{white-space:nowrap;display:inline-block}.vis-graph-group0{fill:#4f81bd;fill-opacity:0;stroke-width:2px;stroke:#4f81bd}.vis-graph-group1{fill:#f79646;fill-opacity:0;stroke-width:2px;stroke:#f79646}.vis-graph-group2{fill:#8c51cf;fill-opacity:0;stroke-width:2px;stroke:#8c51cf}.vis-graph-group3{fill:#75c841;fill-opacity:0;stroke-width:2px;stroke:#75c841}.vis-graph-group4{fill:#ff0100;fill-opacity:0;stroke-width:2px;stroke:#ff0100}.vis-graph-group5{fill:#37d8e6;fill-opacity:0;stroke-width:2px;stroke:#37d8e6}.vis-graph-group6{fill:#042662;fill-opacity:0;stroke-width:2px;stroke:#042662}.vis-graph-group7{fill:#00ff26;fill-opacity:0;stroke-width:2px;stroke:#00ff26}.vis-graph-group8{fill:#f0f;fill-opacity:0;stroke-width:2px;stroke:#f0f}.vis-graph-group9{fill:#8f3938;fill-opacity:0;stroke-width:2px;stroke:#8f3938}.vis-timeline .vis-fill{fill-opacity:.1;stroke:none}.vis-timeline .vis-bar{fill-opacity:.5;stroke-width:1px}.vis-timeline .vis-point{stroke-width:2px;fill-opacity:1}.vis-timeline .vis-legend-background{stroke-width:1px;fill-opacity:.9;fill:#fff;stroke:#c2c2c2}.vis-timeline .vis-outline{stroke-width:1px;fill-opacity:1;fill:#fff;stroke:#e5e5e5}.vis-timeline .vis-icon-fill{fill-opacity:.3;stroke:none}div.vis-network div.vis-manipulation{border-width:0;border-bottom:1px;border-style:solid;border-color:#d6d9d8;background:#fff;background:-moz-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(48%,#fcfcfc),color-stop(50%,#fafafa),color-stop(100%,#fcfcfc));background:-webkit-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-o-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-ms-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:linear-gradient(to bottom,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc', GradientType=0 );padding-top:4px;position:absolute;left:0;top:0;width:100%;height:28px}div.vis-network div.vis-edit-mode{position:absolute;left:0;top:5px;height:30px}div.vis-network div.vis-close{position:absolute;right:0;top:0;width:30px;height:30px;background-position:20px 3px;background-repeat:no-repeat;background-image:url(img/network/cross.png);user-select:none}div.vis-network div.vis-close:hover{opacity:.6}div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{float:left;font-family:verdana;font-size:12px;-moz-border-radius:15px;border-radius:15px;display:inline-block;background-position:0 0;background-repeat:no-repeat;height:24px;margin-left:10px;padding:0 8px;user-select:none}div.vis-network div.vis-manipulation div.vis-button:hover{box-shadow:1px 1px 8px rgba(0,0,0,.2)}div.vis-network div.vis-manipulation div.vis-button:active{box-shadow:1px 1px 8px rgba(0,0,0,.5)}div.vis-network div.vis-manipulation div.vis-button.vis-back{background-image:url(img/network/backIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-none:hover{box-shadow:1px 1px 8px transparent;cursor:default}div.vis-network div.vis-manipulation div.vis-button.vis-none:active{box-shadow:1px 1px 8px transparent}div.vis-network div.vis-manipulation div.vis-button.vis-none{padding:0}div.vis-network div.vis-manipulation div.notification{margin:2px;font-weight:700}div.vis-network div.vis-manipulation div.vis-button.vis-add{background-image:url(img/network/addNodeIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit,div.vis-network div.vis-manipulation div.vis-button.vis-edit{background-image:url(img/network/editIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit.vis-edit-mode{background-color:#fcfcfc;border:1px solid #ccc}div.vis-network div.vis-manipulation div.vis-button.vis-connect{background-image:url(img/network/connectIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-delete{background-image:url(img/network/deleteIcon.png)}div.vis-network div.vis-edit-mode div.vis-label,div.vis-network div.vis-manipulation div.vis-label{margin:0 0 0 23px;line-height:25px}div.vis-network div.vis-manipulation div.vis-separator-line{float:left;display:inline-block;width:1px;height:21px;background-color:#bdbdbd;margin:0 7px 0 15px}div.vis-network-tooltip{position:absolute;visibility:hidden;padding:5px;white-space:nowrap;font-family:verdana;font-size:14px;color:#000;background-color:#f5f4ed;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #808074;box-shadow:3px 3px 10px rgba(0,0,0,.2);pointer-events:none}div.vis-network div.vis-navigation div.vis-button{width:34px;height:34px;-moz-border-radius:17px;border-radius:17px;position:absolute;display:inline-block;background-position:2px 2px;background-repeat:no-repeat;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-navigation div.vis-button:hover{box-shadow:0 0 3px 3px rgba(56,207,21,.3)}div.vis-network div.vis-navigation div.vis-button:active{box-shadow:0 0 1px 3px rgba(56,207,21,.95)}div.vis-network div.vis-navigation div.vis-button.vis-up{background-image:url(img/network/upArrow.png);bottom:50px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-down{background-image:url(img/network/downArrow.png);bottom:10px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-left{background-image:url(img/network/leftArrow.png);bottom:10px;left:15px}div.vis-network div.vis-navigation div.vis-button.vis-right{background-image:url(img/network/rightArrow.png);bottom:10px;left:95px}div.vis-network div.vis-navigation div.vis-button.vis-zoomIn{background-image:url(img/network/plus.png);bottom:10px;right:15px}div.vis-network div.vis-navigation div.vis-button.vis-zoomOut{background-image:url(img/network/minus.png);bottom:10px;right:55px}div.vis-network div.vis-navigation div.vis-button.vis-zoomExtends{background-image:url(img/network/zoomExtends.png);bottom:50px;right:15px}div.vis-color-picker{position:absolute;top:0;left:30px;margin-top:-140px;margin-left:30px;width:310px;height:444px;z-index:1;padding:10px;border-radius:15px;background-color:#fff;display:none;box-shadow:rgba(0,0,0,.5) 0 0 10px 0}div.vis-color-picker div.vis-arrow{position:absolute;top:147px;left:5px}div.vis-color-picker div.vis-arrow::after,div.vis-color-picker div.vis-arrow::before{right:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.vis-color-picker div.vis-arrow:after{border-color:rgba(255,255,255,0);border-right-color:#fff;border-width:30px;margin-top:-30px}div.vis-color-picker div.vis-color{position:absolute;width:289px;height:289px;cursor:pointer}div.vis-color-picker div.vis-brightness{position:absolute;top:313px}div.vis-color-picker div.vis-opacity{position:absolute;top:350px}div.vis-color-picker div.vis-selector{position:absolute;top:137px;left:137px;width:15px;height:15px;border-radius:15px;border:1px solid #fff;background:#4c4c4c;background:-moz-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#4c4c4c),color-stop(12%,#595959),color-stop(25%,#666),color-stop(39%,#474747),color-stop(50%,#2c2c2c),color-stop(51%,#000),color-stop(60%,#111),color-stop(76%,#2b2b2b),color-stop(91%,#1c1c1c),color-stop(100%,#131313));background:-webkit-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-o-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-ms-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:linear-gradient(to bottom,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313', GradientType=0 )}div.vis-color-picker div.vis-initial-color,div.vis-color-picker div.vis-new-color{width:140px;height:20px;top:380px;font-size:10px;color:rgba(0,0,0,.4);line-height:20px;position:absolute;vertical-align:middle}div.vis-color-picker div.vis-new-color{border:1px solid rgba(0,0,0,.1);border-radius:5px;left:159px;text-align:right;padding-right:2px}div.vis-color-picker div.vis-initial-color{border:1px solid rgba(0,0,0,.1);border-radius:5px;left:10px;text-align:left;padding-left:2px}div.vis-color-picker div.vis-label{position:absolute;width:300px;left:10px}div.vis-color-picker div.vis-label.vis-brightness{top:300px}div.vis-color-picker div.vis-label.vis-opacity{top:338px}div.vis-color-picker div.vis-button{position:absolute;width:68px;height:25px;border-radius:10px;vertical-align:middle;text-align:center;line-height:25px;top:410px;border:2px solid #d9d9d9;background-color:#f7f7f7;cursor:pointer}div.vis-color-picker div.vis-button.vis-cancel{left:5px}div.vis-color-picker div.vis-button.vis-load{left:82px}div.vis-color-picker div.vis-button.vis-apply{left:159px}div.vis-color-picker div.vis-button.vis-save{left:236px}div.vis-color-picker input.vis-range{width:290px;height:20px} -------------------------------------------------------------------------------- /demo/assets/img/network/acceptDeleteIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/acceptDeleteIcon.png -------------------------------------------------------------------------------- /demo/assets/img/network/addNodeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/addNodeIcon.png -------------------------------------------------------------------------------- /demo/assets/img/network/backIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/backIcon.png -------------------------------------------------------------------------------- /demo/assets/img/network/connectIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/connectIcon.png -------------------------------------------------------------------------------- /demo/assets/img/network/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/cross.png -------------------------------------------------------------------------------- /demo/assets/img/network/cross2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/cross2.png -------------------------------------------------------------------------------- /demo/assets/img/network/deleteIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/deleteIcon.png -------------------------------------------------------------------------------- /demo/assets/img/network/downArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/downArrow.png -------------------------------------------------------------------------------- /demo/assets/img/network/editIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/editIcon.png -------------------------------------------------------------------------------- /demo/assets/img/network/leftArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/leftArrow.png -------------------------------------------------------------------------------- /demo/assets/img/network/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/minus.png -------------------------------------------------------------------------------- /demo/assets/img/network/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/plus.png -------------------------------------------------------------------------------- /demo/assets/img/network/rightArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/rightArrow.png -------------------------------------------------------------------------------- /demo/assets/img/network/upArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/upArrow.png -------------------------------------------------------------------------------- /demo/assets/img/network/zoomExtends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/network/zoomExtends.png -------------------------------------------------------------------------------- /demo/assets/img/timeline/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveves/angular-vis/efc52100c1c03634989c4aa06ec2b6e38d3c82fe/demo/assets/img/timeline/delete.png -------------------------------------------------------------------------------- /demo/demo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'example-app', 5 | template: ` 6 |
7 |
8 | 15 |

ng2-vis - an angular2 vis.js wrapper

16 |
17 | 18 |
19 | `, 20 | }) 21 | export class DemoComponent { } 22 | -------------------------------------------------------------------------------- /demo/demo.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { CommonModule } from '@angular/common'; 5 | import { RouterModule } from '@angular/router'; 6 | 7 | import { VisNetworkExampleComponent } from './network/network-example.component'; 8 | import { VisTimelineExampleComponent } from './timeline/timeline-example.component'; 9 | import { VisModule } from '../ng2-vis'; 10 | import { DemoComponent } from './demo.component'; 11 | import { HomeComponent } from './home/home.component'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | DemoComponent, 16 | HomeComponent, 17 | VisNetworkExampleComponent, 18 | VisTimelineExampleComponent 19 | ], 20 | imports: [ 21 | BrowserModule, 22 | FormsModule, 23 | VisModule, 24 | CommonModule, 25 | RouterModule.forRoot([ 26 | { path: 'timeline', component: VisTimelineExampleComponent }, 27 | { path: '', redirectTo: '/home', pathMatch: 'full' }, 28 | { path: 'home', component: HomeComponent }, 29 | { path: 'network', component: VisNetworkExampleComponent }, 30 | { path: '**', component: HomeComponent } 31 | ]) 32 | ], 33 | providers: [], 34 | bootstrap: [DemoComponent] 35 | }) 36 | export class VisDemoModule { } 37 | -------------------------------------------------------------------------------- /demo/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'home-example', 5 | template: ` 6 |
7 |

ng2-vis

8 |

Note: these examples mimic the ones for vis, but using ng2-vis.

9 |

10 | Get it on GitHub 11 | Check out visjs.org 12 |

13 |
14 |

Examples

15 |
16 |
17 |
18 | Network Examples 19 |
20 |

Network

21 |

Display dynamic, automatically organised, customizable network views.

22 |

Examples

23 |
24 |
25 |
26 |
27 |
28 | Timeline Examples 29 |
30 |

Timeline

31 |

Create a fully customizable, interactive timeline with items and ranges.

32 |

Examples

33 |
34 |
35 |
36 |
37 | `, 38 | }) 39 | export class HomeComponent {} -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ng2-vis 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Loading... 23 | 24 | 25 | -------------------------------------------------------------------------------- /demo/index.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { VisDemoModule } from './demo.module'; 4 | platformBrowserDynamic().bootstrapModule(VisDemoModule); 5 | -------------------------------------------------------------------------------- /demo/network/network-example.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | 3 | import { 4 | VisNode, 5 | VisNodes, 6 | VisEdges, 7 | VisNetworkService, 8 | VisNetworkData, 9 | VisNetworkOptions } from '../../components/network'; 10 | 11 | class ExampleNetworkData implements VisNetworkData { 12 | public nodes: VisNodes; 13 | public edges: VisEdges; 14 | } 15 | 16 | @Component({ 17 | selector: 'network-example', 18 | styles: [ 19 | `.network-canvas { 20 | width: 100%; 21 | height: 400px; 22 | border: 1px solid lightgray; 23 | }`, 24 | ], 25 | template: ` 26 |

Network

27 |

Basic usage

28 |
33 | 34 |

35 | Note: Open your dev tools to see the console output when the network receives click events. 36 |

37 | `, 38 | }) 39 | export class VisNetworkExampleComponent implements OnInit, OnDestroy { 40 | 41 | public visNetwork: string = 'networkId1'; 42 | public visNetworkData: ExampleNetworkData; 43 | public visNetworkOptions: VisNetworkOptions; 44 | 45 | public constructor(private visNetworkService: VisNetworkService) { } 46 | 47 | public addNode(): void { 48 | const newId = this.visNetworkData.nodes.getLength() + 1; 49 | this.visNetworkData.nodes.add({ id: newId.toString(), label: 'Node ' + newId }); 50 | this.visNetworkService.fit(this.visNetwork); 51 | } 52 | 53 | public networkInitialized(): void { 54 | // now we can use the service to register on events 55 | this.visNetworkService.on(this.visNetwork, 'click'); 56 | 57 | // open your console/dev tools to see the click params 58 | this.visNetworkService.click 59 | .subscribe((eventData: any[]) => { 60 | if (eventData[0] === this.visNetwork) { 61 | console.log(eventData[1]); 62 | } 63 | }); 64 | } 65 | 66 | public ngOnInit(): void { 67 | const nodes = new VisNodes([ 68 | { id: '1', label: 'Node 1' }, 69 | { id: '2', label: 'Node 2' }, 70 | { id: '3', label: 'Node 3' }, 71 | { id: '4', label: 'Node 4' }, 72 | { id: '5', label: 'Node 5', title: 'Title of Node 5' }]); 73 | 74 | const edges = new VisEdges([ 75 | { from: '1', to: '3' }, 76 | { from: '1', to: '2' }, 77 | { from: '2', to: '4' }, 78 | { from: '2', to: '5' }]); 79 | 80 | this.visNetworkData = { 81 | nodes, 82 | edges, 83 | }; 84 | 85 | this.visNetworkOptions = {}; 86 | } 87 | 88 | public ngOnDestroy(): void { 89 | this.visNetworkService.off(this.visNetwork, 'click'); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /demo/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular 2 and is loaded before 2 | // the app. You can add your own extra polyfills to this file. 3 | 4 | // Typescript emit helpers polyfill 5 | import 'ts-helpers'; 6 | 7 | import 'core-js/es6/symbol'; 8 | import 'core-js/es6/object'; 9 | import 'core-js/es6/function'; 10 | import 'core-js/es6/parse-int'; 11 | import 'core-js/es6/parse-float'; 12 | import 'core-js/es6/number'; 13 | import 'core-js/es6/math'; 14 | import 'core-js/es6/string'; 15 | import 'core-js/es6/date'; 16 | import 'core-js/es6/array'; 17 | import 'core-js/es6/regexp'; 18 | import 'core-js/es6/map'; 19 | import 'core-js/es6/set'; 20 | import 'core-js/es6/reflect'; 21 | 22 | import 'core-js/es7/reflect'; 23 | import 'zone.js/dist/zone'; 24 | -------------------------------------------------------------------------------- /demo/timeline/timeline-example.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | 3 | import { VisTimelineService, VisTimelineItems } from '../../components/timeline'; 4 | 5 | @Component({ 6 | selector: 'timeline-example', 7 | template: ` 8 |

Timeline

9 |

Basic usage

10 |
13 | 14 |

15 | Note: Open your dev tools to see the console output when the timeline receives click events. 16 |

17 | `, 18 | }) 19 | export class VisTimelineExampleComponent implements OnInit, OnDestroy { 20 | 21 | public visTimeline: string = 'timelineId1'; 22 | public visTimelineItems: VisTimelineItems; 23 | 24 | public constructor(private visTimelineService: VisTimelineService) {} 25 | 26 | public timelineInitialized(): void { 27 | console.log('timeline initialized'); 28 | 29 | // now we can use the service to register on events 30 | this.visTimelineService.on(this.visTimeline, 'click'); 31 | 32 | // open your console/dev tools to see the click params 33 | this.visTimelineService.click 34 | .subscribe((eventData: any[]) => { 35 | if (eventData[0] === this.visTimeline) { 36 | console.log(eventData[1]); 37 | } 38 | }); 39 | } 40 | 41 | public addItem(): void { 42 | const newLength = this.visTimelineItems.getLength() + 1; 43 | this.visTimelineItems.add( 44 | {id: newLength, content: 'item ' + newLength, start: Date.now() } 45 | ); 46 | this.visTimelineService.focusOnIds(this.visTimeline, [1, newLength]); 47 | } 48 | 49 | public ngOnInit(): void { 50 | this.visTimelineItems = new VisTimelineItems([ 51 | {id: 1, content: 'item 1', start: '2016-04-20'}, 52 | {id: 2, content: 'item 2', start: '2016-04-14'}, 53 | {id: 3, content: 'item 3', start: '2016-04-18'}, 54 | {id: 4, content: 'item 4', start: '2016-04-16', end: '2016-04-19'}, 55 | {id: 5, content: 'item 5', start: '2016-04-25'}, 56 | {id: 6, content: 'item 6', start: '2016-04-27', type: 'point'} 57 | ]); 58 | } 59 | 60 | public ngOnDestroy(): void { 61 | this.visTimelineService.off(this.visTimeline, 'click'); 62 | } 63 | } -------------------------------------------------------------------------------- /demo/vendor.ts: -------------------------------------------------------------------------------- 1 | // For vendors for example jQuery, Lodash, angular2-jwt just import them here unless you plan on 2 | // chunking vendors files for async loading. You would need to import the async loaded vendors 3 | // at the entry point of the async loaded file. Also see custom-typings.d.ts as you also need to 4 | // run `typings install x` where `x` is your module 5 | 6 | // Angular 2 7 | import '@angular/common'; 8 | import '@angular/core'; 9 | import '@angular/forms'; 10 | import '@angular/platform-browser'; 11 | import '@angular/platform-browser-dynamic'; 12 | 13 | import 'vis'; 14 | -------------------------------------------------------------------------------- /gulp-tasks/lint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const gulp = require('gulp'); 4 | const tslint = require('gulp-tslint'); 5 | const gitignore = require('gitignore-to-glob')(); 6 | 7 | gitignore.push('components/**/*.ts'); 8 | gitignore.push('ng2-vis.ts'); 9 | 10 | gulp.task('tslint', () => 11 | gulp 12 | .src(gitignore) 13 | .pipe(tslint({ 14 | formatter: 'verbose', 15 | emitError: true, 16 | summarizeFailureOutput: true, 17 | reportLimit: 50 18 | })) 19 | .pipe(tslint.report()) 20 | ); 21 | 22 | gulp.task('lint', ['tslint']); 23 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const gulp = require('gulp'); 4 | 5 | require('require-dir')('./gulp-tasks'); 6 | 7 | gulp.task('default', () => { 8 | gulp.start('lint'); 9 | }); 10 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: @AngularClass 3 | */ 4 | 'use strict'; 5 | 6 | // Look in ./config for karma.conf.js 7 | const config = require('./.ng2-config'); 8 | 9 | config.src = '/'; 10 | 11 | module.exports = require('./.config/karma.conf')(config); 12 | -------------------------------------------------------------------------------- /ng2-vis.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { VisNetworkDirective, VisNetworkService } from './components/network/index'; 4 | import { VisTimelineDirective, VisTimelineService } from './components/timeline/index'; 5 | 6 | export * from './components/index'; 7 | 8 | @NgModule({ 9 | declarations: [VisNetworkDirective, VisTimelineDirective], 10 | exports: [VisNetworkDirective, VisTimelineDirective], 11 | providers: [VisNetworkService, VisTimelineService], 12 | }) 13 | export class VisModule { } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-vis", 3 | "version": "0.0.6", 4 | "main": "ng2-vis.js", 5 | "types": "ng2-vis.d.ts", 6 | "description": "Angular 2 components for using vis.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/seveves/ng2-vis.git" 10 | }, 11 | "keywords": [ 12 | "angular2", 13 | "ng2", 14 | "vis", 15 | "angular2-vis", 16 | "ng2-vis" 17 | ], 18 | "author": "Severin Friede ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/seveves/ng2-vis/issues" 22 | }, 23 | "homepage": "https://github.com/seveves/ng2-vis#readme", 24 | "scripts": { 25 | "flow.compile": "npm run flow.compile:common && npm run flow.compile:system && npm run flow.extract-metadata", 26 | "flow.compile:tsc": "./node_modules/.bin/tsc -p tsconfig.publish.json", 27 | "flow.compile:common": "npm run flow.compile:tsc", 28 | "flow.compile:system": "node ./.config/bundle-system.js", 29 | "flow.copy:src": "./node_modules/.bin/cpy ng2-vis.ts \"components/*.ts\" ts --parents", 30 | "flow.clean": "./node_modules/.bin/rimraf bundles coverage dist demo-build \"components/**/*.+(metadata.json|js|d.ts|js.map)\" dist \"ng2-vis.+(js|d.ts|js.map)\"", 31 | "flow.eslint": "./node_modules/.bin/eslint --ignore-path .gitignore --ext js --fix . .config", 32 | "flow.extract-metadata": "./node_modules/.bin/ngc -p tsconfig.publish.json", 33 | "flow.tslint": "./node_modules/.bin/gulp lint", 34 | "flow.lint": "npm run flow.eslint && npm run flow.tslint", 35 | "flow.build:prod": "./node_modules/.bin/cross-env NODE_ENV=production ./node_modules/.bin/webpack --config .config/webpack.prod.js --progress --color", 36 | "flow.build:dev": "./node_modules/.bin/webpack --progress --color", 37 | "flow.serve:dev": "./node_modules/.bin/webpack-dev-server --hot --inline --colors", 38 | "flow.serve:prod": "./node_modules/.bin/cross-env NODE_ENV=production ./node_modules/.bin/webpack-dev-server --config .config/webpack.prod.js --hot --inline --colors", 39 | "serve": "npm run flow.serve:dev", 40 | "clean": "npm run flow.clean", 41 | "build": "npm run flow.compile", 42 | "pretest": "npm run flow.lint", 43 | "test": "./node_modules/.bin/karma start", 44 | "test:watch": "./node_modules/.bin/cross-env NODE_ENV=test ./node_modules/.bin/karma start --auto-watch --no-single-run", 45 | "preversion": "npm test", 46 | "version": "git add -A", 47 | "postversion": "git push origin master && git push --tags" 48 | }, 49 | "dependencies": { 50 | "vis": "4.17.0", 51 | "@types/vis": "4.17.4" 52 | }, 53 | "peerDependencies": { 54 | "@angular/common": "^2.4.10", 55 | "@angular/core": "^2.4.10", 56 | "@angular/compiler": "^2.4.10" 57 | }, 58 | "devDependencies": { 59 | "@angular/common": "^2.4.10", 60 | "@angular/compiler": "^2.4.10", 61 | "@angular/compiler-cli": "^2.4.10", 62 | "@angular/core": "^2.4.10", 63 | "@angular/forms": "^2.4.10", 64 | "@angular/platform-browser": "^2.4.10", 65 | "@angular/platform-browser-dynamic": "^2.4.10", 66 | "@angular/platform-server": "^2.4.10", 67 | "@angular/router": "^3.4.10", 68 | "@types/jasmine": "^2.5.46", 69 | "@types/node": "^7.0.8", 70 | "@types/webpack": "^2.2.11", 71 | "angular2-template-loader": "^0.6.2", 72 | "awesome-typescript-loader": "^3.1.2", 73 | "codecov": "^2.0.2", 74 | "copy-webpack-plugin": "^4.0.1", 75 | "cpy-cli": "^1.0.1", 76 | "cross-env": "^3.2.4", 77 | "css-loader": "^0.27.3", 78 | "es6-promise": "^4.1.0", 79 | "es6-shim": "^0.35.3", 80 | "eslint": "^3.18.0", 81 | "extract-text-webpack-plugin": "^2.1.0", 82 | "file-loader": "^0.10.1", 83 | "gitignore-to-glob": "^0.3.0", 84 | "gulp": "3.9.1", 85 | "gulp-tslint": "^7.1.0", 86 | "html-loader": "^0.4.5", 87 | "istanbul-instrumenter-loader": "^2.0.0", 88 | "jasmine-core": "^2.5.2", 89 | "karma": "^1.5.0", 90 | "karma-coverage": "^1.1.1", 91 | "karma-firefox-launcher": "^1.0.1", 92 | "karma-jasmine": "^1.1.0", 93 | "karma-mocha": "^1.3.0", 94 | "karma-mocha-reporter": "^2.2.2", 95 | "karma-sourcemap-loader": "^0.3.7", 96 | "karma-webpack": "^2.0.3", 97 | "null-loader": "^0.1.1", 98 | "pre-commit": "^1.2.2", 99 | "raw-loader": "^0.5.1", 100 | "reflect-metadata": "^0.1.10", 101 | "require-dir": "^0.3.1", 102 | "rimraf": "^2.6.1", 103 | "rxjs": "^5.0.1", 104 | "source-map-loader": "^0.2.0", 105 | "style-loader": "^0.14.1", 106 | "systemjs-builder": "^0.16.4", 107 | "to-string-loader": "^1.1.5", 108 | "ts-helpers": "^1.1.2", 109 | "tslint": "^4.5.1", 110 | "tslint-loader": "^3.4.3", 111 | "typescript": "^2.2.1", 112 | "typings": "^2.1.0", 113 | "webpack": "^2.2.1", 114 | "webpack-dev-server": "^2.4.2", 115 | "webpack-merge": "^4.1.0", 116 | "zone.js": "^0.8.4" 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /spec-bundle.js: -------------------------------------------------------------------------------- 1 | /* eslint no-var: 0, vars-on-top: 0 */ 2 | /** 3 | * @author: @AngularClass 4 | */ 5 | 6 | /* 7 | * When testing with webpack and ES6, we have to do some extra 8 | * things to get testing to work right. Because we are gonna write tests 9 | * in ES6 too, we have to compile those as well. That's handled in 10 | * karma.conf.js with the karma-webpack plugin. This is the entry 11 | * file for webpack test. Just like webpack will create a bundle.js 12 | * file for our client, when we run test, it will compile and bundle them 13 | * all here! Crazy huh. So we need to do some setup 14 | */ 15 | 'use strict'; 16 | Error.stackTraceLimit = Infinity; 17 | 18 | require('core-js'); 19 | 20 | // Typescript emit helpers polyfill 21 | require('ts-helpers'); 22 | 23 | require('zone.js/dist/zone'); 24 | require('zone.js/dist/long-stack-trace-zone'); 25 | require('zone.js/dist/async-test'); 26 | require('zone.js/dist/fake-async-test'); 27 | require('zone.js/dist/sync-test'); 28 | require('zone.js/dist/proxy'); 29 | require('zone.js/dist/jasmine-patch'); 30 | 31 | // RxJS 32 | require('rxjs/Rx'); 33 | 34 | var testing = require('@angular/core/testing'); 35 | var browser = require('@angular/platform-browser-dynamic/testing'); 36 | 37 | testing.TestBed.initTestEnvironment( 38 | browser.BrowserDynamicTestingModule, 39 | browser.platformBrowserDynamicTesting() 40 | ); 41 | 42 | Object.assign(global, testing); 43 | 44 | /* 45 | * Ok, this is kinda crazy. We can use the the context method on 46 | * require that webpack created in order to tell webpack 47 | * what files we actually want to require or import. 48 | * Below, context will be an function/object with file names as keys. 49 | * using that regex we are saying look in ./src/app and ./test then find 50 | * any file that ends with spec.js and get its path. By passing in true 51 | * we say do this recursively 52 | */ 53 | var testContext = require.context('./components', true, /\.spec\.ts/); 54 | 55 | /* 56 | * get all the files, for each file, call the context function 57 | * that will require the file and load it up here. Context will 58 | * loop and require those spec files here 59 | */ 60 | function requireAll(requireContext) { 61 | return requireContext.keys().map(requireContext); 62 | } 63 | 64 | // requires and returns all modules that match 65 | requireAll(testContext); 66 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "sourceMap": true, 9 | "noEmitHelpers": true, 10 | "noImplicitAny": true, 11 | "declaration": false, 12 | "skipLibCheck": true, 13 | "stripInternal": true, 14 | "lib": ["dom", "es6"], 15 | "typeRoots": [ 16 | "./node_modules/@types" 17 | ], 18 | "types": [ 19 | "node", 20 | "jasmine", 21 | "webpack", 22 | "vis" 23 | ] 24 | }, 25 | "exclude": [ 26 | "node_modules", "bundles", "dist" 27 | ], 28 | "files": [ 29 | "./ng2-vis.ts" 30 | ], 31 | "atom": { "rewriteTsconfig": false } 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.publish.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "sourceMap": false, 9 | "noEmitHelpers": false, 10 | "noImplicitAny": true, 11 | "declaration": true, 12 | "skipLibCheck": true, 13 | "stripInternal": true, 14 | "lib": ["dom", "es6"], 15 | "typeRoots": [ 16 | "./node_modules/@types" 17 | ], 18 | "types": [ 19 | "node", 20 | "jasmine", 21 | "webpack", 22 | "vis" 23 | ] 24 | }, 25 | "exclude": [ 26 | "node_modules" 27 | ], 28 | "files": [ 29 | "./ng2-vis.ts" 30 | ], 31 | "angularCompilerOptions": { 32 | "genDir": "factories" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:latest", 3 | "rules": { 4 | "quotemark": false, 5 | "only-arrow-functions": false, 6 | "no-string-literal": false, 7 | "linebreak-style": false, 8 | "interface-name": [false], 9 | "no-reference": false, 10 | "max-classes-per-file": false, 11 | "no-empty-interface": false 12 | } 13 | } -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "vs": "github:seveves/typings-vis#6696261117e363878b0b3a0472dadae93f43fe27" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint no-process-env: 0, global-require:0 */ 2 | 'use strict'; 3 | 4 | module.exports = require('./.config/webpack.dev.js'); --------------------------------------------------------------------------------