'use strict';
import * as d3 from 'd3';
function Neo4jD3(_selector, _options) {
	var container,
		graph,
		info,
		node,
		nodes,
		relationship,
		relationshipOutline,
		relationshipOverlay,
		relationshipText,
		relationships,
		selector,
		simulation,
		svg,
		svgNodes,
		svgRelationships,
		svgScale,
		svgTranslate,
		classes2colors = {},
		justLoaded = false,
		numClasses = 0,
		options = {
			arrowSize: 4,
			colors: colors(),
			highlight: undefined,
			iconMap: fontAwesomeIcons(),
			icons: undefined,
			imageMap: {},
			images: undefined,
			infoPanel: true,
			minCollision: undefined,
			neo4jData: undefined,
			neo4jDataUrl: undefined,
			nodeOutlineFillColor: undefined,
			nodeRadius: 25,
			relationshipColor: '#a5abb6',
			zoomFit: false,
		},
		VERSION = '0.0.1';

	function appendGraph(container) {
		svg = container
			.append('svg')
			.attr('width', '100%')
			.attr('height', '100%')
			.attr('class', 'neo4jd3-graph')
			.call(
				d3.zoom().on('zoom', function() {
					var scale = d3.event.transform.k,
						translate = [d3.event.transform.x, d3.event.transform.y];

					if (svgTranslate) {
						translate[0] += svgTranslate[0];
						translate[1] += svgTranslate[1];
					}

					if (svgScale) {
						scale *= svgScale;
					}

					svg.attr(
						'transform',
						'translate(' + translate[0] + ', ' + translate[1] + ') scale(' + scale + ')',
					);
				}),
			)
			.on('dblclick.zoom', null)
			.append('g')
			.attr('width', '100%')
			.attr('height', '100%');

		svgRelationships = svg.append('g').attr('class', 'relationships');

		svgNodes = svg.append('g').attr('class', 'nodes');
	}

	function appendImageToNode(node) {
		return node
			.append('image')
			.attr('height', function(d) {
				return icon(d) ? '24px' : '30px';
			})
			.attr('x', function(d) {
				return icon(d) ? '5px' : '-15px';
			})
			.attr('xlink:href', function(d) {
				return image(d);
			})
			.attr('y', function(d) {
				return icon(d) ? '5px' : '-16px';
			})
			.attr('width', function(d) {
				return icon(d) ? '24px' : '30px';
			});
	}

	function appendInfoPanel(container) {
		return container.append('div').attr('class', 'neo4jd3-info');
	}

	function appendInfoElement(cls, isNode, property, value) {
		var elem = info.append('a');

		elem
			.attr('href', '#')
			.attr('class', cls)
			.html('<strong>' + property + '</strong>' + (value ? ': ' + value : ''));

		if (!value) {
			elem
				.style('background-color', function(d) {
					return options.nodeOutlineFillColor
						? options.nodeOutlineFillColor
						: isNode
							? class2color(property)
							: defaultColor();
				})
				.style('border-color', function(d) {
					return options.nodeOutlineFillColor
						? class2darkenColor(options.nodeOutlineFillColor)
						: isNode
							? class2darkenColor(property)
							: defaultDarkenColor();
				})
				.style('color', function(d) {
					return options.nodeOutlineFillColor
						? class2darkenColor(options.nodeOutlineFillColor)
						: '#fff';
				});
		}
	}

	function appendInfoElementClass(cls, node) {
		appendInfoElement(cls, true, node);
	}

	function appendInfoElementProperty(cls, property, value) {
		appendInfoElement(cls, false, property, value);
	}

	function appendInfoElementRelationship(cls, relationship) {
		appendInfoElement(cls, false, relationship);
	}

	function appendNode() {
		return node
			.enter()
			.append('g')
			.attr('class', function(d) {
				var highlight,
					i,
					classes = 'node',
					label = d.labels[0];

				if (icon(d)) {
					classes += ' node-icon';
				}

				if (image(d)) {
					classes += ' node-image';
				}

				if (options.highlight) {
					for (i = 0; i < options.highlight.length; i++) {
						highlight = options.highlight[i];

						if (
							d.labels[0] === highlight.class &&
							d.properties[highlight.property] === highlight.value
						) {
							classes += ' node-highlighted';
							break;
						}
					}
				}

				return classes;
			})
			.on('click', function(d) {
				d.fx = d.fy = null;

				if (typeof options.onNodeClick === 'function') {
					options.onNodeClick(d);
				}
			})
			.on('dblclick', function(d) {
				stickNode(d);

				if (typeof options.onNodeDoubleClick === 'function') {
					options.onNodeDoubleClick(d);
				}
			})
			.on('mouseenter', function(d) {
				if (info) {
					updateInfo(d);
				}

				if (typeof options.onNodeMouseEnter === 'function') {
					options.onNodeMouseEnter(d);
				}
			})
			.on('mouseleave', function(d) {
				if (info) {
					clearInfo(d);
				}

				if (typeof options.onNodeMouseLeave === 'function') {
					options.onNodeMouseLeave(d);
				}
			})
			.call(
				d3
					.drag()
					.on('start', dragStarted)
					.on('drag', dragged)
					.on('end', dragEnded),
			);
	}

	function appendNodeToGraph() {
		var n = appendNode();

		appendRingToNode(n);
		appendOutlineToNode(n);

		if (options.icons) {
			appendTextToNode(n);
		}

		if (options.images) {
			appendImageToNode(n);
		}

		return n;
	}

	function appendOutlineToNode(node) {
		return node
			.append('circle')
			.attr('class', 'outline')
			.attr('r', options.nodeRadius)
			.style('fill', function(d) {
				return options.nodeOutlineFillColor
					? options.nodeOutlineFillColor
					: class2color(d.labels[0]);
			})
			.style('stroke', function(d) {
				return options.nodeOutlineFillColor
					? class2darkenColor(options.nodeOutlineFillColor)
					: class2darkenColor(d.labels[0]);
			})
			.append('title')
			.text(function(d) {
				return toString(d);
			});
	}

	function appendRingToNode(node) {
		return node
			.append('circle')
			.attr('class', 'ring')
			.attr('r', options.nodeRadius * 1.16)
			.append('title')
			.text(function(d) {
				return toString(d);
			});
	}

	function appendTextToNode(node) {
		return node
			.append('text')
			.attr('class', function(d) {
				return 'text' + (icon(d) ? ' icon' : '');
			})
			.attr('fill', '#ffffff')
			.attr('font-size', function(d) {
				return icon(d) ? options.nodeRadius + 'px' : '10px';
			})
			.attr('pointer-events', 'none')
			.attr('text-anchor', 'middle')
			.attr('y', function(d) {
				return icon(d) ? parseInt(Math.round(options.nodeRadius * 0.32)) + 'px' : '4px';
			})
			.html(function(d) {
				var _icon = icon(d);
				return _icon ? '&#x' + _icon : d.properties.name;
			});
	}

	function appendRandomDataToNode(d, maxNodesToGenerate) {
		var data = randomD3Data(d, maxNodesToGenerate);
		updateWithNeo4jData(data);
	}

	function appendRelationship() {
		return relationship
			.enter()
			.append('g')
			.attr('class', 'relationship')
			.on('dblclick', function(d) {
				if (typeof options.onRelationshipDoubleClick === 'function') {
					options.onRelationshipDoubleClick(d);
				}
			})
			.on('mouseenter', function(d) {
				if (info) {
					updateInfo(d);
				}
			});
	}

	function appendOutlineToRelationship(r) {
		return r
			.append('path')
			.attr('class', 'outline')
			.attr('fill', '#a5abb6')
			.attr('stroke', 'none');
	}

	function appendOverlayToRelationship(r) {
		return r.append('path').attr('class', 'overlay');
	}

	function appendTextToRelationship(r) {
		return r
			.append('text')
			.attr('class', 'text')
			.attr('fill', '#000000')
			.attr('font-size', '8px')
			.attr('pointer-events', 'none')
			.attr('text-anchor', 'middle')
			.text(function(d) {
				return d.type;
			});
	}

	function appendRelationshipToGraph() {
		var relationship = appendRelationship(),
			text = appendTextToRelationship(relationship),
			outline = appendOutlineToRelationship(relationship),
			overlay = appendOverlayToRelationship(relationship);

		return {
			outline: outline,
			overlay: overlay,
			relationship: relationship,
			text: text,
		};
	}

	function class2color(cls) {
		var color = classes2colors[cls];

		if (!color) {
			//            color = options.colors[Math.min(numClasses, options.colors.length - 1)];
			color = options.colors[numClasses % options.colors.length];
			classes2colors[cls] = color;
			numClasses++;
		}

		return color;
	}

	function class2darkenColor(cls) {
		return d3.rgb(class2color(cls)).darker(1);
	}

	function clearInfo() {
		info.html('');
	}

	function color() {
		return options.colors[(options.colors.length * Math.random()) << 0];
	}

	function colors() {
		// d3.schemeCategory10,
		// d3.schemeCategory20,
		return [
			'#68bdf6', // light blue
			'#6dce9e', // green #1
			'#faafc2', // light pink
			'#f2baf6', // purple
			'#ff928c', // light red
			'#fcea7e', // light yellow
			'#ffc766', // light orange
			'#405f9e', // navy blue
			'#a5abb6', // dark gray
			'#78cecb', // green #2,
			'#b88cbb', // dark purple
			'#ced2d9', // light gray
			'#e84646', // dark red
			'#fa5f86', // dark pink
			'#ffab1a', // dark orange
			'#fcda19', // dark yellow
			'#797b80', // black
			'#c9d96f', // pistacchio
			'#47991f', // green #3
			'#70edee', // turquoise
			'#ff75ea', // pink
		];
	}

	function contains(array, id) {
		var filter = array.filter(function(elem) {
			return elem.id === id;
		});

		return filter.length > 0;
	}

	function defaultColor() {
		return options.relationshipColor;
	}

	function defaultDarkenColor() {
		return d3.rgb(options.colors[options.colors.length - 1]).darker(1);
	}

	function dragEnded(d) {
		if (!d3.event.active) {
			simulation.alphaTarget(0);
		}

		if (typeof options.onNodeDragEnd === 'function') {
			options.onNodeDragEnd(d);
		}
	}

	function dragged(d) {
		stickNode(d);
	}

	function dragStarted(d) {
		if (!d3.event.active) {
			simulation.alphaTarget(0.3).restart();
		}

		d.fx = d.x;
		d.fy = d.y;

		if (typeof options.onNodeDragStart === 'function') {
			options.onNodeDragStart(d);
		}
	}

	function extend(obj1, obj2) {
		var obj = {};

		merge(obj, obj1);
		merge(obj, obj2);

		return obj;
	}

	function fontAwesomeIcons() {
		return {
			glass: 'f000',
			music: 'f001',
			search: 'f002',
			'envelope-o': 'f003',
			heart: 'f004',
			star: 'f005',
			'star-o': 'f006',
			user: 'f007',
			film: 'f008',
			'th-large': 'f009',
			th: 'f00a',
			'th-list': 'f00b',
			check: 'f00c',
			'remove,close,times': 'f00d',
			'search-plus': 'f00e',
			'search-minus': 'f010',
			'power-off': 'f011',
			signal: 'f012',
			'gear,cog': 'f013',
			'trash-o': 'f014',
			home: 'f015',
			'file-o': 'f016',
			'clock-o': 'f017',
			road: 'f018',
			download: 'f019',
			'arrow-circle-o-down': 'f01a',
			'arrow-circle-o-up': 'f01b',
			inbox: 'f01c',
			'play-circle-o': 'f01d',
			'rotate-right,repeat': 'f01e',
			refresh: 'f021',
			'list-alt': 'f022',
			lock: 'f023',
			flag: 'f024',
			headphones: 'f025',
			'volume-off': 'f026',
			'volume-down': 'f027',
			'volume-up': 'f028',
			qrcode: 'f029',
			barcode: 'f02a',
			tag: 'f02b',
			tags: 'f02c',
			book: 'f02d',
			bookmark: 'f02e',
			print: 'f02f',
			camera: 'f030',
			font: 'f031',
			bold: 'f032',
			italic: 'f033',
			'text-height': 'f034',
			'text-width': 'f035',
			'align-left': 'f036',
			'align-center': 'f037',
			'align-right': 'f038',
			'align-justify': 'f039',
			list: 'f03a',
			'dedent,outdent': 'f03b',
			indent: 'f03c',
			'video-camera': 'f03d',
			'photo,image,picture-o': 'f03e',
			pencil: 'f040',
			'map-marker': 'f041',
			adjust: 'f042',
			tint: 'f043',
			'edit,pencil-square-o': 'f044',
			'share-square-o': 'f045',
			'check-square-o': 'f046',
			arrows: 'f047',
			'step-backward': 'f048',
			'fast-backward': 'f049',
			backward: 'f04a',
			play: 'f04b',
			pause: 'f04c',
			stop: 'f04d',
			forward: 'f04e',
			'fast-forward': 'f050',
			'step-forward': 'f051',
			eject: 'f052',
			'chevron-left': 'f053',
			'chevron-right': 'f054',
			'plus-circle': 'f055',
			'minus-circle': 'f056',
			'times-circle': 'f057',
			'check-circle': 'f058',
			'question-circle': 'f059',
			'info-circle': 'f05a',
			crosshairs: 'f05b',
			'times-circle-o': 'f05c',
			'check-circle-o': 'f05d',
			ban: 'f05e',
			'arrow-left': 'f060',
			'arrow-right': 'f061',
			'arrow-up': 'f062',
			'arrow-down': 'f063',
			'mail-forward,share': 'f064',
			expand: 'f065',
			compress: 'f066',
			plus: 'f067',
			minus: 'f068',
			asterisk: 'f069',
			'exclamation-circle': 'f06a',
			gift: 'f06b',
			leaf: 'f06c',
			fire: 'f06d',
			eye: 'f06e',
			'eye-slash': 'f070',
			'warning,exclamation-triangle': 'f071',
			plane: 'f072',
			calendar: 'f073',
			random: 'f074',
			comment: 'f075',
			magnet: 'f076',
			'chevron-up': 'f077',
			'chevron-down': 'f078',
			retweet: 'f079',
			'shopping-cart': 'f07a',
			folder: 'f07b',
			'folder-open': 'f07c',
			'arrows-v': 'f07d',
			'arrows-h': 'f07e',
			'bar-chart-o,bar-chart': 'f080',
			'twitter-square': 'f081',
			'facebook-square': 'f082',
			'camera-retro': 'f083',
			key: 'f084',
			'gears,cogs': 'f085',
			comments: 'f086',
			'thumbs-o-up': 'f087',
			'thumbs-o-down': 'f088',
			'star-half': 'f089',
			'heart-o': 'f08a',
			'sign-out': 'f08b',
			'linkedin-square': 'f08c',
			'thumb-tack': 'f08d',
			'external-link': 'f08e',
			'sign-in': 'f090',
			trophy: 'f091',
			'github-square': 'f092',
			upload: 'f093',
			'lemon-o': 'f094',
			phone: 'f095',
			'square-o': 'f096',
			'bookmark-o': 'f097',
			'phone-square': 'f098',
			twitter: 'f099',
			'facebook-f,facebook': 'f09a',
			github: 'f09b',
			unlock: 'f09c',
			'credit-card': 'f09d',
			'feed,rss': 'f09e',
			'hdd-o': 'f0a0',
			bullhorn: 'f0a1',
			bell: 'f0f3',
			certificate: 'f0a3',
			'hand-o-right': 'f0a4',
			'hand-o-left': 'f0a5',
			'hand-o-up': 'f0a6',
			'hand-o-down': 'f0a7',
			'arrow-circle-left': 'f0a8',
			'arrow-circle-right': 'f0a9',
			'arrow-circle-up': 'f0aa',
			'arrow-circle-down': 'f0ab',
			globe: 'f0ac',
			wrench: 'f0ad',
			tasks: 'f0ae',
			filter: 'f0b0',
			briefcase: 'f0b1',
			'arrows-alt': 'f0b2',
			'group,users': 'f0c0',
			'chain,link': 'f0c1',
			cloud: 'f0c2',
			flask: 'f0c3',
			'cut,scissors': 'f0c4',
			'copy,files-o': 'f0c5',
			paperclip: 'f0c6',
			'save,floppy-o': 'f0c7',
			square: 'f0c8',
			'navicon,reorder,bars': 'f0c9',
			'list-ul': 'f0ca',
			'list-ol': 'f0cb',
			strikethrough: 'f0cc',
			underline: 'f0cd',
			table: 'f0ce',
			magic: 'f0d0',
			truck: 'f0d1',
			pinterest: 'f0d2',
			'pinterest-square': 'f0d3',
			'google-plus-square': 'f0d4',
			'google-plus': 'f0d5',
			money: 'f0d6',
			'caret-down': 'f0d7',
			'caret-up': 'f0d8',
			'caret-left': 'f0d9',
			'caret-right': 'f0da',
			columns: 'f0db',
			'unsorted,sort': 'f0dc',
			'sort-down,sort-desc': 'f0dd',
			'sort-up,sort-asc': 'f0de',
			envelope: 'f0e0',
			linkedin: 'f0e1',
			'rotate-left,undo': 'f0e2',
			'legal,gavel': 'f0e3',
			'dashboard,tachometer': 'f0e4',
			'comment-o': 'f0e5',
			'comments-o': 'f0e6',
			'flash,bolt': 'f0e7',
			sitemap: 'f0e8',
			umbrella: 'f0e9',
			'paste,clipboard': 'f0ea',
			'lightbulb-o': 'f0eb',
			exchange: 'f0ec',
			'cloud-download': 'f0ed',
			'cloud-upload': 'f0ee',
			'user-md': 'f0f0',
			stethoscope: 'f0f1',
			suitcase: 'f0f2',
			'bell-o': 'f0a2',
			coffee: 'f0f4',
			cutlery: 'f0f5',
			'file-text-o': 'f0f6',
			'building-o': 'f0f7',
			'hospital-o': 'f0f8',
			ambulance: 'f0f9',
			medkit: 'f0fa',
			'fighter-jet': 'f0fb',
			beer: 'f0fc',
			'h-square': 'f0fd',
			'plus-square': 'f0fe',
			'angle-double-left': 'f100',
			'angle-double-right': 'f101',
			'angle-double-up': 'f102',
			'angle-double-down': 'f103',
			'angle-left': 'f104',
			'angle-right': 'f105',
			'angle-up': 'f106',
			'angle-down': 'f107',
			desktop: 'f108',
			laptop: 'f109',
			tablet: 'f10a',
			'mobile-phone,mobile': 'f10b',
			'circle-o': 'f10c',
			'quote-left': 'f10d',
			'quote-right': 'f10e',
			spinner: 'f110',
			circle: 'f111',
			'mail-reply,reply': 'f112',
			'github-alt': 'f113',
			'folder-o': 'f114',
			'folder-open-o': 'f115',
			'smile-o': 'f118',
			'frown-o': 'f119',
			'meh-o': 'f11a',
			gamepad: 'f11b',
			'keyboard-o': 'f11c',
			'flag-o': 'f11d',
			'flag-checkered': 'f11e',
			terminal: 'f120',
			code: 'f121',
			'mail-reply-all,reply-all': 'f122',
			'star-half-empty,star-half-full,star-half-o': 'f123',
			'location-arrow': 'f124',
			crop: 'f125',
			'code-fork': 'f126',
			'unlink,chain-broken': 'f127',
			question: 'f128',
			info: 'f129',
			exclamation: 'f12a',
			superscript: 'f12b',
			subscript: 'f12c',
			eraser: 'f12d',
			'puzzle-piece': 'f12e',
			microphone: 'f130',
			'microphone-slash': 'f131',
			shield: 'f132',
			'calendar-o': 'f133',
			'fire-extinguisher': 'f134',
			rocket: 'f135',
			maxcdn: 'f136',
			'chevron-circle-left': 'f137',
			'chevron-circle-right': 'f138',
			'chevron-circle-up': 'f139',
			'chevron-circle-down': 'f13a',
			html5: 'f13b',
			css3: 'f13c',
			anchor: 'f13d',
			'unlock-alt': 'f13e',
			bullseye: 'f140',
			'ellipsis-h': 'f141',
			'ellipsis-v': 'f142',
			'rss-square': 'f143',
			'play-circle': 'f144',
			ticket: 'f145',
			'minus-square': 'f146',
			'minus-square-o': 'f147',
			'level-up': 'f148',
			'level-down': 'f149',
			'check-square': 'f14a',
			'pencil-square': 'f14b',
			'external-link-square': 'f14c',
			'share-square': 'f14d',
			compass: 'f14e',
			'toggle-down,caret-square-o-down': 'f150',
			'toggle-up,caret-square-o-up': 'f151',
			'toggle-right,caret-square-o-right': 'f152',
			'euro,eur': 'f153',
			gbp: 'f154',
			'dollar,usd': 'f155',
			'rupee,inr': 'f156',
			'cny,rmb,yen,jpy': 'f157',
			'ruble,rouble,rub': 'f158',
			'won,krw': 'f159',
			'bitcoin,btc': 'f15a',
			file: 'f15b',
			'file-text': 'f15c',
			'sort-alpha-asc': 'f15d',
			'sort-alpha-desc': 'f15e',
			'sort-amount-asc': 'f160',
			'sort-amount-desc': 'f161',
			'sort-numeric-asc': 'f162',
			'sort-numeric-desc': 'f163',
			'thumbs-up': 'f164',
			'thumbs-down': 'f165',
			'youtube-square': 'f166',
			youtube: 'f167',
			xing: 'f168',
			'xing-square': 'f169',
			'youtube-play': 'f16a',
			dropbox: 'f16b',
			'stack-overflow': 'f16c',
			instagram: 'f16d',
			flickr: 'f16e',
			adn: 'f170',
			bitbucket: 'f171',
			'bitbucket-square': 'f172',
			tumblr: 'f173',
			'tumblr-square': 'f174',
			'long-arrow-down': 'f175',
			'long-arrow-up': 'f176',
			'long-arrow-left': 'f177',
			'long-arrow-right': 'f178',
			apple: 'f179',
			windows: 'f17a',
			android: 'f17b',
			linux: 'f17c',
			dribbble: 'f17d',
			skype: 'f17e',
			foursquare: 'f180',
			trello: 'f181',
			female: 'f182',
			male: 'f183',
			'gittip,gratipay': 'f184',
			'sun-o': 'f185',
			'moon-o': 'f186',
			archive: 'f187',
			bug: 'f188',
			vk: 'f189',
			weibo: 'f18a',
			renren: 'f18b',
			pagelines: 'f18c',
			'stack-exchange': 'f18d',
			'arrow-circle-o-right': 'f18e',
			'arrow-circle-o-left': 'f190',
			'toggle-left,caret-square-o-left': 'f191',
			'dot-circle-o': 'f192',
			wheelchair: 'f193',
			'vimeo-square': 'f194',
			'turkish-lira,try': 'f195',
			'plus-square-o': 'f196',
			'space-shuttle': 'f197',
			slack: 'f198',
			'envelope-square': 'f199',
			wordpress: 'f19a',
			openid: 'f19b',
			'institution,bank,university': 'f19c',
			'mortar-board,graduation-cap': 'f19d',
			yahoo: 'f19e',
			google: 'f1a0',
			reddit: 'f1a1',
			'reddit-square': 'f1a2',
			'stumbleupon-circle': 'f1a3',
			stumbleupon: 'f1a4',
			delicious: 'f1a5',
			digg: 'f1a6',
			'pied-piper-pp': 'f1a7',
			'pied-piper-alt': 'f1a8',
			drupal: 'f1a9',
			joomla: 'f1aa',
			language: 'f1ab',
			fax: 'f1ac',
			building: 'f1ad',
			child: 'f1ae',
			paw: 'f1b0',
			spoon: 'f1b1',
			cube: 'f1b2',
			cubes: 'f1b3',
			behance: 'f1b4',
			'behance-square': 'f1b5',
			steam: 'f1b6',
			'steam-square': 'f1b7',
			recycle: 'f1b8',
			'automobile,car': 'f1b9',
			'cab,taxi': 'f1ba',
			tree: 'f1bb',
			spotify: 'f1bc',
			deviantart: 'f1bd',
			soundcloud: 'f1be',
			database: 'f1c0',
			'file-pdf-o': 'f1c1',
			'file-word-o': 'f1c2',
			'file-excel-o': 'f1c3',
			'file-powerpoint-o': 'f1c4',
			'file-photo-o,file-picture-o,file-image-o': 'f1c5',
			'file-zip-o,file-archive-o': 'f1c6',
			'file-sound-o,file-audio-o': 'f1c7',
			'file-movie-o,file-video-o': 'f1c8',
			'file-code-o': 'f1c9',
			vine: 'f1ca',
			codepen: 'f1cb',
			jsfiddle: 'f1cc',
			'life-bouy,life-buoy,life-saver,support,life-ring': 'f1cd',
			'circle-o-notch': 'f1ce',
			'ra,resistance,rebel': 'f1d0',
			'ge,empire': 'f1d1',
			'git-square': 'f1d2',
			git: 'f1d3',
			'y-combinator-square,yc-square,hacker-news': 'f1d4',
			'tencent-weibo': 'f1d5',
			qq: 'f1d6',
			'wechat,weixin': 'f1d7',
			'send,paper-plane': 'f1d8',
			'send-o,paper-plane-o': 'f1d9',
			history: 'f1da',
			'circle-thin': 'f1db',
			header: 'f1dc',
			paragraph: 'f1dd',
			sliders: 'f1de',
			'share-alt': 'f1e0',
			'share-alt-square': 'f1e1',
			bomb: 'f1e2',
			'soccer-ball-o,futbol-o': 'f1e3',
			tty: 'f1e4',
			binoculars: 'f1e5',
			plug: 'f1e6',
			slideshare: 'f1e7',
			twitch: 'f1e8',
			yelp: 'f1e9',
			'newspaper-o': 'f1ea',
			wifi: 'f1eb',
			calculator: 'f1ec',
			paypal: 'f1ed',
			'google-wallet': 'f1ee',
			'cc-visa': 'f1f0',
			'cc-mastercard': 'f1f1',
			'cc-discover': 'f1f2',
			'cc-amex': 'f1f3',
			'cc-paypal': 'f1f4',
			'cc-stripe': 'f1f5',
			'bell-slash': 'f1f6',
			'bell-slash-o': 'f1f7',
			trash: 'f1f8',
			copyright: 'f1f9',
			at: 'f1fa',
			eyedropper: 'f1fb',
			'paint-brush': 'f1fc',
			'birthday-cake': 'f1fd',
			'area-chart': 'f1fe',
			'pie-chart': 'f200',
			'line-chart': 'f201',
			lastfm: 'f202',
			'lastfm-square': 'f203',
			'toggle-off': 'f204',
			'toggle-on': 'f205',
			bicycle: 'f206',
			bus: 'f207',
			ioxhost: 'f208',
			angellist: 'f209',
			cc: 'f20a',
			'shekel,sheqel,ils': 'f20b',
			meanpath: 'f20c',
			buysellads: 'f20d',
			connectdevelop: 'f20e',
			dashcube: 'f210',
			forumbee: 'f211',
			leanpub: 'f212',
			sellsy: 'f213',
			shirtsinbulk: 'f214',
			simplybuilt: 'f215',
			skyatlas: 'f216',
			'cart-plus': 'f217',
			'cart-arrow-down': 'f218',
			diamond: 'f219',
			ship: 'f21a',
			'user-secret': 'f21b',
			motorcycle: 'f21c',
			'street-view': 'f21d',
			heartbeat: 'f21e',
			venus: 'f221',
			mars: 'f222',
			mercury: 'f223',
			'intersex,transgender': 'f224',
			'transgender-alt': 'f225',
			'venus-double': 'f226',
			'mars-double': 'f227',
			'venus-mars': 'f228',
			'mars-stroke': 'f229',
			'mars-stroke-v': 'f22a',
			'mars-stroke-h': 'f22b',
			neuter: 'f22c',
			genderless: 'f22d',
			'facebook-official': 'f230',
			'pinterest-p': 'f231',
			whatsapp: 'f232',
			server: 'f233',
			'user-plus': 'f234',
			'user-times': 'f235',
			'hotel,bed': 'f236',
			viacoin: 'f237',
			train: 'f238',
			subway: 'f239',
			medium: 'f23a',
			'yc,y-combinator': 'f23b',
			'optin-monster': 'f23c',
			opencart: 'f23d',
			expeditedssl: 'f23e',
			'battery-4,battery-full': 'f240',
			'battery-3,battery-three-quarters': 'f241',
			'battery-2,battery-half': 'f242',
			'battery-1,battery-quarter': 'f243',
			'battery-0,battery-empty': 'f244',
			'mouse-pointer': 'f245',
			'i-cursor': 'f246',
			'object-group': 'f247',
			'object-ungroup': 'f248',
			'sticky-note': 'f249',
			'sticky-note-o': 'f24a',
			'cc-jcb': 'f24b',
			'cc-diners-club': 'f24c',
			clone: 'f24d',
			'balance-scale': 'f24e',
			'hourglass-o': 'f250',
			'hourglass-1,hourglass-start': 'f251',
			'hourglass-2,hourglass-half': 'f252',
			'hourglass-3,hourglass-end': 'f253',
			hourglass: 'f254',
			'hand-grab-o,hand-rock-o': 'f255',
			'hand-stop-o,hand-paper-o': 'f256',
			'hand-scissors-o': 'f257',
			'hand-lizard-o': 'f258',
			'hand-spock-o': 'f259',
			'hand-pointer-o': 'f25a',
			'hand-peace-o': 'f25b',
			trademark: 'f25c',
			registered: 'f25d',
			'creative-commons': 'f25e',
			gg: 'f260',
			'gg-circle': 'f261',
			tripadvisor: 'f262',
			odnoklassniki: 'f263',
			'odnoklassniki-square': 'f264',
			'get-pocket': 'f265',
			'wikipedia-w': 'f266',
			safari: 'f267',
			chrome: 'f268',
			firefox: 'f269',
			opera: 'f26a',
			'internet-explorer': 'f26b',
			'tv,television': 'f26c',
			contao: 'f26d',
			'500px': 'f26e',
			amazon: 'f270',
			'calendar-plus-o': 'f271',
			'calendar-minus-o': 'f272',
			'calendar-times-o': 'f273',
			'calendar-check-o': 'f274',
			industry: 'f275',
			'map-pin': 'f276',
			'map-signs': 'f277',
			'map-o': 'f278',
			map: 'f279',
			commenting: 'f27a',
			'commenting-o': 'f27b',
			houzz: 'f27c',
			vimeo: 'f27d',
			'black-tie': 'f27e',
			fonticons: 'f280',
			'reddit-alien': 'f281',
			edge: 'f282',
			'credit-card-alt': 'f283',
			codiepie: 'f284',
			modx: 'f285',
			'fort-awesome': 'f286',
			usb: 'f287',
			'product-hunt': 'f288',
			mixcloud: 'f289',
			scribd: 'f28a',
			'pause-circle': 'f28b',
			'pause-circle-o': 'f28c',
			'stop-circle': 'f28d',
			'stop-circle-o': 'f28e',
			'shopping-bag': 'f290',
			'shopping-basket': 'f291',
			hashtag: 'f292',
			bluetooth: 'f293',
			'bluetooth-b': 'f294',
			percent: 'f295',
			gitlab: 'f296',
			wpbeginner: 'f297',
			wpforms: 'f298',
			envira: 'f299',
			'universal-access': 'f29a',
			'wheelchair-alt': 'f29b',
			'question-circle-o': 'f29c',
			blind: 'f29d',
			'audio-description': 'f29e',
			'volume-control-phone': 'f2a0',
			braille: 'f2a1',
			'assistive-listening-systems': 'f2a2',
			'asl-interpreting,american-sign-language-interpreting': 'f2a3',
			'deafness,hard-of-hearing,deaf': 'f2a4',
			glide: 'f2a5',
			'glide-g': 'f2a6',
			'signing,sign-language': 'f2a7',
			'low-vision': 'f2a8',
			viadeo: 'f2a9',
			'viadeo-square': 'f2aa',
			snapchat: 'f2ab',
			'snapchat-ghost': 'f2ac',
			'snapchat-square': 'f2ad',
			'pied-piper': 'f2ae',
			'first-order': 'f2b0',
			yoast: 'f2b1',
			themeisle: 'f2b2',
			'google-plus-circle,google-plus-official': 'f2b3',
			'fa,font-awesome': 'f2b4',
		};
	}

	function icon(d) {
		var code;

		if (options.iconMap && options.showIcons && options.icons) {
			if (options.icons[d.labels[0]] && options.iconMap[options.icons[d.labels[0]]]) {
				code = options.iconMap[options.icons[d.labels[0]]];
			} else if (options.iconMap[d.labels[0]]) {
				code = options.iconMap[d.labels[0]];
			} else if (options.icons[d.labels[0]]) {
				code = options.icons[d.labels[0]];
			}
		}

		return code;
	}

	function image(d) {
		var i, imagesForLabel, img, imgLevel, label, labelPropertyValue, property, value;

		if (options.images) {
			imagesForLabel = options.imageMap[d.labels[0]];

			if (imagesForLabel) {
				imgLevel = 0;

				for (i = 0; i < imagesForLabel.length; i++) {
					labelPropertyValue = imagesForLabel[i].split('|');

					switch (labelPropertyValue.length) {
						case 3:
							value = labelPropertyValue[2];
						/* falls through */
						case 2:
							property = labelPropertyValue[1];
						/* falls through */
						case 1:
							label = labelPropertyValue[0];
					}

					if (
						d.labels[0] === label &&
						(!property || d.properties[property] !== undefined) &&
						(!value || d.properties[property] === value)
					) {
						if (labelPropertyValue.length > imgLevel) {
							img = options.images[imagesForLabel[i]];
							imgLevel = labelPropertyValue.length;
						}
					}
				}
			}
		}

		return img;
	}

	function init(_selector, _options) {
		initIconMap();

		merge(options, _options);

		if (options.icons) {
			options.showIcons = true;
		}

		if (!options.minCollision) {
			options.minCollision = options.nodeRadius * 2;
		}

		initImageMap();

		selector = _selector;

		container = d3.select(selector);

		container.attr('class', 'neo4jd3').html('');

		if (options.infoPanel) {
			info = appendInfoPanel(container);
		}

		appendGraph(container);

		simulation = initSimulation();

		if (options.neo4jData) {
			loadNeo4jData(options.neo4jData);
		} else if (options.neo4jDataUrl) {
			loadNeo4jDataFromUrl(options.neo4jDataUrl);
		} else {
			console.error('Error: both neo4jData and neo4jDataUrl are empty!');
		}
	}

	function initIconMap() {
		Object.keys(options.iconMap).forEach(function(key, index) {
			var keys = key.split(','),
				value = options.iconMap[key];

			keys.forEach(function(key) {
				options.iconMap[key] = value;
			});
		});
	}

	function initImageMap() {
		var key, keys, selector;

		for (key in options.images) {
			if (options.images.hasOwnProperty(key)) {
				keys = key.split('|');

				if (!options.imageMap[keys[0]]) {
					options.imageMap[keys[0]] = [key];
				} else {
					options.imageMap[keys[0]].push(key);
				}
			}
		}
	}

	function initSimulation() {
		var simulation = d3
			.forceSimulation()
			//                           .velocityDecay(0.8)
			//                           .force('x', d3.force().strength(0.002))
			//                           .force('y', d3.force().strength(0.002))
			.force(
				'collide',
				d3
					.forceCollide()
					.radius(function(d) {
						return options.minCollision;
					})
					.iterations(2),
			)
			.force('charge', d3.forceManyBody())
			.force(
				'link',
				d3.forceLink().id(function(d) {
					return d.id;
				}),
			)
			.force(
				'center',
				d3.forceCenter(
					svg.node().parentElement.parentElement.clientWidth / 2,
					svg.node().parentElement.parentElement.clientHeight / 2,
				),
			)
			.on('tick', function() {
				tick();
			})
			.on('end', function() {
				if (options.zoomFit && !justLoaded) {
					justLoaded = true;
					zoomFit(2);
				}
			});

		return simulation;
	}

	function loadNeo4jData() {
		nodes = [];
		relationships = [];

		updateWithNeo4jData(options.neo4jData);
	}

	function loadNeo4jDataFromUrl(neo4jDataUrl) {
		nodes = [];
		relationships = [];

		d3.json(neo4jDataUrl, function(error, data) {
			if (error) {
				throw error;
			}

			updateWithNeo4jData(data);
		});
	}

	function merge(target, source) {
		Object.keys(source).forEach(function(property) {
			target[property] = source[property];
		});
	}

	function neo4jDataToD3Data(data) {
		return data;
		var graph = {
			nodes: [],
			relationships: [],
		};

		data.results.forEach(function(result) {
			result.data.forEach(function(data) {
				data.graph.nodes.forEach(function(node) {
					if (!contains(graph.nodes, node.id)) {
						graph.nodes.push(node);
					}
				});

				data.graph.relationships.forEach(function(relationship) {
					relationship.source = relationship.startNode;
					relationship.target = relationship.endNode;
					graph.relationships.push(relationship);
				});

				data.graph.relationships.sort(function(a, b) {
					if (a.source > b.source) {
						return 1;
					} else if (a.source < b.source) {
						return -1;
					} else {
						if (a.target > b.target) {
							return 1;
						}

						if (a.target < b.target) {
							return -1;
						} else {
							return 0;
						}
					}
				});

				for (var i = 0; i < data.graph.relationships.length; i++) {
					if (
						i !== 0 &&
						data.graph.relationships[i].source === data.graph.relationships[i - 1].source &&
						data.graph.relationships[i].target === data.graph.relationships[i - 1].target
					) {
						data.graph.relationships[i].linknum = data.graph.relationships[i - 1].linknum + 1;
					} else {
						data.graph.relationships[i].linknum = 1;
					}
				}
			});
		});

		return graph;
	}

	function randomD3Data(d, maxNodesToGenerate) {
		var data = {
				nodes: [],
				relationships: [],
			},
			i,
			label,
			node,
			numNodes = ((maxNodesToGenerate * Math.random()) << 0) + 1,
			relationship,
			s = size();

		for (i = 0; i < numNodes; i++) {
			label = randomLabel();

			node = {
				id: s.nodes + 1 + i,
				labels: [label],
				properties: {
					random: label,
				},
				x: d.x,
				y: d.y,
			};

			data.nodes[data.nodes.length] = node;

			relationship = {
				id: s.relationships + 1 + i,
				type: label.toUpperCase(),
				startNode: d.id,
				endNode: s.nodes + 1 + i,
				properties: {
					from: Date.now(),
				},
				source: d.id,
				target: s.nodes + 1 + i,
				linknum: s.relationships + 1 + i,
			};

			data.relationships[data.relationships.length] = relationship;
		}

		return data;
	}

	function randomLabel() {
		var icons = Object.keys(options.iconMap);
		return icons[(icons.length * Math.random()) << 0];
	}

	function rotate(cx, cy, x, y, angle) {
		var radians = (Math.PI / 180) * angle,
			cos = Math.cos(radians),
			sin = Math.sin(radians),
			nx = cos * (x - cx) + sin * (y - cy) + cx,
			ny = cos * (y - cy) - sin * (x - cx) + cy;

		return { x: nx, y: ny };
	}

	function rotatePoint(c, p, angle) {
		return rotate(c.x, c.y, p.x, p.y, angle);
	}

	function rotation(source, target) {
		return (Math.atan2(target.y - source.y, target.x - source.x) * 180) / Math.PI;
	}

	function size() {
		return {
			nodes: nodes.length,
			relationships: relationships.length,
		};
	}
	/*
    function smoothTransform(elem, translate, scale) {
        var animationMilliseconds = 5000,
            timeoutMilliseconds = 50,
            steps = parseInt(animationMilliseconds / timeoutMilliseconds);
        setTimeout(function() {
            smoothTransformStep(elem, translate, scale, timeoutMilliseconds, 1, steps);
        }, timeoutMilliseconds);
    }
    function smoothTransformStep(elem, translate, scale, timeoutMilliseconds, step, steps) {
        var progress = step / steps;
        elem.attr('transform', 'translate(' + (translate[0] * progress) + ', ' + (translate[1] * progress) + ') scale(' + (scale * progress) + ')');
        if (step < steps) {
            setTimeout(function() {
                smoothTransformStep(elem, translate, scale, timeoutMilliseconds, step + 1, steps);
            }, timeoutMilliseconds);
        }
    }
*/
	function stickNode(d) {
		d.fx = d3.event.x;
		d.fy = d3.event.y;
	}

	function tick() {
		tickNodes();
		tickRelationships();
	}

	function tickNodes() {
		if (node) {
			node.attr('transform', function(d) {
				return 'translate(' + d.x + ', ' + d.y + ')';
			});
		}
	}

	function tickRelationships() {
		if (relationship) {
			relationship.attr('transform', function(d) {
				var angle = rotation(d.source, d.target);
				return 'translate(' + d.source.x + ', ' + d.source.y + ') rotate(' + angle + ')';
			});

			tickRelationshipsTexts();
			tickRelationshipsOutlines();
			tickRelationshipsOverlays();
		}
	}

	function tickRelationshipsOutlines() {
		relationship.each(function(relationship) {
			var rel = d3.select(this),
				outline = rel.select('.outline'),
				text = rel.select('.text'),
				bbox = text.node().getBBox(),
				padding = 3;

			outline.attr('d', function(d) {
				var center = { x: 0, y: 0 },
					angle = rotation(d.source, d.target),
					textBoundingBox = text.node().getBBox(),
					textPadding = 5,
					u = unitaryVector(d.source, d.target),
					textMargin = {
						x: (d.target.x - d.source.x - (textBoundingBox.width + textPadding) * u.x) * 0.5,
						y: (d.target.y - d.source.y - (textBoundingBox.width + textPadding) * u.y) * 0.5,
					},
					n = unitaryNormalVector(d.source, d.target),
					rotatedPointA1 = rotatePoint(
						center,
						{
							x: 0 + (options.nodeRadius + 1) * u.x - n.x,
							y: 0 + (options.nodeRadius + 1) * u.y - n.y,
						},
						angle,
					),
					rotatedPointB1 = rotatePoint(
						center,
						{ x: textMargin.x - n.x, y: textMargin.y - n.y },
						angle,
					),
					rotatedPointC1 = rotatePoint(center, { x: textMargin.x, y: textMargin.y }, angle),
					rotatedPointD1 = rotatePoint(
						center,
						{ x: 0 + (options.nodeRadius + 1) * u.x, y: 0 + (options.nodeRadius + 1) * u.y },
						angle,
					),
					rotatedPointA2 = rotatePoint(
						center,
						{
							x: d.target.x - d.source.x - textMargin.x - n.x,
							y: d.target.y - d.source.y - textMargin.y - n.y,
						},
						angle,
					),
					rotatedPointB2 = rotatePoint(
						center,
						{
							x:
								d.target.x -
								d.source.x -
								(options.nodeRadius + 1) * u.x -
								n.x -
								u.x * options.arrowSize,
							y:
								d.target.y -
								d.source.y -
								(options.nodeRadius + 1) * u.y -
								n.y -
								u.y * options.arrowSize,
						},
						angle,
					),
					rotatedPointC2 = rotatePoint(
						center,
						{
							x:
								d.target.x -
								d.source.x -
								(options.nodeRadius + 1) * u.x -
								n.x +
								(n.x - u.x) * options.arrowSize,
							y:
								d.target.y -
								d.source.y -
								(options.nodeRadius + 1) * u.y -
								n.y +
								(n.y - u.y) * options.arrowSize,
						},
						angle,
					),
					rotatedPointD2 = rotatePoint(
						center,
						{
							x: d.target.x - d.source.x - (options.nodeRadius + 1) * u.x,
							y: d.target.y - d.source.y - (options.nodeRadius + 1) * u.y,
						},
						angle,
					),
					rotatedPointE2 = rotatePoint(
						center,
						{
							x:
								d.target.x -
								d.source.x -
								(options.nodeRadius + 1) * u.x +
								(-n.x - u.x) * options.arrowSize,
							y:
								d.target.y -
								d.source.y -
								(options.nodeRadius + 1) * u.y +
								(-n.y - u.y) * options.arrowSize,
						},
						angle,
					),
					rotatedPointF2 = rotatePoint(
						center,
						{
							x: d.target.x - d.source.x - (options.nodeRadius + 1) * u.x - u.x * options.arrowSize,
							y: d.target.y - d.source.y - (options.nodeRadius + 1) * u.y - u.y * options.arrowSize,
						},
						angle,
					),
					rotatedPointG2 = rotatePoint(
						center,
						{
							x: d.target.x - d.source.x - textMargin.x,
							y: d.target.y - d.source.y - textMargin.y,
						},
						angle,
					);

				return (
					'M ' +
					rotatedPointA1.x +
					' ' +
					rotatedPointA1.y +
					' L ' +
					rotatedPointB1.x +
					' ' +
					rotatedPointB1.y +
					' L ' +
					rotatedPointC1.x +
					' ' +
					rotatedPointC1.y +
					' L ' +
					rotatedPointD1.x +
					' ' +
					rotatedPointD1.y +
					' Z M ' +
					rotatedPointA2.x +
					' ' +
					rotatedPointA2.y +
					' L ' +
					rotatedPointB2.x +
					' ' +
					rotatedPointB2.y +
					' L ' +
					rotatedPointC2.x +
					' ' +
					rotatedPointC2.y +
					' L ' +
					rotatedPointD2.x +
					' ' +
					rotatedPointD2.y +
					' L ' +
					rotatedPointE2.x +
					' ' +
					rotatedPointE2.y +
					' L ' +
					rotatedPointF2.x +
					' ' +
					rotatedPointF2.y +
					' L ' +
					rotatedPointG2.x +
					' ' +
					rotatedPointG2.y +
					' Z'
				);
			});
		});
	}

	function tickRelationshipsOverlays() {
		relationshipOverlay.attr('d', function(d) {
			var center = { x: 0, y: 0 },
				angle = rotation(d.source, d.target),
				n1 = unitaryNormalVector(d.source, d.target),
				n = unitaryNormalVector(d.source, d.target, 50),
				rotatedPointA = rotatePoint(center, { x: 0 - n.x, y: 0 - n.y }, angle),
				rotatedPointB = rotatePoint(
					center,
					{ x: d.target.x - d.source.x - n.x, y: d.target.y - d.source.y - n.y },
					angle,
				),
				rotatedPointC = rotatePoint(
					center,
					{ x: d.target.x - d.source.x + n.x - n1.x, y: d.target.y - d.source.y + n.y - n1.y },
					angle,
				),
				rotatedPointD = rotatePoint(center, { x: 0 + n.x - n1.x, y: 0 + n.y - n1.y }, angle);

			return (
				'M ' +
				rotatedPointA.x +
				' ' +
				rotatedPointA.y +
				' L ' +
				rotatedPointB.x +
				' ' +
				rotatedPointB.y +
				' L ' +
				rotatedPointC.x +
				' ' +
				rotatedPointC.y +
				' L ' +
				rotatedPointD.x +
				' ' +
				rotatedPointD.y +
				' Z'
			);
		});
	}

	function tickRelationshipsTexts() {
		relationshipText.attr('transform', function(d) {
			var angle = (rotation(d.source, d.target) + 360) % 360,
				mirror = angle > 90 && angle < 270,
				center = { x: 0, y: 0 },
				n = unitaryNormalVector(d.source, d.target),
				nWeight = mirror ? 2 : -3,
				point = {
					x: (d.target.x - d.source.x) * 0.5 + n.x * nWeight,
					y: (d.target.y - d.source.y) * 0.5 + n.y * nWeight,
				},
				rotatedPoint = rotatePoint(center, point, angle);

			return (
				'translate(' +
				rotatedPoint.x +
				', ' +
				rotatedPoint.y +
				') rotate(' +
				(mirror ? 180 : 0) +
				')'
			);
		});
	}

	function toString(d) {
		var s = d.labels ? d.labels[0] : d.type;

		s += ' (<id>: ' + d.id;

		Object.keys(d.properties).forEach(function(property) {
			s += ', ' + property + ': ' + JSON.stringify(d.properties[property]);
		});

		s += ')';

		return s;
	}

	function unitaryNormalVector(source, target, newLength) {
		var center = { x: 0, y: 0 },
			vector = unitaryVector(source, target, newLength);

		return rotatePoint(center, vector, 90);
	}

	function unitaryVector(source, target, newLength) {
		var length =
			Math.sqrt(Math.pow(target.x - source.x, 2) + Math.pow(target.y - source.y, 2)) /
			Math.sqrt(newLength || 1);

		return {
			x: (target.x - source.x) / length,
			y: (target.y - source.y) / length,
		};
	}

	function updateWithD3Data(d3Data) {
		updateNodesAndRelationships(d3Data.nodes, d3Data.relationships);
	}

	function updateWithNeo4jData(neo4jData) {
		var d3Data = neo4jDataToD3Data(neo4jData);
		updateWithD3Data(d3Data);
	}

	function updateInfo(d) {
		clearInfo();

		if (d.labels) {
			appendInfoElementClass('class', d.labels[0]);
		} else {
			appendInfoElementRelationship('class', d.type);
		}

		appendInfoElementProperty('property', '&lt;id&gt;', d.id);

		Object.keys(d.properties).forEach(function(property) {
			appendInfoElementProperty('property', property, JSON.stringify(d.properties[property]));
		});
	}

	function updateNodes(n) {
		Array.prototype.push.apply(nodes, n);

		node = svgNodes.selectAll('.node').data(nodes, function(d) {
			return d.id;
		});
		var nodeEnter = appendNodeToGraph();
		node = nodeEnter.merge(node);
	}

	function updateNodesAndRelationships(n, r) {
		updateRelationships(r);
		updateNodes(n);

		simulation.nodes(nodes);
		simulation.force('link').links(relationships);
	}

	function updateRelationships(r) {
		Array.prototype.push.apply(relationships, r);

		relationship = svgRelationships.selectAll('.relationship').data(relationships, function(d) {
			return d.id;
		});

		var relationshipEnter = appendRelationshipToGraph();

		relationship = relationshipEnter.relationship.merge(relationship);

		relationshipOutline = svg.selectAll('.relationship .outline');
		relationshipOutline = relationshipEnter.outline.merge(relationshipOutline);

		relationshipOverlay = svg.selectAll('.relationship .overlay');
		relationshipOverlay = relationshipEnter.overlay.merge(relationshipOverlay);

		relationshipText = svg.selectAll('.relationship .text');
		relationshipText = relationshipEnter.text.merge(relationshipText);
	}

	function version() {
		return VERSION;
	}

	function zoomFit(transitionDuration) {
		var bounds = svg.node().getBBox(),
			parent = svg.node().parentElement.parentElement,
			fullWidth = parent.clientWidth,
			fullHeight = parent.clientHeight,
			width = bounds.width,
			height = bounds.height,
			midX = bounds.x + width / 2,
			midY = bounds.y + height / 2;

		if (width === 0 || height === 0) {
			return; // nothing to fit
		}

		svgScale = 0.85 / Math.max(width / fullWidth, height / fullHeight);
		svgTranslate = [fullWidth / 2 - svgScale * midX, fullHeight / 2 - svgScale * midY];

		svg.attr(
			'transform',
			'translate(' + svgTranslate[0] + ', ' + svgTranslate[1] + ') scale(' + svgScale + ')',
		);
		//        smoothTransform(svgTranslate, svgScale);
	}

	init(_selector, _options);

	return {
		appendRandomDataToNode: appendRandomDataToNode,
		neo4jDataToD3Data: neo4jDataToD3Data,
		randomD3Data: randomD3Data,
		size: size,
		updateWithD3Data: updateWithD3Data,
		updateWithNeo4jData: updateWithNeo4jData,
		version: version,
	};
}

export default Neo4jD3;
