diff --git a/Gruntfile.js b/Gruntfile.js index 851bcfa..7bcd099 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -32,6 +32,25 @@ module.exports = function(grunt) { ] }, + fontawesome: { + files: [ + { + cwd: 'bower_components/font-awesome/fonts', + src: '**.*', + dest: 'fonts/', + filter: 'isFile', + expand: true + }, + { + cwd: 'bower_components/font-awesome/css', + src: '**.min.css', + dest: 'css/', + filter: 'isFile', + expand: true + } + ] + }, + jquery: { src: 'bower_components/jquery/dist/jquery.min.js', dest: 'js/lib/jquery.min.js' @@ -40,6 +59,11 @@ module.exports = function(grunt) { d3: { src: 'bower_components/d3/d3.min.js', dest: 'js/lib/d3.min.js' + }, + + underscore: { + src: 'bower_components/underscore/underscore.js', + dest: 'js/lib/underscore.js' } }, @@ -55,6 +79,20 @@ module.exports = function(grunt) { 'css/index.css': 'less/index.less' } } + }, + + watch: { + options: { + livereload: true, + }, + less: { + files: ['less/*.less'], + tasks: ['less'] + }, + pty: { + files: ['src/pty.js'], + tasks: ['less'] + } } @@ -63,11 +101,10 @@ module.exports = function(grunt) { // Enable the grunt plugins grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-less'); + grunt.loadNpmTasks('grunt-contrib-watch'); - // Register Tasks - - // Test Task + // Tasks grunt.registerTask('build', ['copy', 'less']); grunt.registerTask('dist', ['build']); grunt.registerTask('default', ['build']); diff --git a/_includes/navbar.html b/_includes/navbar.html index 1923852..9f9f6e8 100644 --- a/_includes/navbar.html +++ b/_includes/navbar.html @@ -15,6 +15,8 @@ diff --git a/bower.json b/bower.json index e8af9e8..a7be23a 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "panama-network", - "version": "0.1.0", + "version": "0.2.0", "authors": [ "gregorio " ], @@ -22,7 +22,9 @@ "tests" ], "dependencies": { - "d3": "~3.4.2" + "d3": "~3.4.2", + "font-awesome": "~4.0.3", + "underscore": "~1.6.0" }, "devDependencies": { "bootstrap": "~3.1.1" diff --git a/css/font-awesome.min.css b/css/font-awesome.min.css new file mode 100644 index 0000000..449d6ac --- /dev/null +++ b/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.0.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.3333333333333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.2857142857142858em;text-align:center}.fa-ul{padding-left:0;margin-left:2.142857142857143em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;top:.14285714285714285em;text-align:center}.fa-li.fa-lg{left:-1.8571428571428572em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0,mirror=1);-webkit-transform:scale(-1,1);-moz-transform:scale(-1,1);-ms-transform:scale(-1,1);-o-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2,mirror=1);-webkit-transform:scale(1,-1);-moz-transform:scale(1,-1);-ms-transform:scale(1,-1);-o-transform:scale(1,-1);transform:scale(1,-1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-asc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-desc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-reply-all:before{content:"\f122"}.fa-mail-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"} \ No newline at end of file diff --git a/css/index.css b/css/index.css index d0bf3af..a039418 100644 --- a/css/index.css +++ b/css/index.css @@ -1 +1 @@ -.network-chart .node{fill:#c7f464}.network-chart .node-highlight{fill:#4ecdc4}.network-chart .node-center{fill:#4ecdc4;stroke:#ddd;stroke-width:3}.network-chart .node-clickable{cursor:pointer;stroke:#4ecdc4;stroke-width:3;stroke-opacity:1}.network-chart text.node-label{fill:#ddd;text-anchor:start;font-family:sans-serif;font-size:12px}.network-chart .link{stroke:#4ecdc4;stroke-opacity:.5;stroke-width:2;stroke-dasharray:5,5}.network-chart .center-link{stroke-opacity:.2;stroke-width:4;stroke-dasharray:none}.network-chart rect.background{fill:#556270}.network-chart .brand text{fill:#ddd;font-family:Roboto,sans-serif;font-size:11px} \ No newline at end of file +.network-chart .node{fill:#c7f464}.network-chart .node-highlight{fill:#4ecdc4;cursor:default}.network-chart .node-center{fill:#4ecdc4;stroke:#ddd;stroke-width:3}.network-chart .node-clickable{cursor:pointer;stroke:#4ecdc4;stroke-width:3;stroke-opacity:1}.network-chart text.node-label{fill:#ddd;text-anchor:start;font-family:sans-serif;font-size:12px}.network-chart .persona{fill:#75507b}.network-chart .candidato{fill:#729fcf}.network-chart .institucion{fill:#8ae234}.network-chart .link{stroke:#4ecdc4;stroke-opacity:.5;stroke-width:2}.network-chart .link{stroke:#4ecdc4;stroke-opacity:.5;stroke-width:2}.network-chart rect.background{fill:#556270;cursor:move}.network-chart .url-container{fill:#c7f464}.network-chart .url-container a{text-decoration:none}.network-chart .url-container .url-container-icon{font-family:FontAwesome}.network-chart .brand text{fill:#ddd;font-family:Roboto,sans-serif;font-size:11px}.network-chart .control-item rect{fill:#fff;fill-opacity:.75}.network-chart .control-item text{fill:#556270}.network-chart .control-highlight{cursor:pointer}.network-chart .legend .persona{fill:#75507b;stroke:#ddd;stroke-width:1}.network-chart .legend .candidato{fill:#729fcf;stroke:#ddd;stroke-width:1}.network-chart .legend .institucion{fill:#8ae234;stroke:#ddd;stroke-width:1}.network-chart .legend text{font-size:11px;fill:#ddd} \ No newline at end of file diff --git a/css/pty.css b/css/pty.css index 5071a68..c036bf0 100644 --- a/css/pty.css +++ b/css/pty.css @@ -1 +1 @@ -.network-chart .node{fill:#c7f464}.network-chart .node-highlight{fill:#4ecdc4}.network-chart .node-center{fill:#4ecdc4;stroke:#556270;stroke-width:3}.network-chart .node-clickable{cursor:pointer;stroke:#556270;stroke-width:2;stroke-opacity:.5}.network-chart text.node-label{fill:#333;text-anchor:start;font-family:sans-serif;font-size:12px}.network-chart .link{stroke:#4ecdc4;stroke-opacity:.5;stroke-width:2;stroke-dasharray:5,5}.network-chart .center-link{stroke-opacity:.2;stroke-width:4;stroke-dasharray:none}.network-chart rect.background{fill:#ddd}.network-chart .brand text{fill:#777;font-family:Roboto,sans-serif;font-size:11px} \ No newline at end of file +.network-chart .node{fill:#c7f464}.network-chart .node-highlight{fill:#4ecdc4;cursor:default}.network-chart .node-center{fill:#4ecdc4;stroke:#556270;stroke-width:3}.network-chart .node-clickable{cursor:pointer;stroke:#556270;stroke-width:2;stroke-opacity:.5}.network-chart text.node-label{fill:#333;text-anchor:start;font-family:sans-serif;font-size:12px}.network-chart .link{stroke:#4ecdc4;stroke-opacity:.5;stroke-width:2}.network-chart .url-container{fill:#000}.network-chart .url-container a{text-decoration:none}.network-chart .url-container .url-container-icon{font-family:FontAwesome}.network-chart rect.background{fill:#ddd;cursor:move}.network-chart .brand text{fill:#777;font-family:Roboto,sans-serif;font-size:11px}.network-chart .control-item rect{fill:#fff;fill-opacity:.75}.network-chart .control-item text{fill:#556270}.network-chart .control-highlight{cursor:pointer} \ No newline at end of file diff --git a/data/G.json b/data/G.json index dd4390b..c4eb700 100644 --- a/data/G.json +++ b/data/G.json @@ -13,7 +13,7 @@ "name": "E Navarro", "value": 20, "type": "candidato", - "numcn": 4 + "numcn": 3 }, { "id": "D", diff --git a/docs.md b/docs.md index bf84656..0f66561 100644 --- a/docs.md +++ b/docs.md @@ -3,10 +3,12 @@ layout: main title: Documentation --- + + # Network Chart
@@ -23,9 +25,12 @@ title: Documentation var chart01 = pty.chart.network() .width(width) .height(height) - .nodeRadius(10); + .nodeRadius(10) + .nodeLabel(function(d) { return d.name; }) + .nodeBaseURL(function(d) { return '{{site.baseurl}}/data/' + d.id + '.json'; }) + .nodeURL(function(d) { return '{{site.baseurl}}/pages/' + d.id; }); - d3.json('{{ site.baseurl }}/data/D.json', function(error, data) { + d3.json('{{ site.baseurl }}/data/A.json', function(error, data) { if (error) { return error; } @@ -85,7 +90,7 @@ The following script initiates a force chart using the data contained in the fil
+

Adding a Legend

+ +To add a legend, the user has to provide a list of all the node types as follows and submit the list to `.legendItems()`. + +{% highlight javascript %} + var legend = [ + {name: 'Persona', type: 'persona'}, + {name: 'Candidato', type: 'candidato'}, + {name: 'InstituciĆ³n', type: 'institucion'} + ]; + + var chart01 = pty.chart.network() + .legendItems(legend); +{% endhighlight %} + +The style of the circles representing each node type in the legend has to be set separately form the style for the nodes in the graph. This allows for instance to draw a stroke around the legend circles in order to differentiate them from the background without altering the style of the nodes of the graph. + +{% highlight javascript %} +// Legend + .legend { + + .persona { + fill: #75507b; + stroke: @grey-light; + stroke-width: 1; + } + + .candidato { + fill: #729fcf; + stroke: @grey-light; + stroke-width: 1; + } + + .institucion { + fill: #8ae234; + stroke: @grey-light; + stroke-width: 1; + } + + text { + font-size: 11px; + fill: @grey-light; + } + } +{% endhighlight %} + +
+ +
+ +
+ +

Adding Labels

@@ -219,16 +332,17 @@ d3.select('div#example05') .call(chart); {% endhighlight %} -
+
+ + +

Setting link to a new entity

+ +In the following example, when the user clicks on a node, a link appears on the bottom left of the chart. The text corresponds to the `.nodeLabel()` while the link can be set using `.nodeURL()`. + +{% highlight javascript %} +// Set the function to generate the URL of each node +var chart = pty.chart.network() + .nodeLabel(function(d) { return d.name; }) + .nodeURL(function(d) { return '{{site.baseurl}}/pages/' + d.id; }); + +// Bind the container div to the data and invoke the chart +d3.select('div#chart') + .data([data]) + .call(chart); +{% endhighlight %} + +
+ + diff --git a/embed.md b/embed.md index fc592bb..020de5a 100644 --- a/embed.md +++ b/embed.md @@ -6,6 +6,7 @@ title: Embed +
@@ -22,28 +23,7 @@ title: Embed .height(height) .nodeRadius(15) .nodeBaseURL(function(d) { return '{{site.baseurl}}/data/' + d.id + '.json'; }); - // .onClick(function(d) { - - // d.isclick = false; - - // var dataurl = "../data/" + d.id + ".json"; - - - // d3.json(dataurl, function(error, data) { - - // if (!error) { - - // var olddata = d3.select('div#chart01').data()[0]; - - // olddata.nodes = olddata.nodes.concat(data.nodes); - // olddata.links = olddata.links.concat(data.links); - - // d3.select('div#chart01') - // .data([olddata]) - // .call(chart01);} - // }); - // }); d3.select('div#chart01').data([data]).call(chart01); }); - \ No newline at end of file + diff --git a/example.html b/example.html new file mode 100644 index 0000000..26ea901 --- /dev/null +++ b/example.html @@ -0,0 +1,114 @@ + + + + + + \ No newline at end of file diff --git a/fonts/FontAwesome.otf b/fonts/FontAwesome.otf new file mode 100644 index 0000000..8b0f54e Binary files /dev/null and b/fonts/FontAwesome.otf differ diff --git a/fonts/fontawesome-webfont.eot b/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..7c79c6a Binary files /dev/null and b/fonts/fontawesome-webfont.eot differ diff --git a/fonts/fontawesome-webfont.svg b/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..45fdf33 --- /dev/null +++ b/fonts/fontawesome-webfont.svg @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/fontawesome-webfont.ttf b/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..e89738d Binary files /dev/null and b/fonts/fontawesome-webfont.ttf differ diff --git a/fonts/fontawesome-webfont.woff b/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..8c1748a Binary files /dev/null and b/fonts/fontawesome-webfont.woff differ diff --git a/index.md b/index.md index 1b91f09..ed539c5 100644 --- a/index.md +++ b/index.md @@ -6,6 +6,7 @@ title: Panama +
@@ -13,7 +14,6 @@ title: Panama
- + + + + + +# {{ page.title }} + + + + +
+
+
+
+
+ + + + diff --git a/pages/fullscreen-demo.md b/pages/fullscreen-demo.md new file mode 100644 index 0000000..8d319bb --- /dev/null +++ b/pages/fullscreen-demo.md @@ -0,0 +1,85 @@ +--- +layout: main +title: Fullscreen Demo +--- + + + + + + + + +# {{ page.title }} + +
+ +
+ + +
+
+
+
+
+ + + +
+ diff --git a/src/pty.js b/src/pty.js index d21851f..66bbc46 100644 --- a/src/pty.js +++ b/src/pty.js @@ -1,7 +1,26 @@ /* globals d3:false */ var pty = { - version: '0.1.0' // semver + version: '0.2.0' // semver +}; + +// SVG Transformations +// ------------------- +pty.svg = {}; + +// SVG Translation +pty.svg.translate = function(dx, dy) { + if ((arguments.length === 1) && (dx.length === 2)) { + dy = dx[1]; dx = dx[0]; + } + + return 'translate(' + [dx, dy] + ')'; +}; + +// SVG Scale +pty.svg.scale = function(sx, sy) { + if (arguments.length < 2) { sy = sx; } + return 'scale(' + [sx, sy] + ')'; }; // Charting Module @@ -21,130 +40,140 @@ pty.chart.network = function() { friction: 0.5, linkStrength: 0.2, linkDistance: 120, - // onClick: function(d, i) {}, nodeClass: function(d, i) { return ''; }, nodeBaseURL: function(d) { return ''; }, + nodeURL: function(d) { return ''; }, nodeLabel: function(d, i) { return ''; }, - fixCenter: true + fixCenter: true, + duration: 2000, + delay: 200, + zoomExtent: [0.5, 8], + controlsPosition: [10, 10], + refreshCallback: true, + zoomInCallback: true, + zoomOutCallback: true, + embedCallback: false, + fullscreenCallback: false, + zoomBehavior: d3.behavior.zoom(), + legendItems: false, + legend: {width: 80, margin: {top: 15, right: 10}} }; - + // Flag to know if the network chart has been drawn + var initialData = false; + // Flag to know if we are in fullscreen mode + var isScreenFull = false; // Charting Function function chart(selection) { selection.each(function(data) { var div = d3.select(this), - svg = div.selectAll('svg').data([data]); - - // Data Preprocessing - // ------------------ - - var idx = {}, - dataNodes = [], - k = 0; - - // Identify unique nodes and construct a dictionary of indices - data.nodes.forEach(function(d) { - if (!idx.hasOwnProperty(d.id)) { - idx[d.id] = k; - k += 1; - dataNodes.push(d); - } - }); - - dataNodes.forEach( function(d) { - d.internalcn = 0; - }); + svg = div.selectAll('svg').data([data]), + svgEnter = svg.enter().append('svg').call(chart.init); + + var gContainer = svg.select('g.network-chart'), + gBackground = gContainer.select('g.background'), + gLegend = gContainer.select('g.legend'), + gControls = gContainer.select('g.controls'), + gZoomCont = gContainer.select('g.zoom-container'), + gChart = gZoomCont.select('g.network'), + gLinks = gChart.select('g.links'), + gNodes = gChart.select('g.nodes'), + gLabels = gChart.select('g.labels'), + gNodeUrl = gControls.select('g.url-container'), + nodeUrlLabel = gNodeUrl.select('text.url-container'), + nodeUrlLink = gNodeUrl.select('a.url-container'); + + // Controls + var gControlRefresh = gControls.select('g.control-item.refresh'), + gControlZoomIn = gControls.select('g.control-item.zoom-in'), + gControlZoomOut = gControls.select('g.control-item.zoom-out'), + gControlEmbed = gControls.select('g.control-item.embed'), + gControlFullscreen = gControls.select('g.control-item.fullscreen'); + + // Process the data to remove duplicate links + var networkData = chart.parseNetworkData(data); + + // Toggle the icon on fullscreen + if (isScreenFull) { + gControlFullscreen.select('text').text('\uf066'); + } else { + gControlFullscreen.select('text').text('\uf065'); + } - // Identify unique links and set the source and target of each one - k = 0; - var idxLinks = {}, dataLinks = []; + // Zoom Behavior + // ------------ - data.links.forEach(function(d) { + // Mouse zoom callback + function onZoom() { + var d = d3.event.translate, + s = d3.event.scale; + gChart.attr('transform', pty.svg.translate(d) + pty.svg.scale(s)); + } - d.source = Math.min(idx[d.from], idx[d.to]); - d.target = Math.max(idx[d.from], idx[d.to]); - d.linkID = d.source + '-' + d.target; + // Configure the zoom behavior + me.zoomBehavior + .scaleExtent(me.zoomExtent) + .on('zoom', onZoom); - if (!idxLinks.hasOwnProperty(d.linkID)) { - idxLinks[d.linkID] = k; - dataLinks.push(d); - dataNodes[idx[d.from]].internalcn += 1; - dataNodes[idx[d.to]].internalcn += 1; - k += 1; - } - }); + // Apply the current zoom scale and translation + gChart + .attr('transform', pty.svg.translate(me.zoomBehavior.translate()) + pty.svg.scale(me.zoomBehavior.scale())); - dataNodes.forEach( function(d) { - d.isclick = (d.internalcn < d.numcn); - }); - // Fix the center node - dataNodes.forEach(function(d) { - if (d.id === data.root) { - d.x = me.width / 2; - d.y = me.height / 2; - d.fixed = me.fixCenter; - } - }); + // Bind the zoom behavior to the background rectangle + gBackground.select('rect.background').call(me.zoomBehavior); + // The click callback requests the network of the clicked node function onClick(d, i) { - d3.json(me.nodeBaseURL(d), function(error, data) { + d3.json(me.nodeBaseURL(d), function(error, nodeData) { var olddata; if (!error) { olddata = div.data()[0]; - olddata.nodes = olddata.nodes.concat(data.nodes); - olddata.links = olddata.links.concat(data.links); + olddata.nodes = olddata.nodes.concat(nodeData.nodes); + olddata.links = olddata.links.concat(nodeData.links); div.data([olddata]).call(chart); } }); + d.isclick = false; } - // Initialization - // -------------- - - // Initialize the SVG element - var svgEnter = svg.enter().append('svg') - .attr('width', me.width) - .attr('height', me.height) - .call(chart.init); - - var g = svg.select('g.network-chart'), - glinks = g.select('g.links'), - gnodes = g.select('g.nodes'), - glabels = g.select('g.labels'), - gbrand = g.select('g.brand'); - // Force layout // ------------ + // Configure and start the force layout var force = d3.layout.force() .charge(me.charge) .friction(me.friction) .linkStrength(me.linkStrength) - .size([me.width, me.height]); - - force.nodes(dataNodes) - .links(dataLinks) + .size([me.width, me.height]) + .nodes(networkData.nodes) + .links(networkData.links) .start(); + // Set the label to the root node + if (!svgEnter.empty()) { + nodeUrlLink.attr('xlink:href', me.nodeURL(networkData.rootNode)); + nodeUrlLabel.text(me.nodeLabel(networkData.rootNode)); + } + // Graphic Elements // ---------------- // Links // ----- - var links = glinks.selectAll('line.link') + var links = gLinks.selectAll('line.link') .data(force.links(), function(d) { return d.linkID; }); links.enter().append('line') .attr('class', 'link') .classed('center-link', function(d) { - return (d.from === data.root) || (d.to === data.root); + return (d.from === networkData.root) || (d.to === networkData.root); }); // Remove unused links @@ -153,69 +182,141 @@ pty.chart.network = function() { // Nodes // ----- - var circles = gnodes.selectAll('circle.node') + var circles = gNodes.selectAll('circle.node') .data(force.nodes(), function(d) { return d.id; }); // Update circles.classed('node-clickable', function(d) { return d.isclick; }); circles.enter().append('circle') - .attr('class', function(d, i) { - return me.nodeClass(d, i) + ' node'; - }) + .attr('class', function(d, i) { return me.nodeClass(d, i) + ' node'; }) .attr('r', me.nodeRadius) .classed('node', true) .classed('node-center', function(d) { return d.id === data.root; }) .classed('node-clickable', function(d) { return (d.id !== data.root) && (d.isclick); }) - .on('mouseover', function(d) { - d3.select(this).classed('node-highlight', true); - }) - .on('mouseout', function(d) { - d3.select(this).classed('node-highlight', false); - }) + .on('mouseover', function(d) { d3.select(this).classed('node-highlight', true); }) + .on('mouseout', function(d) { d3.select(this).classed('node-highlight', false); }) .on('click', function(d, i) { - if (d3.select(this).classed('node-clickable')) { - onClick(d, i); - } + if (d3.select(this).classed('node-clickable')) { onClick(d, i); } + // Update the title and link of the lower left label + nodeUrlLink.attr('xlink:href', me.nodeURL(d)); + nodeUrlLabel.text('' + me.nodeLabel(d)); }); - circles.call(force.drag); - circles.exit().remove(); + // Drop the circles on exit + circles.exit().transition() + .delay(me.delay) + .duration(me.duration) + .attr('cy', me.width) + .remove(); // Labels // ------ - var words = glabels.selectAll('text.node-label') + // Select the labels + var nodeLabels = gLabels.selectAll('text.node-label') .data(force.nodes(), function(d) { return d.id; }); - words.enter().append('text') + // Append the text elements on enter + nodeLabels.enter().append('text') .text(me.nodeLabel) .attr('x', function(d, i) { return d.x + me.nodeRadius; }) .attr('y', function(d, i) { return d.y - me.nodeRadius; }) .attr('class', 'node-label'); - words.exit().remove(); + // Remove the labels on exit + nodeLabels.exit().remove(); - // Force On Tick + // Update the position of nodes, labels and links on each tick event + // of the force layout. force.on('tick', function() { links - .attr("x1", function(d) { return d.source.x; }) - .attr("y1", function(d) { return d.source.y; }) - .attr("x2", function(d) { return d.target.x; }) - .attr("y2", function(d) { return d.target.y; }); + .attr('x1', function(d) { return d.source.x; }) + .attr('y1', function(d) { return d.source.y; }) + .attr('x2', function(d) { return d.target.x; }) + .attr('y2', function(d) { return d.target.y; }); circles .attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }); - words + nodeLabels .attr('x', function(d, i) { return d.x + me.nodeRadius; }) .attr('y', function(d, i) { return d.y - me.nodeRadius; }); - }); + // Control Callbacks + //------------------ + + // Refresh + gControlRefresh + .on('mouseover', function(d) { d3.select(this).classed('control-highlight','true'); }) + .on('mouseout', function(d) { d3.select(this).classed('control-highlight','false'); }) + .on('click', function() { + var firstData = { + root: data.root, + nodes: data.nodes.filter(function(d) { return d.__first; }), + links: data.links.filter(function(d) { return d.__first; }) + }; + div.data([firstData]).call(chart); + }); + + // Zoom In + gControlZoomIn + .on('mouseover', function(d) { d3.select(this).classed('control-highlight','true'); }) + .on('mouseout', function(d) { d3.select(this).classed('control-highlight','false'); }) + .on('click', function() { + // Compute the new zoom level and update the zoom behavior + var newScale = d3.min([me.zoomBehavior.scale() + 0.2, me.zoomExtent[1]]), + translate = pty.svg.translate(me.zoomBehavior.translate()); + + me.zoomBehavior.scale(newScale); + + // Scale the chart group + gChart.transition().duration(me.duration) + .attr('transform', translate + pty.svg.scale(newScale)); + }); + + // Zoom Out + gControlZoomOut + .on('mouseover', function(d) { d3.select(this).classed('control-highlight','true'); }) + .on('mouseout', function(d) { d3.select(this).classed('control-highlight','false'); }) + .on('click', function() { + // Compute the new zoom level and update the zoom behavior + var newScale = d3.max([me.zoomBehavior.scale() - 0.2, me.zoomExtent[0]]), + translate = pty.svg.translate(me.zoomBehavior.translate()); + + me.zoomBehavior.scale(newScale); + + // Scale the chart group + gChart.transition().duration(me.duration) + .attr('transform', translate + pty.svg.scale(newScale)); + }); + + // Embed + if (me.embedCallback) { + gControlEmbed + .on('mouseover', function(d) { d3.select(this).classed('control-highlight','true'); }) + .on('mouseout', function(d) { d3.select(this).classed('control-highlight','false'); }) + .on('click', me.embedCallback); + } else { + gControlEmbed.remove(); + } + + // Fullscreen + if (me.fullscreenCallback) { + gControlFullscreen + .on('mouseover', function(d) { d3.select(this).classed('control-highlight','true'); }) + .on('mouseout', function(d) { d3.select(this).classed('control-highlight','false'); }) + .on('click', function() { + isScreenFull = !isScreenFull; + d3.select(this).call(me.fullscreenCallback); + }); + } else { + gControlFullscreen.remove(); + } }); } @@ -224,35 +325,207 @@ pty.chart.network = function() { selection.each(function(data) { var svgEnter = d3.select(this), - gcont = svgEnter.append('g').attr('class', 'network-chart'); - - // Add a background group and rectangle - gcont.append('g') - .attr('class', 'background') - .append('rect') + gContainer = svgEnter.append('g').attr('class', 'network-chart'), + gBackground = gContainer.append('g').attr('class', 'background'), + gBrand = gContainer.append('g').attr('class', 'brand'), + gZoomCont = gContainer.append('g').attr('class', 'zoom-container'), + gControls = gContainer.append('g').attr('class', 'controls'), + gChart = gZoomCont.append('g').attr('class', 'network'), + gLegend = gContainer.append('g').attr('class','legend'); + + // Set the SVG element width and height + svgEnter.attr('width', me.width).attr('height', me.height); + + // Background + gBackground.append('rect') .attr('width', me.width) .attr('height', me.height) .attr('class', 'background'); - gcont.append('g').attr('class', 'links'); - gcont.append('g').attr('class', 'nodes'); - gcont.append('g').attr('class', 'labels'); + // Nodes, Links and Labels + gChart.append('g').attr('class', 'links'); + gChart.append('g').attr('class', 'nodes'); + gChart.append('g').attr('class', 'labels'); + + // Controls + // -------- + + gControls + .attr('transform', pty.svg.translate(me.controlsPosition)); - var gbrand = gcont.append('g') - .attr('class', 'brand') - .attr('transform', 'translate(' + [me.width - 4, me.height - 4] + ')'); + // Refresh Button + var controls = [ + {name: 'refresh', icon: '\uf0e2', callback: me.refreshCallback}, + {name: 'zoom-in', icon: '\uf067', callback: me.zoomInCallback}, + {name: 'zoom-out', icon: '\uf068', callback: me.zoomOutCallback}, + {name: 'embed', icon: '\uf121', callback: me.embedCallback}, + {name: 'fullscreen', icon: '\uf065', callback: me.fullscreenCallback} + ]; - var brandLabel = gbrand.append('a') + var activeControls = controls.filter(function(d) { + return d.callback; + }); + + // Controls Scale + var yScale = d3.scale.ordinal() + .domain(d3.range(activeControls.length)) + .rangeBands([0, 30 * activeControls.length], 0.1); + + var bgSize = yScale.rangeBand(); + + var gControlItem = gControls.selectAll('g.control-item') + .data(activeControls); + + gControlItem.enter().append('g') + .attr('class', function(d) { return 'control-item ' + d.name; }) + .attr('transform', function(d, i) { return pty.svg.translate(0, yScale(i)); }); + + gControlItem.append('rect') + .attr('width', bgSize) + .attr('height', bgSize) + .attr('rx', 0.25 * bgSize) + .attr('ry', 0.25 * bgSize); + + var iconLabel = gControlItem.append('text') + .attr('class', function(d) { return d.name; }) + .attr('font-family', 'FontAwesome') + .attr('fill', 'white') + .text(function(d) { return d.icon; }); + + iconLabel + .attr('x', function() { return 0.5 * (bgSize - this.getBBox().width) - 1; }) + .attr('y', function() { return 0.5 * (bgSize + this.getBBox().height) - 2; }); + + // Brand + // ----- + gBrand.attr('transform', pty.svg.translate(me.width - 4, me.height - 4)); + + var brandLabel = gBrand.append('a') .attr('xlink:href', 'http://www.masega.co') .append('text') .attr('class', 'masega-brand') .attr('text-anchor', 'end') .text('masega.co'); + + // Node Label and Link + // ------------------- + var gNodeUrl = gControls.append('g') + .attr('class','url-container') + .attr('transform', pty.svg.translate(10, me.height - 16)); + + // Link + var gNodeLink = gNodeUrl.append('a') + .attr('class', 'url-container'); + + // Icon + gNodeLink.append('text') + .attr('class', 'url-container-icon') + .text('\uf0c1'); + + // Label + gNodeLink.append('text') + .attr('x', 20) + .attr('class', 'url-container') + .text(''); + + // Legend + var legendX = me.width - me.legend.width - me.legend.margin.right, + legendY = me.legend.margin.top; + + gLegend.attr('transform', pty.svg.translate(legendX, legendY)); + + var gLegendItems = gLegend.selectAll('g.legend-item') + .data(me.legendItems); + + var legendScale = d3.scale.ordinal() + .domain(d3.range(me.legendItems.length)) + .rangePoints([0, 15 * me.legendItems.length]); + + gLegendItems.enter().append('g') + .attr('class', 'legend-item') + .attr('transform', function(d, i) { + return pty.svg.translate(0, legendScale(i)); + }); + + gLegendItems.append('circle') + .attr('r', 6) + .attr('class', function(d) { return d.type; }); + + var legendItemLabel = gLegendItems.append('text') + .attr('class', 'legend-label') + .attr('x', 10) + .text(function(d) { return d.name; }); + + legendItemLabel + .attr('y', function() { return 0.35 * this.getBBox().height; }); + }); }; + chart.parseNetworkData = function(jsonData) { + + var data = { + root: jsonData.root, + rootNode: null, + nodes: [], + links: [] + }; + + if (!initialData) { + jsonData.nodes.forEach(function(d) { d.__first = true; }); + jsonData.links.forEach(function(d) { d.__first = true; }); + initialData = true; + } + + var idxNodes = {}, + idxLinks = {}, + k = 0; + + // Identify unique nodes and construct a dictionary of indices + jsonData.nodes.forEach(function(d) { + if (!idxNodes.hasOwnProperty(d.id)) { + idxNodes[d.id] = k; + k += 1; + data.nodes.push(d); + } + }); + + data.nodes.forEach( function(d) { d.internalcn = 0; }); + + // Identify unique links and set the source and target of each one + k = 0; + jsonData.links.forEach(function(d) { + + d.source = Math.min(idxNodes[d.from], idxNodes[d.to]); + d.target = Math.max(idxNodes[d.from], idxNodes[d.to]); + d.linkID = d.source + '-' + d.target; + + if (!idxLinks.hasOwnProperty(d.linkID)) { + idxLinks[d.linkID] = k; + data.links.push(d); + data.nodes[idxNodes[d.from]].internalcn += 1; + data.nodes[idxNodes[d.to]].internalcn += 1; + k += 1; + } + }); + + data.nodes.forEach( function(d) { d.isclick = (d.internalcn < d.numcn); }); + + // Set the position of the root node and fix it to the center + data.nodes.forEach(function(d) { + if (d.id === data.root) { + d.x = me.width / 2; + d.y = me.height / 2; + d.fixed = me.fixCenter; + data.rootNode = d; + } + }); + + return data; + }; // Accessor Methods + // ---------------- // Generate Accessor Methods function createAccessor(attr) {