diff --git a/assets/css/portfolio.css b/assets/css/portfolio.css new file mode 100644 index 0000000..f63ec40 --- /dev/null +++ b/assets/css/portfolio.css @@ -0,0 +1,322 @@ +@charset "UTF-8"; +@import url("https://fontlibrary.org/face/d-din"); +body { + margin: 0; + overflow: hidden; + font-family: "DDINRegular", helvetica, sans-serif; + font-weight: light; + height: 100vh; + background: black; + font-size: 12pt; } + +a, a:link, a:visited { + color: blue; + text-decoration: none; + cursor: pointer; + /* Not all links have a href, so force pointer for those too */ } + +a:hover { + text-decoration: underline; } + +a:active { + color: red; } + +svg { + width: 100%; + height: 100%; + cursor: grab; } + +svg.dragging { + cursor: grabbing; } + +g.node { + cursor: pointer; + stroke: blue; + stroke-width: 0; + transition: stroke-width .5s, opacity 0s; + transition-delay: 0s, 1s; + opacity: 0; } + .graphInitialised g.node { + opacity: 1; } + g.node.centeredNode { + opacity: 1; + transition: stroke-width .5s, opacity 0s; + transition-delay: 0s, 0s; } + g.node.visibleNode { + pointer-events: auto; + transition-delay: 0s, 0s; } + g.node .highlightCircle { + fill: none; + stroke-width: 0px; + stroke-dasharray: 3 2; } + g.node.typeHighlight .highlightCircle { + stroke-width: 1px; + stroke: yellow; } + g.node:hover .highlightCircle { + stroke-width: 1px; + stroke: yellow; } + g.node:hover .nodeBg { + fill: yellow; + stroke: yellow; } + g.node.drag { + cursor: grabbing; } + g.node text.nodeType { + text-anchor: middle; + font-size: 10pt; + display: none; } + g.node text.nodeYear { + transition: transform .5s; + text-anchor: middle; + font-size: 8pt; } + g.node text.nodeTitle { + text-anchor: middle; + font-size: 10pt; } + g.node text.nodeTitle tspan { + text-anchor: middle; } + g.node.ImageObject text.nodeTitle { + display: none; } + +.relationship { + display: none; + opacity: 0; + transition: opacity .5s; } + body.graphInitialised .relationship { + opacity: 1; } + .relationship.activeLink, .relationship.hoverLink { + display: block; } + .relationship.activeLink text, .relationship.hoverLink text { + display: block; + fill: #999; } + .relationship line { + fill: none; + stroke: #999; + stroke-width: 2px; } + .relationship text { + fill: black; + font-size: 9pt; + display: none; } + .relationship.activeLink line { + stroke: white; } + .relationship.activeLink text { + fill: white; + display: block; } + .relationship.breadcrumbLink { + display: block; } + .relationship.breadcrumbLink line { + stroke: yellow !important; + display: block; } + .relationship.breadcrumbLink text { + fill: yellow !important; + display: block; } + +circle.nodeBg { + fill: white; + stroke-width: 3px; + stroke: white; } + .visibleNode circle.nodeBg { + stroke: yellow; } + .centeredNode circle.nodeBg { + fill: yellow; + stroke: yellow; } + +text { + text-anchor: middle; } + +.drag { + fill: #00f; } + +.relationship.address line { + /* stroke:#90F7FE; */ } + +.relationship.location line { + /* stroke:darkgreen; */ } + +.relationship.contributor line { + /* stroke:orange; */ + /* stroke-width:.4em; */ } + +#nodeDetails { + position: absolute; + top: 0; + right: -740px; + width: 740px; + background: white; + padding: 20px; + /* opacity: 0; */ + transition: opacity 1s, right 1s; + height: 100%; + overflow-y: auto; + box-sizing: border-box; } + #nodeDetails #nodeDetailsScaler { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 20px; + cursor: col-resize; + padding: 5px; } + #nodeDetails #nodeDetailsScaler #scalarbar { + height: 100%; + border-right: solid 1px black; + border-left: solid 1px #333; + width: 0; } + #nodeDetails .nodeType { + font-size: 80%; + text-transform: uppercase; + color: #999; + margin-left: 10px; } + #nodeDetails .nodeType:hover { + cursor: pointer; + color: blue; } + #nodeDetails ul.breadcrumbs { + list-style: none; + margin: 0; + padding: 0; } + #nodeDetails ul.breadcrumbs li { + display: inline-block; } + #nodeDetails ul.breadcrumbs li:not(:first-child)::before { + content: "::"; + color: black; + text-decoration: none; + margin: 0 10px; } + #nodeDetails ul.breadcrumbs .crumb { + cursor: pointer; + color: blue; } + #nodeDetails ul.breadcrumbs .crumb:hover { + text-decoration: underline; } + #nodeDetails span.nodeYear { + margin-left: 15px; } + #nodeDetails span.nodeYear::before { + content: '('; } + #nodeDetails span.nodeYear::after { + content: ')'; } + #nodeDetails h4 { + border-top: solid 1px black; + padding-top: 40px; + font-size: 120%; } + #nodeDetails dl:last-child { + margin-bottom: 250px; } + #nodeDetails dt { + float: left; + width: 170px; + font-weight: bold; + min-height: 25px; + clear: both; } + #nodeDetails dd { + min-height: 30px; + margin-top: 5px; } + #nodeDetails dd:not(.nodeTitleNr1) { + margin-left: 170px; } + #nodeDetails dt.dt-description { + float: none; } + #nodeDetails dd.dd-description { + margin-left: 0; } + #nodeDetails dd.dd-contentobject, #nodeDetails dd.dd-embed { + margin-left: 0; } + #nodeDetails dd.dd-contentobject object { + width: 100%; } + #nodeDetails dd.dd-contentobject video { + width: 100%; } + #nodeDetails dd.dd-embed embed { + width: 100%; + min-height: 500px; } + +body.detailsOpen #nodeDetails { + /* opacity:1; */ + right: 0px; } + +body.detailsOpen svg#portfolioGraph { + right: 370px; } + +svg#portfolioGraph { + position: relative; + right: 0; + top: 0; + transition: right 1s, top 1s; } + +#graphControls { + position: fixed; + left: 10px; + top: 10px; + height: auto; + background: white; + padding: 10px; } + #graphControls .typeCount::before { + content: "("; + padding-left: 5px; } + #graphControls .typeCount::after { + content: ")"; } + #graphControls ul#typeLinks { + margin: 0; + padding: 0; + display: inline-block; } + #graphControls ul#typeLinks li { + list-style: none; + display: inline-block; + margin: 10px 10px; + cursor: pointer; } + #graphControls #showMoreTypeLinks { + display: inline-block; + width: 20px; + text-align: right; + cursor: pointer; } + #graphControls #showMoreTypeLinks::before { + content: "≡"; + font-size: 150%; + position: relative; + top: 4px; } + #graphControls #showMoreTypeLinks:hover { + color: red; + text-decoration: none; } + .showMoreLinks #graphControls #showMoreTypeLinks { + pointer-events: none; } + .showMoreLinks #graphControls #showMoreTypeLinks::before { + content: "x"; } + #graphControls #moreTypeLinks { + position: absolute; + right: 0; + background: white; + list-style: none; + padding: 20px 30px; + text-align: left; + margin: 0; + display: none; } + .showMoreLinks #graphControls #moreTypeLinks { + display: block; } + #graphControls .typeJump { + font-weight: bold; } + +@media (max-width: 1496px) { + body { + overflow-y: hidden; + overflow-x: hidden; + font-size: 16pt; } + body.detailsOpen { + overflow-y: auto; } + svg#portfolioGraph { + position: fixed; + top: 0; + left: -35vw; + width: 160vw; + height: 100vh; + z-index: -5; } + #nodeDetails { + /* display:none; */ + position: static; + width: 100%; + box-sizing: border-box; + background: white; + padding: 20px; + /* opacity: 0; */ + height: auto; + min-height: 100vh; + margin-top: 100vh; + transition: margin 1s; } + body.detailsOpen #nodeDetails { + displaY: block; + margin-top: calc( 100vh + -30vh); + position: relative; + z-index: 1000; } + body.detailsOpen svg#portfolioGraph { + right: 0; + top: -15vh; } } diff --git a/assets/image/after_effects1.png b/assets/image/after_effects1.png deleted file mode 100644 index 7a7b525..0000000 --- a/assets/image/after_effects1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7070fdc4b3907c5d7c5bc0746a1c301cd8120739e7e5cadf5456f4f8ee70b54 -size 2792346 diff --git a/assets/image/after_effects2.png b/assets/image/after_effects2.png deleted file mode 100644 index a6f5a48..0000000 --- a/assets/image/after_effects2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf648c1b518e8148fcd27543a4390b9e3c59967aa419ca4644abc5ca8ebcddc8 -size 3634983 diff --git a/assets/image/after_effects3.png b/assets/image/after_effects3.png deleted file mode 100644 index 4066935..0000000 --- a/assets/image/after_effects3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40f17b222bfdf004795136f2a1431ccf4a9963a18d9714aab57fd458f8dd90c7 -size 2752945 diff --git a/assets/image/after_effects4.png b/assets/image/after_effects4.png deleted file mode 100644 index 7c5a30a..0000000 --- a/assets/image/after_effects4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d16e9803ce6230b5c5ceb86d0ca0a2fe90f56d128d2fba552e374de8b31a277 -size 404166 diff --git a/assets/image/bep1.png b/assets/image/bep1.png deleted file mode 100644 index b7be317..0000000 --- a/assets/image/bep1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1dd29ee337df00954f81ed13019bf02727f36774431703e8ae1fa740a68f8a61 -size 340878 diff --git a/assets/image/bep2.jpg b/assets/image/bep2.jpg deleted file mode 100644 index 1ada5c6..0000000 --- a/assets/image/bep2.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b122d0470ddf3e4676b37e176e7fe309c1de287b98e98ad901689a5eddad8ca7 -size 105203 diff --git a/assets/image/bep2.png b/assets/image/bep2.png deleted file mode 100644 index 36d7136..0000000 --- a/assets/image/bep2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5c999819ea658cfe2ad9aa815bf5e5563b7896cdb6b92d6cbf6071d1808ea2d0 -size 884783 diff --git a/assets/image/bep3.png b/assets/image/bep3.png deleted file mode 100644 index 82c948d..0000000 --- a/assets/image/bep3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50a51328b5c82b28c84d3ea0267caee917635a7d4077a026a8320807472ac380 -size 902789 diff --git a/assets/image/bernard1.jpg b/assets/image/bernard1.jpg deleted file mode 100644 index a3d5e06..0000000 --- a/assets/image/bernard1.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86c084f2dc723b82b90c8ca446cfffa8976aafc4579d7355d94950f3dbf3f681 -size 123459 diff --git a/assets/image/bernard1.png b/assets/image/bernard1.png deleted file mode 100644 index 6ffb115..0000000 --- a/assets/image/bernard1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8d666c235e6bdb40e434fe4fe38ce3291beec1a1c26181b58a65ccb6d6268aa1 -size 630849 diff --git a/assets/image/bernard2.png b/assets/image/bernard2.png deleted file mode 100644 index cd84827..0000000 --- a/assets/image/bernard2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f08bd741887aa3a4fdf022d3bec56cfebb078717549411c2612681230347915c -size 697484 diff --git a/assets/image/bernard3.png b/assets/image/bernard3.png deleted file mode 100644 index ac5b064..0000000 --- a/assets/image/bernard3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c5b0ebbef4410636bb903e09ed79083e4ee86bd293029d63f9f218c4d13b0497 -size 690846 diff --git a/assets/image/create_tomorrow1.png b/assets/image/create_tomorrow1.png deleted file mode 100644 index 6b675f8..0000000 --- a/assets/image/create_tomorrow1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b67432a587b0438e685b2a181a0e821e0750ed829eaee7bf3a8e584a57fca8d0 -size 1020489 diff --git a/assets/image/create_tomorrow2.jpg b/assets/image/create_tomorrow2.jpg deleted file mode 100644 index f48c6d2..0000000 --- a/assets/image/create_tomorrow2.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd1f21246b2a3f4f5125ae2c2f03cef197aae4cfbda4aeff902960ff4691988d -size 101829 diff --git a/assets/image/create_tomorrow2.png b/assets/image/create_tomorrow2.png deleted file mode 100644 index 343361d..0000000 --- a/assets/image/create_tomorrow2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c306530a9a427c336c686e7c0b71ab25244280ccfef7b5aee7e00630c53af3cc -size 714463 diff --git a/assets/image/create_tomorrow3.png b/assets/image/create_tomorrow3.png deleted file mode 100644 index d330d8f..0000000 --- a/assets/image/create_tomorrow3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:30cc68a38b4ceb8a9561b897d017144a3a1220b0dbe8ecdf110d517531619936 -size 383509 diff --git a/assets/image/create_tomorrow4.png b/assets/image/create_tomorrow4.png deleted file mode 100644 index ab27ef1..0000000 --- a/assets/image/create_tomorrow4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:27961f74ef6124678e1e01b6ade3b363fe69cd574224855a919b3f88f2faf4db -size 72371 diff --git a/assets/image/dialogue_des_carmelites1.jpg b/assets/image/dialogue_des_carmelites1.jpg deleted file mode 100644 index d960d6c..0000000 --- a/assets/image/dialogue_des_carmelites1.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c7e63328bd89fe7e6f42c76c4f6d7db471d44e2bebaffd1b7436db0f6bf6d6f -size 177242 diff --git a/assets/image/dialogue_des_carmelites1.png b/assets/image/dialogue_des_carmelites1.png deleted file mode 100644 index 6cfb386..0000000 --- a/assets/image/dialogue_des_carmelites1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a4b7cbd0cdf71af2e81bee9712b1ad1a96ad83af9d68e3bbcee6d5cd7487e97 -size 3805665 diff --git a/assets/image/dialogue_des_carmelites2.png b/assets/image/dialogue_des_carmelites2.png deleted file mode 100644 index 1e4be25..0000000 --- a/assets/image/dialogue_des_carmelites2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5e6364d17f60ffc2df248dcde082c58b43b27e11d190239da26ee86d28eb9b71 -size 4964801 diff --git a/assets/image/dialogue_des_carmelites3.png b/assets/image/dialogue_des_carmelites3.png deleted file mode 100644 index bb86855..0000000 --- a/assets/image/dialogue_des_carmelites3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ac38d355e9260c1da545a45edea08752913a26384c1461b605c0c666accd7bb -size 1758599 diff --git a/assets/image/nachtwacht1.jpg b/assets/image/nachtwacht1.jpg deleted file mode 100644 index 18a5602..0000000 --- a/assets/image/nachtwacht1.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60e5f83f5dc255f5176344dcd1622e6609ba483320799f5d183fe2300c70dafd -size 118944 diff --git a/assets/image/nachtwacht1.png b/assets/image/nachtwacht1.png deleted file mode 100755 index 04668cd..0000000 --- a/assets/image/nachtwacht1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd4386ce84d9b72d8b1bce107949b135aae3322fcb04ec976adabb41bbee5c60 -size 945814 diff --git a/assets/image/nachtwacht2.png b/assets/image/nachtwacht2.png deleted file mode 100755 index eb76cad..0000000 --- a/assets/image/nachtwacht2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:226720413434b569cd9721aba77cb7bd4ead8949d34027b88686eee818d379de -size 734718 diff --git a/assets/image/nachtwacht3.png b/assets/image/nachtwacht3.png deleted file mode 100755 index 94d7e42..0000000 --- a/assets/image/nachtwacht3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f64b57893c60984b2884f2377992ae61f6c0bc0a5d7785bd6f020c0c587e414 -size 570990 diff --git a/assets/image/nachtwacht4.png b/assets/image/nachtwacht4.png deleted file mode 100755 index d8eb214..0000000 --- a/assets/image/nachtwacht4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:33d1d473db5cc7167ec508973d8e86229ef3cf7302c8930068128fc23e7acdb4 -size 688471 diff --git a/assets/image/nachtwacht5.png b/assets/image/nachtwacht5.png deleted file mode 100755 index 47af45f..0000000 --- a/assets/image/nachtwacht5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:415bb0492b37c01d05002fb87a28bc2ce8057f53b6077fbe64345c39769efa0a -size 621681 diff --git a/assets/image/nachtwacht6.png b/assets/image/nachtwacht6.png deleted file mode 100755 index b77d80e..0000000 --- a/assets/image/nachtwacht6.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5224feb03882a9581fd3e95325641b373a00856f713789524cacebf98e1b7e1e -size 581081 diff --git a/assets/image/nachtwacht7.png b/assets/image/nachtwacht7.png deleted file mode 100755 index 626a1ac..0000000 --- a/assets/image/nachtwacht7.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dcf194d44507046410411d18420e1d81a7e6102f20db33157540ba5c386a2fec -size 653251 diff --git a/assets/image/nachtwacht8.png b/assets/image/nachtwacht8.png deleted file mode 100755 index fcbe4b1..0000000 --- a/assets/image/nachtwacht8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40e28597673ab6a41f69a7f53d13685b63e6c34dffea5a09c4520fbcf67ae782 -size 675163 diff --git a/assets/image/nachtwacht9.png b/assets/image/nachtwacht9.png deleted file mode 100755 index 0d3ef1c..0000000 --- a/assets/image/nachtwacht9.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4da83aab36920b39b43b6a58a1a05154b15a667d7a55e0c0523e47fc3bc6fe76 -size 710190 diff --git a/assets/image/notfound.png b/assets/image/notfound.png deleted file mode 100644 index 936c939..0000000 --- a/assets/image/notfound.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1395e9f67c3b8f7a6def4bd6424d52c88bd11c83da41b0efe19ffb61723e09bb -size 2984 diff --git a/assets/image/portfolio1.jpg b/assets/image/portfolio1.jpg deleted file mode 100755 index 361115d..0000000 --- a/assets/image/portfolio1.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7296e0233b96e4493d8dcc4ccd92e563130a7d79c1445c2e0550b72c0403860a -size 419526 diff --git a/assets/image/portfolio2.jpg b/assets/image/portfolio2.jpg deleted file mode 100755 index fcf7b17..0000000 --- a/assets/image/portfolio2.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ddb92ecd8f1fadfb68fd582eadfcf7cabdf57d35ecf6cdc55d061743b9baac6 -size 312088 diff --git a/assets/image/portfolio3.jpg b/assets/image/portfolio3.jpg deleted file mode 100755 index 0e2f192..0000000 --- a/assets/image/portfolio3.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e0b5c6a826f2d41eb7bb79d6cfdd04d2ce740f585e2216e50af3987d790e909 -size 489689 diff --git a/assets/image/specimens-of-discriminatory-composite-portraiture-1.png b/assets/image/specimens-of-discriminatory-composite-portraiture-1.png deleted file mode 100644 index 4cc3bda..0000000 --- a/assets/image/specimens-of-discriminatory-composite-portraiture-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3fa944340e076b1ef2d7f23452fbc4c71d8c3a9a22c6bb27108ac64478032313 -size 894053 diff --git a/assets/image/twister1.png b/assets/image/twister1.png deleted file mode 100644 index 7f1465c..0000000 --- a/assets/image/twister1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2e2b00c23416528e8090a5d2324d2938662cffd35d9044c41a65cf1ea96b51bc -size 1297471 diff --git a/assets/js/d3.bundle.js b/assets/js/d3.bundle.js new file mode 100644 index 0000000..ed74bb2 --- /dev/null +++ b/assets/js/d3.bundle.js @@ -0,0 +1,4665 @@ +var d3 = (function (exports) { +'use strict'; + +var noop = {value: function() {}}; + +function dispatch() { + for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) { + if (!(t = arguments[i] + "") || (t in _)) throw new Error("illegal type: " + t); + _[t] = []; + } + return new Dispatch(_); +} + +function Dispatch(_) { + this._ = _; +} + +function parseTypenames(typenames, types) { + return typenames.trim().split(/^|\s+/).map(function(t) { + var name = "", i = t.indexOf("."); + if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); + if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t); + return {type: t, name: name}; + }); +} + +Dispatch.prototype = dispatch.prototype = { + constructor: Dispatch, + on: function(typename, callback) { + var _ = this._, + T = parseTypenames(typename + "", _), + t, + i = -1, + n = T.length; + + // If no callback was specified, return the callback of the given type and name. + if (arguments.length < 2) { + while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t; + return; + } + + // If a type was specified, set the callback for the given type and name. + // Otherwise, if a null callback was specified, remove callbacks of the given name. + if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback); + while (++i < n) { + if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback); + else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null); + } + + return this; + }, + copy: function() { + var copy = {}, _ = this._; + for (var t in _) copy[t] = _[t].slice(); + return new Dispatch(copy); + }, + call: function(type, that) { + if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2]; + if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); + for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args); + }, + apply: function(type, that, args) { + if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); + for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args); + } +}; + +function get(type, name) { + for (var i = 0, n = type.length, c; i < n; ++i) { + if ((c = type[i]).name === name) { + return c.value; + } + } +} + +function set(type, name, callback) { + for (var i = 0, n = type.length; i < n; ++i) { + if (type[i].name === name) { + type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1)); + break; + } + } + if (callback != null) type.push({name: name, value: callback}); + return type; +} + +var xhtml = "http://www.w3.org/1999/xhtml"; + +var namespaces = { + svg: "http://www.w3.org/2000/svg", + xhtml: xhtml, + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" +}; + +var namespace = function(name) { + var prefix = name += "", i = prefix.indexOf(":"); + if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); + return namespaces.hasOwnProperty(prefix) ? {space: namespaces[prefix], local: name} : name; +}; + +function creatorInherit(name) { + return function() { + var document = this.ownerDocument, + uri = this.namespaceURI; + return uri === xhtml && document.documentElement.namespaceURI === xhtml + ? document.createElement(name) + : document.createElementNS(uri, name); + }; +} + +function creatorFixed(fullname) { + return function() { + return this.ownerDocument.createElementNS(fullname.space, fullname.local); + }; +} + +var creator = function(name) { + var fullname = namespace(name); + return (fullname.local + ? creatorFixed + : creatorInherit)(fullname); +}; + +function none() {} + +var selector = function(selector) { + return selector == null ? none : function() { + return this.querySelector(selector); + }; +}; + +var selection_select = function(select) { + if (typeof select !== "function") select = selector(select); + + for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { + if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + subgroup[i] = subnode; + } + } + } + + return new Selection(subgroups, this._parents); +}; + +function empty() { + return []; +} + +var selectorAll = function(selector) { + return selector == null ? empty : function() { + return this.querySelectorAll(selector); + }; +}; + +var selection_selectAll = function(select) { + if (typeof select !== "function") select = selectorAll(select); + + for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { + if (node = group[i]) { + subgroups.push(select.call(node, node.__data__, i, group)); + parents.push(node); + } + } + } + + return new Selection(subgroups, parents); +}; + +var matcher = function(selector) { + return function() { + return this.matches(selector); + }; +}; + +if (typeof document !== "undefined") { + var element = document.documentElement; + if (!element.matches) { + var vendorMatches = element.webkitMatchesSelector + || element.msMatchesSelector + || element.mozMatchesSelector + || element.oMatchesSelector; + matcher = function(selector) { + return function() { + return vendorMatches.call(this, selector); + }; + }; + } +} + +var matcher$1 = matcher; + +var selection_filter = function(match) { + if (typeof match !== "function") match = matcher$1(match); + + for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { + if ((node = group[i]) && match.call(node, node.__data__, i, group)) { + subgroup.push(node); + } + } + } + + return new Selection(subgroups, this._parents); +}; + +var sparse = function(update) { + return new Array(update.length); +}; + +var selection_enter = function() { + return new Selection(this._enter || this._groups.map(sparse), this._parents); +}; + +function EnterNode(parent, datum) { + this.ownerDocument = parent.ownerDocument; + this.namespaceURI = parent.namespaceURI; + this._next = null; + this._parent = parent; + this.__data__ = datum; +} + +EnterNode.prototype = { + constructor: EnterNode, + appendChild: function(child) { return this._parent.insertBefore(child, this._next); }, + insertBefore: function(child, next) { return this._parent.insertBefore(child, next); }, + querySelector: function(selector) { return this._parent.querySelector(selector); }, + querySelectorAll: function(selector) { return this._parent.querySelectorAll(selector); } +}; + +var constant = function(x) { + return function() { + return x; + }; +}; + +var keyPrefix = "$"; // Protect against keys like “__proto__”. + +function bindIndex(parent, group, enter, update, exit, data) { + var i = 0, + node, + groupLength = group.length, + dataLength = data.length; + + // Put any non-null nodes that fit into update. + // Put any null nodes into enter. + // Put any remaining data into enter. + for (; i < dataLength; ++i) { + if (node = group[i]) { + node.__data__ = data[i]; + update[i] = node; + } else { + enter[i] = new EnterNode(parent, data[i]); + } + } + + // Put any non-null nodes that don’t fit into exit. + for (; i < groupLength; ++i) { + if (node = group[i]) { + exit[i] = node; + } + } +} + +function bindKey(parent, group, enter, update, exit, data, key) { + var i, + node, + nodeByKeyValue = {}, + groupLength = group.length, + dataLength = data.length, + keyValues = new Array(groupLength), + keyValue; + + // Compute the key for each node. + // If multiple nodes have the same key, the duplicates are added to exit. + for (i = 0; i < groupLength; ++i) { + if (node = group[i]) { + keyValues[i] = keyValue = keyPrefix + key.call(node, node.__data__, i, group); + if (keyValue in nodeByKeyValue) { + exit[i] = node; + } else { + nodeByKeyValue[keyValue] = node; + } + } + } + + // Compute the key for each datum. + // If there a node associated with this key, join and add it to update. + // If there is not (or the key is a duplicate), add it to enter. + for (i = 0; i < dataLength; ++i) { + keyValue = keyPrefix + key.call(parent, data[i], i, data); + if (node = nodeByKeyValue[keyValue]) { + update[i] = node; + node.__data__ = data[i]; + nodeByKeyValue[keyValue] = null; + } else { + enter[i] = new EnterNode(parent, data[i]); + } + } + + // Add any remaining nodes that were not bound to data to exit. + for (i = 0; i < groupLength; ++i) { + if ((node = group[i]) && (nodeByKeyValue[keyValues[i]] === node)) { + exit[i] = node; + } + } +} + +var selection_data = function(value, key) { + if (!value) { + data = new Array(this.size()), j = -1; + this.each(function(d) { data[++j] = d; }); + return data; + } + + var bind = key ? bindKey : bindIndex, + parents = this._parents, + groups = this._groups; + + if (typeof value !== "function") value = constant(value); + + for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) { + var parent = parents[j], + group = groups[j], + groupLength = group.length, + data = value.call(parent, parent && parent.__data__, j, parents), + dataLength = data.length, + enterGroup = enter[j] = new Array(dataLength), + updateGroup = update[j] = new Array(dataLength), + exitGroup = exit[j] = new Array(groupLength); + + bind(parent, group, enterGroup, updateGroup, exitGroup, data, key); + + // Now connect the enter nodes to their following update node, such that + // appendChild can insert the materialized enter node before this node, + // rather than at the end of the parent node. + for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) { + if (previous = enterGroup[i0]) { + if (i0 >= i1) i1 = i0 + 1; + while (!(next = updateGroup[i1]) && ++i1 < dataLength); + previous._next = next || null; + } + } + } + + update = new Selection(update, parents); + update._enter = enter; + update._exit = exit; + return update; +}; + +var selection_exit = function() { + return new Selection(this._exit || this._groups.map(sparse), this._parents); +}; + +var selection_merge = function(selection$$1) { + + for (var groups0 = this._groups, groups1 = selection$$1._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { + for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { + if (node = group0[i] || group1[i]) { + merge[i] = node; + } + } + } + + for (; j < m0; ++j) { + merges[j] = groups0[j]; + } + + return new Selection(merges, this._parents); +}; + +var selection_order = function() { + + for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) { + for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + + return this; +}; + +var selection_sort = function(compare) { + if (!compare) compare = ascending; + + function compareNode(a, b) { + return a && b ? compare(a.__data__, b.__data__) : !a - !b; + } + + for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) { + if (node = group[i]) { + sortgroup[i] = node; + } + } + sortgroup.sort(compareNode); + } + + return new Selection(sortgroups, this._parents).order(); +}; + +function ascending(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; +} + +var selection_call = function() { + var callback = arguments[0]; + arguments[0] = this; + callback.apply(null, arguments); + return this; +}; + +var selection_nodes = function() { + var nodes = new Array(this.size()), i = -1; + this.each(function() { nodes[++i] = this; }); + return nodes; +}; + +var selection_node = function() { + + for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { + for (var group = groups[j], i = 0, n = group.length; i < n; ++i) { + var node = group[i]; + if (node) return node; + } + } + + return null; +}; + +var selection_size = function() { + var size = 0; + this.each(function() { ++size; }); + return size; +}; + +var selection_empty = function() { + return !this.node(); +}; + +var selection_each = function(callback) { + + for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { + if (node = group[i]) callback.call(node, node.__data__, i, group); + } + } + + return this; +}; + +function attrRemove(name) { + return function() { + this.removeAttribute(name); + }; +} + +function attrRemoveNS(fullname) { + return function() { + this.removeAttributeNS(fullname.space, fullname.local); + }; +} + +function attrConstant(name, value) { + return function() { + this.setAttribute(name, value); + }; +} + +function attrConstantNS(fullname, value) { + return function() { + this.setAttributeNS(fullname.space, fullname.local, value); + }; +} + +function attrFunction(name, value) { + return function() { + var v = value.apply(this, arguments); + if (v == null) this.removeAttribute(name); + else this.setAttribute(name, v); + }; +} + +function attrFunctionNS(fullname, value) { + return function() { + var v = value.apply(this, arguments); + if (v == null) this.removeAttributeNS(fullname.space, fullname.local); + else this.setAttributeNS(fullname.space, fullname.local, v); + }; +} + +var selection_attr = function(name, value) { + var fullname = namespace(name); + + if (arguments.length < 2) { + var node = this.node(); + return fullname.local + ? node.getAttributeNS(fullname.space, fullname.local) + : node.getAttribute(fullname); + } + + return this.each((value == null + ? (fullname.local ? attrRemoveNS : attrRemove) : (typeof value === "function" + ? (fullname.local ? attrFunctionNS : attrFunction) + : (fullname.local ? attrConstantNS : attrConstant)))(fullname, value)); +}; + +var defaultView = function(node) { + return (node.ownerDocument && node.ownerDocument.defaultView) // node is a Node + || (node.document && node) // node is a Window + || node.defaultView; // node is a Document +}; + +function styleRemove(name) { + return function() { + this.style.removeProperty(name); + }; +} + +function styleConstant(name, value, priority) { + return function() { + this.style.setProperty(name, value, priority); + }; +} + +function styleFunction(name, value, priority) { + return function() { + var v = value.apply(this, arguments); + if (v == null) this.style.removeProperty(name); + else this.style.setProperty(name, v, priority); + }; +} + +var selection_style = function(name, value, priority) { + return arguments.length > 1 + ? this.each((value == null + ? styleRemove : typeof value === "function" + ? styleFunction + : styleConstant)(name, value, priority == null ? "" : priority)) + : styleValue(this.node(), name); +}; + +function styleValue(node, name) { + return node.style.getPropertyValue(name) + || defaultView(node).getComputedStyle(node, null).getPropertyValue(name); +} + +function propertyRemove(name) { + return function() { + delete this[name]; + }; +} + +function propertyConstant(name, value) { + return function() { + this[name] = value; + }; +} + +function propertyFunction(name, value) { + return function() { + var v = value.apply(this, arguments); + if (v == null) delete this[name]; + else this[name] = v; + }; +} + +var selection_property = function(name, value) { + return arguments.length > 1 + ? this.each((value == null + ? propertyRemove : typeof value === "function" + ? propertyFunction + : propertyConstant)(name, value)) + : this.node()[name]; +}; + +function classArray(string) { + return string.trim().split(/^|\s+/); +} + +function classList(node) { + return node.classList || new ClassList(node); +} + +function ClassList(node) { + this._node = node; + this._names = classArray(node.getAttribute("class") || ""); +} + +ClassList.prototype = { + add: function(name) { + var i = this._names.indexOf(name); + if (i < 0) { + this._names.push(name); + this._node.setAttribute("class", this._names.join(" ")); + } + }, + remove: function(name) { + var i = this._names.indexOf(name); + if (i >= 0) { + this._names.splice(i, 1); + this._node.setAttribute("class", this._names.join(" ")); + } + }, + contains: function(name) { + return this._names.indexOf(name) >= 0; + } +}; + +function classedAdd(node, names) { + var list = classList(node), i = -1, n = names.length; + while (++i < n) list.add(names[i]); +} + +function classedRemove(node, names) { + var list = classList(node), i = -1, n = names.length; + while (++i < n) list.remove(names[i]); +} + +function classedTrue(names) { + return function() { + classedAdd(this, names); + }; +} + +function classedFalse(names) { + return function() { + classedRemove(this, names); + }; +} + +function classedFunction(names, value) { + return function() { + (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names); + }; +} + +var selection_classed = function(name, value) { + var names = classArray(name + ""); + + if (arguments.length < 2) { + var list = classList(this.node()), i = -1, n = names.length; + while (++i < n) if (!list.contains(names[i])) return false; + return true; + } + + return this.each((typeof value === "function" + ? classedFunction : value + ? classedTrue + : classedFalse)(names, value)); +}; + +function textRemove() { + this.textContent = ""; +} + +function textConstant(value) { + return function() { + this.textContent = value; + }; +} + +function textFunction(value) { + return function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + }; +} + +var selection_text = function(value) { + return arguments.length + ? this.each(value == null + ? textRemove : (typeof value === "function" + ? textFunction + : textConstant)(value)) + : this.node().textContent; +}; + +function htmlRemove() { + this.innerHTML = ""; +} + +function htmlConstant(value) { + return function() { + this.innerHTML = value; + }; +} + +function htmlFunction(value) { + return function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + }; +} + +var selection_html = function(value) { + return arguments.length + ? this.each(value == null + ? htmlRemove : (typeof value === "function" + ? htmlFunction + : htmlConstant)(value)) + : this.node().innerHTML; +}; + +function raise() { + if (this.nextSibling) this.parentNode.appendChild(this); +} + +var selection_raise = function() { + return this.each(raise); +}; + +function lower() { + if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild); +} + +var selection_lower = function() { + return this.each(lower); +}; + +var selection_append = function(name) { + var create = typeof name === "function" ? name : creator(name); + return this.select(function() { + return this.appendChild(create.apply(this, arguments)); + }); +}; + +function constantNull() { + return null; +} + +var selection_insert = function(name, before) { + var create = typeof name === "function" ? name : creator(name), + select = before == null ? constantNull : typeof before === "function" ? before : selector(before); + return this.select(function() { + return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null); + }); +}; + +function remove() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); +} + +var selection_remove = function() { + return this.each(remove); +}; + +function selection_cloneShallow() { + return this.parentNode.insertBefore(this.cloneNode(false), this.nextSibling); +} + +function selection_cloneDeep() { + return this.parentNode.insertBefore(this.cloneNode(true), this.nextSibling); +} + +var selection_clone = function(deep) { + return this.select(deep ? selection_cloneDeep : selection_cloneShallow); +}; + +var selection_datum = function(value) { + return arguments.length + ? this.property("__data__", value) + : this.node().__data__; +}; + +var filterEvents = {}; + +exports.event = null; + +if (typeof document !== "undefined") { + var element$1 = document.documentElement; + if (!("onmouseenter" in element$1)) { + filterEvents = {mouseenter: "mouseover", mouseleave: "mouseout"}; + } +} + +function filterContextListener(listener, index, group) { + listener = contextListener(listener, index, group); + return function(event) { + var related = event.relatedTarget; + if (!related || (related !== this && !(related.compareDocumentPosition(this) & 8))) { + listener.call(this, event); + } + }; +} + +function contextListener(listener, index, group) { + return function(event1) { + var event0 = exports.event; // Events can be reentrant (e.g., focus). + exports.event = event1; + try { + listener.call(this, this.__data__, index, group); + } finally { + exports.event = event0; + } + }; +} + +function parseTypenames$1(typenames) { + return typenames.trim().split(/^|\s+/).map(function(t) { + var name = "", i = t.indexOf("."); + if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); + return {type: t, name: name}; + }); +} + +function onRemove(typename) { + return function() { + var on = this.__on; + if (!on) return; + for (var j = 0, i = -1, m = on.length, o; j < m; ++j) { + if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) { + this.removeEventListener(o.type, o.listener, o.capture); + } else { + on[++i] = o; + } + } + if (++i) on.length = i; + else delete this.__on; + }; +} + +function onAdd(typename, value, capture) { + var wrap = filterEvents.hasOwnProperty(typename.type) ? filterContextListener : contextListener; + return function(d, i, group) { + var on = this.__on, o, listener = wrap(value, i, group); + if (on) for (var j = 0, m = on.length; j < m; ++j) { + if ((o = on[j]).type === typename.type && o.name === typename.name) { + this.removeEventListener(o.type, o.listener, o.capture); + this.addEventListener(o.type, o.listener = listener, o.capture = capture); + o.value = value; + return; + } + } + this.addEventListener(typename.type, listener, capture); + o = {type: typename.type, name: typename.name, value: value, listener: listener, capture: capture}; + if (!on) this.__on = [o]; + else on.push(o); + }; +} + +var selection_on = function(typename, value, capture) { + var typenames = parseTypenames$1(typename + ""), i, n = typenames.length, t; + + if (arguments.length < 2) { + var on = this.node().__on; + if (on) for (var j = 0, m = on.length, o; j < m; ++j) { + for (i = 0, o = on[j]; i < n; ++i) { + if ((t = typenames[i]).type === o.type && t.name === o.name) { + return o.value; + } + } + } + return; + } + + on = value ? onAdd : onRemove; + if (capture == null) capture = false; + for (i = 0; i < n; ++i) this.each(on(typenames[i], value, capture)); + return this; +}; + +function customEvent(event1, listener, that, args) { + var event0 = exports.event; + event1.sourceEvent = exports.event; + exports.event = event1; + try { + return listener.apply(that, args); + } finally { + exports.event = event0; + } +} + +function dispatchEvent(node, type, params) { + var window = defaultView(node), + event = window.CustomEvent; + + if (typeof event === "function") { + event = new event(type, params); + } else { + event = window.document.createEvent("Event"); + if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail; + else event.initEvent(type, false, false); + } + + node.dispatchEvent(event); +} + +function dispatchConstant(type, params) { + return function() { + return dispatchEvent(this, type, params); + }; +} + +function dispatchFunction(type, params) { + return function() { + return dispatchEvent(this, type, params.apply(this, arguments)); + }; +} + +var selection_dispatch = function(type, params) { + return this.each((typeof params === "function" + ? dispatchFunction + : dispatchConstant)(type, params)); +}; + +var root = [null]; + +function Selection(groups, parents) { + this._groups = groups; + this._parents = parents; +} + +function selection() { + return new Selection([[document.documentElement]], root); +} + +Selection.prototype = selection.prototype = { + constructor: Selection, + select: selection_select, + selectAll: selection_selectAll, + filter: selection_filter, + data: selection_data, + enter: selection_enter, + exit: selection_exit, + merge: selection_merge, + order: selection_order, + sort: selection_sort, + call: selection_call, + nodes: selection_nodes, + node: selection_node, + size: selection_size, + empty: selection_empty, + each: selection_each, + attr: selection_attr, + style: selection_style, + property: selection_property, + classed: selection_classed, + text: selection_text, + html: selection_html, + raise: selection_raise, + lower: selection_lower, + append: selection_append, + insert: selection_insert, + remove: selection_remove, + clone: selection_clone, + datum: selection_datum, + on: selection_on, + dispatch: selection_dispatch +}; + +var select = function(selector) { + return typeof selector === "string" + ? new Selection([[document.querySelector(selector)]], [document.documentElement]) + : new Selection([[selector]], root); +}; + +var create = function(name) { + return select(creator(name).call(document.documentElement)); +}; + +var nextId = 0; + +function local() { + return new Local; +} + +function Local() { + this._ = "@" + (++nextId).toString(36); +} + +Local.prototype = local.prototype = { + constructor: Local, + get: function(node) { + var id = this._; + while (!(id in node)) if (!(node = node.parentNode)) return; + return node[id]; + }, + set: function(node, value) { + return node[this._] = value; + }, + remove: function(node) { + return this._ in node && delete node[this._]; + }, + toString: function() { + return this._; + } +}; + +var sourceEvent = function() { + var current = exports.event, source; + while (source = current.sourceEvent) current = source; + return current; +}; + +var point = function(node, event) { + var svg = node.ownerSVGElement || node; + + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + point.x = event.clientX, point.y = event.clientY; + point = point.matrixTransform(node.getScreenCTM().inverse()); + return [point.x, point.y]; + } + + var rect = node.getBoundingClientRect(); + return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop]; +}; + +var mouse = function(node) { + var event = sourceEvent(); + if (event.changedTouches) event = event.changedTouches[0]; + return point(node, event); +}; + +var selectAll = function(selector) { + return typeof selector === "string" + ? new Selection([document.querySelectorAll(selector)], [document.documentElement]) + : new Selection([selector == null ? [] : selector], root); +}; + +var touch = function(node, touches, identifier) { + if (arguments.length < 3) identifier = touches, touches = sourceEvent().changedTouches; + + for (var i = 0, n = touches ? touches.length : 0, touch; i < n; ++i) { + if ((touch = touches[i]).identifier === identifier) { + return point(node, touch); + } + } + + return null; +}; + +var touches = function(node, touches) { + if (touches == null) touches = sourceEvent().touches; + + for (var i = 0, n = touches ? touches.length : 0, points = new Array(n); i < n; ++i) { + points[i] = point(node, touches[i]); + } + + return points; +}; + +function nopropagation() { + exports.event.stopImmediatePropagation(); +} + +var noevent = function() { + exports.event.preventDefault(); + exports.event.stopImmediatePropagation(); +}; + +var dragDisable = function(view) { + var root = view.document.documentElement, + selection = select(view).on("dragstart.drag", noevent, true); + if ("onselectstart" in root) { + selection.on("selectstart.drag", noevent, true); + } else { + root.__noselect = root.style.MozUserSelect; + root.style.MozUserSelect = "none"; + } +}; + +function yesdrag(view, noclick) { + var root = view.document.documentElement, + selection = select(view).on("dragstart.drag", null); + if (noclick) { + selection.on("click.drag", noevent, true); + setTimeout(function() { selection.on("click.drag", null); }, 0); + } + if ("onselectstart" in root) { + selection.on("selectstart.drag", null); + } else { + root.style.MozUserSelect = root.__noselect; + delete root.__noselect; + } +} + +var constant$1 = function(x) { + return function() { + return x; + }; +}; + +function DragEvent(target, type, subject, id, active, x, y, dx, dy, dispatch) { + this.target = target; + this.type = type; + this.subject = subject; + this.identifier = id; + this.active = active; + this.x = x; + this.y = y; + this.dx = dx; + this.dy = dy; + this._ = dispatch; +} + +DragEvent.prototype.on = function() { + var value = this._.on.apply(this._, arguments); + return value === this._ ? this : value; +}; + +// Ignore right-click, since that should open the context menu. +function defaultFilter() { + return !exports.event.button; +} + +function defaultContainer() { + return this.parentNode; +} + +function defaultSubject(d) { + return d == null ? {x: exports.event.x, y: exports.event.y} : d; +} + +function defaultTouchable() { + return "ontouchstart" in this; +} + +var drag = function() { + var filter = defaultFilter, + container = defaultContainer, + subject = defaultSubject, + touchable = defaultTouchable, + gestures = {}, + listeners = dispatch("start", "drag", "end"), + active = 0, + mousedownx, + mousedowny, + mousemoving, + touchending, + clickDistance2 = 0; + + function drag(selection) { + selection + .on("mousedown.drag", mousedowned) + .filter(touchable) + .on("touchstart.drag", touchstarted) + .on("touchmove.drag", touchmoved) + .on("touchend.drag touchcancel.drag", touchended) + .style("touch-action", "none") + .style("-webkit-tap-highlight-color", "rgba(0,0,0,0)"); + } + + function mousedowned() { + if (touchending || !filter.apply(this, arguments)) return; + var gesture = beforestart("mouse", container.apply(this, arguments), mouse, this, arguments); + if (!gesture) return; + select(exports.event.view).on("mousemove.drag", mousemoved, true).on("mouseup.drag", mouseupped, true); + dragDisable(exports.event.view); + nopropagation(); + mousemoving = false; + mousedownx = exports.event.clientX; + mousedowny = exports.event.clientY; + gesture("start"); + } + + function mousemoved() { + noevent(); + if (!mousemoving) { + var dx = exports.event.clientX - mousedownx, dy = exports.event.clientY - mousedowny; + mousemoving = dx * dx + dy * dy > clickDistance2; + } + gestures.mouse("drag"); + } + + function mouseupped() { + select(exports.event.view).on("mousemove.drag mouseup.drag", null); + yesdrag(exports.event.view, mousemoving); + noevent(); + gestures.mouse("end"); + } + + function touchstarted() { + if (!filter.apply(this, arguments)) return; + var touches = exports.event.changedTouches, + c = container.apply(this, arguments), + n = touches.length, i, gesture; + + for (i = 0; i < n; ++i) { + if (gesture = beforestart(touches[i].identifier, c, touch, this, arguments)) { + nopropagation(); + gesture("start"); + } + } + } + + function touchmoved() { + var touches = exports.event.changedTouches, + n = touches.length, i, gesture; + + for (i = 0; i < n; ++i) { + if (gesture = gestures[touches[i].identifier]) { + noevent(); + gesture("drag"); + } + } + } + + function touchended() { + var touches = exports.event.changedTouches, + n = touches.length, i, gesture; + + if (touchending) clearTimeout(touchending); + touchending = setTimeout(function() { touchending = null; }, 500); // Ghost clicks are delayed! + for (i = 0; i < n; ++i) { + if (gesture = gestures[touches[i].identifier]) { + nopropagation(); + gesture("end"); + } + } + } + + function beforestart(id, container, point, that, args) { + var p = point(container, id), s, dx, dy, + sublisteners = listeners.copy(); + + if (!customEvent(new DragEvent(drag, "beforestart", s, id, active, p[0], p[1], 0, 0, sublisteners), function() { + if ((exports.event.subject = s = subject.apply(that, args)) == null) return false; + dx = s.x - p[0] || 0; + dy = s.y - p[1] || 0; + return true; + })) return; + + return function gesture(type) { + var p0 = p, n; + switch (type) { + case "start": gestures[id] = gesture, n = active++; break; + case "end": delete gestures[id], --active; // nobreak + case "drag": p = point(container, id), n = active; break; + } + customEvent(new DragEvent(drag, type, s, id, n, p[0] + dx, p[1] + dy, p[0] - p0[0], p[1] - p0[1], sublisteners), sublisteners.apply, sublisteners, [type, that, args]); + }; + } + + drag.filter = function(_) { + return arguments.length ? (filter = typeof _ === "function" ? _ : constant$1(!!_), drag) : filter; + }; + + drag.container = function(_) { + return arguments.length ? (container = typeof _ === "function" ? _ : constant$1(_), drag) : container; + }; + + drag.subject = function(_) { + return arguments.length ? (subject = typeof _ === "function" ? _ : constant$1(_), drag) : subject; + }; + + drag.touchable = function(_) { + return arguments.length ? (touchable = typeof _ === "function" ? _ : constant$1(!!_), drag) : touchable; + }; + + drag.on = function() { + var value = listeners.on.apply(listeners, arguments); + return value === listeners ? drag : value; + }; + + drag.clickDistance = function(_) { + return arguments.length ? (clickDistance2 = (_ = +_) * _, drag) : Math.sqrt(clickDistance2); + }; + + return drag; +}; + +var define = function(constructor, factory, prototype) { + constructor.prototype = factory.prototype = prototype; + prototype.constructor = constructor; +}; + +function extend(parent, definition) { + var prototype = Object.create(parent.prototype); + for (var key in definition) prototype[key] = definition[key]; + return prototype; +} + +function Color() {} + +var darker = 0.7; +var brighter = 1 / darker; + +var reI = "\\s*([+-]?\\d+)\\s*"; +var reN = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*"; +var reP = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*"; +var reHex3 = /^#([0-9a-f]{3})$/; +var reHex6 = /^#([0-9a-f]{6})$/; +var reRgbInteger = new RegExp("^rgb\\(" + [reI, reI, reI] + "\\)$"); +var reRgbPercent = new RegExp("^rgb\\(" + [reP, reP, reP] + "\\)$"); +var reRgbaInteger = new RegExp("^rgba\\(" + [reI, reI, reI, reN] + "\\)$"); +var reRgbaPercent = new RegExp("^rgba\\(" + [reP, reP, reP, reN] + "\\)$"); +var reHslPercent = new RegExp("^hsl\\(" + [reN, reP, reP] + "\\)$"); +var reHslaPercent = new RegExp("^hsla\\(" + [reN, reP, reP, reN] + "\\)$"); + +var named = { + aliceblue: 0xf0f8ff, + antiquewhite: 0xfaebd7, + aqua: 0x00ffff, + aquamarine: 0x7fffd4, + azure: 0xf0ffff, + beige: 0xf5f5dc, + bisque: 0xffe4c4, + black: 0x000000, + blanchedalmond: 0xffebcd, + blue: 0x0000ff, + blueviolet: 0x8a2be2, + brown: 0xa52a2a, + burlywood: 0xdeb887, + cadetblue: 0x5f9ea0, + chartreuse: 0x7fff00, + chocolate: 0xd2691e, + coral: 0xff7f50, + cornflowerblue: 0x6495ed, + cornsilk: 0xfff8dc, + crimson: 0xdc143c, + cyan: 0x00ffff, + darkblue: 0x00008b, + darkcyan: 0x008b8b, + darkgoldenrod: 0xb8860b, + darkgray: 0xa9a9a9, + darkgreen: 0x006400, + darkgrey: 0xa9a9a9, + darkkhaki: 0xbdb76b, + darkmagenta: 0x8b008b, + darkolivegreen: 0x556b2f, + darkorange: 0xff8c00, + darkorchid: 0x9932cc, + darkred: 0x8b0000, + darksalmon: 0xe9967a, + darkseagreen: 0x8fbc8f, + darkslateblue: 0x483d8b, + darkslategray: 0x2f4f4f, + darkslategrey: 0x2f4f4f, + darkturquoise: 0x00ced1, + darkviolet: 0x9400d3, + deeppink: 0xff1493, + deepskyblue: 0x00bfff, + dimgray: 0x696969, + dimgrey: 0x696969, + dodgerblue: 0x1e90ff, + firebrick: 0xb22222, + floralwhite: 0xfffaf0, + forestgreen: 0x228b22, + fuchsia: 0xff00ff, + gainsboro: 0xdcdcdc, + ghostwhite: 0xf8f8ff, + gold: 0xffd700, + goldenrod: 0xdaa520, + gray: 0x808080, + green: 0x008000, + greenyellow: 0xadff2f, + grey: 0x808080, + honeydew: 0xf0fff0, + hotpink: 0xff69b4, + indianred: 0xcd5c5c, + indigo: 0x4b0082, + ivory: 0xfffff0, + khaki: 0xf0e68c, + lavender: 0xe6e6fa, + lavenderblush: 0xfff0f5, + lawngreen: 0x7cfc00, + lemonchiffon: 0xfffacd, + lightblue: 0xadd8e6, + lightcoral: 0xf08080, + lightcyan: 0xe0ffff, + lightgoldenrodyellow: 0xfafad2, + lightgray: 0xd3d3d3, + lightgreen: 0x90ee90, + lightgrey: 0xd3d3d3, + lightpink: 0xffb6c1, + lightsalmon: 0xffa07a, + lightseagreen: 0x20b2aa, + lightskyblue: 0x87cefa, + lightslategray: 0x778899, + lightslategrey: 0x778899, + lightsteelblue: 0xb0c4de, + lightyellow: 0xffffe0, + lime: 0x00ff00, + limegreen: 0x32cd32, + linen: 0xfaf0e6, + magenta: 0xff00ff, + maroon: 0x800000, + mediumaquamarine: 0x66cdaa, + mediumblue: 0x0000cd, + mediumorchid: 0xba55d3, + mediumpurple: 0x9370db, + mediumseagreen: 0x3cb371, + mediumslateblue: 0x7b68ee, + mediumspringgreen: 0x00fa9a, + mediumturquoise: 0x48d1cc, + mediumvioletred: 0xc71585, + midnightblue: 0x191970, + mintcream: 0xf5fffa, + mistyrose: 0xffe4e1, + moccasin: 0xffe4b5, + navajowhite: 0xffdead, + navy: 0x000080, + oldlace: 0xfdf5e6, + olive: 0x808000, + olivedrab: 0x6b8e23, + orange: 0xffa500, + orangered: 0xff4500, + orchid: 0xda70d6, + palegoldenrod: 0xeee8aa, + palegreen: 0x98fb98, + paleturquoise: 0xafeeee, + palevioletred: 0xdb7093, + papayawhip: 0xffefd5, + peachpuff: 0xffdab9, + peru: 0xcd853f, + pink: 0xffc0cb, + plum: 0xdda0dd, + powderblue: 0xb0e0e6, + purple: 0x800080, + rebeccapurple: 0x663399, + red: 0xff0000, + rosybrown: 0xbc8f8f, + royalblue: 0x4169e1, + saddlebrown: 0x8b4513, + salmon: 0xfa8072, + sandybrown: 0xf4a460, + seagreen: 0x2e8b57, + seashell: 0xfff5ee, + sienna: 0xa0522d, + silver: 0xc0c0c0, + skyblue: 0x87ceeb, + slateblue: 0x6a5acd, + slategray: 0x708090, + slategrey: 0x708090, + snow: 0xfffafa, + springgreen: 0x00ff7f, + steelblue: 0x4682b4, + tan: 0xd2b48c, + teal: 0x008080, + thistle: 0xd8bfd8, + tomato: 0xff6347, + turquoise: 0x40e0d0, + violet: 0xee82ee, + wheat: 0xf5deb3, + white: 0xffffff, + whitesmoke: 0xf5f5f5, + yellow: 0xffff00, + yellowgreen: 0x9acd32 +}; + +define(Color, color, { + displayable: function() { + return this.rgb().displayable(); + }, + hex: function() { + return this.rgb().hex(); + }, + toString: function() { + return this.rgb() + ""; + } +}); + +function color(format) { + var m; + format = (format + "").trim().toLowerCase(); + return (m = reHex3.exec(format)) ? (m = parseInt(m[1], 16), new Rgb((m >> 8 & 0xf) | (m >> 4 & 0x0f0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1)) // #f00 + : (m = reHex6.exec(format)) ? rgbn(parseInt(m[1], 16)) // #ff0000 + : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0) + : (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%) + : (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1) + : (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1) + : (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%) + : (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1) + : named.hasOwnProperty(format) ? rgbn(named[format]) + : format === "transparent" ? new Rgb(NaN, NaN, NaN, 0) + : null; +} + +function rgbn(n) { + return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1); +} + +function rgba(r, g, b, a) { + if (a <= 0) r = g = b = NaN; + return new Rgb(r, g, b, a); +} + +function rgbConvert(o) { + if (!(o instanceof Color)) o = color(o); + if (!o) return new Rgb; + o = o.rgb(); + return new Rgb(o.r, o.g, o.b, o.opacity); +} + +function rgb(r, g, b, opacity) { + return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity); +} + +function Rgb(r, g, b, opacity) { + this.r = +r; + this.g = +g; + this.b = +b; + this.opacity = +opacity; +} + +define(Rgb, rgb, extend(Color, { + brighter: function(k) { + k = k == null ? brighter : Math.pow(brighter, k); + return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); + }, + darker: function(k) { + k = k == null ? darker : Math.pow(darker, k); + return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); + }, + rgb: function() { + return this; + }, + displayable: function() { + return (0 <= this.r && this.r <= 255) + && (0 <= this.g && this.g <= 255) + && (0 <= this.b && this.b <= 255) + && (0 <= this.opacity && this.opacity <= 1); + }, + hex: function() { + return "#" + hex(this.r) + hex(this.g) + hex(this.b); + }, + toString: function() { + var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a)); + return (a === 1 ? "rgb(" : "rgba(") + + Math.max(0, Math.min(255, Math.round(this.r) || 0)) + ", " + + Math.max(0, Math.min(255, Math.round(this.g) || 0)) + ", " + + Math.max(0, Math.min(255, Math.round(this.b) || 0)) + + (a === 1 ? ")" : ", " + a + ")"); + } +})); + +function hex(value) { + value = Math.max(0, Math.min(255, Math.round(value) || 0)); + return (value < 16 ? "0" : "") + value.toString(16); +} + +function hsla(h, s, l, a) { + if (a <= 0) h = s = l = NaN; + else if (l <= 0 || l >= 1) h = s = NaN; + else if (s <= 0) h = NaN; + return new Hsl(h, s, l, a); +} + +function hslConvert(o) { + if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity); + if (!(o instanceof Color)) o = color(o); + if (!o) return new Hsl; + if (o instanceof Hsl) return o; + o = o.rgb(); + var r = o.r / 255, + g = o.g / 255, + b = o.b / 255, + min = Math.min(r, g, b), + max = Math.max(r, g, b), + h = NaN, + s = max - min, + l = (max + min) / 2; + if (s) { + if (r === max) h = (g - b) / s + (g < b) * 6; + else if (g === max) h = (b - r) / s + 2; + else h = (r - g) / s + 4; + s /= l < 0.5 ? max + min : 2 - max - min; + h *= 60; + } else { + s = l > 0 && l < 1 ? 0 : h; + } + return new Hsl(h, s, l, o.opacity); +} + +function hsl(h, s, l, opacity) { + return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity); +} + +function Hsl(h, s, l, opacity) { + this.h = +h; + this.s = +s; + this.l = +l; + this.opacity = +opacity; +} + +define(Hsl, hsl, extend(Color, { + brighter: function(k) { + k = k == null ? brighter : Math.pow(brighter, k); + return new Hsl(this.h, this.s, this.l * k, this.opacity); + }, + darker: function(k) { + k = k == null ? darker : Math.pow(darker, k); + return new Hsl(this.h, this.s, this.l * k, this.opacity); + }, + rgb: function() { + var h = this.h % 360 + (this.h < 0) * 360, + s = isNaN(h) || isNaN(this.s) ? 0 : this.s, + l = this.l, + m2 = l + (l < 0.5 ? l : 1 - l) * s, + m1 = 2 * l - m2; + return new Rgb( + hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2), + hsl2rgb(h, m1, m2), + hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2), + this.opacity + ); + }, + displayable: function() { + return (0 <= this.s && this.s <= 1 || isNaN(this.s)) + && (0 <= this.l && this.l <= 1) + && (0 <= this.opacity && this.opacity <= 1); + } +})); + +/* From FvD 13.37, CSS Color Module Level 3 */ +function hsl2rgb(h, m1, m2) { + return (h < 60 ? m1 + (m2 - m1) * h / 60 + : h < 180 ? m2 + : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60 + : m1) * 255; +} + +var deg2rad = Math.PI / 180; +var rad2deg = 180 / Math.PI; + +// https://beta.observablehq.com/@mbostock/lab-and-rgb +var K = 18; +var Xn = 0.96422; +var Yn = 1; +var Zn = 0.82521; +var t0 = 4 / 29; +var t1 = 6 / 29; +var t2 = 3 * t1 * t1; +var t3 = t1 * t1 * t1; + +function labConvert(o) { + if (o instanceof Lab) return new Lab(o.l, o.a, o.b, o.opacity); + if (o instanceof Hcl) { + if (isNaN(o.h)) return new Lab(o.l, 0, 0, o.opacity); + var h = o.h * deg2rad; + return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity); + } + if (!(o instanceof Rgb)) o = rgbConvert(o); + var r = rgb2lrgb(o.r), + g = rgb2lrgb(o.g), + b = rgb2lrgb(o.b), + y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn), x, z; + if (r === g && g === b) x = z = y; else { + x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn); + z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn); + } + return new Lab(116 * y - 16, 500 * (x - y), 200 * (y - z), o.opacity); +} + + + +function lab(l, a, b, opacity) { + return arguments.length === 1 ? labConvert(l) : new Lab(l, a, b, opacity == null ? 1 : opacity); +} + +function Lab(l, a, b, opacity) { + this.l = +l; + this.a = +a; + this.b = +b; + this.opacity = +opacity; +} + +define(Lab, lab, extend(Color, { + brighter: function(k) { + return new Lab(this.l + K * (k == null ? 1 : k), this.a, this.b, this.opacity); + }, + darker: function(k) { + return new Lab(this.l - K * (k == null ? 1 : k), this.a, this.b, this.opacity); + }, + rgb: function() { + var y = (this.l + 16) / 116, + x = isNaN(this.a) ? y : y + this.a / 500, + z = isNaN(this.b) ? y : y - this.b / 200; + x = Xn * lab2xyz(x); + y = Yn * lab2xyz(y); + z = Zn * lab2xyz(z); + return new Rgb( + lrgb2rgb( 3.1338561 * x - 1.6168667 * y - 0.4906146 * z), + lrgb2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z), + lrgb2rgb( 0.0719453 * x - 0.2289914 * y + 1.4052427 * z), + this.opacity + ); + } +})); + +function xyz2lab(t) { + return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0; +} + +function lab2xyz(t) { + return t > t1 ? t * t * t : t2 * (t - t0); +} + +function lrgb2rgb(x) { + return 255 * (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055); +} + +function rgb2lrgb(x) { + return (x /= 255) <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); +} + +function hclConvert(o) { + if (o instanceof Hcl) return new Hcl(o.h, o.c, o.l, o.opacity); + if (!(o instanceof Lab)) o = labConvert(o); + if (o.a === 0 && o.b === 0) return new Hcl(NaN, 0, o.l, o.opacity); + var h = Math.atan2(o.b, o.a) * rad2deg; + return new Hcl(h < 0 ? h + 360 : h, Math.sqrt(o.a * o.a + o.b * o.b), o.l, o.opacity); +} + + + +function hcl(h, c, l, opacity) { + return arguments.length === 1 ? hclConvert(h) : new Hcl(h, c, l, opacity == null ? 1 : opacity); +} + +function Hcl(h, c, l, opacity) { + this.h = +h; + this.c = +c; + this.l = +l; + this.opacity = +opacity; +} + +define(Hcl, hcl, extend(Color, { + brighter: function(k) { + return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity); + }, + darker: function(k) { + return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity); + }, + rgb: function() { + return labConvert(this).rgb(); + } +})); + +var A = -0.14861; +var B = +1.78277; +var C = -0.29227; +var D = -0.90649; +var E = +1.97294; +var ED = E * D; +var EB = E * B; +var BC_DA = B * C - D * A; + +function cubehelixConvert(o) { + if (o instanceof Cubehelix) return new Cubehelix(o.h, o.s, o.l, o.opacity); + if (!(o instanceof Rgb)) o = rgbConvert(o); + var r = o.r / 255, + g = o.g / 255, + b = o.b / 255, + l = (BC_DA * b + ED * r - EB * g) / (BC_DA + ED - EB), + bl = b - l, + k = (E * (g - l) - C * bl) / D, + s = Math.sqrt(k * k + bl * bl) / (E * l * (1 - l)), // NaN if l=0 or l=1 + h = s ? Math.atan2(k, bl) * rad2deg - 120 : NaN; + return new Cubehelix(h < 0 ? h + 360 : h, s, l, o.opacity); +} + +function cubehelix(h, s, l, opacity) { + return arguments.length === 1 ? cubehelixConvert(h) : new Cubehelix(h, s, l, opacity == null ? 1 : opacity); +} + +function Cubehelix(h, s, l, opacity) { + this.h = +h; + this.s = +s; + this.l = +l; + this.opacity = +opacity; +} + +define(Cubehelix, cubehelix, extend(Color, { + brighter: function(k) { + k = k == null ? brighter : Math.pow(brighter, k); + return new Cubehelix(this.h, this.s, this.l * k, this.opacity); + }, + darker: function(k) { + k = k == null ? darker : Math.pow(darker, k); + return new Cubehelix(this.h, this.s, this.l * k, this.opacity); + }, + rgb: function() { + var h = isNaN(this.h) ? 0 : (this.h + 120) * deg2rad, + l = +this.l, + a = isNaN(this.s) ? 0 : this.s * l * (1 - l), + cosh = Math.cos(h), + sinh = Math.sin(h); + return new Rgb( + 255 * (l + a * (A * cosh + B * sinh)), + 255 * (l + a * (C * cosh + D * sinh)), + 255 * (l + a * (E * cosh)), + this.opacity + ); + } +})); + +var constant$2 = function(x) { + return function() { + return x; + }; +}; + +function linear(a, d) { + return function(t) { + return a + t * d; + }; +} + +function exponential(a, b, y) { + return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function(t) { + return Math.pow(a + t * b, y); + }; +} + + + +function gamma(y) { + return (y = +y) === 1 ? nogamma : function(a, b) { + return b - a ? exponential(a, b, y) : constant$2(isNaN(a) ? b : a); + }; +} + +function nogamma(a, b) { + var d = b - a; + return d ? linear(a, d) : constant$2(isNaN(a) ? b : a); +} + +var interpolateRgb = (function rgbGamma(y) { + var color$$1 = gamma(y); + + function rgb$$1(start, end) { + var r = color$$1((start = rgb(start)).r, (end = rgb(end)).r), + g = color$$1(start.g, end.g), + b = color$$1(start.b, end.b), + opacity = nogamma(start.opacity, end.opacity); + return function(t) { + start.r = r(t); + start.g = g(t); + start.b = b(t); + start.opacity = opacity(t); + return start + ""; + }; + } + + rgb$$1.gamma = rgbGamma; + + return rgb$$1; +})(1); + +var interpolateNumber = function(a, b) { + return a = +a, b -= a, function(t) { + return a + b * t; + }; +}; + +var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; +var reB = new RegExp(reA.source, "g"); + +function zero(b) { + return function() { + return b; + }; +} + +function one(b) { + return function(t) { + return b(t) + ""; + }; +} + +var interpolateString = function(a, b) { + var bi = reA.lastIndex = reB.lastIndex = 0, // scan index for next number in b + am, // current match in a + bm, // current match in b + bs, // string preceding current number in b, if any + i = -1, // index in s + s = [], // string constants and placeholders + q = []; // number interpolators + + // Coerce inputs to strings. + a = a + "", b = b + ""; + + // Interpolate pairs of numbers in a & b. + while ((am = reA.exec(a)) + && (bm = reB.exec(b))) { + if ((bs = bm.index) > bi) { // a string precedes the next number in b + bs = b.slice(bi, bs); + if (s[i]) s[i] += bs; // coalesce with previous string + else s[++i] = bs; + } + if ((am = am[0]) === (bm = bm[0])) { // numbers in a & b match + if (s[i]) s[i] += bm; // coalesce with previous string + else s[++i] = bm; + } else { // interpolate non-matching numbers + s[++i] = null; + q.push({i: i, x: interpolateNumber(am, bm)}); + } + bi = reB.lastIndex; + } + + // Add remains of b. + if (bi < b.length) { + bs = b.slice(bi); + if (s[i]) s[i] += bs; // coalesce with previous string + else s[++i] = bs; + } + + // Special optimization for only a single match. + // Otherwise, interpolate each of the numbers and rejoin the string. + return s.length < 2 ? (q[0] + ? one(q[0].x) + : zero(b)) + : (b = q.length, function(t) { + for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }); +}; + +var degrees = 180 / Math.PI; + +var identity = { + translateX: 0, + translateY: 0, + rotate: 0, + skewX: 0, + scaleX: 1, + scaleY: 1 +}; + +var decompose = function(a, b, c, d, e, f) { + var scaleX, scaleY, skewX; + if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX; + if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX; + if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY; + if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX; + return { + translateX: e, + translateY: f, + rotate: Math.atan2(b, a) * degrees, + skewX: Math.atan(skewX) * degrees, + scaleX: scaleX, + scaleY: scaleY + }; +}; + +var cssNode; +var cssRoot; +var cssView; +var svgNode; + +function parseCss(value) { + if (value === "none") return identity; + if (!cssNode) cssNode = document.createElement("DIV"), cssRoot = document.documentElement, cssView = document.defaultView; + cssNode.style.transform = value; + value = cssView.getComputedStyle(cssRoot.appendChild(cssNode), null).getPropertyValue("transform"); + cssRoot.removeChild(cssNode); + value = value.slice(7, -1).split(","); + return decompose(+value[0], +value[1], +value[2], +value[3], +value[4], +value[5]); +} + +function parseSvg(value) { + if (value == null) return identity; + if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g"); + svgNode.setAttribute("transform", value); + if (!(value = svgNode.transform.baseVal.consolidate())) return identity; + value = value.matrix; + return decompose(value.a, value.b, value.c, value.d, value.e, value.f); +} + +function interpolateTransform(parse, pxComma, pxParen, degParen) { + + function pop(s) { + return s.length ? s.pop() + " " : ""; + } + + function translate(xa, ya, xb, yb, s, q) { + if (xa !== xb || ya !== yb) { + var i = s.push("translate(", null, pxComma, null, pxParen); + q.push({i: i - 4, x: interpolateNumber(xa, xb)}, {i: i - 2, x: interpolateNumber(ya, yb)}); + } else if (xb || yb) { + s.push("translate(" + xb + pxComma + yb + pxParen); + } + } + + function rotate(a, b, s, q) { + if (a !== b) { + if (a - b > 180) b += 360; else if (b - a > 180) a += 360; // shortest path + q.push({i: s.push(pop(s) + "rotate(", null, degParen) - 2, x: interpolateNumber(a, b)}); + } else if (b) { + s.push(pop(s) + "rotate(" + b + degParen); + } + } + + function skewX(a, b, s, q) { + if (a !== b) { + q.push({i: s.push(pop(s) + "skewX(", null, degParen) - 2, x: interpolateNumber(a, b)}); + } else if (b) { + s.push(pop(s) + "skewX(" + b + degParen); + } + } + + function scale(xa, ya, xb, yb, s, q) { + if (xa !== xb || ya !== yb) { + var i = s.push(pop(s) + "scale(", null, ",", null, ")"); + q.push({i: i - 4, x: interpolateNumber(xa, xb)}, {i: i - 2, x: interpolateNumber(ya, yb)}); + } else if (xb !== 1 || yb !== 1) { + s.push(pop(s) + "scale(" + xb + "," + yb + ")"); + } + } + + return function(a, b) { + var s = [], // string constants and placeholders + q = []; // number interpolators + a = parse(a), b = parse(b); + translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q); + rotate(a.rotate, b.rotate, s, q); + skewX(a.skewX, b.skewX, s, q); + scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q); + a = b = null; // gc + return function(t) { + var i = -1, n = q.length, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + }; +} + +var interpolateTransformCss = interpolateTransform(parseCss, "px, ", "px)", "deg)"); +var interpolateTransformSvg = interpolateTransform(parseSvg, ", ", ")", ")"); + +var rho = Math.SQRT2; +var rho2 = 2; +var rho4 = 4; +var epsilon2 = 1e-12; + +function cosh(x) { + return ((x = Math.exp(x)) + 1 / x) / 2; +} + +function sinh(x) { + return ((x = Math.exp(x)) - 1 / x) / 2; +} + +function tanh(x) { + return ((x = Math.exp(2 * x)) - 1) / (x + 1); +} + +// p0 = [ux0, uy0, w0] +// p1 = [ux1, uy1, w1] +var interpolateZoom = function(p0, p1) { + var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], + ux1 = p1[0], uy1 = p1[1], w1 = p1[2], + dx = ux1 - ux0, + dy = uy1 - uy0, + d2 = dx * dx + dy * dy, + i, + S; + + // Special case for u0 ≅ u1. + if (d2 < epsilon2) { + S = Math.log(w1 / w0) / rho; + i = function(t) { + return [ + ux0 + t * dx, + uy0 + t * dy, + w0 * Math.exp(rho * t * S) + ]; + }; + } + + // General case. + else { + var d1 = Math.sqrt(d2), + b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1), + b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1), + r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), + r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1); + S = (r1 - r0) / rho; + i = function(t) { + var s = t * S, + coshr0 = cosh(r0), + u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0)); + return [ + ux0 + u * dx, + uy0 + u * dy, + w0 * coshr0 / cosh(rho * s + r0) + ]; + }; + } + + i.duration = S * 1000; + + return i; +}; + +var frame = 0; +var timeout = 0; +var interval = 0; +var pokeDelay = 1000; +var taskHead; +var taskTail; +var clockLast = 0; +var clockNow = 0; +var clockSkew = 0; +var clock = typeof performance === "object" && performance.now ? performance : Date; +var setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); }; + +function now() { + return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew); +} + +function clearNow() { + clockNow = 0; +} + +function Timer() { + this._call = + this._time = + this._next = null; +} + +Timer.prototype = timer.prototype = { + constructor: Timer, + restart: function(callback, delay, time) { + if (typeof callback !== "function") throw new TypeError("callback is not a function"); + time = (time == null ? now() : +time) + (delay == null ? 0 : +delay); + if (!this._next && taskTail !== this) { + if (taskTail) taskTail._next = this; + else taskHead = this; + taskTail = this; + } + this._call = callback; + this._time = time; + sleep(); + }, + stop: function() { + if (this._call) { + this._call = null; + this._time = Infinity; + sleep(); + } + } +}; + +function timer(callback, delay, time) { + var t = new Timer; + t.restart(callback, delay, time); + return t; +} + +function timerFlush() { + now(); // Get the current time, if not already set. + ++frame; // Pretend we’ve set an alarm, if we haven’t already. + var t = taskHead, e; + while (t) { + if ((e = clockNow - t._time) >= 0) t._call.call(null, e); + t = t._next; + } + --frame; +} + +function wake() { + clockNow = (clockLast = clock.now()) + clockSkew; + frame = timeout = 0; + try { + timerFlush(); + } finally { + frame = 0; + nap(); + clockNow = 0; + } +} + +function poke() { + var now = clock.now(), delay = now - clockLast; + if (delay > pokeDelay) clockSkew -= delay, clockLast = now; +} + +function nap() { + var t0, t1 = taskHead, t2, time = Infinity; + while (t1) { + if (t1._call) { + if (time > t1._time) time = t1._time; + t0 = t1, t1 = t1._next; + } else { + t2 = t1._next, t1._next = null; + t1 = t0 ? t0._next = t2 : taskHead = t2; + } + } + taskTail = t0; + sleep(time); +} + +function sleep(time) { + if (frame) return; // Soonest alarm already set, or will be. + if (timeout) timeout = clearTimeout(timeout); + var delay = time - clockNow; // Strictly less than if we recomputed clockNow. + if (delay > 24) { + if (time < Infinity) timeout = setTimeout(wake, time - clock.now() - clockSkew); + if (interval) interval = clearInterval(interval); + } else { + if (!interval) clockLast = clock.now(), interval = setInterval(poke, pokeDelay); + frame = 1, setFrame(wake); + } +} + +var timeout$1 = function(callback, delay, time) { + var t = new Timer; + delay = delay == null ? 0 : +delay; + t.restart(function(elapsed) { + t.stop(); + callback(elapsed + delay); + }, delay, time); + return t; +}; + +var emptyOn = dispatch("start", "end", "interrupt"); +var emptyTween = []; + +var CREATED = 0; +var SCHEDULED = 1; +var STARTING = 2; +var STARTED = 3; +var RUNNING = 4; +var ENDING = 5; +var ENDED = 6; + +var schedule = function(node, name, id, index, group, timing) { + var schedules = node.__transition; + if (!schedules) node.__transition = {}; + else if (id in schedules) return; + create$1(node, id, { + name: name, + index: index, // For context during callback. + group: group, // For context during callback. + on: emptyOn, + tween: emptyTween, + time: timing.time, + delay: timing.delay, + duration: timing.duration, + ease: timing.ease, + timer: null, + state: CREATED + }); +}; + +function init(node, id) { + var schedule = get$1(node, id); + if (schedule.state > CREATED) throw new Error("too late; already scheduled"); + return schedule; +} + +function set$1(node, id) { + var schedule = get$1(node, id); + if (schedule.state > STARTING) throw new Error("too late; already started"); + return schedule; +} + +function get$1(node, id) { + var schedule = node.__transition; + if (!schedule || !(schedule = schedule[id])) throw new Error("transition not found"); + return schedule; +} + +function create$1(node, id, self) { + var schedules = node.__transition, + tween; + + // Initialize the self timer when the transition is created. + // Note the actual delay is not known until the first callback! + schedules[id] = self; + self.timer = timer(schedule, 0, self.time); + + function schedule(elapsed) { + self.state = SCHEDULED; + self.timer.restart(start, self.delay, self.time); + + // If the elapsed delay is less than our first sleep, start immediately. + if (self.delay <= elapsed) start(elapsed - self.delay); + } + + function start(elapsed) { + var i, j, n, o; + + // If the state is not SCHEDULED, then we previously errored on start. + if (self.state !== SCHEDULED) return stop(); + + for (i in schedules) { + o = schedules[i]; + if (o.name !== self.name) continue; + + // While this element already has a starting transition during this frame, + // defer starting an interrupting transition until that transition has a + // chance to tick (and possibly end); see d3/d3-transition#54! + if (o.state === STARTED) return timeout$1(start); + + // Interrupt the active transition, if any. + // Dispatch the interrupt event. + if (o.state === RUNNING) { + o.state = ENDED; + o.timer.stop(); + o.on.call("interrupt", node, node.__data__, o.index, o.group); + delete schedules[i]; + } + + // Cancel any pre-empted transitions. No interrupt event is dispatched + // because the cancelled transitions never started. Note that this also + // removes this transition from the pending list! + else if (+i < id) { + o.state = ENDED; + o.timer.stop(); + delete schedules[i]; + } + } + + // Defer the first tick to end of the current frame; see d3/d3#1576. + // Note the transition may be canceled after start and before the first tick! + // Note this must be scheduled before the start event; see d3/d3-transition#16! + // Assuming this is successful, subsequent callbacks go straight to tick. + timeout$1(function() { + if (self.state === STARTED) { + self.state = RUNNING; + self.timer.restart(tick, self.delay, self.time); + tick(elapsed); + } + }); + + // Dispatch the start event. + // Note this must be done before the tween are initialized. + self.state = STARTING; + self.on.call("start", node, node.__data__, self.index, self.group); + if (self.state !== STARTING) return; // interrupted + self.state = STARTED; + + // Initialize the tween, deleting null tween. + tween = new Array(n = self.tween.length); + for (i = 0, j = -1; i < n; ++i) { + if (o = self.tween[i].value.call(node, node.__data__, self.index, self.group)) { + tween[++j] = o; + } + } + tween.length = j + 1; + } + + function tick(elapsed) { + var t = elapsed < self.duration ? self.ease.call(null, elapsed / self.duration) : (self.timer.restart(stop), self.state = ENDING, 1), + i = -1, + n = tween.length; + + while (++i < n) { + tween[i].call(null, t); + } + + // Dispatch the end event. + if (self.state === ENDING) { + self.on.call("end", node, node.__data__, self.index, self.group); + stop(); + } + } + + function stop() { + self.state = ENDED; + self.timer.stop(); + delete schedules[id]; + for (var i in schedules) return; // eslint-disable-line no-unused-vars + delete node.__transition; + } +} + +var interrupt = function(node, name) { + var schedules = node.__transition, + schedule$$1, + active, + empty = true, + i; + + if (!schedules) return; + + name = name == null ? null : name + ""; + + for (i in schedules) { + if ((schedule$$1 = schedules[i]).name !== name) { empty = false; continue; } + active = schedule$$1.state > STARTING && schedule$$1.state < ENDING; + schedule$$1.state = ENDED; + schedule$$1.timer.stop(); + if (active) schedule$$1.on.call("interrupt", node, node.__data__, schedule$$1.index, schedule$$1.group); + delete schedules[i]; + } + + if (empty) delete node.__transition; +}; + +var selection_interrupt = function(name) { + return this.each(function() { + interrupt(this, name); + }); +}; + +function tweenRemove(id, name) { + var tween0, tween1; + return function() { + var schedule$$1 = set$1(this, id), + tween = schedule$$1.tween; + + // If this node shared tween with the previous node, + // just assign the updated shared tween and we’re done! + // Otherwise, copy-on-write. + if (tween !== tween0) { + tween1 = tween0 = tween; + for (var i = 0, n = tween1.length; i < n; ++i) { + if (tween1[i].name === name) { + tween1 = tween1.slice(); + tween1.splice(i, 1); + break; + } + } + } + + schedule$$1.tween = tween1; + }; +} + +function tweenFunction(id, name, value) { + var tween0, tween1; + if (typeof value !== "function") throw new Error; + return function() { + var schedule$$1 = set$1(this, id), + tween = schedule$$1.tween; + + // If this node shared tween with the previous node, + // just assign the updated shared tween and we’re done! + // Otherwise, copy-on-write. + if (tween !== tween0) { + tween1 = (tween0 = tween).slice(); + for (var t = {name: name, value: value}, i = 0, n = tween1.length; i < n; ++i) { + if (tween1[i].name === name) { + tween1[i] = t; + break; + } + } + if (i === n) tween1.push(t); + } + + schedule$$1.tween = tween1; + }; +} + +var transition_tween = function(name, value) { + var id = this._id; + + name += ""; + + if (arguments.length < 2) { + var tween = get$1(this.node(), id).tween; + for (var i = 0, n = tween.length, t; i < n; ++i) { + if ((t = tween[i]).name === name) { + return t.value; + } + } + return null; + } + + return this.each((value == null ? tweenRemove : tweenFunction)(id, name, value)); +}; + +function tweenValue(transition, name, value) { + var id = transition._id; + + transition.each(function() { + var schedule$$1 = set$1(this, id); + (schedule$$1.value || (schedule$$1.value = {}))[name] = value.apply(this, arguments); + }); + + return function(node) { + return get$1(node, id).value[name]; + }; +} + +var interpolate = function(a, b) { + var c; + return (typeof b === "number" ? interpolateNumber + : b instanceof color ? interpolateRgb + : (c = color(b)) ? (b = c, interpolateRgb) + : interpolateString)(a, b); +}; + +function attrRemove$1(name) { + return function() { + this.removeAttribute(name); + }; +} + +function attrRemoveNS$1(fullname) { + return function() { + this.removeAttributeNS(fullname.space, fullname.local); + }; +} + +function attrConstant$1(name, interpolate$$1, value1) { + var value00, + interpolate0; + return function() { + var value0 = this.getAttribute(name); + return value0 === value1 ? null + : value0 === value00 ? interpolate0 + : interpolate0 = interpolate$$1(value00 = value0, value1); + }; +} + +function attrConstantNS$1(fullname, interpolate$$1, value1) { + var value00, + interpolate0; + return function() { + var value0 = this.getAttributeNS(fullname.space, fullname.local); + return value0 === value1 ? null + : value0 === value00 ? interpolate0 + : interpolate0 = interpolate$$1(value00 = value0, value1); + }; +} + +function attrFunction$1(name, interpolate$$1, value) { + var value00, + value10, + interpolate0; + return function() { + var value0, value1 = value(this); + if (value1 == null) return void this.removeAttribute(name); + value0 = this.getAttribute(name); + return value0 === value1 ? null + : value0 === value00 && value1 === value10 ? interpolate0 + : interpolate0 = interpolate$$1(value00 = value0, value10 = value1); + }; +} + +function attrFunctionNS$1(fullname, interpolate$$1, value) { + var value00, + value10, + interpolate0; + return function() { + var value0, value1 = value(this); + if (value1 == null) return void this.removeAttributeNS(fullname.space, fullname.local); + value0 = this.getAttributeNS(fullname.space, fullname.local); + return value0 === value1 ? null + : value0 === value00 && value1 === value10 ? interpolate0 + : interpolate0 = interpolate$$1(value00 = value0, value10 = value1); + }; +} + +var transition_attr = function(name, value) { + var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate; + return this.attrTween(name, typeof value === "function" + ? (fullname.local ? attrFunctionNS$1 : attrFunction$1)(fullname, i, tweenValue(this, "attr." + name, value)) + : value == null ? (fullname.local ? attrRemoveNS$1 : attrRemove$1)(fullname) + : (fullname.local ? attrConstantNS$1 : attrConstant$1)(fullname, i, value + "")); +}; + +function attrTweenNS(fullname, value) { + function tween() { + var node = this, i = value.apply(node, arguments); + return i && function(t) { + node.setAttributeNS(fullname.space, fullname.local, i(t)); + }; + } + tween._value = value; + return tween; +} + +function attrTween(name, value) { + function tween() { + var node = this, i = value.apply(node, arguments); + return i && function(t) { + node.setAttribute(name, i(t)); + }; + } + tween._value = value; + return tween; +} + +var transition_attrTween = function(name, value) { + var key = "attr." + name; + if (arguments.length < 2) return (key = this.tween(key)) && key._value; + if (value == null) return this.tween(key, null); + if (typeof value !== "function") throw new Error; + var fullname = namespace(name); + return this.tween(key, (fullname.local ? attrTweenNS : attrTween)(fullname, value)); +}; + +function delayFunction(id, value) { + return function() { + init(this, id).delay = +value.apply(this, arguments); + }; +} + +function delayConstant(id, value) { + return value = +value, function() { + init(this, id).delay = value; + }; +} + +var transition_delay = function(value) { + var id = this._id; + + return arguments.length + ? this.each((typeof value === "function" + ? delayFunction + : delayConstant)(id, value)) + : get$1(this.node(), id).delay; +}; + +function durationFunction(id, value) { + return function() { + set$1(this, id).duration = +value.apply(this, arguments); + }; +} + +function durationConstant(id, value) { + return value = +value, function() { + set$1(this, id).duration = value; + }; +} + +var transition_duration = function(value) { + var id = this._id; + + return arguments.length + ? this.each((typeof value === "function" + ? durationFunction + : durationConstant)(id, value)) + : get$1(this.node(), id).duration; +}; + +function easeConstant(id, value) { + if (typeof value !== "function") throw new Error; + return function() { + set$1(this, id).ease = value; + }; +} + +var transition_ease = function(value) { + var id = this._id; + + return arguments.length + ? this.each(easeConstant(id, value)) + : get$1(this.node(), id).ease; +}; + +var transition_filter = function(match) { + if (typeof match !== "function") match = matcher$1(match); + + for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { + if ((node = group[i]) && match.call(node, node.__data__, i, group)) { + subgroup.push(node); + } + } + } + + return new Transition(subgroups, this._parents, this._name, this._id); +}; + +var transition_merge = function(transition$$1) { + if (transition$$1._id !== this._id) throw new Error; + + for (var groups0 = this._groups, groups1 = transition$$1._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { + for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { + if (node = group0[i] || group1[i]) { + merge[i] = node; + } + } + } + + for (; j < m0; ++j) { + merges[j] = groups0[j]; + } + + return new Transition(merges, this._parents, this._name, this._id); +}; + +function start(name) { + return (name + "").trim().split(/^|\s+/).every(function(t) { + var i = t.indexOf("."); + if (i >= 0) t = t.slice(0, i); + return !t || t === "start"; + }); +} + +function onFunction(id, name, listener) { + var on0, on1, sit = start(name) ? init : set$1; + return function() { + var schedule$$1 = sit(this, id), + on = schedule$$1.on; + + // If this node shared a dispatch with the previous node, + // just assign the updated shared dispatch and we’re done! + // Otherwise, copy-on-write. + if (on !== on0) (on1 = (on0 = on).copy()).on(name, listener); + + schedule$$1.on = on1; + }; +} + +var transition_on = function(name, listener) { + var id = this._id; + + return arguments.length < 2 + ? get$1(this.node(), id).on.on(name) + : this.each(onFunction(id, name, listener)); +}; + +function removeFunction(id) { + return function() { + var parent = this.parentNode; + for (var i in this.__transition) if (+i !== id) return; + if (parent) parent.removeChild(this); + }; +} + +var transition_remove = function() { + return this.on("end.remove", removeFunction(this._id)); +}; + +var transition_select = function(select) { + var name = this._name, + id = this._id; + + if (typeof select !== "function") select = selector(select); + + for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { + if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + subgroup[i] = subnode; + schedule(subgroup[i], name, id, i, subgroup, get$1(node, id)); + } + } + } + + return new Transition(subgroups, this._parents, name, id); +}; + +var transition_selectAll = function(select) { + var name = this._name, + id = this._id; + + if (typeof select !== "function") select = selectorAll(select); + + for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { + if (node = group[i]) { + for (var children = select.call(node, node.__data__, i, group), child, inherit = get$1(node, id), k = 0, l = children.length; k < l; ++k) { + if (child = children[k]) { + schedule(child, name, id, k, children, inherit); + } + } + subgroups.push(children); + parents.push(node); + } + } + } + + return new Transition(subgroups, parents, name, id); +}; + +var Selection$1 = selection.prototype.constructor; + +var transition_selection = function() { + return new Selection$1(this._groups, this._parents); +}; + +function styleRemove$1(name, interpolate$$1) { + var value00, + value10, + interpolate0; + return function() { + var value0 = styleValue(this, name), + value1 = (this.style.removeProperty(name), styleValue(this, name)); + return value0 === value1 ? null + : value0 === value00 && value1 === value10 ? interpolate0 + : interpolate0 = interpolate$$1(value00 = value0, value10 = value1); + }; +} + +function styleRemoveEnd(name) { + return function() { + this.style.removeProperty(name); + }; +} + +function styleConstant$1(name, interpolate$$1, value1) { + var value00, + interpolate0; + return function() { + var value0 = styleValue(this, name); + return value0 === value1 ? null + : value0 === value00 ? interpolate0 + : interpolate0 = interpolate$$1(value00 = value0, value1); + }; +} + +function styleFunction$1(name, interpolate$$1, value) { + var value00, + value10, + interpolate0; + return function() { + var value0 = styleValue(this, name), + value1 = value(this); + if (value1 == null) value1 = (this.style.removeProperty(name), styleValue(this, name)); + return value0 === value1 ? null + : value0 === value00 && value1 === value10 ? interpolate0 + : interpolate0 = interpolate$$1(value00 = value0, value10 = value1); + }; +} + +var transition_style = function(name, value, priority) { + var i = (name += "") === "transform" ? interpolateTransformCss : interpolate; + return value == null ? this + .styleTween(name, styleRemove$1(name, i)) + .on("end.style." + name, styleRemoveEnd(name)) + : this.styleTween(name, typeof value === "function" + ? styleFunction$1(name, i, tweenValue(this, "style." + name, value)) + : styleConstant$1(name, i, value + ""), priority); +}; + +function styleTween(name, value, priority) { + function tween() { + var node = this, i = value.apply(node, arguments); + return i && function(t) { + node.style.setProperty(name, i(t), priority); + }; + } + tween._value = value; + return tween; +} + +var transition_styleTween = function(name, value, priority) { + var key = "style." + (name += ""); + if (arguments.length < 2) return (key = this.tween(key)) && key._value; + if (value == null) return this.tween(key, null); + if (typeof value !== "function") throw new Error; + return this.tween(key, styleTween(name, value, priority == null ? "" : priority)); +}; + +function textConstant$1(value) { + return function() { + this.textContent = value; + }; +} + +function textFunction$1(value) { + return function() { + var value1 = value(this); + this.textContent = value1 == null ? "" : value1; + }; +} + +var transition_text = function(value) { + return this.tween("text", typeof value === "function" + ? textFunction$1(tweenValue(this, "text", value)) + : textConstant$1(value == null ? "" : value + "")); +}; + +var transition_transition = function() { + var name = this._name, + id0 = this._id, + id1 = newId(); + + for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { + if (node = group[i]) { + var inherit = get$1(node, id0); + schedule(node, name, id1, i, group, { + time: inherit.time + inherit.delay + inherit.duration, + delay: 0, + duration: inherit.duration, + ease: inherit.ease + }); + } + } + } + + return new Transition(groups, this._parents, name, id1); +}; + +var id = 0; + +function Transition(groups, parents, name, id) { + this._groups = groups; + this._parents = parents; + this._name = name; + this._id = id; +} + +function transition(name) { + return selection().transition(name); +} + +function newId() { + return ++id; +} + +var selection_prototype = selection.prototype; + +Transition.prototype = transition.prototype = { + constructor: Transition, + select: transition_select, + selectAll: transition_selectAll, + filter: transition_filter, + merge: transition_merge, + selection: transition_selection, + transition: transition_transition, + call: selection_prototype.call, + nodes: selection_prototype.nodes, + node: selection_prototype.node, + size: selection_prototype.size, + empty: selection_prototype.empty, + each: selection_prototype.each, + on: transition_on, + attr: transition_attr, + attrTween: transition_attrTween, + style: transition_style, + styleTween: transition_styleTween, + text: transition_text, + remove: transition_remove, + tween: transition_tween, + delay: transition_delay, + duration: transition_duration, + ease: transition_ease +}; + +function linear$1(t) { + return +t; +} + +function cubicInOut(t) { + return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2; +} + +var defaultTiming = { + time: null, // Set on use. + delay: 0, + duration: 250, + ease: cubicInOut +}; + +function inherit(node, id) { + var timing; + while (!(timing = node.__transition) || !(timing = timing[id])) { + if (!(node = node.parentNode)) { + return defaultTiming.time = now(), defaultTiming; + } + } + return timing; +} + +var selection_transition = function(name) { + var id, + timing; + + if (name instanceof Transition) { + id = name._id, name = name._name; + } else { + id = newId(), (timing = defaultTiming).time = now(), name = name == null ? null : name + ""; + } + + for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { + if (node = group[i]) { + schedule(node, name, id, i, group, timing || inherit(node, id)); + } + } + } + + return new Transition(groups, this._parents, name, id); +}; + +selection.prototype.interrupt = selection_interrupt; +selection.prototype.transition = selection_transition; + +var root$1 = [null]; + +var active = function(node, name) { + var schedules = node.__transition, + schedule$$1, + i; + + if (schedules) { + name = name == null ? null : name + ""; + for (i in schedules) { + if ((schedule$$1 = schedules[i]).state > SCHEDULED && schedule$$1.name === name) { + return new Transition([[node]], root$1, name, +i); + } + } + } + + return null; +}; + +var constant$3 = function(x) { + return function() { + return x; + }; +}; + +function ZoomEvent(target, type, transform) { + this.target = target; + this.type = type; + this.transform = transform; +} + +function Transform(k, x, y) { + this.k = k; + this.x = x; + this.y = y; +} + +Transform.prototype = { + constructor: Transform, + scale: function(k) { + return k === 1 ? this : new Transform(this.k * k, this.x, this.y); + }, + translate: function(x, y) { + return x === 0 & y === 0 ? this : new Transform(this.k, this.x + this.k * x, this.y + this.k * y); + }, + apply: function(point) { + return [point[0] * this.k + this.x, point[1] * this.k + this.y]; + }, + applyX: function(x) { + return x * this.k + this.x; + }, + applyY: function(y) { + return y * this.k + this.y; + }, + invert: function(location) { + return [(location[0] - this.x) / this.k, (location[1] - this.y) / this.k]; + }, + invertX: function(x) { + return (x - this.x) / this.k; + }, + invertY: function(y) { + return (y - this.y) / this.k; + }, + rescaleX: function(x) { + return x.copy().domain(x.range().map(this.invertX, this).map(x.invert, x)); + }, + rescaleY: function(y) { + return y.copy().domain(y.range().map(this.invertY, this).map(y.invert, y)); + }, + toString: function() { + return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")"; + } +}; + +var identity$1 = new Transform(1, 0, 0); + +transform.prototype = Transform.prototype; + +function transform(node) { + return node.__zoom || identity$1; +} + +function nopropagation$1() { + exports.event.stopImmediatePropagation(); +} + +var noevent$1 = function() { + exports.event.preventDefault(); + exports.event.stopImmediatePropagation(); +}; + +// Ignore right-click, since that should open the context menu. +function defaultFilter$1() { + return !exports.event.button; +} + +function defaultExtent() { + var e = this, w, h; + if (e instanceof SVGElement) { + e = e.ownerSVGElement || e; + w = e.width.baseVal.value; + h = e.height.baseVal.value; + } else { + w = e.clientWidth; + h = e.clientHeight; + } + return [[0, 0], [w, h]]; +} + +function defaultTransform() { + return this.__zoom || identity$1; +} + +function defaultWheelDelta() { + return -exports.event.deltaY * (exports.event.deltaMode ? 120 : 1) / 500; +} + +function defaultTouchable$1() { + return "ontouchstart" in this; +} + +function defaultConstrain(transform$$1, extent, translateExtent) { + var dx0 = transform$$1.invertX(extent[0][0]) - translateExtent[0][0], + dx1 = transform$$1.invertX(extent[1][0]) - translateExtent[1][0], + dy0 = transform$$1.invertY(extent[0][1]) - translateExtent[0][1], + dy1 = transform$$1.invertY(extent[1][1]) - translateExtent[1][1]; + return transform$$1.translate( + dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1), + dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1) + ); +} + +var zoom = function() { + var filter = defaultFilter$1, + extent = defaultExtent, + constrain = defaultConstrain, + wheelDelta = defaultWheelDelta, + touchable = defaultTouchable$1, + scaleExtent = [0, Infinity], + translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], + duration = 250, + interpolate = interpolateZoom, + gestures = [], + listeners = dispatch("start", "zoom", "end"), + touchstarting, + touchending, + touchDelay = 500, + wheelDelay = 150, + clickDistance2 = 0; + + function zoom(selection) { + selection + .property("__zoom", defaultTransform) + .on("wheel.zoom", wheeled) + .on("mousedown.zoom", mousedowned) + .on("dblclick.zoom", dblclicked) + .filter(touchable) + .on("touchstart.zoom", touchstarted) + .on("touchmove.zoom", touchmoved) + .on("touchend.zoom touchcancel.zoom", touchended) + .style("touch-action", "none") + .style("-webkit-tap-highlight-color", "rgba(0,0,0,0)"); + } + + zoom.transform = function(collection, transform$$1) { + var selection = collection.selection ? collection.selection() : collection; + selection.property("__zoom", defaultTransform); + if (collection !== selection) { + schedule(collection, transform$$1); + } else { + selection.interrupt().each(function() { + gesture(this, arguments) + .start() + .zoom(null, typeof transform$$1 === "function" ? transform$$1.apply(this, arguments) : transform$$1) + .end(); + }); + } + }; + + zoom.scaleBy = function(selection, k) { + zoom.scaleTo(selection, function() { + var k0 = this.__zoom.k, + k1 = typeof k === "function" ? k.apply(this, arguments) : k; + return k0 * k1; + }); + }; + + zoom.scaleTo = function(selection, k) { + zoom.transform(selection, function() { + var e = extent.apply(this, arguments), + t0 = this.__zoom, + p0 = centroid(e), + p1 = t0.invert(p0), + k1 = typeof k === "function" ? k.apply(this, arguments) : k; + return constrain(translate(scale(t0, k1), p0, p1), e, translateExtent); + }); + }; + + zoom.translateBy = function(selection, x, y) { + zoom.transform(selection, function() { + return constrain(this.__zoom.translate( + typeof x === "function" ? x.apply(this, arguments) : x, + typeof y === "function" ? y.apply(this, arguments) : y + ), extent.apply(this, arguments), translateExtent); + }); + }; + + zoom.translateTo = function(selection, x, y) { + zoom.transform(selection, function() { + var e = extent.apply(this, arguments), + t = this.__zoom, + p = centroid(e); + return constrain(identity$1.translate(p[0], p[1]).scale(t.k).translate( + typeof x === "function" ? -x.apply(this, arguments) : -x, + typeof y === "function" ? -y.apply(this, arguments) : -y + ), e, translateExtent); + }); + }; + + function scale(transform$$1, k) { + k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], k)); + return k === transform$$1.k ? transform$$1 : new Transform(k, transform$$1.x, transform$$1.y); + } + + function translate(transform$$1, p0, p1) { + var x = p0[0] - p1[0] * transform$$1.k, y = p0[1] - p1[1] * transform$$1.k; + return x === transform$$1.x && y === transform$$1.y ? transform$$1 : new Transform(transform$$1.k, x, y); + } + + function centroid(extent) { + return [(+extent[0][0] + +extent[1][0]) / 2, (+extent[0][1] + +extent[1][1]) / 2]; + } + + function schedule(transition, transform$$1, center) { + transition + .on("start.zoom", function() { gesture(this, arguments).start(); }) + .on("interrupt.zoom end.zoom", function() { gesture(this, arguments).end(); }) + .tween("zoom", function() { + var that = this, + args = arguments, + g = gesture(that, args), + e = extent.apply(that, args), + p = center || centroid(e), + w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]), + a = that.__zoom, + b = typeof transform$$1 === "function" ? transform$$1.apply(that, args) : transform$$1, + i = interpolate(a.invert(p).concat(w / a.k), b.invert(p).concat(w / b.k)); + return function(t) { + if (t === 1) t = b; // Avoid rounding error on end. + else { var l = i(t), k = w / l[2]; t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k); } + g.zoom(null, t); + }; + }); + } + + function gesture(that, args) { + for (var i = 0, n = gestures.length, g; i < n; ++i) { + if ((g = gestures[i]).that === that) { + return g; + } + } + return new Gesture(that, args); + } + + function Gesture(that, args) { + this.that = that; + this.args = args; + this.index = -1; + this.active = 0; + this.extent = extent.apply(that, args); + } + + Gesture.prototype = { + start: function() { + if (++this.active === 1) { + this.index = gestures.push(this) - 1; + this.emit("start"); + } + return this; + }, + zoom: function(key, transform$$1) { + if (this.mouse && key !== "mouse") this.mouse[1] = transform$$1.invert(this.mouse[0]); + if (this.touch0 && key !== "touch") this.touch0[1] = transform$$1.invert(this.touch0[0]); + if (this.touch1 && key !== "touch") this.touch1[1] = transform$$1.invert(this.touch1[0]); + this.that.__zoom = transform$$1; + this.emit("zoom"); + return this; + }, + end: function() { + if (--this.active === 0) { + gestures.splice(this.index, 1); + this.index = -1; + this.emit("end"); + } + return this; + }, + emit: function(type) { + customEvent(new ZoomEvent(zoom, type, this.that.__zoom), listeners.apply, listeners, [type, this.that, this.args]); + } + }; + + function wheeled() { + if (!filter.apply(this, arguments)) return; + var g = gesture(this, arguments), + t = this.__zoom, + k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], t.k * Math.pow(2, wheelDelta.apply(this, arguments)))), + p = mouse(this); + + // If the mouse is in the same location as before, reuse it. + // If there were recent wheel events, reset the wheel idle timeout. + if (g.wheel) { + if (g.mouse[0][0] !== p[0] || g.mouse[0][1] !== p[1]) { + g.mouse[1] = t.invert(g.mouse[0] = p); + } + clearTimeout(g.wheel); + } + + // If this wheel event won’t trigger a transform change, ignore it. + else if (t.k === k) return; + + // Otherwise, capture the mouse point and location at the start. + else { + g.mouse = [p, t.invert(p)]; + interrupt(this); + g.start(); + } + + noevent$1(); + g.wheel = setTimeout(wheelidled, wheelDelay); + g.zoom("mouse", constrain(translate(scale(t, k), g.mouse[0], g.mouse[1]), g.extent, translateExtent)); + + function wheelidled() { + g.wheel = null; + g.end(); + } + } + + function mousedowned() { + if (touchending || !filter.apply(this, arguments)) return; + var g = gesture(this, arguments), + v = select(exports.event.view).on("mousemove.zoom", mousemoved, true).on("mouseup.zoom", mouseupped, true), + p = mouse(this), + x0 = exports.event.clientX, + y0 = exports.event.clientY; + + dragDisable(exports.event.view); + nopropagation$1(); + g.mouse = [p, this.__zoom.invert(p)]; + interrupt(this); + g.start(); + + function mousemoved() { + noevent$1(); + if (!g.moved) { + var dx = exports.event.clientX - x0, dy = exports.event.clientY - y0; + g.moved = dx * dx + dy * dy > clickDistance2; + } + g.zoom("mouse", constrain(translate(g.that.__zoom, g.mouse[0] = mouse(g.that), g.mouse[1]), g.extent, translateExtent)); + } + + function mouseupped() { + v.on("mousemove.zoom mouseup.zoom", null); + yesdrag(exports.event.view, g.moved); + noevent$1(); + g.end(); + } + } + + function dblclicked() { + if (!filter.apply(this, arguments)) return; + var t0 = this.__zoom, + p0 = mouse(this), + p1 = t0.invert(p0), + k1 = t0.k * (exports.event.shiftKey ? 0.5 : 2), + t1 = constrain(translate(scale(t0, k1), p0, p1), extent.apply(this, arguments), translateExtent); + + noevent$1(); + if (duration > 0) select(this).transition().duration(duration).call(schedule, t1, p0); + else select(this).call(zoom.transform, t1); + } + + function touchstarted() { + if (!filter.apply(this, arguments)) return; + var g = gesture(this, arguments), + touches = exports.event.changedTouches, + started, + n = touches.length, i, t, p; + + nopropagation$1(); + for (i = 0; i < n; ++i) { + t = touches[i], p = touch(this, touches, t.identifier); + p = [p, this.__zoom.invert(p), t.identifier]; + if (!g.touch0) g.touch0 = p, started = true; + else if (!g.touch1) g.touch1 = p; + } + + // If this is a dbltap, reroute to the (optional) dblclick.zoom handler. + if (touchstarting) { + touchstarting = clearTimeout(touchstarting); + if (!g.touch1) { + g.end(); + p = select(this).on("dblclick.zoom"); + if (p) p.apply(this, arguments); + return; + } + } + + if (started) { + touchstarting = setTimeout(function() { touchstarting = null; }, touchDelay); + interrupt(this); + g.start(); + } + } + + function touchmoved() { + var g = gesture(this, arguments), + touches = exports.event.changedTouches, + n = touches.length, i, t, p, l; + + noevent$1(); + if (touchstarting) touchstarting = clearTimeout(touchstarting); + for (i = 0; i < n; ++i) { + t = touches[i], p = touch(this, touches, t.identifier); + if (g.touch0 && g.touch0[2] === t.identifier) g.touch0[0] = p; + else if (g.touch1 && g.touch1[2] === t.identifier) g.touch1[0] = p; + } + t = g.that.__zoom; + if (g.touch1) { + var p0 = g.touch0[0], l0 = g.touch0[1], + p1 = g.touch1[0], l1 = g.touch1[1], + dp = (dp = p1[0] - p0[0]) * dp + (dp = p1[1] - p0[1]) * dp, + dl = (dl = l1[0] - l0[0]) * dl + (dl = l1[1] - l0[1]) * dl; + t = scale(t, Math.sqrt(dp / dl)); + p = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2]; + l = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2]; + } + else if (g.touch0) p = g.touch0[0], l = g.touch0[1]; + else return; + g.zoom("touch", constrain(translate(t, p, l), g.extent, translateExtent)); + } + + function touchended() { + var g = gesture(this, arguments), + touches = exports.event.changedTouches, + n = touches.length, i, t; + + nopropagation$1(); + if (touchending) clearTimeout(touchending); + touchending = setTimeout(function() { touchending = null; }, touchDelay); + for (i = 0; i < n; ++i) { + t = touches[i]; + if (g.touch0 && g.touch0[2] === t.identifier) delete g.touch0; + else if (g.touch1 && g.touch1[2] === t.identifier) delete g.touch1; + } + if (g.touch1 && !g.touch0) g.touch0 = g.touch1, delete g.touch1; + if (g.touch0) g.touch0[1] = this.__zoom.invert(g.touch0[0]); + else g.end(); + } + + zoom.wheelDelta = function(_) { + return arguments.length ? (wheelDelta = typeof _ === "function" ? _ : constant$3(+_), zoom) : wheelDelta; + }; + + zoom.filter = function(_) { + return arguments.length ? (filter = typeof _ === "function" ? _ : constant$3(!!_), zoom) : filter; + }; + + zoom.touchable = function(_) { + return arguments.length ? (touchable = typeof _ === "function" ? _ : constant$3(!!_), zoom) : touchable; + }; + + zoom.extent = function(_) { + return arguments.length ? (extent = typeof _ === "function" ? _ : constant$3([[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]]), zoom) : extent; + }; + + zoom.scaleExtent = function(_) { + return arguments.length ? (scaleExtent[0] = +_[0], scaleExtent[1] = +_[1], zoom) : [scaleExtent[0], scaleExtent[1]]; + }; + + zoom.translateExtent = function(_) { + return arguments.length ? (translateExtent[0][0] = +_[0][0], translateExtent[1][0] = +_[1][0], translateExtent[0][1] = +_[0][1], translateExtent[1][1] = +_[1][1], zoom) : [[translateExtent[0][0], translateExtent[0][1]], [translateExtent[1][0], translateExtent[1][1]]]; + }; + + zoom.constrain = function(_) { + return arguments.length ? (constrain = _, zoom) : constrain; + }; + + zoom.duration = function(_) { + return arguments.length ? (duration = +_, zoom) : duration; + }; + + zoom.interpolate = function(_) { + return arguments.length ? (interpolate = _, zoom) : interpolate; + }; + + zoom.on = function() { + var value = listeners.on.apply(listeners, arguments); + return value === listeners ? zoom : value; + }; + + zoom.clickDistance = function(_) { + return arguments.length ? (clickDistance2 = (_ = +_) * _, zoom) : Math.sqrt(clickDistance2); + }; + + return zoom; +}; + +var center = function(x, y) { + var nodes; + + if (x == null) x = 0; + if (y == null) y = 0; + + function force() { + var i, + n = nodes.length, + node, + sx = 0, + sy = 0; + + for (i = 0; i < n; ++i) { + node = nodes[i], sx += node.x, sy += node.y; + } + + for (sx = sx / n - x, sy = sy / n - y, i = 0; i < n; ++i) { + node = nodes[i], node.x -= sx, node.y -= sy; + } + } + + force.initialize = function(_) { + nodes = _; + }; + + force.x = function(_) { + return arguments.length ? (x = +_, force) : x; + }; + + force.y = function(_) { + return arguments.length ? (y = +_, force) : y; + }; + + return force; +}; + +var constant$4 = function(x) { + return function() { + return x; + }; +}; + +var jiggle = function() { + return (Math.random() - 0.5) * 1e-6; +}; + +var tree_add = function(d) { + var x = +this._x.call(null, d), + y = +this._y.call(null, d); + return add(this.cover(x, y), x, y, d); +}; + +function add(tree, x, y, d) { + if (isNaN(x) || isNaN(y)) return tree; // ignore invalid points + + var parent, + node = tree._root, + leaf = {data: d}, + x0 = tree._x0, + y0 = tree._y0, + x1 = tree._x1, + y1 = tree._y1, + xm, + ym, + xp, + yp, + right, + bottom, + i, + j; + + // If the tree is empty, initialize the root as a leaf. + if (!node) return tree._root = leaf, tree; + + // Find the existing leaf for the new point, or add it. + while (node.length) { + if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm; + if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym; + if (parent = node, !(node = node[i = bottom << 1 | right])) return parent[i] = leaf, tree; + } + + // Is the new point is exactly coincident with the existing point? + xp = +tree._x.call(null, node.data); + yp = +tree._y.call(null, node.data); + if (x === xp && y === yp) return leaf.next = node, parent ? parent[i] = leaf : tree._root = leaf, tree; + + // Otherwise, split the leaf node until the old and new point are separated. + do { + parent = parent ? parent[i] = new Array(4) : tree._root = new Array(4); + if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm; + if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym; + } while ((i = bottom << 1 | right) === (j = (yp >= ym) << 1 | (xp >= xm))); + return parent[j] = node, parent[i] = leaf, tree; +} + +function addAll(data) { + var d, i, n = data.length, + x, + y, + xz = new Array(n), + yz = new Array(n), + x0 = Infinity, + y0 = Infinity, + x1 = -Infinity, + y1 = -Infinity; + + // Compute the points and their extent. + for (i = 0; i < n; ++i) { + if (isNaN(x = +this._x.call(null, d = data[i])) || isNaN(y = +this._y.call(null, d))) continue; + xz[i] = x; + yz[i] = y; + if (x < x0) x0 = x; + if (x > x1) x1 = x; + if (y < y0) y0 = y; + if (y > y1) y1 = y; + } + + // If there were no (valid) points, inherit the existing extent. + if (x1 < x0) x0 = this._x0, x1 = this._x1; + if (y1 < y0) y0 = this._y0, y1 = this._y1; + + // Expand the tree to cover the new points. + this.cover(x0, y0).cover(x1, y1); + + // Add the new points. + for (i = 0; i < n; ++i) { + add(this, xz[i], yz[i], data[i]); + } + + return this; +} + +var tree_cover = function(x, y) { + if (isNaN(x = +x) || isNaN(y = +y)) return this; // ignore invalid points + + var x0 = this._x0, + y0 = this._y0, + x1 = this._x1, + y1 = this._y1; + + // If the quadtree has no extent, initialize them. + // Integer extent are necessary so that if we later double the extent, + // the existing quadrant boundaries don’t change due to floating point error! + if (isNaN(x0)) { + x1 = (x0 = Math.floor(x)) + 1; + y1 = (y0 = Math.floor(y)) + 1; + } + + // Otherwise, double repeatedly to cover. + else if (x0 > x || x > x1 || y0 > y || y > y1) { + var z = x1 - x0, + node = this._root, + parent, + i; + + switch (i = (y < (y0 + y1) / 2) << 1 | (x < (x0 + x1) / 2)) { + case 0: { + do parent = new Array(4), parent[i] = node, node = parent; + while (z *= 2, x1 = x0 + z, y1 = y0 + z, x > x1 || y > y1); + break; + } + case 1: { + do parent = new Array(4), parent[i] = node, node = parent; + while (z *= 2, x0 = x1 - z, y1 = y0 + z, x0 > x || y > y1); + break; + } + case 2: { + do parent = new Array(4), parent[i] = node, node = parent; + while (z *= 2, x1 = x0 + z, y0 = y1 - z, x > x1 || y0 > y); + break; + } + case 3: { + do parent = new Array(4), parent[i] = node, node = parent; + while (z *= 2, x0 = x1 - z, y0 = y1 - z, x0 > x || y0 > y); + break; + } + } + + if (this._root && this._root.length) this._root = node; + } + + // If the quadtree covers the point already, just return. + else return this; + + this._x0 = x0; + this._y0 = y0; + this._x1 = x1; + this._y1 = y1; + return this; +}; + +var tree_data = function() { + var data = []; + this.visit(function(node) { + if (!node.length) do data.push(node.data); while (node = node.next) + }); + return data; +}; + +var tree_extent = function(_) { + return arguments.length + ? this.cover(+_[0][0], +_[0][1]).cover(+_[1][0], +_[1][1]) + : isNaN(this._x0) ? undefined : [[this._x0, this._y0], [this._x1, this._y1]]; +}; + +var Quad = function(node, x0, y0, x1, y1) { + this.node = node; + this.x0 = x0; + this.y0 = y0; + this.x1 = x1; + this.y1 = y1; +}; + +var tree_find = function(x, y, radius) { + var data, + x0 = this._x0, + y0 = this._y0, + x1, + y1, + x2, + y2, + x3 = this._x1, + y3 = this._y1, + quads = [], + node = this._root, + q, + i; + + if (node) quads.push(new Quad(node, x0, y0, x3, y3)); + if (radius == null) radius = Infinity; + else { + x0 = x - radius, y0 = y - radius; + x3 = x + radius, y3 = y + radius; + radius *= radius; + } + + while (q = quads.pop()) { + + // Stop searching if this quadrant can’t contain a closer node. + if (!(node = q.node) + || (x1 = q.x0) > x3 + || (y1 = q.y0) > y3 + || (x2 = q.x1) < x0 + || (y2 = q.y1) < y0) continue; + + // Bisect the current quadrant. + if (node.length) { + var xm = (x1 + x2) / 2, + ym = (y1 + y2) / 2; + + quads.push( + new Quad(node[3], xm, ym, x2, y2), + new Quad(node[2], x1, ym, xm, y2), + new Quad(node[1], xm, y1, x2, ym), + new Quad(node[0], x1, y1, xm, ym) + ); + + // Visit the closest quadrant first. + if (i = (y >= ym) << 1 | (x >= xm)) { + q = quads[quads.length - 1]; + quads[quads.length - 1] = quads[quads.length - 1 - i]; + quads[quads.length - 1 - i] = q; + } + } + + // Visit this point. (Visiting coincident points isn’t necessary!) + else { + var dx = x - +this._x.call(null, node.data), + dy = y - +this._y.call(null, node.data), + d2 = dx * dx + dy * dy; + if (d2 < radius) { + var d = Math.sqrt(radius = d2); + x0 = x - d, y0 = y - d; + x3 = x + d, y3 = y + d; + data = node.data; + } + } + } + + return data; +}; + +var tree_remove = function(d) { + if (isNaN(x = +this._x.call(null, d)) || isNaN(y = +this._y.call(null, d))) return this; // ignore invalid points + + var parent, + node = this._root, + retainer, + previous, + next, + x0 = this._x0, + y0 = this._y0, + x1 = this._x1, + y1 = this._y1, + x, + y, + xm, + ym, + right, + bottom, + i, + j; + + // If the tree is empty, initialize the root as a leaf. + if (!node) return this; + + // Find the leaf node for the point. + // While descending, also retain the deepest parent with a non-removed sibling. + if (node.length) while (true) { + if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm; + if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym; + if (!(parent = node, node = node[i = bottom << 1 | right])) return this; + if (!node.length) break; + if (parent[(i + 1) & 3] || parent[(i + 2) & 3] || parent[(i + 3) & 3]) retainer = parent, j = i; + } + + // Find the point to remove. + while (node.data !== d) if (!(previous = node, node = node.next)) return this; + if (next = node.next) delete node.next; + + // If there are multiple coincident points, remove just the point. + if (previous) return (next ? previous.next = next : delete previous.next), this; + + // If this is the root point, remove it. + if (!parent) return this._root = next, this; + + // Remove this leaf. + next ? parent[i] = next : delete parent[i]; + + // If the parent now contains exactly one leaf, collapse superfluous parents. + if ((node = parent[0] || parent[1] || parent[2] || parent[3]) + && node === (parent[3] || parent[2] || parent[1] || parent[0]) + && !node.length) { + if (retainer) retainer[j] = node; + else this._root = node; + } + + return this; +}; + +function removeAll(data) { + for (var i = 0, n = data.length; i < n; ++i) this.remove(data[i]); + return this; +} + +var tree_root = function() { + return this._root; +}; + +var tree_size = function() { + var size = 0; + this.visit(function(node) { + if (!node.length) do ++size; while (node = node.next) + }); + return size; +}; + +var tree_visit = function(callback) { + var quads = [], q, node = this._root, child, x0, y0, x1, y1; + if (node) quads.push(new Quad(node, this._x0, this._y0, this._x1, this._y1)); + while (q = quads.pop()) { + if (!callback(node = q.node, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1) && node.length) { + var xm = (x0 + x1) / 2, ym = (y0 + y1) / 2; + if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1)); + if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1)); + if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym)); + if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym)); + } + } + return this; +}; + +var tree_visitAfter = function(callback) { + var quads = [], next = [], q; + if (this._root) quads.push(new Quad(this._root, this._x0, this._y0, this._x1, this._y1)); + while (q = quads.pop()) { + var node = q.node; + if (node.length) { + var child, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1, xm = (x0 + x1) / 2, ym = (y0 + y1) / 2; + if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym)); + if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym)); + if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1)); + if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1)); + } + next.push(q); + } + while (q = next.pop()) { + callback(q.node, q.x0, q.y0, q.x1, q.y1); + } + return this; +}; + +function defaultX(d) { + return d[0]; +} + +var tree_x = function(_) { + return arguments.length ? (this._x = _, this) : this._x; +}; + +function defaultY(d) { + return d[1]; +} + +var tree_y = function(_) { + return arguments.length ? (this._y = _, this) : this._y; +}; + +function quadtree(nodes, x, y) { + var tree = new Quadtree(x == null ? defaultX : x, y == null ? defaultY : y, NaN, NaN, NaN, NaN); + return nodes == null ? tree : tree.addAll(nodes); +} + +function Quadtree(x, y, x0, y0, x1, y1) { + this._x = x; + this._y = y; + this._x0 = x0; + this._y0 = y0; + this._x1 = x1; + this._y1 = y1; + this._root = undefined; +} + +function leaf_copy(leaf) { + var copy = {data: leaf.data}, next = copy; + while (leaf = leaf.next) next = next.next = {data: leaf.data}; + return copy; +} + +var treeProto = quadtree.prototype = Quadtree.prototype; + +treeProto.copy = function() { + var copy = new Quadtree(this._x, this._y, this._x0, this._y0, this._x1, this._y1), + node = this._root, + nodes, + child; + + if (!node) return copy; + + if (!node.length) return copy._root = leaf_copy(node), copy; + + nodes = [{source: node, target: copy._root = new Array(4)}]; + while (node = nodes.pop()) { + for (var i = 0; i < 4; ++i) { + if (child = node.source[i]) { + if (child.length) nodes.push({source: child, target: node.target[i] = new Array(4)}); + else node.target[i] = leaf_copy(child); + } + } + } + + return copy; +}; + +treeProto.add = tree_add; +treeProto.addAll = addAll; +treeProto.cover = tree_cover; +treeProto.data = tree_data; +treeProto.extent = tree_extent; +treeProto.find = tree_find; +treeProto.remove = tree_remove; +treeProto.removeAll = removeAll; +treeProto.root = tree_root; +treeProto.size = tree_size; +treeProto.visit = tree_visit; +treeProto.visitAfter = tree_visitAfter; +treeProto.x = tree_x; +treeProto.y = tree_y; + +function x(d) { + return d.x + d.vx; +} + +function y(d) { + return d.y + d.vy; +} + +var collide = function(radius) { + var nodes, + radii, + strength = 1, + iterations = 1; + + if (typeof radius !== "function") radius = constant$4(radius == null ? 1 : +radius); + + function force() { + var i, n = nodes.length, + tree, + node, + xi, + yi, + ri, + ri2; + + for (var k = 0; k < iterations; ++k) { + tree = quadtree(nodes, x, y).visitAfter(prepare); + for (i = 0; i < n; ++i) { + node = nodes[i]; + ri = radii[node.index], ri2 = ri * ri; + xi = node.x + node.vx; + yi = node.y + node.vy; + tree.visit(apply); + } + } + + function apply(quad, x0, y0, x1, y1) { + var data = quad.data, rj = quad.r, r = ri + rj; + if (data) { + if (data.index > node.index) { + var x = xi - data.x - data.vx, + y = yi - data.y - data.vy, + l = x * x + y * y; + if (l < r * r) { + if (x === 0) x = jiggle(), l += x * x; + if (y === 0) y = jiggle(), l += y * y; + l = (r - (l = Math.sqrt(l))) / l * strength; + node.vx += (x *= l) * (r = (rj *= rj) / (ri2 + rj)); + node.vy += (y *= l) * r; + data.vx -= x * (r = 1 - r); + data.vy -= y * r; + } + } + return; + } + return x0 > xi + r || x1 < xi - r || y0 > yi + r || y1 < yi - r; + } + } + + function prepare(quad) { + if (quad.data) return quad.r = radii[quad.data.index]; + for (var i = quad.r = 0; i < 4; ++i) { + if (quad[i] && quad[i].r > quad.r) { + quad.r = quad[i].r; + } + } + } + + function initialize() { + if (!nodes) return; + var i, n = nodes.length, node; + radii = new Array(n); + for (i = 0; i < n; ++i) node = nodes[i], radii[node.index] = +radius(node, i, nodes); + } + + force.initialize = function(_) { + nodes = _; + initialize(); + }; + + force.iterations = function(_) { + return arguments.length ? (iterations = +_, force) : iterations; + }; + + force.strength = function(_) { + return arguments.length ? (strength = +_, force) : strength; + }; + + force.radius = function(_) { + return arguments.length ? (radius = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : radius; + }; + + return force; +}; + +var prefix = "$"; + +function Map() {} + +Map.prototype = map.prototype = { + constructor: Map, + has: function(key) { + return (prefix + key) in this; + }, + get: function(key) { + return this[prefix + key]; + }, + set: function(key, value) { + this[prefix + key] = value; + return this; + }, + remove: function(key) { + var property = prefix + key; + return property in this && delete this[property]; + }, + clear: function() { + for (var property in this) if (property[0] === prefix) delete this[property]; + }, + keys: function() { + var keys = []; + for (var property in this) if (property[0] === prefix) keys.push(property.slice(1)); + return keys; + }, + values: function() { + var values = []; + for (var property in this) if (property[0] === prefix) values.push(this[property]); + return values; + }, + entries: function() { + var entries = []; + for (var property in this) if (property[0] === prefix) entries.push({key: property.slice(1), value: this[property]}); + return entries; + }, + size: function() { + var size = 0; + for (var property in this) if (property[0] === prefix) ++size; + return size; + }, + empty: function() { + for (var property in this) if (property[0] === prefix) return false; + return true; + }, + each: function(f) { + for (var property in this) if (property[0] === prefix) f(this[property], property.slice(1), this); + } +}; + +function map(object, f) { + var map = new Map; + + // Copy constructor. + if (object instanceof Map) object.each(function(value, key) { map.set(key, value); }); + + // Index array by numeric index or specified key function. + else if (Array.isArray(object)) { + var i = -1, + n = object.length, + o; + + if (f == null) while (++i < n) map.set(i, object[i]); + else while (++i < n) map.set(f(o = object[i], i, object), o); + } + + // Convert object to map. + else if (object) for (var key in object) map.set(key, object[key]); + + return map; +} + +function index(d) { + return d.index; +} + +function find(nodeById, nodeId) { + var node = nodeById.get(nodeId); + if (!node) throw new Error("missing: " + nodeId); + return node; +} + +var link = function(links) { + var id = index, + strength = defaultStrength, + strengths, + distance = constant$4(30), + distances, + nodes, + count, + bias, + iterations = 1; + + if (links == null) links = []; + + function defaultStrength(link) { + return 1 / Math.min(count[link.source.index], count[link.target.index]); + } + + function force(alpha) { + for (var k = 0, n = links.length; k < iterations; ++k) { + for (var i = 0, link, source, target, x, y, l, b; i < n; ++i) { + link = links[i], source = link.source, target = link.target; + x = target.x + target.vx - source.x - source.vx || jiggle(); + y = target.y + target.vy - source.y - source.vy || jiggle(); + l = Math.sqrt(x * x + y * y); + l = (l - distances[i]) / l * alpha * strengths[i]; + x *= l, y *= l; + target.vx -= x * (b = bias[i]); + target.vy -= y * b; + source.vx += x * (b = 1 - b); + source.vy += y * b; + } + } + } + + function initialize() { + if (!nodes) return; + + var i, + n = nodes.length, + m = links.length, + nodeById = map(nodes, id), + link; + + for (i = 0, count = new Array(n); i < m; ++i) { + link = links[i], link.index = i; + if (typeof link.source !== "object") link.source = find(nodeById, link.source); + if (typeof link.target !== "object") link.target = find(nodeById, link.target); + count[link.source.index] = (count[link.source.index] || 0) + 1; + count[link.target.index] = (count[link.target.index] || 0) + 1; + } + + for (i = 0, bias = new Array(m); i < m; ++i) { + link = links[i], bias[i] = count[link.source.index] / (count[link.source.index] + count[link.target.index]); + } + + strengths = new Array(m), initializeStrength(); + distances = new Array(m), initializeDistance(); + } + + function initializeStrength() { + if (!nodes) return; + + for (var i = 0, n = links.length; i < n; ++i) { + strengths[i] = +strength(links[i], i, links); + } + } + + function initializeDistance() { + if (!nodes) return; + + for (var i = 0, n = links.length; i < n; ++i) { + distances[i] = +distance(links[i], i, links); + } + } + + force.initialize = function(_) { + nodes = _; + initialize(); + }; + + force.links = function(_) { + return arguments.length ? (links = _, initialize(), force) : links; + }; + + force.id = function(_) { + return arguments.length ? (id = _, force) : id; + }; + + force.iterations = function(_) { + return arguments.length ? (iterations = +_, force) : iterations; + }; + + force.strength = function(_) { + return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initializeStrength(), force) : strength; + }; + + force.distance = function(_) { + return arguments.length ? (distance = typeof _ === "function" ? _ : constant$4(+_), initializeDistance(), force) : distance; + }; + + return force; +}; + +function x$1(d) { + return d.x; +} + +function y$1(d) { + return d.y; +} + +var initialRadius = 10; +var initialAngle = Math.PI * (3 - Math.sqrt(5)); + +var simulation = function(nodes) { + var simulation, + alpha = 1, + alphaMin = 0.001, + alphaDecay = 1 - Math.pow(alphaMin, 1 / 300), + alphaTarget = 0, + velocityDecay = 0.6, + forces = map(), + stepper = timer(step), + event = dispatch("tick", "end"); + + if (nodes == null) nodes = []; + + function step() { + tick(); + event.call("tick", simulation); + if (alpha < alphaMin) { + stepper.stop(); + event.call("end", simulation); + } + } + + function tick() { + var i, n = nodes.length, node; + + alpha += (alphaTarget - alpha) * alphaDecay; + + forces.each(function(force) { + force(alpha); + }); + + for (i = 0; i < n; ++i) { + node = nodes[i]; + if (node.fx == null) node.x += node.vx *= velocityDecay; + else node.x = node.fx, node.vx = 0; + if (node.fy == null) node.y += node.vy *= velocityDecay; + else node.y = node.fy, node.vy = 0; + } + } + + function initializeNodes() { + for (var i = 0, n = nodes.length, node; i < n; ++i) { + node = nodes[i], node.index = i; + if (isNaN(node.x) || isNaN(node.y)) { + var radius = initialRadius * Math.sqrt(i), angle = i * initialAngle; + node.x = radius * Math.cos(angle); + node.y = radius * Math.sin(angle); + } + if (isNaN(node.vx) || isNaN(node.vy)) { + node.vx = node.vy = 0; + } + } + } + + function initializeForce(force) { + if (force.initialize) force.initialize(nodes); + return force; + } + + initializeNodes(); + + return simulation = { + tick: tick, + + restart: function() { + return stepper.restart(step), simulation; + }, + + stop: function() { + return stepper.stop(), simulation; + }, + + nodes: function(_) { + return arguments.length ? (nodes = _, initializeNodes(), forces.each(initializeForce), simulation) : nodes; + }, + + alpha: function(_) { + return arguments.length ? (alpha = +_, simulation) : alpha; + }, + + alphaMin: function(_) { + return arguments.length ? (alphaMin = +_, simulation) : alphaMin; + }, + + alphaDecay: function(_) { + return arguments.length ? (alphaDecay = +_, simulation) : +alphaDecay; + }, + + alphaTarget: function(_) { + return arguments.length ? (alphaTarget = +_, simulation) : alphaTarget; + }, + + velocityDecay: function(_) { + return arguments.length ? (velocityDecay = 1 - _, simulation) : 1 - velocityDecay; + }, + + force: function(name, _) { + return arguments.length > 1 ? ((_ == null ? forces.remove(name) : forces.set(name, initializeForce(_))), simulation) : forces.get(name); + }, + + find: function(x, y, radius) { + var i = 0, + n = nodes.length, + dx, + dy, + d2, + node, + closest; + + if (radius == null) radius = Infinity; + else radius *= radius; + + for (i = 0; i < n; ++i) { + node = nodes[i]; + dx = x - node.x; + dy = y - node.y; + d2 = dx * dx + dy * dy; + if (d2 < radius) closest = node, radius = d2; + } + + return closest; + }, + + on: function(name, _) { + return arguments.length > 1 ? (event.on(name, _), simulation) : event.on(name); + } + }; +}; + +var manyBody = function() { + var nodes, + node, + alpha, + strength = constant$4(-30), + strengths, + distanceMin2 = 1, + distanceMax2 = Infinity, + theta2 = 0.81; + + function force(_) { + var i, n = nodes.length, tree = quadtree(nodes, x$1, y$1).visitAfter(accumulate); + for (alpha = _, i = 0; i < n; ++i) node = nodes[i], tree.visit(apply); + } + + function initialize() { + if (!nodes) return; + var i, n = nodes.length, node; + strengths = new Array(n); + for (i = 0; i < n; ++i) node = nodes[i], strengths[node.index] = +strength(node, i, nodes); + } + + function accumulate(quad) { + var strength = 0, q, c, weight = 0, x, y, i; + + // For internal nodes, accumulate forces from child quadrants. + if (quad.length) { + for (x = y = i = 0; i < 4; ++i) { + if ((q = quad[i]) && (c = Math.abs(q.value))) { + strength += q.value, weight += c, x += c * q.x, y += c * q.y; + } + } + quad.x = x / weight; + quad.y = y / weight; + } + + // For leaf nodes, accumulate forces from coincident quadrants. + else { + q = quad; + q.x = q.data.x; + q.y = q.data.y; + do strength += strengths[q.data.index]; + while (q = q.next); + } + + quad.value = strength; + } + + function apply(quad, x1, _, x2) { + if (!quad.value) return true; + + var x = quad.x - node.x, + y = quad.y - node.y, + w = x2 - x1, + l = x * x + y * y; + + // Apply the Barnes-Hut approximation if possible. + // Limit forces for very close nodes; randomize direction if coincident. + if (w * w / theta2 < l) { + if (l < distanceMax2) { + if (x === 0) x = jiggle(), l += x * x; + if (y === 0) y = jiggle(), l += y * y; + if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); + node.vx += x * quad.value * alpha / l; + node.vy += y * quad.value * alpha / l; + } + return true; + } + + // Otherwise, process points directly. + else if (quad.length || l >= distanceMax2) return; + + // Limit forces for very close nodes; randomize direction if coincident. + if (quad.data !== node || quad.next) { + if (x === 0) x = jiggle(), l += x * x; + if (y === 0) y = jiggle(), l += y * y; + if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); + } + + do if (quad.data !== node) { + w = strengths[quad.data.index] * alpha / l; + node.vx += x * w; + node.vy += y * w; + } while (quad = quad.next); + } + + force.initialize = function(_) { + nodes = _; + initialize(); + }; + + force.strength = function(_) { + return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; + }; + + force.distanceMin = function(_) { + return arguments.length ? (distanceMin2 = _ * _, force) : Math.sqrt(distanceMin2); + }; + + force.distanceMax = function(_) { + return arguments.length ? (distanceMax2 = _ * _, force) : Math.sqrt(distanceMax2); + }; + + force.theta = function(_) { + return arguments.length ? (theta2 = _ * _, force) : Math.sqrt(theta2); + }; + + return force; +}; + +var radial = function(radius, x, y) { + var nodes, + strength = constant$4(0.1), + strengths, + radiuses; + + if (typeof radius !== "function") radius = constant$4(+radius); + if (x == null) x = 0; + if (y == null) y = 0; + + function force(alpha) { + for (var i = 0, n = nodes.length; i < n; ++i) { + var node = nodes[i], + dx = node.x - x || 1e-6, + dy = node.y - y || 1e-6, + r = Math.sqrt(dx * dx + dy * dy), + k = (radiuses[i] - r) * strengths[i] * alpha / r; + node.vx += dx * k; + node.vy += dy * k; + } + } + + function initialize() { + if (!nodes) return; + var i, n = nodes.length; + strengths = new Array(n); + radiuses = new Array(n); + for (i = 0; i < n; ++i) { + radiuses[i] = +radius(nodes[i], i, nodes); + strengths[i] = isNaN(radiuses[i]) ? 0 : +strength(nodes[i], i, nodes); + } + } + + force.initialize = function(_) { + nodes = _, initialize(); + }; + + force.strength = function(_) { + return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; + }; + + force.radius = function(_) { + return arguments.length ? (radius = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : radius; + }; + + force.x = function(_) { + return arguments.length ? (x = +_, force) : x; + }; + + force.y = function(_) { + return arguments.length ? (y = +_, force) : y; + }; + + return force; +}; + +var x$2 = function(x) { + var strength = constant$4(0.1), + nodes, + strengths, + xz; + + if (typeof x !== "function") x = constant$4(x == null ? 0 : +x); + + function force(alpha) { + for (var i = 0, n = nodes.length, node; i < n; ++i) { + node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha; + } + } + + function initialize() { + if (!nodes) return; + var i, n = nodes.length; + strengths = new Array(n); + xz = new Array(n); + for (i = 0; i < n; ++i) { + strengths[i] = isNaN(xz[i] = +x(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes); + } + } + + force.initialize = function(_) { + nodes = _; + initialize(); + }; + + force.strength = function(_) { + return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; + }; + + force.x = function(_) { + return arguments.length ? (x = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : x; + }; + + return force; +}; + +var y$2 = function(y) { + var strength = constant$4(0.1), + nodes, + strengths, + yz; + + if (typeof y !== "function") y = constant$4(y == null ? 0 : +y); + + function force(alpha) { + for (var i = 0, n = nodes.length, node; i < n; ++i) { + node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha; + } + } + + function initialize() { + if (!nodes) return; + var i, n = nodes.length; + strengths = new Array(n); + yz = new Array(n); + for (i = 0; i < n; ++i) { + strengths[i] = isNaN(yz[i] = +y(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes); + } + } + + force.initialize = function(_) { + nodes = _; + initialize(); + }; + + force.strength = function(_) { + return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; + }; + + force.y = function(_) { + return arguments.length ? (y = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : y; + }; + + return force; +}; + +// export * from "d3-axis"; + +exports.drag = drag; +exports.easeLinear = linear$1; +exports.create = create; +exports.creator = creator; +exports.local = local; +exports.matcher = matcher$1; +exports.mouse = mouse; +exports.namespace = namespace; +exports.namespaces = namespaces; +exports.clientPoint = point; +exports.select = select; +exports.selectAll = selectAll; +exports.selection = selection; +exports.selector = selector; +exports.selectorAll = selectorAll; +exports.style = styleValue; +exports.touch = touch; +exports.touches = touches; +exports.window = defaultView; +exports.customEvent = customEvent; +exports.zoom = zoom; +exports.zoomTransform = transform; +exports.zoomIdentity = identity$1; +exports.forceCenter = center; +exports.forceCollide = collide; +exports.forceLink = link; +exports.forceManyBody = manyBody; +exports.forceRadial = radial; +exports.forceSimulation = simulation; +exports.forceX = x$2; +exports.forceY = y$2; +exports.transition = transition; +exports.active = active; +exports.interrupt = interrupt; + +return exports; + +}({})); diff --git a/assets/js/d3.bundle.min.js b/assets/js/d3.bundle.min.js new file mode 100644 index 0000000..4b4a61f --- /dev/null +++ b/assets/js/d3.bundle.min.js @@ -0,0 +1 @@ +var d3=function(T){"use strict";var o={value:function(){}};function q(){for(var t,n=0,e=arguments.length,r={};n>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1):(n=wt.exec(t))?St(parseInt(n[1],16)):(n=xt.exec(t))?new Xt(n[1],n[2],n[3],1):(n=bt.exec(t))?new Xt(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=Nt.exec(t))?Tt(n[1],n[2],n[3],n[4]):(n=Mt.exec(t))?Tt(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=kt.exec(t))?Yt(n[1],n[2]/100,n[3]/100,1):(n=At.exec(t))?Yt(n[1],n[2]/100,n[3]/100,n[4]):zt.hasOwnProperty(t)?St(zt[t]):"transparent"===t?new Xt(NaN,NaN,NaN,0):null}function St(t){return new Xt(t>>16&255,t>>8&255,255&t,1)}function Tt(t,n,e,r){return r<=0&&(t=n=e=NaN),new Xt(t,n,e,r)}function qt(t){return t instanceof vt||(t=Et(t)),t?new Xt((t=t.rgb()).r,t.g,t.b,t.opacity):new Xt}function Pt(t,n,e,r){return 1===arguments.length?qt(t):new Xt(t,n,e,null==r?1:r)}function Xt(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function Ct(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function Yt(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||1<=e?t=n=NaN:n<=0&&(t=NaN),new It(t,n,e,r)}function It(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function Dt(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}ft(vt,Et,{displayable:function(){return this.rgb().displayable()},hex:function(){return this.rgb().hex()},toString:function(){return this.rgb()+""}}),ft(Xt,Pt,pt(vt,{brighter:function(t){return t=null==t?yt:Math.pow(yt,t),new Xt(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new Xt(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return 0<=this.r&&this.r<=255&&0<=this.g&&this.g<=255&&0<=this.b&&this.b<=255&&0<=this.opacity&&this.opacity<=1},hex:function(){return"#"+Ct(this.r)+Ct(this.g)+Ct(this.b)},toString:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}})),ft(It,function(t,n,e,r){return 1===arguments.length?function(t){if(t instanceof It)return new It(t.h,t.s,t.l,t.opacity);if(t instanceof vt||(t=Et(t)),!t)return new It;if(t instanceof It)return t;var n=(t=t.rgb()).r/255,e=t.g/255,r=t.b/255,i=Math.min(n,e,r),o=Math.max(n,e,r),u=NaN,a=o-i,s=(o+i)/2;return a?(u=n===o?(e-r)/a+6*(ee._time&&(r=e._time),e=(t=e)._next):(n=e._next,e._next=null,e=t?t._next=n:En=n);Sn=t,Gn(r)}(),In=0}}function Un(){var t=Vn.now(),n=t-Yn;CnQn)throw new Error("too late; already scheduled");return e}function te(t,n){var e=ne(t,n);if(e.state>Wn)throw new Error("too late; already started");return e}function ne(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("transition not found");return e}var ee=function(t,n){var e,r,i,o=t.__transition,u=!0;if(o){for(i in n=null==n?null:n+"",o)(e=o[i]).name===n?(r=e.state>Wn&&e.state<5,e.state=6,e.timer.stop(),r&&e.on.call("interrupt",t,t.__data__,e.index,e.group),delete o[i]):u=!1;u&&delete t.__transition}};function re(t,n,e){var r=t._id;return t.each(function(){var t=te(this,r);(t.value||(t.value={}))[n]=e.apply(this,arguments)}),function(t){return ne(t,r).value[n]}}var ie=function(t,n){var e;return("number"==typeof n?pn:n instanceof Et?fn:(e=Et(n))?(n=e,fn):function(t,r){var n,e,i,o,u,a=vn.lastIndex=yn.lastIndex=0,s=-1,h=[],c=[];for(t+="",r+="";(n=vn.exec(t))&&(e=yn.exec(r));)(i=e.index)>a&&(i=r.slice(a,i),h[s]?h[s]+=i:h[++s]=i),(n=n[0])===(e=e[0])?h[s]?h[s]+=e:h[++s]=e:(h[++s]=null,c.push({i:s,x:pn(n,e)})),a=yn.lastIndex;return a=(o=(y+g)/2))?y=o:g=o,(c=e>=(u=(d+m)/2))?d=u:m=u,!(p=(i=p)[l=c<<1|h]))return i[l]=v,t;if(a=+t._x.call(null,p.data),s=+t._y.call(null,p.data),n===a&&e===s)return v.next=p,i?i[l]=v:t._root=v,t;for(;i=i?i[l]=new Array(4):t._root=new Array(4),(h=n>=(o=(y+g)/2))?y=o:g=o,(c=e>=(u=(d+m)/2))?d=u:m=u,(l=c<<1|h)==(f=(u<=s)<<1|o<=a););return i[f]=p,i[l]=v,t}var Te=function(t,n,e,r,i){this.node=t,this.x0=n,this.y0=e,this.x1=r,this.y1=i};function qe(t){return t[0]}function Pe(t){return t[1]}function Xe(t,n,e){var r=new Ce(null==n?qe:n,null==e?Pe:e,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function Ce(t,n,e,r,i,o){this._x=t,this._y=n,this._x0=e,this._y0=r,this._x1=i,this._y1=o,this._root=void 0}function Ye(t){for(var n={data:t.data},e=n;t=t.next;)e=e.next={data:t.data};return n}var Ie=Xe.prototype=Ce.prototype;function De(t){return t.x+t.vx}function Ve(t){return t.y+t.vy}Ie.copy=function(){var t,n,e=new Ce(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return e;if(!r.length)return e._root=Ye(r),e;for(t=[{source:r,target:e._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(n=r.source[i])&&(n.length?t.push({source:n,target:r.target[i]=new Array(4)}):r.target[i]=Ye(n));return e},Ie.add=function(t){var n=+this._x.call(null,t),e=+this._y.call(null,t);return Se(this.cover(n,e),n,e,t)},Ie.addAll=function(t){var n,e,r,i,o=t.length,u=new Array(o),a=new Array(o),s=1/0,h=1/0,c=-1/0,l=-1/0;for(e=0;ef||(o=s.y0)>p||(u=s.x1)=(a=(v+d)/2))?v=a:d=a,(c=u>=(s=(y+g)/2))?y=s:g=s,!(p=(n=p)[l=c<<1|h]))return this;if(!p.length)break;(n[l+1&3]||n[l+2&3]||n[l+3&3])&&(e=n,f=l)}for(;p.data!==t;)if(!(p=(r=p).next))return this;return(i=p.next)&&delete p.next,r?i?r.next=i:delete r.next:n?(i?n[l]=i:delete n[l],(p=n[0]||n[1]||n[2]||n[3])&&p===(n[3]||n[2]||n[1]||n[0])&&!p.length&&(e?e[f]=p:this._root=p)):this._root=i,this},Ie.removeAll=function(t){for(var n=0,e=t.length;nl.index){var s=f-o.x-o.vx,h=p-o.y-o.vy,c=s*s+h*h;ct.r&&(t.r=t[n].r)}function e(){if(o){var t,n,e=o.length;for(u=new Array(e),t=0;t r || dyAbs > r) { + return false; + } else if (dxAbs + dyAbs <= r) { + return true; + } else if (Math.pow(dx, 2) + Math.pow(dy, 2) <= Math.pow(r, 2)) { + return true; + } else { + return false; + } +} + +function createLinkMap(graph) { + var linkMap = {}; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = graph['links'][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var link = _step.value; + + if (typeof linkMap[link['source']] == 'undefined') { + linkMap[link['source']] = []; + } + linkMap[link['source']][linkMap[link['source']].length] = { 'id': link['target'], 'name': link['name'] }; + + if (typeof linkMap[link['target']] == 'undefined') { + linkMap[link['target']] = []; + } + + linkMap[link['target']][linkMap[link['target']].length] = { 'id': link['source'], 'name': link['name'] }; + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + return linkMap; +} + +// config +var nodeSize = 40; +var selectedNodeSize = 140; +var firstNodeId = "https://rubenvandeven.com/"; + +function getSizeForNode(node) { + if (node.hasOwnProperty('https://schema.org/thumbnailUrl')) return nodeSize; + if (node['@id'] == firstNodeId) return nodeSize * 1.2; + // everynode has at least one link. these should equal 1 + return nodeSize * (.7 + Math.min(20, linkMap[node['@id']].length) / 40); + return nodeSize; +} + +// TODO: make sure, 'shortest' path is favoured. +function createBreadcrumbs(linkMap, srcId) { + var crumbs = {}; + + var createBreadcrumbLayer = function createBreadcrumbLayer(srcId) { + var path = crumbs[srcId]; + var newPath = path.slice(); + newPath.push(srcId); + + var nextSrcIds = []; + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = linkMap[srcId][Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var link = _step2.value; + + if (typeof crumbs[link['id']] !== 'undefined') continue; + crumbs[link['id']] = newPath; + nextSrcIds.push(link['id']); + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + return nextSrcIds; + }; + crumbs[srcId] = []; + var nextIds = [srcId]; + while (nextIds.length > 0) { + var newNextIds = []; + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = nextIds[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var nextId = _step3.value; + + var r = createBreadcrumbLayer(nextId); + newNextIds = newNextIds.concat(r); + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + + nextIds = newNextIds; + } + return crumbs; +} + +var nodePositions = {}; +function startGraph(graph) { + + // set some vars + var currentNodeIdx = 0; + var currentNodePositionRadius = 0; + var types = {}; + + linkMap = createLinkMap(graph); + breadcrumbs = createBreadcrumbs(linkMap, firstNodeId); + + for (var nodeIdx in graph['nodes']) { + var type = graph['nodes'][nodeIdx]["@type"]; + if (typeof types[type] == 'undefined') { + types[type] = []; + } + types[type].push(nodeIdx); + } + var graphControlsEl = document.getElementById('graphControls'); + var typeLinksEl = document.getElementById('typeLinks'); + var showMoreTypeLinksEl = document.getElementById('showMoreTypeLinks'); + var moreTypeLinksEl = document.getElementById('moreTypeLinks'); + var relLinksEl = document.getElementById('relLinks'); + + // sort types by count: + var typeCounts = Object.keys(types).map(function (key) { + return [key, types[key].length]; + }); + typeCounts.sort(function (first, second) { + return second[1] - first[1]; + }); + + // make controls + var i = 0; + + var _loop = function _loop(typeCountIdx) { + var typeName = typeCounts[typeCountIdx][0]; + var typeLinkEl = document.createElement("li"); + var typeLinkAEl = document.createElement("a"); + var typeLinkCountEl = document.createElement("span"); + typeLinkCountEl.innerHTML = typeCounts[typeCountIdx][1]; + typeLinkCountEl.classList.add('typeCount'); + typeLinkAEl.innerHTML = getDisplayAttr(typeName); + typeLinkAEl.title = typeName; + typeLinkAEl.addEventListener('click', function () { + centerByType(typeName); + // positionNodesInCenter(types[typeName]); + }); + typeLinkAEl.addEventListener('mouseover', function () { + var typeNodeEls = document.getElementsByClassName(typeName); + var _iteratorNormalCompletion7 = true; + var _didIteratorError7 = false; + var _iteratorError7 = undefined; + + try { + for (var _iterator7 = typeNodeEls[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { + var typeNodeEl = _step7.value; + + typeNodeEl.classList.add('typeHighlight'); + } + } catch (err) { + _didIteratorError7 = true; + _iteratorError7 = err; + } finally { + try { + if (!_iteratorNormalCompletion7 && _iterator7.return) { + _iterator7.return(); + } + } finally { + if (_didIteratorError7) { + throw _iteratorError7; + } + } + } + }); + typeLinkAEl.addEventListener('mouseout', function () { + var typeNodeEls = document.getElementsByClassName(typeName); + var _iteratorNormalCompletion8 = true; + var _didIteratorError8 = false; + var _iteratorError8 = undefined; + + try { + for (var _iterator8 = typeNodeEls[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) { + var typeNodeEl = _step8.value; + + typeNodeEl.classList.remove('typeHighlight'); + } + } catch (err) { + _didIteratorError8 = true; + _iteratorError8 = err; + } finally { + try { + if (!_iteratorNormalCompletion8 && _iterator8.return) { + _iterator8.return(); + } + } finally { + if (_didIteratorError8) { + throw _iteratorError8; + } + } + } + }); + typeLinkEl.append(typeLinkAEl); + typeLinkEl.append(typeLinkCountEl); + (i < 5 ? typeLinksEl : moreTypeLinksEl).appendChild(typeLinkEl); + i++; + // typeLinksEl.appendChild(typeLinkEl); + }; + + for (var typeCountIdx in typeCounts) { + _loop(typeCountIdx); + } + + showMoreTypeLinksEl.addEventListener('click', function () { + document.body.classList.add('showMoreLinks'); + var hideMoreTypeLinks = function hideMoreTypeLinks(e) { + e.preventDefault(); + e.stopPropagation(); + document.body.removeEventListener('mouseup', hideMoreTypeLinks, true); + document.body.classList.remove('showMoreLinks'); + }; + document.body.addEventListener('mouseup', hideMoreTypeLinks, true); + }, false); + + // make svg + var svg = d3.select("svg"), + width = +svg.attr("width"), + height = +svg.attr("height"); + var container = svg.append("g").attr("id", "container"); + + var simulation = d3.forceSimulation().force("link", d3.forceLink().id(function (d) { + return d["@id"]; + }).strength(.005)).force("charge", d3.forceManyBody()) // doesn't seem necessary? + .force("collision", d3.forceCollide(function (d) { + return getSizeForNode(d) * 1.1; // avoid overlapping nodes + })) + // .force("center", d3.forceCenter(width / 2, height / 2)) // position around center + + // .force("x", d3.forceX()) + // .force("y", d3.forceY()) + // .force("y", d3.forceY()) + ; + + var link = container.append("g").attr("class", "links").selectAll(".relationship").data(graph['links']).enter().append("g").attr("class", function (l) { + return "relationship " + l.name; + }); + var linkLine = link + // .append("line"); + .append("line").attr("marker-end", "url(#arrowHead)"); + var linkText = link.append("text").text(function (l) { + // l == Object { source: "https://rubenvandeven.com/#codesandmodes", target: "_:b34", name: "https://schema.org/location" } + return getDisplayAttr(l.name); + }); + + var node = container.append("g").attr("class", "nodes").selectAll(".node").data(graph.nodes).enter().append("g").attr("class", function (d) { + var baseClasses = 'node ' + d['@type']; + if (d['@type']) { + var slashpos = d['@type'].lastIndexOf('/'); + if (slashpos > -1) { + baseClasses += ' ' + d['@type'].substr(slashpos + 1); + } + } + return baseClasses; + }); + var getViewbox = function getViewbox() { + return svg.attr("viewBox").split(" ").map(parseFloat); + }; + var positionNodesInCenter = function positionNodesInCenter(idxs) { + setViewboxForceCenter(); // sets forceCx & forceCy + if ((typeof idxs === 'undefined' ? 'undefined' : _typeof(idxs)) == "object" && idxs !== null && idxs.length == 1) { + idxs = idxs[0]; + } + + nodePositions = {}; // reset + if (idxs === null) { + return; + } else if ((typeof idxs === 'undefined' ? 'undefined' : _typeof(idxs)) == "object") { + // array or object -> each + // calculate grid: + // let itemsX = 4; + // let itemsY = Math.ceil(idxs.length/itemsX); + // console.log(itemsX,itemsY); + // let rowDiffX = viewBox[3] * (1/(itemsX+1)); + // let rowDiffY = viewBox[2] * (1/(itemsY+1)); + // console.log(rowDiffX, rowDiffY); + // for (var i = 0; i < idxs.length; i++) { + // nodePositions[idxs[i]] = [ + // cx - itemsX/2*rowDiffX + rowDiffX * ((i % itemsX)), + // cy - itemsY/2*rowDiffY + rowDiffY * (Math.floor(i / itemsX)) + // ]; + // } + positionNodesInCircle(idxs); + // console.log(nodePositions); + } else { + nodePositions[idxs] = [forceCx, forceCy]; + // console.log("singleNode", idxs, nodePositions); + } + + node.each(function (d, nIdx, nodeEls) { + if (typeof nodePositions[nIdx] != 'undefined') { + nodeEls[nIdx].classList.add('centeredNode'); + nodeEls[nIdx].classList.add('visibleNode'); + } else { + nodeEls[nIdx].classList.remove('centeredNode'); + nodeEls[nIdx].classList.remove('visibleNode'); + } + }); + + // restart animation (they call that 'alpha' in d3 force) + simulation.alpha(1); + simulation.restart(); + }; + var positionNodesInCircle = function positionNodesInCircle(idxs, r) { + var viewBox = getViewbox(); + var zoom = getZoomValues(); + setViewboxForceCenter(); // sets forceCx & forceCy + if (typeof r == 'undefined') { + if (idxs.length == 1) { + r = viewBox[2] / 6; + } else { + r = viewBox[2] / (4 + Math.max(0, 2.5 - idxs.length)); + } + } + currentNodePositionRadius = r; + var forceCx = viewBox[0] + viewBox[2] / 2 - zoom['dx']; + var forceCy = viewBox[1] + viewBox[3] / 2 - zoom['dy']; + + var stepSize = 2 * Math.PI / idxs.length; + + for (var i = 0; i < idxs.length; i++) { + nodePositions[idxs[i]] = [forceCx + Math.sin(stepSize * i) * r, forceCy + Math.cos(stepSize * i) * r]; + } + + // restart animation (they call that 'alpha' in d3 force) + simulation.alpha(1); + simulation.restart(); + }; + var centerByType = function centerByType(types, updateHistory) { + if (typeof updateHistory == 'undefined') { + updateHistory = true; + } + if (!Array.isArray(types)) { + types = [types]; + } + var idxs = []; + for (var idx in graph.nodes) { + if (types.indexOf(graph.nodes[idx]['@type']) > -1) { + idxs[idxs.length] = idx; + } + } + deselectNode(); + if (updateHistory) { + // TODO: working + console.log(types[0], getDisplayAttr(types[0]), types.map(getDisplayAttr)); + history.pushState({ types: types }, "", "/@type/" + types.map(getDisplayAttr).join("+")); + } else { + history.replaceState({ types: types }, "", "/@type/" + types.map(getDisplayAttr).join("+")); + } + positionNodesInCenter(idxs.length ? idxs : null); + }; + + var selectedNodeTransition = d3.transition().duration(750).ease(d3.easeLinear); + + var nodeDetailEl = document.getElementById("nodeDetails"); + + var createRelationshipEl = function createRelationshipEl(relNode, i) { + var el = document.createElement("dd"); + el.classList.add('relLink'); + var titleEl = document.createElement('a'); + titleEl.innerHTML = getNodeLabel(relNode); + var year = getNodeYear(relNode); + if (year !== null) { + titleEl.innerHTML += '' + getNodeYear(relNode) + ''; + } + titleEl.classList.add('nodeTitle'); + titleEl.classList.add('nodeTitleNr' + i); + titleEl.addEventListener('click', function (e) { + var idx = graph.nodes.indexOf(relNode); + selectNode(idx); + }); + var typeEl = document.createElement('a'); + typeEl.classList.add('nodeType'); + typeEl.innerHTML = getDisplayAttr(relNode['@type']); + typeEl.title = relNode['@type']; + typeEl.addEventListener('click', function (e) { + centerByType(relNode['@type']); + }); + el.appendChild(titleEl); + el.appendChild(typeEl); + return el; + }; + + var setDetails = function setDetails(nodeDatum, nodeIdx) { + document.body.classList.add("detailsOpen"); + scrollToY(0, 4000); + while (nodeDetailEl.hasChildNodes()) { + nodeDetailEl.removeChild(nodeDetailEl.lastChild); + } + + // TODO: replace relUp & relDown with linkMap + var relUp = []; + var relDown = []; + var pageTitles = []; + var nodeDetailScalerEl = document.createElement('div'); + // nodeDetailScalerEl.innerHTML = `
`; + nodeDetailScalerEl.id = 'nodeDetailsScaler'; + nodeDetailScalerEl.addEventListener('mousedown', function (e) { + // console.log('go'); + var drag = function drag(e) { + // 5px for padding + nodeDetailEl.style.width = window.innerWidth - e.clientX + 5 + 'px'; + }; + document.body.addEventListener('mousemove', drag); + document.body.addEventListener('mouseup', function () { + document.body.removeEventListener('mousemove', drag); + }); + }); + nodeDetails.appendChild(nodeDetailScalerEl); + + var breadcrumbsEl = document.createElement('ul'); + breadcrumbsEl.classList.add('breadcrumbs'); + + var _loop2 = function _loop2(crumbNodeId) { + var crumbWrapEl = document.createElement('li'); + var crumbEl = document.createElement('span'); + crumbEl.classList.add('crumb'); + crumbEl.addEventListener('click', function (e) { + var idx = graph.nodes.indexOf(nodeMap[crumbNodeId]); + selectNode(idx); + }); + crumbEl.innerHTML = '' + getNodeLabel(nodeMap[crumbNodeId]); + var nodeYear = getNodeYear(nodeMap[crumbNodeId]); + if (nodeYear !== null) { + crumbEl.innerHTML += '' + nodeYear + ''; + } + crumbWrapEl.appendChild(crumbEl); + breadcrumbsEl.appendChild(crumbWrapEl); + pageTitles.push(getNodeLabel(nodeMap[crumbNodeId])); + }; + + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = breadcrumbs[nodeDatum['@id']][Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var crumbNodeId = _step4.value; + + _loop2(crumbNodeId); + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + + nodeDetailEl.appendChild(breadcrumbsEl); + pageTitles.push(getNodeLabel(nodeDatum)); + + var titleAttr = getLabelAttribute(nodeDatum); + var titleEl = document.createElement('h2'); + titleEl.innerHTML = getNodeLabel(nodeDatum); + + var typeEl = document.createElement('span'); + typeEl.classList.add('nodeType'); + typeEl.innerHTML = getDisplayAttr(nodeDatum['@type']); + typeEl.title = nodeDatum['@type']; + typeEl.addEventListener('click', function (e) { + centerByType(nodeDatum['@type']); + }); + titleEl.appendChild(typeEl); + nodeDetailEl.appendChild(titleEl); + + var listEl = document.createElement("dl"); + // listEl.innerHTML += `
type
${nodeDatum['@type']}
`; + + var skipNodeAttributes = ['@id', 'x', 'y', 'index', '@type', 'vy', 'vx', 'fx', 'fy', 'leftX', 'rightX']; + if (titleAttr !== 'https://schema.org/contentUrl') { + skipNodeAttributes[skipNodeAttributes.length] = titleAttr; + } + for (var attr in nodeDatum) { + if (skipNodeAttributes.indexOf(attr) != -1) { + continue; + } + + // approach all as array + var nodeAttr = Array.isArray(nodeDatum[attr]) ? nodeDatum[attr] : [nodeDatum[attr]]; + for (var _i in nodeAttr) { + // check if relationship: + if (typeof nodeAttr[_i] === "string" && nodeMap[nodeAttr[_i]]) { + continue; + } else if (typeof nodeAttr[_i]['@id'] !== 'undefined') { + continue; + } + if (attr == 'https://schema.org/url' || attr == 'http://www.w3.org/2000/01/rdf-schema#seeAlso') { + listEl.innerHTML += '
' + getDisplayAttr(attr) + '
' + nodeAttr[_i] + '
'; + } else if (attr == 'https://schema.org/embedUrl') { + listEl.innerHTML += '
' + getDisplayAttr(attr) + '
' + nodeAttr[_i] + '
'; + listEl.innerHTML += '
'; + } else if (attr == 'https://schema.org/contentUrl') { + listEl.innerHTML += '
' + getDisplayAttr(attr) + '
' + nodeAttr[_i] + '
'; + if (nodeDatum['@type'] == 'https://schema.org/VideoObject') { + var videoType = nodeAttr['https://schema.org/encodingFormat'] ? 'type=\'' + nodeAttr['https://schema.org/encodingFormat'] + '\'' : ""; + var poster = nodeAttr['https://schema.org/thumbnailUrl'] ? 'poster=\'' + nodeAttr['https://schema.org/thumbnailUrl'] + '\'' : ""; + listEl.innerHTML += '
'; + } else { + listEl.innerHTML += '
'; + } + } else { + var valueHtml = nodeAttr[_i].replace(/\n/g, "
"); + listEl.innerHTML += '
' + getDisplayAttr(attr) + '
' + valueHtml + '
'; + } + } + } + nodeDetailEl.appendChild(listEl); + + // let relTitleEl = document.createElement("h4"); + // relTitleEl.classList.add('linkTitle'); + // relTitleEl.innerHTML = "links"; + // nodeDetailEl.appendChild(relTitleEl); + + var relsEl = document.createElement("dl"); + // collect relationships + for (var i = 0; i < graph.links.length; i++) { + var _link = graph.links[i]; + if (_link['source']['@id'] == nodeDatum['@id']) { + if (typeof relDown[_link['name']] == "undefined") { + relDown[_link['name']] = []; + } + relDown[_link['name']][relDown[_link['name']].length] = _link['target']; + } + if (_link['target']['@id'] == nodeDatum['@id']) { + if (typeof relUp[_link['name']] == "undefined") { + relUp[_link['name']] = []; + } + relUp[_link['name']][relUp[_link['name']].length] = _link['source']; + } + } + + // relationships / links in
+ for (var _attr in relDown) { + var attrEl = document.createElement("dt"); + attrEl.innerHTML = getDisplayAttr(_attr); + relsEl.appendChild(attrEl); + for (var _i2 in relDown[_attr]) { + var rel = relDown[_attr][_i2]; + relsEl.appendChild(createRelationshipEl(rel)); + if (typeof rel['https://schema.org/contentUrl'] != 'undefined') { + var ddEl = document.createElement('dd'); + ddEl.classList.add('dd-contentobject'); + if (rel['@type'] == 'https://schema.org/VideoObject') { + var _videoType = rel['https://schema.org/encodingFormat'] ? 'type=\'' + rel['https://schema.org/encodingFormat'] + '\'' : ""; + var _poster = rel['https://schema.org/thumbnailUrl'] ? 'poster=\'' + rel['https://schema.org/thumbnailUrl'] + '\'' : ""; + ddEl.innerHTML += ''; + } else { + ddEl.innerHTML = ''; + } + relsEl.appendChild(ddEl); + } + } + } + + for (var _attr2 in relUp) { + var _attrEl = document.createElement("dt"); + _attrEl.innerHTML = getDisplayAttr(_attr2); + relsEl.appendChild(_attrEl); + for (var _i3 in relUp[_attr2]) { + var _rel = relUp[_attr2][_i3]; + relsEl.appendChild(createRelationshipEl(_rel, _i3)); + if (typeof _rel['https://schema.org/contentUrl'] != 'undefined') { + var _ddEl = document.createElement('dd'); + _ddEl.classList.add('dd-contentobject'); + _ddEl.innerHTML = ''; + relsEl.appendChild(_ddEl); + } + } + } + + nodeDetailEl.appendChild(relsEl); + + node.each(function (d, nIdx, nodeEls) { + if (nIdx == nodeIdx) { + nodeEls[nIdx].classList.add('selectedNode'); + } else { + nodeEls[nIdx].classList.remove('selectedNode'); + } + }); + + // TODO: update history & title + document.title = pageTitles.join(" :: "); + }; + var closeDetails = function closeDetails() { + document.body.classList.remove("detailsOpen"); + scrollToY(0, 4000); // for mobile + }; + + /** + * Select a node, and center it + show details + * @param int idx The index of the node in the graph.nodes array + * @param Element|null nodeEl Optional, provide node element, so loop doesn't have to be used to change the Element + * @return void + */ + var selectNode = function selectNode(idx, updateHistory) { + if (typeof updateHistory == 'undefined') { + updateHistory = true; + } + + var nodeEl = null; + var nodeDatum = null; + + node.each(function (d, nIdx, nodeEls) { + if (nIdx == idx) { + nodeEl = nodeEls[idx]; + nodeDatum = d; + } + }); + if (!nodeEl) { + return; + } + + if (true) { + // always set history state, but replace instead of update on 'updatehistory' + var id = null; + if (nodeDatum['@id'].startsWith( /*location.origin*/'https://rubenvandeven.com/')) { + id = nodeDatum['@id'].substr(26); + } else { + id = '?id=' + nodeDatum['@id']; + } + + if (updateHistory) { + history.pushState({ node: idx }, getNodeLabel(nodeDatum), "/" + id); + } else { + history.replaceState({ node: idx }, getNodeLabel(nodeDatum), "/" + id); + } + } + + // set global var + positionNodesInCenter(idx); + + var currentCrumbs = breadcrumbs[nodeDatum['@id']].slice(); + currentCrumbs[currentCrumbs.length] = nodeDatum['@id']; + + // set active links. + var linkedIdxs = []; + link.each(function (d, idx, linkEls, q) { + // set nodes 'visible'/highlighted when linked to active node + if (d.source == nodeDatum || d.target == nodeDatum) { + linkEls[idx].classList.add('activeLink', 'visibleLink'); + linkEls[idx].getElementsByTagName("line")[0].setAttribute("marker-end", "url(#arrowHeadSelected)"); + node.filter(function (a, fnodeIdx) { + var r = a['@id'] == d.source['@id'] || a['@id'] == d.target['@id']; //connected node: true/false + if (r && linkedIdxs.indexOf(fnodeIdx) === -1) { + linkedIdxs[linkedIdxs.length] = fnodeIdx; + } + return r; + }).classed('visibleNode', true); + } else { + linkEls[idx].classList.remove('activeLink'); + linkEls[idx].getElementsByTagName("line")[0].setAttribute("marker-end", "url(#arrowHead)"); + } + // check if link is part of breadcrumb trail + var posSrc = currentCrumbs.indexOf(d.source['@id']); + var posTrg = currentCrumbs.indexOf(d.target['@id']); + if (posSrc > -1 && posTrg > -1 && Math.abs(posSrc - posTrg) == 1) { + linkEls[idx].classList.add('breadcrumbLink'); + linkEls[idx].getElementsByTagName("line")[0].setAttribute("marker-end", "url(#arrowHeadCrumbTrail)"); + } else { + linkEls[idx].classList.remove('breadcrumbLink'); + } + }); + + var i = linkedIdxs.indexOf(idx); + + if (i !== -1) { + linkedIdxs.splice(i, 1); + } + + positionNodesInCircle(linkedIdxs); + + setDetails(nodeDatum, idx); + }; + var deselectNode = function deselectNode() { + positionNodesInCenter(null); + link.each(function (d, idx, linkEls, q) { + linkEls[idx].classList.remove('activeLink'); + linkEls[idx].classList.remove('breadcrumbLink'); + linkEls[idx].getElementsByTagName("line")[0].setAttribute("marker-end", "url(#arrowHead)"); + }); + closeDetails(); + }; + + window.addEventListener('popstate', function (event) { + if (event.state.hasOwnProperty('node')) { + selectNode(event.state['node'], false); + } else { + // if not sure what to do, fall back to first node (also used to return to opening page) + var firstNode = graph['nodes'].find(function (n) { + return n['@id'] === firstNodeId; + }); + selectNode(graph['nodes'].indexOf(firstNode), false); + } + }); + + var forceCx, forceCy; + var setViewboxForceCenter = function setViewboxForceCenter() { + var viewBox = getViewbox(); + var zoom = getZoomValues(); + forceCx = viewBox[0] + viewBox[2] / 2 - zoom['dx']; + forceCy = viewBox[1] + viewBox[3] / 2 - zoom['dy']; + }; + + var getZoomValues = function getZoomValues() { + var zoomContainer = document.getElementById("container"); + var dx = 0, + dy = 0, + scale = 1; + if (zoomContainer.transform.baseVal.length > 0) { + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; + + try { + for (var _iterator5 = zoomContainer.transform.baseVal[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + var transform = _step5.value; + + if (transform.type == SVGTransform.SVG_TRANSFORM_TRANSLATE) { + dx += transform.matrix.e; + dy += transform.matrix.f; + } else if (transform.type == SVGTransform.SVG_TRANSFORM_SCALE) { + scale *= transform.matrix.a; // assume simple scale + } + } + } catch (err) { + _didIteratorError5 = true; + _iteratorError5 = err; + } finally { + try { + if (!_iteratorNormalCompletion5 && _iterator5.return) { + _iterator5.return(); + } + } finally { + if (_didIteratorError5) { + throw _iteratorError5; + } + } + } + } + + return { 'dx': dx, 'dy': dy, 'scale': scale }; + }; + + setViewboxForceCenter(); // sets forceCx & forceCy + + var graphInitialised = false; + simulation.force('centerActive', function force(alpha) { + // let currentNode = node.selectAll('.detail'); + // console.log(currentNode); + // console.log(forceCx, forceCy); + node.each(function (d, idx, nodes) { + var n = d; + var k = alpha * 0.1; + n.fx = null; + n.fy = null; + if (typeof nodePositions[idx] != 'undefined') { + if (graphInitialised == false) { + n.x = nodePositions[idx][0]; + n.y = nodePositions[idx][1]; + n.vx = 0; + n.vy = 0; + } else { + n.vx -= (n.x - nodePositions[idx][0]) * k * 5; + n.vy -= (n.y - nodePositions[idx][1]) * k * 5; + } + } else { + // if it's not positioned, move it out of the circle + if (currentNodePositionRadius < 1) { + return; + } + + var dx = n.x - forceCx; + var dy = n.y - forceCy; + if (!inCircle(dx, dy, currentNodePositionRadius)) { + return; + } + + if (graphInitialised == false) { + // on init, fixate items outside of circle + n.fx = n.x + dx * (2 + Math.random()); + n.fy = n.y + dy * (2 + Math.random()); + } else { + // if initialised, gradually move them outwards + n.vx += dx * k * 4; + n.vy += dy * k * 4; + } + } + }); + }); + + //path to curve the tile + var nodePath = node.append("path").attr("id", function (d, idx) { + return "nodePath" + idx; + }).attr("d", function (d) { + var r = getSizeForNode(d) * 0.9; + var startX = getSizeForNode(d); + // M cx cy + // m -r, 0 + // a r,r 0 1,0 (r * 2),0 + // a r,r 0 1,0 -(r * 2),0 + // return 'M' + nodeSize/2 + ' ' + nodeSize/2 + ' ' + + return 'M' + 0 + ' ' + 0 + ' ' + 'm -' + r + ', 0' + ' ' + 'a ' + r + ',' + r + ' 0 1,0 ' + r * 2 + ',0 ' + 'a ' + r + ',' + r + ' 0 1,0 -' + r * 2 + ',0'; + // return 'm' + startX + ',' + nodeSize + ' ' + + // 'a' + r + ',' + r + ' 0 0 0 ' + (2*r) + ',0'; + }); + + node.call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended)).on("click", function (d, idx, nodes) { + var node = nodes[idx]; + selectNode(idx, node, d); + }).on('mouseover', function (n, nIdx) { + link.each(function (l, idx, linkEls, q) { + // set nodes 'visible'/highlighted when linked to active node + if (l.source == n || l.target == n) { + linkEls[idx].classList.add('hoverLink'); + } + }); + }).on('mouseout', function () { + var hoverLinkEls = document.getElementsByClassName('hoverLink'); + while (hoverLinkEls.length > 0) { + hoverLinkEls[0].classList.remove('hoverLink'); + } + }); + + // svg.call(d3.drag() + // .on("start", function(d){ + // if(d3.event.sourceEvent.type == 'touchstart' && d3.event.sourceEvent.touches.length > 1) { + // } else { + // d3.event.sourceEvent.stopPropagation(); + // svg.node().classList.add("dragging"); + // } + // }) + // .on("drag", function(){ + // moveViewboxPx(d3.event.dx, d3.event.dy); + // }) + // .on("end", function(){ + // svg.node().classList.remove("dragging"); + // })); + svg.call(d3.zoom().scaleExtent([0.3, 3]).on("start", function () { + svg.node().classList.add("dragging"); + }).on("end", function () { + svg.node().classList.remove("dragging"); + }).on("zoom", function (a, b, c) { + container.attr("transform", d3.event.transform); + })); + + // svg.call(d3.zoom.transform, d3.zoomIdentity); + + node.append('circle').attr("r", function (d) { + return getSizeForNode(d); + }).attr("class", "nodeBg"); + node.append('circle').attr("r", function (d) { + return getSizeForNode(d) * 1.08; + }) // nodeSize + margin + .attr("class", "highlightCircle"); + + node.append('text').attr("class", "nodeType").text(function (n) { + return n['@type']; + }); + + node.append('text').attr("class", "nodeYear").attr("y", "22").text(function (n) { + return getNodeYear(n); + }); + var splitText = function splitText(text) { + var characters = [" ", "-", '\xAD']; + var charSplitPos = {}; + var mid = Math.floor(text.length / 2); + var splitPos = false; + var splitPosChar = false; + // split sentences + var _iteratorNormalCompletion6 = true; + var _didIteratorError6 = false; + var _iteratorError6 = undefined; + + try { + for (var _iterator6 = characters[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { + var char = _step6.value; + + if (text.indexOf(char) < 0) { + continue; + } + var tmid = text.substr(0, mid).lastIndexOf(char); + if (tmid === -1) { + tmid = text.indexOf(char); + } + tmid += 1; // we want to cut _after_ the character + // console.log("Char", char, tmid); + if (splitPos === false || Math.abs(tmid - mid) < Math.abs(splitPos - mid)) { + // console.log("least!"); + splitPos = tmid; + splitPosChar = char; + } + } + // console.log("pos",splitPos) + + } catch (err) { + _didIteratorError6 = true; + _iteratorError6 = err; + } finally { + try { + if (!_iteratorNormalCompletion6 && _iterator6.return) { + _iterator6.return(); + } + } finally { + if (_didIteratorError6) { + throw _iteratorError6; + } + } + } + + if (splitPos === false) { + return false; + } + + var text1 = text.substr(0, splitPos).trim(); + var text2 = text.substr(splitPos).trim(); + + if (splitPosChar == '\xAD') { + text1 += "-"; + } + + // find most equal split + return [text1, text2]; + }; + var nodeTitle = node.append('text').attr("class", "nodeTitle").attr("y", "5"); + nodeTitle + // .append("textPath") + // .attr( "xlink:href",function(d, idx){return '#nodePath'+idx;}) + // .text(getNodeLabel) + .each(function (node, nodes) { + var textLength = void 0; + var self = d3.select(this); + var titleText = getNodeLabel(node); + var titleTexts = false; + if (titleText.length > 20) { + titleTexts = splitText(titleText); + } + if (titleTexts !== false) { + var tspan1 = self.append("tspan").text(titleTexts[0]).attr("y", "-10").attr("x", "0"); + var tspan = self.append("tspan").text(titleTexts[1]).attr("y", "10").attr("x", "0"); + var textLength1 = tspan.node().getComputedTextLength(); + var textLength2 = tspan.node().getComputedTextLength(); + textLength = Math.max(textLength1, textLength2); + } else { + self.text(titleText); + textLength = self.node().getComputedTextLength(); + } + + // scale according to text length: + if (textLength > getSizeForNode(node) * 2) { + self.attr('transform', 'scale(' + getSizeForNode(node) * 2 / textLength / 1.05 + ')'); + } + }); + + node.each(function (d) { + if (!d['https://schema.org/thumbnailUrl']) { + return; + } + d3.select(this).append('svg:image').attr("xlink:href", d['https://schema.org/thumbnailUrl']).attr("width", function (d) { + return getSizeForNode(d) * 2; + }).attr("height", function (d) { + return getSizeForNode(d) * 2; + }).attr("transform", function (d) { + return "translate(-" + getSizeForNode(d) + " -" + getSizeForNode(d) + ")"; + }).attr("clip-path", "url(#clipNodeImage)").attr("preserveAspectRatio", "xMidYMid slice"); + }); + + simulation.nodes(graph.nodes).on("tick", ticked); + + simulation.force("link").links(graph.links).distance(function (l) { + switch (l.name) { + // case 'publishedAt': + // return 200; + // case 'image': + // return 200; + default: + return 300; + } + }) // distance between the nodes / link length + // .charge(-100) + ; + + // run on each draw + function ticked() { + graph.nodes.forEach(function (d, idx) { + d.leftX = d.rightX = d.x; + + // fix first node on center + // if(idx === 0) { + // d.fx = width/2; + // d.fy = height/2; + // return; + // } + }); + + linkLine.each(function (d) { + var sourceX, targetX, midX, dx, dy, angle; + + // This mess makes the arrows exactly perfect. + // thanks to http://bl.ocks.org/curran/9b73eb564c1c8a3d8f3ab207de364bf4 + if (d.source.rightX < d.target.leftX) { + sourceX = d.source.rightX; + targetX = d.target.leftX; + } else if (d.target.rightX < d.source.leftX) { + targetX = d.target.rightX; + sourceX = d.source.leftX; + } else if (d.target.isCircle) { + targetX = sourceX = d.target.x; + } else if (d.source.isCircle) { + targetX = sourceX = d.source.x; + } else { + midX = (d.source.x + d.target.x) / 2; + if (midX > d.target.rightX) { + midX = d.target.rightX; + } else if (midX > d.source.rightX) { + midX = d.source.rightX; + } else if (midX < d.target.leftX) { + midX = d.target.leftX; + } else if (midX < d.source.leftX) { + midX = d.source.leftX; + } + targetX = sourceX = midX; + } + + dx = targetX - sourceX; + dy = d.target.y - d.source.y; + angle = Math.atan2(dx, dy); + + /* DISABLED + srcSize = (typeof nodePositions[d.source.index] != 'undefined') ? selectedNodeSize : nodeSize; + tgtSize = (typeof nodePositions[d.target.index] != 'undefined') ? selectedNodeSize : nodeSize; + */ + var srcSize = getSizeForNode(d.source) + 3.2; + var tgtSize = getSizeForNode(d.target) + 3.2; + + // Compute the line endpoint such that the arrow + // is touching the edge of the node rectangle perfectly. + d.sourceX = sourceX + Math.sin(angle) * srcSize; + d.targetX = targetX - Math.sin(angle) * tgtSize; + d.sourceY = d.source.y + Math.cos(angle) * srcSize; + d.targetY = d.target.y - Math.cos(angle) * tgtSize; + }).attr("x1", function (d) { + return d.sourceX; + }).attr("y1", function (d) { + return d.sourceY; + }).attr("x2", function (d) { + return d.targetX; + }).attr("y2", function (d) { + return d.targetY; + }); + linkText.attr("transform", function (d) { + var dx = (d.target.x - d.source.x) / 2; + var dy = (d.target.y - d.source.y) / 2; + var x = d.source.x + dx; + var y = d.source.y + dy; + var deg = Math.atan(dy / dx) * 180 / Math.PI; + // if dx/dy == 0/0 -> deg == NaN + if (isNaN(deg)) { + return ""; + } + return "translate(" + x + " " + y + ") rotate(" + deg + ") translate(0, -10)"; + }); + + node.attr("transform", function (d) { + return "translate(" + d.x + "," + d.y + ")"; + }); + } + + function dragstarted(d, idx, nodes) { + if (!d3.event.active) simulation.alphaTarget(0.3).restart(); + var nodeEl = nodes[idx]; + d.fx = d.x; + d.fy = d.y; + // nodeEl.style.fill = '#00f'; + nodeEl.classList.add('drag'); + } + + // use to validate drag + // function validate(x, a, b) { + // if (x =< a) return a; + // return b; + // } + + function dragged(d, idx) { + d.fx = d3.event.x; + d.fy = d3.event.y; + } + + function dragended(d, idx, nodes) { + if (!d3.event.active) simulation.alphaTarget(0); + var nodeEl = nodes[idx]; + d.fx = null; + d.fy = null; + nodeEl.classList.remove('drag'); + } + + function moveViewboxPx(dx, dy) { + var viewBox = svg.attr("viewBox").split(" ").map(parseFloat); + viewBox[0] -= dx * 1; + viewBox[1] -= dy * 1; + svg.attr("viewBox", viewBox.join(" ")); + } + + // start by selecting the first node :-) + // selectNode(currentNodeIdx+1); + // positionNodesInCenter(currentNodeIdx); + + if (location.pathname.startsWith('/@type/')) { + for (var t in types) { + if (getDisplayAttr(t) == location.pathname.substr(7)) { + centerByType(t, false); + } + } + } else { + var startNodeId = location.search.startsWith("?id=") ? location.search.substr(4) : 'https://rubenvandeven.com' + location.pathname; + var firstNode = graph['nodes'].find(function (n) { + return n['@id'] === startNodeId; + }); + selectNode(graph['nodes'].indexOf(firstNode), false); + } + + // closeDetails(); // hide details at first + // positionNodesInCenter(currentNodeIdx+1); + + // setTimeout(function(){ + // document.body.classList.add('graphInitialised'); + // }, 10); + + var initPlaceholder = document.getElementById('initPlaceholder'); + svg.node().removeChild(initPlaceholder); + setTimeout(function () { + graphInitialised = true; + document.body.classList.add('graphInitialised'); + }, 500); +} + +// Detect request animation frame +var reqAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || +// IE Fallback, you can even fallback to onscroll +function (callback) { + window.setTimeout(callback, 1000 / 60); +}; +// all credits go to https://stackoverflow.com/a/26798337 +function scrollToY(scrollTargetY, speed, easing, finishFunction) { + // scrollTargetY: the target scrollY property of the window + // speed: time in pixels per second + // easing: easing equation to use + + var scrollY = window.scrollY, + scrollTargetY = scrollTargetY || 0, + speed = speed || 2000, + easing = easing || 'easeOutSine', + currentTime = 0, + finishFunction = finishFunction || false; + + // min time .1, max time .8 seconds + var time = Math.max(.1, Math.min(Math.abs(scrollY - scrollTargetY) / speed, .8)); + + // easing equations from https://github.com/danro/easing-js/blob/master/easing.js + var PI_D2 = Math.PI / 2, + easingEquations = { + easeOutSine: function easeOutSine(pos) { + return Math.sin(pos * (Math.PI / 2)); + }, + easeInOutSine: function easeInOutSine(pos) { + return -0.5 * (Math.cos(Math.PI * pos) - 1); + }, + easeInOutQuint: function easeInOutQuint(pos) { + if ((pos /= 0.5) < 1) { + return 0.5 * Math.pow(pos, 5); + } + return 0.5 * (Math.pow(pos - 2, 5) + 2); + } + }; + + // add animation loop + function tick() { + currentTime += 1 / 60; + + var p = currentTime / time; + var t = easingEquations[easing](p); + + if (p < 1) { + reqAnimFrame(tick); + + window.scrollTo(0, scrollY + (scrollTargetY - scrollY) * t); + } else { + window.scrollTo(0, scrollTargetY); + if (finishFunction) { + finishFunction(); + } + } + } + + // call it once to get started + tick(); +} \ No newline at end of file diff --git a/assets/js/portfolio.min.js b/assets/js/portfolio.min.js new file mode 100644 index 0000000..24e209e --- /dev/null +++ b/assets/js/portfolio.min.js @@ -0,0 +1,2 @@ +"use strict";var data,graph,_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function getLabelAttribute(e){if(void 0!==e["https://schema.org/name"])return"https://schema.org/name";switch(e["@type"]){case"https://schema.org/WebSite":if(void 0!==e["https://schema.org/url"])return"https://schema.org/url";break;case"https://schema.org/ImageObject":if(void 0!==e["https://schema.org/caption"])return"https://schema.org/caption";if(void 0!==e["https://schema.org/contentUrl"])return"https://schema.org/contentUrl";break;case"https://schema.org/PostalAddress":if(void 0!==e["https://schema.org/addressLocality"])return"https://schema.org/addressLocality"}return"@id"}function getNodeLabel(e){var t=e[getLabelAttribute(e)];return void 0===t&&(t=e["@id"]),void 0===t&&(t=""),t}function getNodeYear(e){return void 0!==e["https://schema.org/dateCreated"]?e["https://schema.org/dateCreated"].substr(0,4):void 0!==e["https://schema.org/datePublished"]?e["https://schema.org/datePublished"].substr(0,4):void 0!==e["https://schema.org/startDate"]?e["https://schema.org/startDate"].substr(0,4):void 0!==e["https://schema.org/endDate"]?e["https://schema.org/endDate"].substr(0,4):void 0!==e["https://schema.org/foundingDate"]?e["https://schema.org/foundingDate"].substr(0,4):null}function getDisplayAttr(e){return e.replace(/.*[#|\/]/,"")}function jsonLdToGraph(e){var t={},r=[];for(var n in e)t[e[n]["@id"]]=e[n];for(var a in e){var o=e[a],i=o["@id"];for(var s in o){var d=Array.isArray(o[s])?o[s]:[o[s]];for(var c in d)"@id"!==s&&"string"==typeof d[c]&&t[d[c]]?r[r.length]={source:i,target:d[c],name:s}:void 0!==d[c]["@id"]&&(1==Object.keys(d[c]).length&&void 0===t[d[c]["@id"]]||(r[r.length]={source:i,target:d[c]["@id"],name:s}))}}return{nodes:Object.values(t),links:r}}var nodeMap={},linkMap={},breadcrumbs={},requestPromise=fetch("/assets/js/rubenvandeven.jsonld").then(function(e){return e.json()}).then(function(e){for(var t in(graph=jsonLdToGraph(e["@graph"])).nodes)nodeMap[graph.nodes[t]["@id"]]=graph.nodes[t];startGraph(graph)});function inCircle(e,t,r){var n=Math.abs(e),a=Math.abs(t);return!(r"+getNodeYear(r)+""),n.classList.add("nodeTitle"),n.classList.add("nodeTitleNr"+e),n.addEventListener("click",function(e){var t=z.nodes.indexOf(r);q(t)});var a=document.createElement("a");return a.classList.add("nodeType"),a.innerHTML=getDisplayAttr(r["@type"]),a.title=r["@type"],a.addEventListener("click",function(e){U(r["@type"])}),t.appendChild(n),t.appendChild(a),t},q=function(n,e){void 0===e&&(e=!0);var a=null,i=null;if(B.each(function(e,t,r){t==n&&(a=r[n],i=e)}),a){var t=null;t=i["@id"].startsWith("https://rubenvandeven.com/")?i["@id"].substr(26):"?id="+i["@id"],e?history.pushState({node:n},getNodeLabel(i),"/"+t):history.replaceState({node:n},getNodeLabel(i),"/"+t),L(n);var s=breadcrumbs[i["@id"]].slice();s[s.length]=i["@id"];var d=[];g.each(function(n,e,t,r){n.source==i||n.target==i?(t[e].classList.add("activeLink","visibleLink"),t[e].getElementsByTagName("line")[0].setAttribute("marker-end","url(#arrowHeadSelected)"),B.filter(function(e,t){var r=e["@id"]==n.source["@id"]||e["@id"]==n.target["@id"];return r&&-1===d.indexOf(t)&&(d[d.length]=t),r}).classed("visibleNode",!0)):(t[e].classList.remove("activeLink"),t[e].getElementsByTagName("line")[0].setAttribute("marker-end","url(#arrowHead)"));var a=s.indexOf(n.source["@id"]),o=s.indexOf(n.target["@id"]);-1"+n+""),e.appendChild(t),i.appendChild(e),a.push(getNodeLabel(nodeMap[r]))},d=!0,c=!1,l=void 0;try{for(var u,h=breadcrumbs[t["@id"]][Symbol.iterator]();!(d=(u=h.next()).done);d=!0)s(u.value)}catch(e){c=!0,l=e}finally{try{!d&&h.return&&h.return()}finally{if(c)throw l}}Y.appendChild(i),a.push(getNodeLabel(t));var p=getLabelAttribute(t),m=document.createElement("h2");m.innerHTML=getNodeLabel(t);var f=document.createElement("span");f.classList.add("nodeType"),f.innerHTML=getDisplayAttr(t["@type"]),f.title=t["@type"],f.addEventListener("click",function(e){U(t["@type"])}),m.appendChild(f),Y.appendChild(m);var g=document.createElement("dl"),v=["@id","x","y","index","@type","vy","vx","fx","fy","leftX","rightX"];for(var y in"https://schema.org/contentUrl"!==p&&(v[v.length]=p),t)if(-1==v.indexOf(y)){var b=Array.isArray(t[y])?t[y]:[t[y]];for(var L in b)if(("string"!=typeof b[L]||!nodeMap[b[L]])&&void 0===b[L]["@id"])if("https://schema.org/url"==y||"http://www.w3.org/2000/01/rdf-schema#seeAlso"==y)g.innerHTML+="
"+getDisplayAttr(y)+"
"+b[L]+"
";else if("https://schema.org/embedUrl"==y)g.innerHTML+="
"+getDisplayAttr(y)+"
"+b[L]+"
",g.innerHTML+="
";else if("https://schema.org/contentUrl"==y)if(g.innerHTML+="
"+getDisplayAttr(y)+"
"+b[L]+"
","https://schema.org/VideoObject"==t["@type"]){var x=b["https://schema.org/encodingFormat"]?"type='"+b["https://schema.org/encodingFormat"]+"'":"",M=b["https://schema.org/thumbnailUrl"]?"poster='"+b["https://schema.org/thumbnailUrl"]+"'":"";g.innerHTML+="
"}else g.innerHTML+="
";else{var w=b[L].replace(/\n/g,"
");g.innerHTML+="
"+getDisplayAttr(y)+"
"+w+"
"}}Y.appendChild(g);for(var A=document.createElement("dl"),N=0;N"}else D.innerHTML="";A.appendChild(D)}}}for(var H in e){var j=document.createElement("dt");for(var X in j.innerHTML=getDisplayAttr(H),A.appendChild(j),e[H]){var I=e[H][X];if(A.appendChild(R(I,X)),void 0!==I["https://schema.org/contentUrl"]){var P=document.createElement("dd");P.classList.add("dd-contentobject"),P.innerHTML="",A.appendChild(P)}}}Y.appendChild(A),B.each(function(e,t,r){t==n?r[t].classList.add("selectedNode"):r[t].classList.remove("selectedNode")}),document.title=a.join(" :: ")}(i,n)}},M=function(){L(null),g.each(function(e,t,r,n){r[t].classList.remove("activeLink"),r[t].classList.remove("breadcrumbLink"),r[t].getElementsByTagName("line")[0].setAttribute("marker-end","url(#arrowHead)")}),document.body.classList.remove("detailsOpen"),scrollToY(0,4e3)};window.addEventListener("popstate",function(e){if(e.state.hasOwnProperty("node"))q(e.state.node,!1);else{var t=z.nodes.find(function(e){return e["@id"]===firstNodeId});q(z.nodes.indexOf(t),!1)}});var w=function(){var e=b(),t=A();u=e[0]+e[2]/2-t.dx,h=e[1]+e[3]/2-t.dy},A=function(){var e=document.getElementById("container"),t=0,r=0,n=1;if(02*getSizeForNode(e)&&n.attr("transform","scale("+2*getSizeForNode(e)/r/1.05+")")}),B.each(function(e){e["https://schema.org/thumbnailUrl"]&&d3.select(this).append("svg:image").attr("xlink:href",e["https://schema.org/thumbnailUrl"]).attr("width",function(e){return 2*getSizeForNode(e)}).attr("height",function(e){return 2*getSizeForNode(e)}).attr("transform",function(e){return"translate(-"+getSizeForNode(e)+" -"+getSizeForNode(e)+")"}).attr("clip-path","url(#clipNodeImage)").attr("preserveAspectRatio","xMidYMid slice")}),f.nodes(z.nodes).on("tick",function(){z.nodes.forEach(function(e,t){e.leftX=e.rightX=e.x}),v.each(function(e){var t,r,n,a,o,i;e.source.rightXe.target.rightX?n=e.target.rightX:n>e.source.rightX?n=e.source.rightX:n r.json())\n .then(data => {\n graph = jsonLdToGraph(data['@graph']);\n // create a map of nodes by id.\n for(let i in graph.nodes) {\n nodeMap[graph.nodes[i]['@id']] = graph.nodes[i];\n }\n startGraph(graph);\n });\n\nfunction inCircle(dx, dy, r) {\n // fastest check if in circle: https://stackoverflow.com/a/7227057\n let dxAbs = Math.abs(dx);\n let dyAbs = Math.abs(dy);\n\n if(dxAbs > r || dyAbs > r) {\n return false;\n } else if(dxAbs + dyAbs <= r){\n return true;\n } else if( Math.pow(dx,2) + Math.pow(dy, 2) <= Math.pow(r,2)){\n return true;\n } else {\n return false;\n }\n}\n\nfunction createLinkMap(graph) {\n let linkMap = {};\n for(let link of graph['links']){\n if(typeof linkMap[link['source']] == 'undefined') {\n linkMap[link['source']] = [];\n }\n linkMap[link['source']][linkMap[link['source']].length] = {'id': link['target'], 'name': link['name']};\n\n\n if(typeof linkMap[link['target']] == 'undefined') {\n linkMap[link['target']] = [];\n }\n\n linkMap[link['target']][linkMap[link['target']].length] = {'id': link['source'], 'name': link['name']};\n }\n return linkMap;\n}\n\n\n // config\nvar nodeSize = 40;\nvar selectedNodeSize = 140;\nvar firstNodeId = \"https://rubenvandeven.com/\";\n\nfunction getSizeForNode(node) {\n if(node.hasOwnProperty('https://schema.org/thumbnailUrl'))\n return nodeSize;\n if(node['@id'] == firstNodeId)\n return nodeSize*1.2;\n // everynode has at least one link. these should equal 1\n return nodeSize * (.7 + Math.min(20, linkMap[node['@id']].length) / 40)\n return nodeSize;\n}\n\n// TODO: make sure, 'shortest' path is favoured.\nfunction createBreadcrumbs(linkMap, srcId) {\n let crumbs = {};\n\n let createBreadcrumbLayer = function(srcId) {\n let path = crumbs[srcId];\n let newPath = path.slice();\n newPath.push(srcId);\n\n let nextSrcIds = [];\n for (let link of linkMap[srcId]) {\n if(typeof crumbs[link['id']] !== 'undefined') continue;\n crumbs[link['id']] = newPath;\n nextSrcIds.push(link['id']);\n }\n\n return nextSrcIds;\n }\n crumbs[srcId] = [];\n let nextIds = [srcId];\n while(nextIds.length > 0) {\n let newNextIds = [];\n for (let nextId of nextIds) {\n let r = createBreadcrumbLayer(nextId);\n newNextIds = newNextIds.concat(r);\n }\n nextIds = newNextIds;\n }\n return crumbs;\n}\n\nvar nodePositions = {};\nfunction startGraph(graph){\n\n\n// set some vars\nvar currentNodeIdx = 0;\nvar currentNodePositionRadius = 0;\nvar types = {};\n\nlinkMap = createLinkMap(graph);\nbreadcrumbs = createBreadcrumbs(linkMap, firstNodeId);\n\nfor (let nodeIdx in graph['nodes']) {\n let type = graph['nodes'][nodeIdx][\"@type\"];\n if(typeof types[type] == 'undefined') {\n types[type] = [];\n }\n types[type].push(nodeIdx);\n}\nvar graphControlsEl = document.getElementById('graphControls');\nvar typeLinksEl = document.getElementById('typeLinks');\nvar showMoreTypeLinksEl = document.getElementById('showMoreTypeLinks');\nvar moreTypeLinksEl = document.getElementById('moreTypeLinks');\nvar relLinksEl = document.getElementById('relLinks');\n\n// sort types by count:\nvar typeCounts = Object.keys(types).map(function(key) {\n return [key, types[key].length];\n});\ntypeCounts.sort(function(first, second) {\n return second[1] - first[1];\n});\n\n// make controls\nlet i = 0;\nfor (let typeCountIdx in typeCounts) {\n let typeName = typeCounts[typeCountIdx][0];\n let typeLinkEl = document.createElement(\"li\");\n let typeLinkAEl = document.createElement(\"a\");\n let typeLinkCountEl = document.createElement(\"span\");\n typeLinkCountEl.innerHTML = typeCounts[typeCountIdx][1];\n typeLinkCountEl.classList.add('typeCount');\n typeLinkAEl.innerHTML = getDisplayAttr(typeName);\n typeLinkAEl.title = typeName;\n typeLinkAEl.addEventListener('click', function(){\n centerByType(typeName);\n // positionNodesInCenter(types[typeName]);\n });\n typeLinkAEl.addEventListener('mouseover', function() {\n let typeNodeEls = document.getElementsByClassName(typeName);\n for(let typeNodeEl of typeNodeEls) {\n typeNodeEl.classList.add('typeHighlight');\n }\n });\n typeLinkAEl.addEventListener('mouseout', function() {\n let typeNodeEls = document.getElementsByClassName(typeName);\n for(let typeNodeEl of typeNodeEls) {\n typeNodeEl.classList.remove('typeHighlight');\n }\n });\n typeLinkEl.append(typeLinkAEl);\n typeLinkEl.append(typeLinkCountEl);\n (i < 5 ? typeLinksEl: moreTypeLinksEl).appendChild(typeLinkEl);\n i++;\n // typeLinksEl.appendChild(typeLinkEl);\n}\n\nshowMoreTypeLinksEl.addEventListener('click', function () {\n document.body.classList.add('showMoreLinks');\n var hideMoreTypeLinks = function(e) {\n e.preventDefault();\n e.stopPropagation();\n document.body.removeEventListener('mouseup', hideMoreTypeLinks, true);\n document.body.classList.remove('showMoreLinks');\n }\n document.body.addEventListener('mouseup', hideMoreTypeLinks, true);\n}, false)\n\n\n// make svg\nvar svg = d3.select(\"svg\"),\n width = +svg.attr(\"width\"),\n height = +svg.attr(\"height\");\nvar container = svg.append(\"g\")\n .attr(\"id\", \"container\")\n ;\n\nvar simulation = d3.forceSimulation()\n .force(\"link\", d3.forceLink().id(function(d) { return d[\"@id\"]; }).strength(.005))\n .force(\"charge\", d3.forceManyBody()) // doesn't seem necessary?\n .force(\"collision\", d3.forceCollide(function(d){\n return getSizeForNode(d) * 1.1; // avoid overlapping nodes\n }))\n // .force(\"center\", d3.forceCenter(width / 2, height / 2)) // position around center\n\n // .force(\"x\", d3.forceX())\n // .force(\"y\", d3.forceY())\n // .force(\"y\", d3.forceY())\n ;\n\n\nvar link = container.append(\"g\")\n .attr(\"class\", \"links\")\n .selectAll(\".relationship\")\n .data(graph['links'])\n .enter().append(\"g\")\n .attr(\"class\", function(l){return \"relationship \"+l.name;})\n ;\nvar linkLine = link\n // .append(\"line\");\n .append(\"line\").attr(\"marker-end\", \"url(#arrowHead)\")\n ;\nvar linkText = link\n .append(\"text\")\n .text(function(l){\n // l == Object { source: \"https://rubenvandeven.com/#codesandmodes\", target: \"_:b34\", name: \"https://schema.org/location\" }\n return getDisplayAttr(l.name);\n })\n ;\n\n var node = container.append(\"g\")\n .attr(\"class\", \"nodes\")\n .selectAll(\".node\")\n .data(graph.nodes)\n .enter().append(\"g\")\n .attr(\"class\", function(d) {\n let baseClasses = 'node ' + d['@type'];\n if(d['@type']) {\n let slashpos = d['@type'].lastIndexOf('/');\n if(slashpos > -1) {\n baseClasses += ' ' + d['@type'].substr(slashpos + 1);\n }\n }\n return baseClasses;\n })\n ;\nvar getViewbox = function() {\n return svg.attr(\"viewBox\").split(\" \").map(parseFloat);\n}\nvar positionNodesInCenter = function(idxs) {\n setViewboxForceCenter(); // sets forceCx & forceCy\n if(typeof idxs == \"object\" && idxs !== null && idxs.length == 1) {\n idxs = idxs[0];\n }\n\n nodePositions = {}; // reset\n if(idxs === null) {\n return;\n }\n else if(typeof idxs == \"object\") {\n // array or object -> each\n // calculate grid:\n // let itemsX = 4;\n // let itemsY = Math.ceil(idxs.length/itemsX);\n // console.log(itemsX,itemsY);\n // let rowDiffX = viewBox[3] * (1/(itemsX+1));\n // let rowDiffY = viewBox[2] * (1/(itemsY+1));\n // console.log(rowDiffX, rowDiffY);\n // for (var i = 0; i < idxs.length; i++) {\n // nodePositions[idxs[i]] = [\n // cx - itemsX/2*rowDiffX + rowDiffX * ((i % itemsX)),\n // cy - itemsY/2*rowDiffY + rowDiffY * (Math.floor(i / itemsX))\n // ];\n // }\n positionNodesInCircle(idxs);\n // console.log(nodePositions);\n }\n else{\n nodePositions[idxs] = [\n forceCx,\n forceCy\n ];\n // console.log(\"singleNode\", idxs, nodePositions);\n }\n\n node.each(function(d,nIdx,nodeEls){\n if(typeof nodePositions[nIdx] != 'undefined') {\n nodeEls[nIdx].classList.add('centeredNode');\n nodeEls[nIdx].classList.add('visibleNode');\n } else {\n nodeEls[nIdx].classList.remove('centeredNode');\n nodeEls[nIdx].classList.remove('visibleNode');\n }\n });\n\n // restart animation (they call that 'alpha' in d3 force)\n simulation.alpha(1);\n simulation.restart();\n}\nvar positionNodesInCircle = function(idxs, r) {\n let viewBox = getViewbox();\n let zoom = getZoomValues();\n setViewboxForceCenter(); // sets forceCx & forceCy\n if(typeof r == 'undefined') {\n if(idxs.length == 1) {\n r = viewBox[2] / 6;\n } else {\n r = viewBox[2] / (4 + Math.max(0, 2.5 - idxs.length));\n }\n }\n currentNodePositionRadius = r;\n let forceCx = viewBox[0] + viewBox[2]/2 - zoom['dx'];\n let forceCy = viewBox[1] + viewBox[3]/2 - zoom['dy'];\n\n let stepSize = 2*Math.PI / idxs.length;\n\n for (var i = 0; i < idxs.length; i++) {\n nodePositions[idxs[i]] = [\n forceCx + Math.sin(stepSize * i) * r,\n forceCy + Math.cos(stepSize * i) * r\n ];\n }\n\n // restart animation (they call that 'alpha' in d3 force)\n simulation.alpha(1);\n simulation.restart();\n}\nvar centerByType = function(types, updateHistory) {\n if(typeof updateHistory == 'undefined') {\n updateHistory = true;\n }\n if(!Array.isArray(types)) {\n types = [types];\n }\n let idxs = [];\n for(let idx in graph.nodes) {\n if(types.indexOf(graph.nodes[idx]['@type']) > -1) {\n idxs[idxs.length] = idx;\n }\n }\n deselectNode();\n if(updateHistory) {\n // TODO: working\n console.log(types[0], getDisplayAttr(types[0]),types.map(getDisplayAttr));\n history.pushState({types: types}, \"\", \"/@type/\"+(types.map(getDisplayAttr).join(\"+\")));\n } else {\n history.replaceState({types: types}, \"\", \"/@type/\"+(types.map(getDisplayAttr).join(\"+\")));\n }\n positionNodesInCenter(idxs.length ? idxs : null);\n}\n\nvar selectedNodeTransition = d3.transition()\n .duration(750)\n .ease(d3.easeLinear);\n\nvar nodeDetailEl = document.getElementById(\"nodeDetails\");\n\nvar createRelationshipEl = function(relNode, i) {\n let el = document.createElement(\"dd\");\n el.classList.add('relLink');\n let titleEl = document.createElement('a');\n titleEl.innerHTML = getNodeLabel(relNode)\n let year = getNodeYear(relNode);\n if(year !== null) {\n titleEl.innerHTML += `${getNodeYear(relNode)}`;\n }\n titleEl.classList.add('nodeTitle');\n titleEl.classList.add('nodeTitleNr'+i);\n titleEl.addEventListener('click',function(e){\n let idx = graph.nodes.indexOf(relNode);\n selectNode(idx);\n });\n let typeEl = document.createElement('a');\n typeEl.classList.add('nodeType');\n typeEl.innerHTML = getDisplayAttr(relNode['@type']);\n typeEl.title = relNode['@type'];\n typeEl.addEventListener('click',function(e){\n centerByType(relNode['@type']);\n });\n el.appendChild(titleEl);\n el.appendChild(typeEl);\n return el;\n}\n\nvar setDetails = function(nodeDatum, nodeIdx) {\n document.body.classList.add(\"detailsOpen\");\n scrollToY(0, 4000);\n while (nodeDetailEl.hasChildNodes()) {\n nodeDetailEl.removeChild(nodeDetailEl.lastChild);\n }\n\n // TODO: replace relUp & relDown with linkMap\n let relUp = [];\n let relDown = [];\n let pageTitles = [];\n let nodeDetailScalerEl = document.createElement('div');\n // nodeDetailScalerEl.innerHTML = `
`;\n nodeDetailScalerEl.id = 'nodeDetailsScaler';\n nodeDetailScalerEl.addEventListener('mousedown', function(e){\n // console.log('go');\n let drag = function(e) {\n // 5px for padding\n nodeDetailEl.style.width = (window.innerWidth - e.clientX + 5) +'px';\n }\n document.body.addEventListener('mousemove', drag);\n document.body.addEventListener('mouseup', function(){\n document.body.removeEventListener('mousemove', drag);\n });\n });\n nodeDetails.appendChild(nodeDetailScalerEl);\n\n let breadcrumbsEl = document.createElement('ul');\n breadcrumbsEl.classList.add('breadcrumbs');\n for(let crumbNodeId of breadcrumbs[nodeDatum['@id']]) {\n let crumbWrapEl = document.createElement('li');\n let crumbEl = document.createElement('span');\n crumbEl.classList.add('crumb');\n crumbEl.addEventListener('click', function(e){\n let idx = graph.nodes.indexOf(nodeMap[crumbNodeId]);\n selectNode(idx);\n });\n crumbEl.innerHTML = `${getNodeLabel(nodeMap[crumbNodeId])}`;\n let nodeYear = getNodeYear(nodeMap[crumbNodeId]);\n if(nodeYear !== null) {\n crumbEl.innerHTML += `${nodeYear}`;\n }\n crumbWrapEl.appendChild(crumbEl);\n breadcrumbsEl.appendChild(crumbWrapEl);\n pageTitles.push(getNodeLabel(nodeMap[crumbNodeId]));\n }\n nodeDetailEl.appendChild(breadcrumbsEl);\n pageTitles.push(getNodeLabel(nodeDatum));\n\n let titleAttr = getLabelAttribute(nodeDatum);\n let titleEl = document.createElement('h2');\n titleEl.innerHTML = getNodeLabel(nodeDatum);\n\n let typeEl = document.createElement('span');\n typeEl.classList.add('nodeType')\n typeEl.innerHTML = getDisplayAttr(nodeDatum['@type']);\n typeEl.title = nodeDatum['@type']\n typeEl.addEventListener('click',function(e){\n centerByType(nodeDatum['@type']);\n });\n titleEl.appendChild(typeEl);\n nodeDetailEl.appendChild(titleEl);\n\n let listEl = document.createElement(\"dl\");\n // listEl.innerHTML += `
type
${nodeDatum['@type']}
`;\n\n let skipNodeAttributes = [\n '@id','x','y','index','@type','vy','vx','fx','fy','leftX','rightX'\n ];\n if(titleAttr !== 'https://schema.org/contentUrl') {\n skipNodeAttributes[skipNodeAttributes.length] = titleAttr;\n }\n for (let attr in nodeDatum) {\n if(skipNodeAttributes.indexOf(attr) != -1) {\n continue;\n }\n\n // approach all as array\n let nodeAttr = Array.isArray(nodeDatum[attr]) ? nodeDatum[attr] : [nodeDatum[attr]];\n for (let i in nodeAttr) {\n // check if relationship:\n if(typeof nodeAttr[i] === \"string\" && nodeMap[nodeAttr[i]]) {\n continue;\n } else if(typeof nodeAttr[i]['@id'] !== 'undefined') {\n continue;\n }\n if(attr == 'https://schema.org/url' || attr == 'http://www.w3.org/2000/01/rdf-schema#seeAlso') {\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${nodeAttr[i]}
`;\n } else if(attr == 'https://schema.org/embedUrl') {\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${nodeAttr[i]}
`;\n listEl.innerHTML += `
`;\n } else if(attr == 'https://schema.org/contentUrl') {\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${nodeAttr[i]}
`;\n if(nodeDatum['@type'] == 'https://schema.org/VideoObject') {\n let videoType = nodeAttr['https://schema.org/encodingFormat'] ? `type='${nodeAttr['https://schema.org/encodingFormat']}'`: \"\";\n let poster = nodeAttr['https://schema.org/thumbnailUrl'] ? `poster='${nodeAttr['https://schema.org/thumbnailUrl']}'`: \"\";\n listEl.innerHTML += `
`;\n } else{\n listEl.innerHTML += `
`;\n }\n } else {\n let valueHtml = nodeAttr[i].replace(/\\n/g,\"
\");\n listEl.innerHTML += `
${getDisplayAttr(attr)}
${valueHtml}
`;\n }\n }\n }\n nodeDetailEl.appendChild(listEl);\n\n // let relTitleEl = document.createElement(\"h4\");\n // relTitleEl.classList.add('linkTitle');\n // relTitleEl.innerHTML = \"links\";\n // nodeDetailEl.appendChild(relTitleEl);\n\n let relsEl = document.createElement(\"dl\");\n // collect relationships\n for (var i = 0; i < graph.links.length; i++) {\n let link = graph.links[i];\n if(link['source']['@id'] == nodeDatum['@id']) {\n if(typeof relDown[link['name']] == \"undefined\") {\n relDown[link['name']] = [];\n }\n relDown[link['name']][relDown[link['name']].length] = link['target'];\n }\n if(link['target']['@id'] == nodeDatum['@id']) {\n if(typeof relUp[link['name']] == \"undefined\") {\n relUp[link['name']] = [];\n }\n relUp[link['name']][relUp[link['name']].length] = link['source'];\n }\n }\n\n // relationships / links in
\n for(let attr in relDown) {\n let attrEl = document.createElement(\"dt\");\n attrEl.innerHTML = getDisplayAttr(attr);\n relsEl.appendChild(attrEl);\n for(let i in relDown[attr]) {\n let rel = relDown[attr][i];\n relsEl.appendChild(createRelationshipEl(rel));\n if(typeof rel['https://schema.org/contentUrl'] != 'undefined') {\n let ddEl = document.createElement('dd')\n ddEl.classList.add('dd-contentobject');\n if(rel['@type'] == 'https://schema.org/VideoObject') {\n let videoType = rel['https://schema.org/encodingFormat'] ? `type='${rel['https://schema.org/encodingFormat']}'`: \"\";\n let poster = rel['https://schema.org/thumbnailUrl'] ? `poster='${rel['https://schema.org/thumbnailUrl']}'`: \"\";\n ddEl.innerHTML += ``;\n } else{\n ddEl.innerHTML = ``\n }\n relsEl.appendChild(ddEl);\n }\n }\n }\n\n for(let attr in relUp) {\n let attrEl = document.createElement(\"dt\");\n attrEl.innerHTML = getDisplayAttr(attr);\n relsEl.appendChild(attrEl);\n for(let i in relUp[attr]) {\n let rel = relUp[attr][i];\n relsEl.appendChild(createRelationshipEl(rel, i));\n if(typeof rel['https://schema.org/contentUrl'] != 'undefined') {\n let ddEl = document.createElement('dd')\n ddEl.classList.add('dd-contentobject');\n ddEl.innerHTML = ``\n relsEl.appendChild(ddEl);\n }\n }\n }\n\n nodeDetailEl.appendChild(relsEl);\n\n node.each(function(d,nIdx,nodeEls){\n if(nIdx == nodeIdx) {\n nodeEls[nIdx].classList.add('selectedNode');\n } else {\n nodeEls[nIdx].classList.remove('selectedNode');\n }\n });\n\n // TODO: update history & title\n document.title = pageTitles.join(\" :: \");\n};\nvar closeDetails = function() {\n document.body.classList.remove(\"detailsOpen\");\n scrollToY(0, 4000); // for mobile\n}\n\n/**\n * Select a node, and center it + show details\n * @param int idx The index of the node in the graph.nodes array\n * @param Element|null nodeEl Optional, provide node element, so loop doesn't have to be used to change the Element\n * @return void\n */\nvar selectNode = function(idx, updateHistory){\n if(typeof updateHistory == 'undefined') {\n updateHistory = true;\n }\n \n let nodeEl = null;\n let nodeDatum = null;\n\n node.each(function(d,nIdx,nodeEls){\n if(nIdx == idx) {\n nodeEl = nodeEls[idx];\n nodeDatum = d;\n }\n });\n if(!nodeEl) {\n return;\n }\n\n\n if(true) { // always set history state, but replace instead of update on 'updatehistory'\n let id = null;\n if(nodeDatum['@id'].startsWith(/*location.origin*/'https://rubenvandeven.com/')){\n id = nodeDatum['@id'].substr(26);\n } else {\n id = '?id=' + nodeDatum['@id'];\n }\n\n if(updateHistory) {\n history.pushState({node: idx}, getNodeLabel(nodeDatum), \"/\"+id);\n } else {\n history.replaceState({node: idx}, getNodeLabel(nodeDatum), \"/\"+id);\n }\n }\n\n // set global var\n positionNodesInCenter(idx);\n\n let currentCrumbs = breadcrumbs[nodeDatum['@id']].slice();\n currentCrumbs[currentCrumbs.length] = nodeDatum['@id'];\n\n // set active links.\n let linkedIdxs = [];\n link.each(function(d,idx,linkEls,q){\n // set nodes 'visible'/highlighted when linked to active node\n if(d.source == nodeDatum || d.target == nodeDatum) {\n linkEls[idx].classList.add('activeLink','visibleLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHeadSelected)\");\n node.filter(function(a, fnodeIdx){\n let r = a['@id'] == d.source['@id'] || a['@id'] == d.target['@id']; //connected node: true/false\n if(r && linkedIdxs.indexOf(fnodeIdx) === -1){\n linkedIdxs[linkedIdxs.length] = fnodeIdx;\n }\n return r;\n }).classed('visibleNode', true);\n } else {\n linkEls[idx].classList.remove('activeLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHead)\");\n }\n // check if link is part of breadcrumb trail\n let posSrc = currentCrumbs.indexOf(d.source['@id']);\n let posTrg = currentCrumbs.indexOf(d.target['@id']);\n if(posSrc > -1 && posTrg > -1 && Math.abs(posSrc - posTrg) == 1) {\n linkEls[idx].classList.add('breadcrumbLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHeadCrumbTrail)\");\n } else {\n linkEls[idx].classList.remove('breadcrumbLink');\n }\n });\n\n let i = linkedIdxs.indexOf(idx);\n\n if(i !== -1) {\n linkedIdxs.splice(i, 1);\n }\n\n positionNodesInCircle(linkedIdxs);\n\n setDetails(nodeDatum ,idx);\n}\nvar deselectNode = function() {\n positionNodesInCenter(null);\n link.each(function(d,idx,linkEls,q){\n linkEls[idx].classList.remove('activeLink');\n linkEls[idx].classList.remove('breadcrumbLink');\n linkEls[idx].getElementsByTagName(\"line\")[0].setAttribute(\"marker-end\", \"url(#arrowHead)\")\n });\n closeDetails();\n}\n\n\nwindow.addEventListener('popstate', function(event) {\n if(event.state.hasOwnProperty('node')) {\n selectNode(event.state['node'], false);\n }\n else {\n // if not sure what to do, fall back to first node (also used to return to opening page)\n let firstNode = graph['nodes'].find(n => n['@id'] === firstNodeId);\n selectNode(graph['nodes'].indexOf(firstNode), false);\n }\n});\n\nvar forceCx, forceCy;\nvar setViewboxForceCenter = function() {\n let viewBox = getViewbox();\n let zoom = getZoomValues();\n forceCx = viewBox[0] + viewBox[2]/2 - zoom['dx'];\n forceCy = viewBox[1] + viewBox[3]/2 - zoom['dy'];\n}\n\nvar getZoomValues = function(){\n let zoomContainer = document.getElementById(\"container\");\n let dx = 0, dy = 0, scale = 1;\n if(zoomContainer.transform.baseVal.length > 0) {\n for(let transform of zoomContainer.transform.baseVal) {\n if(transform.type == SVGTransform.SVG_TRANSFORM_TRANSLATE) {\n dx += transform.matrix.e;\n dy += transform.matrix.f;\n }\n else if (transform.type == SVGTransform.SVG_TRANSFORM_SCALE) {\n scale *= transform.matrix.a; // assume simple scale\n }\n }\n }\n\n return {'dx': dx, 'dy': dy, 'scale': scale};\n}\n\nsetViewboxForceCenter(); // sets forceCx & forceCy\n\nvar graphInitialised = false;\nsimulation.force('centerActive', function force(alpha) {\n // let currentNode = node.selectAll('.detail');\n // console.log(currentNode);\n // console.log(forceCx, forceCy);\n node.each(function(d, idx, nodes){\n let n = d;\n let k = alpha * 0.1;\n n.fx = null;\n n.fy = null;\n if(typeof nodePositions[idx] != 'undefined') {\n if(graphInitialised == false) {\n n.x = nodePositions[idx][0];\n n.y = nodePositions[idx][1];\n n.vx = 0;\n n.vy = 0;\n } else {\n n.vx -= (n.x - nodePositions[idx][0]) * k * 5;\n n.vy -= (n.y - nodePositions[idx][1]) * k * 5;\n }\n } else {\n // if it's not positioned, move it out of the circle\n if(currentNodePositionRadius < 1) {\n return;\n }\n\n let dx = n.x - forceCx;\n let dy = n.y - forceCy;\n if(!inCircle(dx, dy, currentNodePositionRadius)) {\n return;\n }\n\n if(graphInitialised == false) {\n // on init, fixate items outside of circle\n n.fx = n.x + dx * (2+Math.random());\n n.fy = n.y + dy * (2+Math.random());\n } else {\n // if initialised, gradually move them outwards\n n.vx += dx * k*4;\n n.vy += dy * k*4;\n }\n }\n });\n});\n\n//path to curve the tile\nvar nodePath = node.append(\"path\")\n .attr(\"id\", function(d,idx){return \"nodePath\"+idx;})\n .attr(\"d\", function(d){\n var r = getSizeForNode(d) * 0.9;\n var startX = getSizeForNode(d);\n // M cx cy\n // m -r, 0\n // a r,r 0 1,0 (r * 2),0\n // a r,r 0 1,0 -(r * 2),0\n // return 'M' + nodeSize/2 + ' ' + nodeSize/2 + ' ' +\n return 'M' + 0 + ' ' + 0 + ' ' +\n 'm -' + r + ', 0'+' ' +\n 'a ' + r +','+r+' 0 1,0 '+ (r*2) +',0 '+\n 'a ' + r +','+r+' 0 1,0 -'+ (r*2) +',0'\n ;\n // return 'm' + startX + ',' + nodeSize + ' ' +\n // 'a' + r + ',' + r + ' 0 0 0 ' + (2*r) + ',0';\n })\n ;\n\nnode.call(d3.drag()\n .on(\"start\", dragstarted)\n .on(\"drag\", dragged)\n .on(\"end\", dragended))\n .on(\"click\", function(d, idx, nodes){\n let node = nodes[idx];\n selectNode(idx, node, d);\n })\n .on('mouseover', function(n, nIdx){\n link.each(function(l,idx,linkEls,q){\n // set nodes 'visible'/highlighted when linked to active node\n if(l.source == n || l.target == n) {\n linkEls[idx].classList.add('hoverLink');\n }\n });\n })\n .on('mouseout', function(){\n let hoverLinkEls = document.getElementsByClassName('hoverLink');\n while(hoverLinkEls.length > 0){\n hoverLinkEls[0].classList.remove('hoverLink');\n }\n });\n\n// svg.call(d3.drag()\n// .on(\"start\", function(d){\n// if(d3.event.sourceEvent.type == 'touchstart' && d3.event.sourceEvent.touches.length > 1) {\n// } else {\n// d3.event.sourceEvent.stopPropagation();\n// svg.node().classList.add(\"dragging\");\n// }\n// })\n// .on(\"drag\", function(){\n// moveViewboxPx(d3.event.dx, d3.event.dy);\n// })\n// .on(\"end\", function(){\n// svg.node().classList.remove(\"dragging\");\n// }));\nsvg.call(d3.zoom()\n .scaleExtent([0.3,3])\n .on(\"start\", function(){\n svg.node().classList.add(\"dragging\");\n })\n .on(\"end\", function(){\n svg.node().classList.remove(\"dragging\");\n })\n .on(\"zoom\", function(a,b,c){\n container.attr(\"transform\", d3.event.transform);\n })\n);\n\n// svg.call(d3.zoom.transform, d3.zoomIdentity);\n\nnode.append('circle')\n .attr(\"r\", (d) => getSizeForNode(d))\n .attr(\"class\", \"nodeBg\")\n ;\nnode.append('circle')\n .attr(\"r\", (d) => getSizeForNode(d) * 1.08) // nodeSize + margin\n .attr(\"class\", \"highlightCircle\")\n ;\n\nnode.append('text')\n .attr(\"class\", \"nodeType\")\n .text(function(n){\n return n['@type'];\n })\n\nnode.append('text')\n .attr(\"class\", \"nodeYear\")\n .attr(\"y\", \"22\")\n .text(function(n){\n return getNodeYear(n);\n })\n ;\nlet splitText = function(text){\n let characters = [\" \",\"-\",\"\\u00AD\"];\n let charSplitPos = {};\n let mid = Math.floor(text.length / 2);\n let splitPos = false;\n let splitPosChar = false;\n // split sentences\n for(let char of characters) {\n if(text.indexOf(char) < 0) {\n continue;\n }\n let tmid = text.substr(0,mid).lastIndexOf(char);\n if(tmid === -1) {\n tmid = text.indexOf(char);\n }\n tmid += 1; // we want to cut _after_ the character\n // console.log(\"Char\", char, tmid);\n if(splitPos === false || Math.abs(tmid-mid) < Math.abs(splitPos - mid)){\n // console.log(\"least!\");\n splitPos = tmid;\n splitPosChar = char;\n }\n }\n // console.log(\"pos\",splitPos)\n\n\n if(splitPos === false) {\n return false;\n }\n\n let text1 = text.substr(0, splitPos).trim();\n let text2 = text.substr(splitPos).trim();\n\n if(splitPosChar == \"\\u00AD\") {\n text1 += \"-\";\n }\n\n // find most equal split\n return [text1, text2];\n}\nlet nodeTitle = node.append('text')\n .attr(\"class\", \"nodeTitle\")\n .attr(\"y\", \"5\")\n ;\nnodeTitle\n // .append(\"textPath\")\n // .attr( \"xlink:href\",function(d, idx){return '#nodePath'+idx;})\n // .text(getNodeLabel)\n .each(function(node, nodes){\n let textLength;\n let self = d3.select(this);\n let titleText = getNodeLabel(node);\n let titleTexts = false;\n if(titleText.length > 20) {\n titleTexts = splitText(titleText);\n }\n if(titleTexts !== false) {\n let tspan1 = self.append(\"tspan\")\n .text(titleTexts[0])\n .attr(\"y\", \"-10\")\n .attr(\"x\", \"0\")\n ;\n let tspan = self.append(\"tspan\")\n .text(titleTexts[1])\n .attr(\"y\", \"10\")\n .attr(\"x\", \"0\")\n ;\n let textLength1 = tspan.node().getComputedTextLength();\n let textLength2 = tspan.node().getComputedTextLength();\n textLength = Math.max(textLength1, textLength2);\n } else {\n self.text(titleText);\n textLength = self.node().getComputedTextLength();\n }\n\n // scale according to text length:\n if(textLength > getSizeForNode(node) * 2) {\n self.attr('transform', `scale(${(getSizeForNode(node) * 2) / textLength / 1.05})`);\n }\n })\n ;\n\nnode.each(function(d) {\n if(!d['https://schema.org/thumbnailUrl']) {\n return;\n }\n d3.select(this).append('svg:image')\n .attr(\"xlink:href\", d['https://schema.org/thumbnailUrl'])\n .attr(\"width\", (d) => getSizeForNode(d)*2)\n .attr(\"height\", (d) => getSizeForNode(d)* 2)\n .attr(\"transform\",(d) => \"translate(-\"+getSizeForNode(d)+\" -\"+getSizeForNode(d)+\")\")\n .attr(\"clip-path\",\"url(#clipNodeImage)\")\n .attr(\"preserveAspectRatio\",\"xMidYMid slice\")\n ;\n });\n\nsimulation\n .nodes(graph.nodes)\n .on(\"tick\", ticked);\n\nsimulation.force(\"link\")\n .links(graph.links)\n .distance(function(l){\n switch (l.name) {\n // case 'publishedAt':\n // return 200;\n // case 'image':\n // return 200;\n default:\n return 300;\n }\n }) // distance between the nodes / link length\n // .charge(-100)\n;\n\n// run on each draw\nfunction ticked() {\n graph.nodes.forEach(function (d, idx) {\n d.leftX = d.rightX = d.x;\n\n // fix first node on center\n // if(idx === 0) {\n // d.fx = width/2;\n // d.fy = height/2;\n // return;\n // }\n });\n\n linkLine.each(function (d) {\n var sourceX, targetX, midX, dx, dy, angle;\n\n // This mess makes the arrows exactly perfect.\n // thanks to http://bl.ocks.org/curran/9b73eb564c1c8a3d8f3ab207de364bf4\n if( d.source.rightX < d.target.leftX ){\n sourceX = d.source.rightX;\n targetX = d.target.leftX;\n } else if( d.target.rightX < d.source.leftX ){\n targetX = d.target.rightX;\n sourceX = d.source.leftX;\n } else if (d.target.isCircle) {\n targetX = sourceX = d.target.x;\n } else if (d.source.isCircle) {\n targetX = sourceX = d.source.x;\n } else {\n midX = (d.source.x + d.target.x) / 2;\n if(midX > d.target.rightX){\n midX = d.target.rightX;\n } else if(midX > d.source.rightX){\n midX = d.source.rightX;\n } else if(midX < d.target.leftX){\n midX = d.target.leftX;\n } else if(midX < d.source.leftX){\n midX = d.source.leftX;\n }\n targetX = sourceX = midX;\n }\n\n dx = targetX - sourceX;\n dy = d.target.y - d.source.y;\n angle = Math.atan2(dx, dy);\n\n /* DISABLED\n srcSize = (typeof nodePositions[d.source.index] != 'undefined') ? selectedNodeSize : nodeSize;\n tgtSize = (typeof nodePositions[d.target.index] != 'undefined') ? selectedNodeSize : nodeSize;\n */\n let srcSize = getSizeForNode(d.source)+3.2;\n let tgtSize = getSizeForNode(d.target)+3.2;\n\n // Compute the line endpoint such that the arrow\n // is touching the edge of the node rectangle perfectly.\n d.sourceX = sourceX + Math.sin(angle) * srcSize;\n d.targetX = targetX - Math.sin(angle) * tgtSize;\n d.sourceY = d.source.y + Math.cos(angle) * srcSize;\n d.targetY = d.target.y - Math.cos(angle) * tgtSize;\n })\n .attr(\"x1\", function(d) { return d.sourceX; })\n .attr(\"y1\", function(d) { return d.sourceY; })\n .attr(\"x2\", function(d) { return d.targetX; })\n .attr(\"y2\", function(d) { return d.targetY; });\n linkText.attr(\"transform\", function(d){\n let dx = (d.target.x - d.source.x) /2;\n let dy = (d.target.y - d.source.y) /2;\n let x = d.source.x + dx;\n let y = d.source.y + dy;\n let deg = Math.atan(dy / dx) * 180 / Math.PI;\n // if dx/dy == 0/0 -> deg == NaN\n if(isNaN(deg)) {\n return \"\";\n }\n return \"translate(\"+x+\" \"+y+\") rotate(\"+deg+\") translate(0, -10)\";\n });\n\n node.attr(\"transform\", function(d) { return \"translate(\" + d.x + \",\" + d.y + \")\"; });\n}\n\nfunction dragstarted(d,idx,nodes) {\n if (!d3.event.active) simulation.alphaTarget(0.3).restart();\n let nodeEl = nodes[idx];\n d.fx = d.x;\n d.fy = d.y;\n // nodeEl.style.fill = '#00f';\n nodeEl.classList.add('drag');\n}\n\n// use to validate drag\n// function validate(x, a, b) {\n// if (x =< a) return a;\n// return b;\n// }\n\nfunction dragged(d, idx) {\n d.fx = d3.event.x;\n d.fy = d3.event.y;\n}\n\nfunction dragended(d, idx, nodes) {\n if (!d3.event.active) simulation.alphaTarget(0);\n let nodeEl = nodes[idx];\n d.fx = null;\n d.fy = null;\n nodeEl.classList.remove('drag');\n}\n\nfunction moveViewboxPx(dx, dy){\n let viewBox = svg.attr(\"viewBox\").split(\" \").map(parseFloat);\n viewBox[0] -= dx * 1;\n viewBox[1] -= dy * 1;\n svg.attr(\"viewBox\", viewBox.join(\" \"));\n}\n\n// start by selecting the first node :-)\n// selectNode(currentNodeIdx+1);\n// positionNodesInCenter(currentNodeIdx);\n\nif(location.pathname.startsWith('/@type/')) {\n for(let t in types) {\n if(getDisplayAttr(t) == location.pathname.substr(7)) {\n centerByType(t, false);\n }\n }\n} else{\n let startNodeId = location.search.startsWith(\"?id=\") ? location.search.substr(4) : 'https://rubenvandeven.com'+location.pathname;\n let firstNode = graph['nodes'].find(n => n['@id'] === startNodeId);\n selectNode(graph['nodes'].indexOf(firstNode), false);\n}\n\n\n\n// closeDetails(); // hide details at first\n// positionNodesInCenter(currentNodeIdx+1);\n\n// setTimeout(function(){\n // document.body.classList.add('graphInitialised');\n// }, 10);\n\nlet initPlaceholder = document.getElementById('initPlaceholder');\nsvg.node().removeChild(initPlaceholder);\nsetTimeout(function(){\n graphInitialised = true;\n document.body.classList.add('graphInitialised');\n }, 500);\n}\n\n\n// Detect request animation frame\nvar reqAnimFrame = window.requestAnimationFrame ||\n window.webkitRequestAnimationFrame ||\n window.mozRequestAnimationFrame ||\n window.msRequestAnimationFrame ||\n window.oRequestAnimationFrame ||\n // IE Fallback, you can even fallback to onscroll\n function(callback){ window.setTimeout(callback, 1000/60) };\n// all credits go to https://stackoverflow.com/a/26798337\nfunction scrollToY(scrollTargetY, speed, easing, finishFunction) {\n // scrollTargetY: the target scrollY property of the window\n // speed: time in pixels per second\n // easing: easing equation to use\n\n var scrollY = window.scrollY,\n scrollTargetY = scrollTargetY || 0,\n speed = speed || 2000,\n easing = easing || 'easeOutSine',\n currentTime = 0,\n finishFunction = finishFunction || false;\n\n // min time .1, max time .8 seconds\n let time = Math.max(.1, Math.min(Math.abs(scrollY - scrollTargetY) / speed, .8));\n\n // easing equations from https://github.com/danro/easing-js/blob/master/easing.js\n let PI_D2 = Math.PI / 2,\n easingEquations = {\n easeOutSine: function (pos) {\n return Math.sin(pos * (Math.PI / 2));\n },\n easeInOutSine: function (pos) {\n return (-0.5 * (Math.cos(Math.PI * pos) - 1));\n },\n easeInOutQuint: function (pos) {\n if ((pos /= 0.5) < 1) {\n return 0.5 * Math.pow(pos, 5);\n }\n return 0.5 * (Math.pow((pos - 2), 5) + 2);\n }\n };\n\n // add animation loop\n function tick() {\n currentTime += 1 / 60;\n\n var p = currentTime / time;\n var t = easingEquations[easing](p);\n\n if (p < 1) {\n reqAnimFrame(tick);\n\n window.scrollTo(0, scrollY + ((scrollTargetY - scrollY) * t));\n } else {\n window.scrollTo(0, scrollTargetY);\n if(finishFunction) {\n finishFunction();\n }\n }\n }\n\n // call it once to get started\n tick();\n}\n"]} \ No newline at end of file diff --git a/assets/js/rubenvandeven.jsonld b/assets/js/rubenvandeven.jsonld new file mode 100644 index 0000000..50abf7d --- /dev/null +++ b/assets/js/rubenvandeven.jsonld @@ -0,0 +1 @@ +{"@graph":[{"@id":"_:b0","@type":"https://schema.org/ExhibitionEvent","https://schema.org/location":{"@id":"https://rubenvandeven.com/organisation/v2_"},"https://schema.org/name":"Evening of the Black Box Concerns","https://schema.org/startDate":"2017-12-7","https://schema.org/url":"http://v2.nl/events/evening-of-the-black-box-concerns","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/spectacular-spectator-mood-meter"}},{"@id":"_:b1","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/moodmeter1-1.jpg","https://schema.org/thumbnailUrl":"assets/thumb/moodmeter1-1.jpg"},{"@id":"_:b10","@type":"https://schema.org/Museum","https://schema.org/address":"Karlsrue","https://schema.org/name":"ZKM"},{"@id":"_:b11","@type":"https://schema.org/ExhibitionEvent","https://schema.org/awarded":"honorable mention","https://schema.org/endDate":"2018-10","https://schema.org/location":"Zagreb","https://schema.org/name":"Plan D","https://schema.org/startDate":"2017-10","https://schema.org/url":"http://2017.pland.hr/vijesti/dodijeljena-nagrada-i-pocasne-diplome-festivala-plan-d/","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/emotionhero"}},{"@id":"_:b12","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2017-09","https://schema.org/funded":"Creative Industries Fund","https://schema.org/location":{"@id":"_:b13"},"https://schema.org/name":"Ars Electronica","https://schema.org/organiser":{"@id":"https://rubenvandeven.com/organisation/v2_"},"https://schema.org/startDate":"2017-09","https://schema.org/url":"http://v2.nl/events/summer-sessions-at-ars-electronica-festival-2017","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/emotionhero"}},{"@id":"_:b13","@type":"https://schema.org/Festival","https://schema.org/address":"Linz","https://schema.org/name":"Ars Electronica"},{"@id":"_:b14","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2017-06","https://schema.org/location":{"@id":"_:b15"},"https://schema.org/name":"Microbites of Creativity","https://schema.org/organiser":"ACM Creativity & Cognition","https://schema.org/startDate":"2017-06","https://schema.org/url":"http://microbites.me/","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/emotionhero"}},{"@id":"_:b15","@type":"https://schema.org/Museum","https://schema.org/address":"Singapore","https://schema.org/name":"Museum of Arts & Sciences"},{"@id":"_:b16","@type":"https://schema.org/CollegeOrUniversity","https://schema.org/address":"New York City","https://schema.org/name":"Hunter College"},{"@id":"_:b17","@type":"https://schema.org/ExhibitionEvent","https://schema.org/description":"I organised this exhibition as part of my Summer Sessions residency at Arquivo237. It was a modest exhibition covering my research on emotion recognition software.","https://schema.org/endDate":"2016-09","https://schema.org/location":{"@id":"_:b18"},"https://schema.org/name":"Manipulation: Emotion Hero","https://schema.org/startDate":"2016-09","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/emotionhero"}},{"@id":"_:b18","@type":"https://schema.org/EventVenue","https://schema.org/address":"Lisbon","https://schema.org/name":"Arquivo237"},{"@id":"_:b19","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/emotionhero1-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/emotionhero1-2.jpg"},{"@id":"_:b2","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/moodmeter2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/moodmeter2.jpg"},{"@id":"_:b20","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/emotionhero2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/emotionhero2.jpg"},{"@id":"_:b21","@type":"https://schema.org/MediaObject","https://schema.org/artworkSurface":"3 projections","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2016","https://schema.org/description":"What does it mean to feel 47% happy and 21% surprised? Choose how you feel; you have seven options is a video work that revolves around this question as it looks at software that derives emotional parameters from facial expressions. It combines human accounts and algorithmic processing to examine the intersection of highly cognitive procedures and ambiguous experiences. Born from a fascination with the technological achievements, the work interrogates the discursive apparatus the software is embedded in.\n\nThis work builds on my research into the workings of expression analysis technologies and the assumptions that underlie it, scrutinising the claims that are made by the companies developing the software.","https://schema.org/duration":"9M9S (∞ loop)","https://schema.org/image":[{"@id":"_:b27"},{"@id":"_:b28"}],"https://schema.org/name":"Choose How You Feel; You Have Seven Options"},{"@id":"_:b22","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2018-03-04","https://schema.org/location":{"@id":"_:b23"},"https://schema.org/name":"Big Stories Need Human Stakes","https://schema.org/startDate":"2018-02-02","https://schema.org/workFeatured":{"@id":"_:b21"}},{"@id":"_:b23","@type":"https://schema.org/Museum","https://schema.org/address":"Haarlem","https://schema.org/name":"Nieuwe Vide"},{"@id":"_:b24","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2016-07-07 23:00","https://schema.org/location":{"@id":"https://rubenvandeven.com/organisation/v2_"},"https://schema.org/name":"Test_Lab the Graduation Edition","https://schema.org/startDate":"2016-07-07 20:00","https://schema.org/url":"http://v2.nl/events/test_lab-the-graduation-edition-2015-1/","https://schema.org/workFeatured":{"@id":"_:b21"}},{"@id":"_:b25","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2016-06-26","https://schema.org/location":{"@id":"_:b26"},"https://schema.org/name":"Fuzzy Logic - Graduation Show","https://schema.org/startDate":"2016-06-17","https://schema.org/url":"https://pzimediadesign.nl/2016.html","https://schema.org/workFeatured":{"@id":"_:b21"}},{"@id":"_:b26","@type":"https://schema.org/CollegeOrUniversity","https://schema.org/address":"Rotterdam","https://schema.org/name":"Piet Zwart Institute"},{"@id":"_:b27","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/choosehowyoufeel2-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/choosehowyoufeel2-2.jpg"},{"@id":"_:b28","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/choosehowyoufeel.jpg","https://schema.org/thumbnailUrl":"assets/thumb/choosehowyoufeel.jpg"},{"@id":"_:b29","@type":"https://schema.org/MediaObject","https://schema.org/author":[{"@id":"https://rubenvandeven.com/person/cristina-cochior"},{"@id":"https://rubenvandeven.com/"}],"https://schema.org/dateCreated":"2016","https://schema.org/description":"Whether the video frames are ordered by time or by emotion will not make a difference to a computer. For it, both orderings are just as logical. However, for the human spectator the reordered display of frames becomes a disruptive process. The human is positioned as a required agent for meaning making in an algorithmic procedure.\n\nIn collaboration with Cristina Cochior I went manually through the Eye's public collection, and catalogued faces by surrendering them to an emotion detection algorithm. Cutting from one face to another,its uncritical selection produced a new portrait of emotional gradients moving in-between anger and happiness.","https://schema.org/duration":"11:32 (∞ loop)","https://schema.org/image":[{"@id":"_:b36"},{"@id":"_:b37"},{"@id":"_:b38"}],"https://schema.org/name":"EYE Without A Face"},{"@id":"_:b3","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/moodmeter3.jpg","https://schema.org/thumbnailUrl":"assets/thumb/moodmeter3.jpg"},{"@id":"_:b30","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2017-04-26","https://schema.org/location":{"@id":"_:b31"},"https://schema.org/name":"Video Vortex","https://schema.org/startDate":"2017-04-23","https://schema.org/workFeatured":{"@id":"_:b29"}},{"@id":"_:b31","@type":"https://schema.org/Place","https://schema.org/address":"Kochi (India)","https://schema.org/name":" Mill Hall / Kochi-Muzeris biennial"},{"@id":"_:b32","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2016-12","https://schema.org/location":{"@id":"_:b33"},"https://schema.org/name":"Grand Instant Fiction","https://schema.org/startDate":"2016-12","https://schema.org/workFeatured":{"@id":"_:b29"}},{"@id":"_:b33","@type":"https://schema.org/EventVenue","https://schema.org/address":"Brno","https://schema.org/name":"Umakart Gallery"},{"@id":"_:b34","@type":"https://schema.org/ExhibitionEvent","https://schema.org/description":"The Piet Zwart Institute at the Eye Film Museum in Amsterdam as part of its ResearchLab series. The exhibition focussed on the boundaries of the archive. Studying the structures and cultural impacts of our media technologies, it concentrated on the intricate and usually hidden aspects of EYE's extensive archive","https://schema.org/endDate":"2014-04-24","https://schema.org/location":{"@id":"_:b35"},"https://schema.org/name":"Boundaries of the Archive","https://schema.org/startDate":"2016-04-12","https://schema.org/workFeatured":{"@id":"_:b29"}},{"@id":"_:b35","@type":"https://schema.org/Museum","https://schema.org/address":"Amsterdam","https://schema.org/name":"EYE Film Museum"},{"@id":"_:b36","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/eye_0-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/eye_0-2.jpg"},{"@id":"_:b37","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/eye_3-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/eye_3-2.jpg"},{"@id":"_:b38","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/eye_6-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/eye_6-2.jpg"},{"@id":"_:b39","@type":"https://schema.org/ScholarlyArticle","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/datePublished":"2017-01-27","https://schema.org/description":"What does it mean to feel 62% joy, and 15% surprised? Over the past years the digitization of emotions is booming business: multimillion dollar investments are made in technologies of which is claimed that they give companies an objective view in their consumers' feelings. The video-game-artwork Emotion Hero challenges the user to investigate this claim and question the premise of the technology. Emotion Hero is a two-part artwork. On the one hand is a video-game that is freely downloadable for everybody with an Android device. Inspired by Guitar Hero, the user scores points by following given cues. It provides detailed feedback on the mechanics of the face. The second part is a projection that shows a grid with aggregated scores of the game, that updates live. In its design, the grid refers to 19th century positivist practices.","https://schema.org/isPartOf":"C&C '17 Proceedings of the 2017 ACM SIGCHI Conference on Creativity and Cognition","https://schema.org/name":"Article - Emotion Hero","https://schema.org/pageEnd":"423","https://schema.org/pageStart":"422","https://schema.org/publisher":"ACM New York, NY, USA","https://schema.org/url":"https://doi.org/10.1145/3059454.3059490/"},{"@id":"_:b4","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/moodmeter4.jpg","https://schema.org/thumbnailUrl":"assets/thumb/moodmeter4.jpg"},{"@id":"_:b40","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2019-06-08","https://schema.org/location":"Doornroosje, Rotterdam","https://schema.org/name":"Presentation @ Act Natural 04","https://schema.org/organizer":"Jeisson Drenth","https://schema.org/startDate":"2019-06-08"},{"@id":"_:b41","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2018-10-26","https://schema.org/location":"Doornroosje, Rotterdam","https://schema.org/name":"Presentation @ BARTALK #12: Coded Gestures","https://schema.org/organizer":["Yun Ingrid Lee","Rae Parnell"],"https://schema.org/startDate":"2018-10-26","https://schema.org/url":"https://bartalkdh.wordpress.com/bartalk-12-coded-gestures/"},{"@id":"_:b42","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2018-05-25","https://schema.org/location":"Rio de Janeiro","https://schema.org/name":"Presentation @ Media Lab UFRJ","https://schema.org/organizer":"Media Lab UFRJ & Dutch Consulate","https://schema.org/startDate":"2018-05-25"},{"@id":"_:b43","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2018-05-22","https://schema.org/location":"São Paulo","https://schema.org/name":"Presentation @ Instituto Europeo di Design","https://schema.org/organizer":"Instituto Europeo di Design & Dutch Consulate","https://schema.org/startDate":"2018-05-22","https://schema.org/url":"https://www.ied.edu/ied-locations/sao-paulo"},{"@id":"_:b44","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2018-05-21","https://schema.org/location":"São Paulo","https://schema.org/name":"Presentation @ Nuffic Neso","https://schema.org/organizer":"Nuffic Neso & Dutch Consulate","https://schema.org/startDate":"2018-05-21","https://schema.org/url":"https://www.nuffic.nl/onderwerpen/kantoren-het-buitenland/"},{"@id":"_:b45","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2018-05-19","https://schema.org/location":"São Paulo","https://schema.org/name":"Presentation @ Festival PATH","https://schema.org/organizer":"Dutch Consulate","https://schema.org/startDate":"2018-05-19","https://schema.org/url":"https://www.festivalpath.com.br/palestras/#palestrante-ruben-van-de-ven"},{"@id":"_:b46","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2018-03-28","https://schema.org/location":"Baut, Amsterdam","https://schema.org/name":"Presentation @ The Hmm","https://schema.org/organizer":"The Hmm","https://schema.org/startDate":"2018-03-28","https://schema.org/url":"https://thehmm.nl/the-hmm-x-baut/"},{"@id":"_:b47","@type":"https://schema.org/EducationEvent","https://schema.org/about":[{"@id":"http://plottingd.at/a"},{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"}],"https://schema.org/endDate":"2018-03-06","https://schema.org/location":"Utrecht","https://schema.org/name":"Workshop: Urban Frictions","https://schema.org/organizer":"Urban Interfaces, Utrecht University","https://schema.org/startDate":"2018-03-06","https://schema.org/url":"https://urbaninterfaces.sites.uu.nl"},{"@id":"_:b48","@type":"https://schema.org/EducationEvent","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2018-03-06","https://schema.org/location":"TU Delft","https://schema.org/name":"Presentation @ Industrial Design","https://schema.org/organizer":"Felienne Hermans, TU Delft","https://schema.org/startDate":"2018-03-06"},{"@id":"_:b49","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2017-11-04","https://schema.org/location":"CREA, Amsterdam","https://schema.org/name":"Presentation @ Worlding the Brain II","https://schema.org/organizer":"ASCA Research Group Neuroaesthetics and Neurocultures, University of Amsterdam","https://schema.org/startDate":"2017-11-02","https://schema.org/url":"https://worldingthebrain2017.com"},{"@id":"_:b5","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2018-11-15","https://schema.org/location":{"@id":"_:b6"},"https://schema.org/name":"ECP Conference","https://schema.org/startDate":"2018-11-15","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/emotionhero"}},{"@id":"_:b50","@type":"https://schema.org/EducationEvent","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2017-03-14","https://schema.org/location":"Breda","https://schema.org/name":"Workshop: Artistic Point of Interference","https://schema.org/organizer":"St. Joost Academy","https://schema.org/startDate":"2017-03-13","https://schema.org/url":"https://caradt.com/2017/03/07/ekv-13-march-lecture-by-human-index/"},{"@id":"_:b51","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2016-06-11","https://schema.org/location":"FoAM, Brussels","https://schema.org/name":"Presentation @ Cqrrelations publication launch","https://schema.org/organizer":"Constant VZW.","https://schema.org/startDate":"2016-06-11","https://schema.org/url":"http://cqrrelations.constantvzw.org/0x0/"},{"@id":"_:b52","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2018-11-04","https://schema.org/name":"Panel: Tracking Emotions","https://schema.org/recordedIn":{"@id":"https://www.youtube.com/watch?v=L2O6ecO-8PM&feature=youtu.be&t=2062"},"https://schema.org/startDate":"2018-11-04","https://schema.org/url":"https://state-studio.com/participant/2016/ruben-van-de-ven"},{"@id":"_:b53","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2016-11-19","https://schema.org/location":"Frankfurt am Main","https://schema.org/name":"Presentation @ Digitalorders","https://schema.org/organizer":"Goethe University","https://schema.org/startDate":"2016-11-17","https://schema.org/url":"https://www.normativeorders.net/en/events/young-researchers-conferences/34-veranstaltungen/nachwuchskonferenz/5114-seventh-international-young-researchers-conference"},{"@id":"_:b54","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"016-02","https://schema.org/location":"University of Namur","https://schema.org/name":"Presentation @ Winter school: What happens to the data in 'Big Data' ?","https://schema.org/organizer":"Workgroup: algorithmic governmentality","https://schema.org/startDate":"2016-02","https://schema.org/url":"www.bsts.be/wp-content/uploads/winter_school_2016-1.pdf"},{"@id":"_:b55","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2018-05-28","https://schema.org/location":"Linz","https://schema.org/name":"Presentation @ Art Meets Radical Openness","https://schema.org/startDate":"2018-05-25","https://schema.org/url":"https://www.radical-openness.org/en/festival/2016"},{"@id":"_:b56","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options"},"https://schema.org/endDate":"2016","https://schema.org/location":"University of Amsterdam","https://schema.org/name":"Presentation @ Digital Emotions Workgroup","https://schema.org/organizer":"Amsterdam School for Cultral Analysis","https://schema.org/startDate":"2016","https://schema.org/url":"http://asca.uva.nl/content/research-groups/digital-emotions/digital-emotions.html"},{"@id":"_:b57","@type":"https://schema.org/Organization","https://schema.org/name":"Institute of Network Cultures","https://schema.org/url":"http://networkcultures.org"},{"@id":"_:b58","@type":"https://schema.org/MediaObject","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2015","https://schema.org/description":"A two-part artwork and my first work on software that derives emotional parameters from facial expressions. The first part displays the Mind Reading Emotions Library, an interactive collection of videos, audio fragments and scenes depicting 412 distinct emotions —ranging from `angry' to `unsure'— grouped in 24 categories. The second part of the project is a tablet with a modified demo app by Affectiva, a major player in the field of emotion analysis software. This app acts as an interactive mirror which displays the various parameters that the Affectiva software derives from someone's facial expression, while a voice over reads a text extracted from the Dutch classic Beyond Sleep by W.F. Hermans (1966), concerning the impact of the mirror, photography and video on the human self-image.","https://schema.org/image":[{"@id":"_:b60"},{"@id":"_:b61"}],"https://schema.org/name":"We know how you feel"},{"@id":"_:b59","@type":"https://schema.org/ExhibitionEvent","https://schema.org/description":"What is an encyclopedia? What are media? What is an object? Students of the Piet Zwart Institute Media Design Masters programme invite you to look at these issues. Twelve installations and one choreography present a taxonomy of disappearing, human and non-human, introvert and collected media objects.\n\nThis exhibition was developed in a 'Thematic Seminar' run in the summer trimester 2015 taught by Florian Cramer.\n\nArtists: Lucas Battich [IT], Manetta Berends [NL], Julie Boschat Thorez [FR], Cihad Caner [TR], Joana Chicau [PT], Cristina Cochior [RO], Solange Frankort [NL], Arantxa Gonlag [NL], Anne Lamb [US], Benjamin Li [NL], Yuzhen Tang [CN], Ruben van de Ven [NL] & Thomas Walskaar [NO]","https://schema.org/endDate":"2015-06-27","https://schema.org/location":{"@id":"https://rubenvandeven.com/organisation/v2_"},"https://schema.org/name":"An Encyclopedia of Media Objects","https://schema.org/startDate":"2015-06-26","https://schema.org/url":"http://v2.nl/events/an-encyclopedia-of-media-objects","https://schema.org/workFeatured":{"@id":"_:b58"}},{"@id":"_:b6","@type":"https://schema.org/EventVenue","https://schema.org/name":"Fokker Terminal"},{"@id":"_:b60","@type":"https://schema.org/ImageObject","https://schema.org/caption":"We know how you feel","https://schema.org/contentUrl":"assets/image/we_know_how_you_feel.jpg","https://schema.org/thumbnailUrl":"assets/thumb/we_know_how_you_feel.jpg"},{"@id":"_:b61","@type":"https://schema.org/ImageObject","https://schema.org/caption":"Mind Reading Emotions Library","https://schema.org/contentUrl":"assets/image/we_know_how_you_feel-3.jpg","https://schema.org/thumbnailUrl":"assets/thumb/we_know_how_you_feel-3.jpg"},{"@id":"_:b62","@type":"https://schema.org/MediaObject","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2015-01-01","https://schema.org/description":"In western society, images are generally perceived as objective traces of events. As evidence that things happened as how they are captured in the frame.\n\nAs Susan Sontag elaborates, this counts for photographic images in particular. They are seen, not as subjective statements about the world, but rather as pieces of it —as fragments of a reality. Photographic images are often seen as unbiased; they carry in them the \"burden of truth\". We use them to give meaning, to asses and to judge. It exactly is in this passivity of the image, that its aggression lurks.\nFragments of Reality is a newspaper that assembles descriptions of five images of one event: a press conference with Jeroen Dijsselbloem and Yanis Varoufakis. Each interviewee was shown one of the images, in an attempt to highlight how we read events trough photographs.\n\nIt considers an event not as a single factual moment, but as the cumulative of subjective experiences, inherently proposing that an image is always lacking in its representation of that which it presents.","https://schema.org/image":[{"@id":"_:b63"},{"@id":"_:b64"},{"@id":"_:b65"}],"https://schema.org/name":"Fragments of reality"},{"@id":"_:b63","@type":"https://schema.org/ImageObject","https://schema.org/caption":"Fragments of reality","https://schema.org/contentUrl":"assets/image/fragments-6-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/fragments-6-2.jpg"},{"@id":"_:b64","@type":"https://schema.org/ImageObject","https://schema.org/caption":"Fragments of reality","https://schema.org/contentUrl":"assets/image/fragments-9-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/fragments-9-2.jpg"},{"@id":"_:b65","@type":"https://schema.org/ImageObject","https://schema.org/caption":"Fragments of reality","https://schema.org/contentUrl":"assets/image/fragments-7-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/fragments-7-2.jpg"},{"@id":"_:b66","@type":"https://schema.org/Movie","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2013","https://schema.org/description":"Adam is security guard at a nursing home with comatose patients. During his night shifts he has unsettling visions of someone drowning. He seeks his comfort in the company of his pregnant girlfriend. Days of Water is a story on guilt and shame; and on a longing for hope.\nI wrote and directed this film for my graduation as film director for fiction at the Utrecht School of the Arts.\nIt was accompanied by my MA research on spectatorial emotion and the influence emotions on the spectator's sympathy for the protagonist.","https://schema.org/image":[{"@id":"_:b69"},{"@id":"_:b70"}],"https://schema.org/name":"Waterdagen"},{"@id":"_:b67","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2014-05","https://schema.org/location":{"@id":"_:b68"},"https://schema.org/name":"High Desert International Film Festival ","https://schema.org/startDate":"2014-05","https://schema.org/url":"http://routedunord.nl/portfolio-item/ruben-van-de-ven-2/","https://schema.org/workFeatured":{"@id":"_:b66"}},{"@id":"_:b68","@type":"https://schema.org/Festival","https://schema.org/address":"Pahrump, Nevada","https://schema.org/name":"High Desert International Film Festival "},{"@id":"_:b69","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/waterdagen-teaser.jpg","https://schema.org/thumbnailUrl":"assets/thumb/waterdagen-teaser.jpg"},{"@id":"_:b7","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2019-05","https://schema.org/image":{"@id":"_:b8"},"https://schema.org/location":"Pingshan Cultural Center Exhibition Gallery, Shenzen","https://schema.org/name":"Free Panorama - Shenzen MAF","https://schema.org/startDate":"2019-04-20","https://schema.org/url":"http://shenzhenmaf.cn/Portal/en-US/Exhibition/Detail/10f64524-be11-1985-bbfa-7dd7d27868de","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/emotionhero"}},{"@id":"_:b70","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/waterdagen1.jpg","https://schema.org/thumbnailUrl":"assets/thumb/waterdagen1.jpg"},{"@id":"_:b71","@type":"https://schema.org/MediaObject","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2012","https://schema.org/description":"For the visitors of the Crossing Border festival in The Hague we desgined an installation to collaboratively contribute to a poem.\nBased on the input of the user, a sentence is added to the poem, originating from digitalised texts of artists participating in the festival.\nThe text was combined with an audiovisual presentation that changed depending on the user's input.","https://schema.org/image":{"@id":"_:b74"},"https://schema.org/name":"Co-Poet","https://schema.org/url":"http://projectkaleido.nl/#copoet"},{"@id":"_:b72","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2012-11-17","https://schema.org/location":{"@id":"_:b73"},"https://schema.org/name":"Crossing Border","https://schema.org/startDate":"2012-11-14","https://schema.org/workFeatured":{"@id":"_:b71"}},{"@id":"_:b73","@type":"https://schema.org/PerformingArtsTheater","https://schema.org/address":"Leiden","https://schema.org/name":"Leidse Schouwburg"},{"@id":"_:b74","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/copoet1.jpg","https://schema.org/thumbnailUrl":"assets/thumb/copoet1.jpg"},{"@id":"_:b75","@type":"https://schema.org/MediaObject","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2012","https://schema.org/description":"The Colour of Blue. A diptych about two Tanzanians: a miner and a lobster fisher. How do they live, work and survive; and how do they see their own future? Each of the two men is in his own way depending on nature, everyday gambling for a good catch.\nThis documentary was shot complementary to our work at the Kilimanjaro Film Institute in Arusha.\nDocumentary, 14′42″. The full film is available on Youtube.","https://schema.org/image":[{"@id":"_:b76"},{"@id":"_:b77"}],"https://schema.org/name":"Rangi Ya Samawati","https://schema.org/video":{"@id":"https://rubenvandeven.com/assets/video/samawati.mp4#videoobject"}},{"@id":"_:b76","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/samawati-prev2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/samawati-prev2.jpg"},{"@id":"_:b77","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/samawati-prev1.jpg","https://schema.org/thumbnailUrl":"assets/thumb/samawati-prev1.jpg"},{"@id":"_:b78","@type":"https://schema.org/MediaObject","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2010","https://schema.org/description":"A short HKU project on the future of social media, when it was still an upcomming phenomenon. It is made in two weeks for a series of shorts with a given start and ending image.\nWe asked ourselves: what happens if our online identity lives on after we die? Do we still live on if a part of our online identity does? How can this virtual identity be shaped?\n2′10″","https://schema.org/image":{"@id":"_:b79"},"https://schema.org/name":"Virtual Afterlife","https://schema.org/video":{"@id":"https://rubenvandeven.com/assets/video/guerilla_project.mp4#videoobject"}},{"@id":"_:b79","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/guerilla2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/guerilla2.jpg"},{"@id":"_:b8","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/eh-shenzen.jpg","https://schema.org/thumbnailUrl":"assets/thumb/eh-shenzen.jpg"},{"@id":"_:b80","@type":"https://schema.org/MediaObject","https://schema.org/contributor":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2019","https://schema.org/description":"The voice is surely an uncanny phenomenon. In the digital age it leaves us constantly in doubt, if a person or a non-person is speaking. In this immersive performance, doubt and conviction change roles in the blink of an eye. Spread out on a hilly landscape, audience engages in a conversation with something that is there and not there at the same time. An artificial voice functions as a mediator, a partner and a mirror to one's own.\n\nWhile conversations are constructed word-by-word, impressions are shared, naps are taken, and time is passed, Pillow Talk is an invitation to suspend eye-to-eye relations and to reposition yourself in direct relation to the non-human.\n\nI developed the interface for the participants, as well as a custom story flow editor for the director/writer Begüm Erciyas.","https://schema.org/image":[{"@id":"_:b87"},{"@id":"_:b88"},{"@id":"_:b89"}],"https://schema.org/name":"Pillow Talk","https://schema.org/url":"https://www.begumerciyas.com/work/pillow-talk-2019/"},{"@id":"_:b81","@type":"https://schema.org/Person","https://schema.org/author":{"@id":"_:b80"},"https://schema.org/name":"Begüm Erciyas","https://schema.org/url":"https://www.begumerciyas.com"},{"@id":"_:b82","@type":"https://schema.org/TheaterEvent","https://schema.org/endDate":"2019-05-20","https://schema.org/location":"KVS, Brussels","https://schema.org/name":"Kunstenfestivaldesarts","https://schema.org/startDate":"2019-05-15","https://schema.org/url":"https://www.kfda.be/en/program/pillow-talk","https://schema.org/workFeatured":{"@id":"_:b80"}},{"@id":"_:b83","@type":"https://schema.org/TheaterEvent","https://schema.org/endDate":"2019-06-16","https://schema.org/location":"Münchner Kammerspiele","https://schema.org/name":"Festival Politics of Algorithms","https://schema.org/startDate":"2019-06-14","https://schema.org/workFeatured":{"@id":"_:b80"}},{"@id":"_:b84","@type":"https://schema.org/TheaterEvent","https://schema.org/endDate":"2019-08-31","https://schema.org/location":"Radialsystem, Berlin","https://schema.org/name":"New Empathies Program Series","https://schema.org/startDate":"2019-08-29","https://schema.org/workFeatured":{"@id":"_:b80"}},{"@id":"_:b85","@type":"https://schema.org/TheaterEvent","https://schema.org/endDate":"2019-11-16","https://schema.org/location":"Theatre Nanterre-Amandiers, Paris","https://schema.org/name":"New Settings/Fondation d'Enterprise Hermés","https://schema.org/startDate":"2019-11-13","https://schema.org/workFeatured":{"@id":"_:b80"}},{"@id":"_:b86","@type":"https://schema.org/TheaterEvent","https://schema.org/endDate":"2019-11-31","https://schema.org/location":"Kortrijk","https://schema.org/name":"NEXT Festival","https://schema.org/startDate":"2019-11-29","https://schema.org/workFeatured":{"@id":"_:b80"}},{"@id":"_:b87","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/pillow_talk01.jpg","https://schema.org/thumbnailUrl":"assets/thumb/pillow_talk01.jpg"},{"@id":"_:b88","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/pillow_talk02.jpg","https://schema.org/thumbnailUrl":"assets/thumb/pillow_talk02.jpg"},{"@id":"_:b89","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/pillow_talk03.jpg","https://schema.org/thumbnailUrl":"assets/thumb/pillow_talk03.jpg"},{"@id":"_:b9","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2018-08-05","https://schema.org/location":{"@id":"_:b10"},"https://schema.org/name":"Open Codes","https://schema.org/startDate":"2017-10-20","https://schema.org/url":"https://open-codes.zkm.de/","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/emotionhero"}},{"@id":"_:b90","@type":"https://schema.org/MediaObject","https://schema.org/author":{"@id":"https://rubenvandeven.com/person/ward-goes"},"https://schema.org/contributor":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2013-12-04","https://schema.org/description":"Originally initiated as a graduation project (cum laude) by Ward Goes, The Spectacular Times is an ongoing visual inquiry into the presentation of news. It investigates how news, rather than being subjective, is representational of its social, cultural, (geo)political and ideological contexts. The project sets out to lay bare these representations in currently circulating news.\n\nBy layering and contrasting different news elements The Spectacular Times re-contextualises news and makes explicit the intangible notions that lie beyond an increasingly universal guise of news reporting. Not in an effort to tell apart true from false, left from right, or right from wrong, but in order to accentuate a variety of articulations of news.\n\nThis web based project uses user defined variables to animate news headers, texts and images, which are directly sourced from different news websites world wide. By adjusting these parameters (among which region, scope and speed) the spectator actively perceives how news content is de- and reformed through its aesthetics.","https://schema.org/image":[{"@id":"_:b91"},{"@id":"_:b92"}],"https://schema.org/name":"The Specta­cular Times","https://schema.org/url":"http://spectacularspectacular.news"},{"@id":"_:b91","@type":"https://schema.org/ImageObject","https://schema.org/caption":"The Spectacular Times","https://schema.org/contentUrl":"assets/image/times1.jpg","https://schema.org/thumbnailUrl":"assets/thumb/times1.jpg"},{"@id":"_:b92","@type":"https://schema.org/ImageObject","https://schema.org/caption":"The Spectacular Times","https://schema.org/contentUrl":"assets/image/times2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/times2.jpg"},{"@id":"http://summersessions.net/17-projects/projects-2016/55-emotion-hero#id","https://schema.org/subEvent":{"@id":"_:b17"}},{"@id":"https://rubenvandeven.com#site","@type":"https://schema.org/WebSite","http://www.w3.org/2000/01/rdf-schema#seeAlso":["https://rubenvandeven.com/rubenvandeven.jsonld","https://rubenvandeven.com/foaf.rdf#me"],"https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/url":"https://rubenvandeven.com"},{"@id":"https://rubenvandeven.com/","@type":"https://schema.org/Person","https://schema.org/email":"info@rubenvandeven.com","https://schema.org/jobTitle":"digital artist / researcher of software culture","https://schema.org/name":"Ruben van de Ven","https://schema.org/nationality":"The Netherlands"},{"@id":"https://rubenvandeven.com/2018/data-flaneur","@type":"https://schema.org/Event","https://schema.org/alternateName":"Data Flâneur - seeing and being seen in the data driven city","https://schema.org/description":"Inspired by Alison Powell’s ‘data walk’, the Flaneur twists her concept by not only making participants aware of data collection within the city, but also by making them experiment with data collection for their own purposes. By zooming in on the procedures of selection, classification and digitisation, participants experience the intricacies of data collection.","https://schema.org/endDate":"2018-11-04","https://schema.org/name":"Data Flâneur","https://schema.org/organizer":{"@id":"https://rubenvandeven.com/plottingdata"},"https://schema.org/startDate":"2018-11-04","https://schema.org/superEvent":{"@id":"https://rubenvandeven.com/exhibition/hello-world"},"https://schema.org/url":"https://creativecodingutrecht.nl/2018/10/20/the-data-flaneur-seen-and-being-seen-in-the-data-driven-city-tour/"},{"@id":"https://rubenvandeven.com/2018/digital-cultures","@type":"https://schema.org/Event","https://schema.org/about":{"@id":"https://rubenvandeven.com/plottingdata"},"https://schema.org/endDate":"2018-09-22","https://schema.org/location":"Leuphana University, Lüneburg","https://schema.org/name":"Digital Cultures: Knowledge / Culture / Technology","https://schema.org/organizer":"Centre for Digital Cultures & Institute for Culture and Society","https://schema.org/startDate":"2018-09-19","https://schema.org/url":"https://digitalculturesconference.org/"},{"@id":"https://rubenvandeven.com/article/choose-how-you-feel-you-have-seven-options","@type":"https://schema.org/Report","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/datePublished":"2017-01-27","https://schema.org/description":"What does it mean to feel 82% surprised or 93% joy? As part of their Longform series, the Institute of Network Cultures published my research into software that derives emotional scores from facial expressions.","https://schema.org/name":"Longform - Choose How You Feel; You Have Seven Options","https://schema.org/publisher":{"@id":"_:b57"},"https://schema.org/url":"http://networkcultures.org/longform/2017/01/25/choose-how-you-feel-you-have-seven-options/"},{"@id":"https://rubenvandeven.com/assets/video/guerilla_project.mp4#videoobject","@type":"https://schema.org/VideoObject","https://schema.org/contentUrl":"assets/video/guerilla_project.mp4","https://schema.org/encodingFormat":"video/mp4","https://schema.org/thumbnailUrl":"assets/thumb/guerilla_project.mp4.jpg","https://schema.org/videoFrameSize":"1280x1024"},{"@id":"https://rubenvandeven.com/assets/video/samawati.mp4#videoobject","@type":"https://schema.org/VideoObject","https://schema.org/contentUrl":"assets/video/samawati.mp4","https://schema.org/encodingFormat":"video/mp4","https://schema.org/thumbnailUrl":"assets/thumb/samawati.mp4.jpg","https://schema.org/videoFrameSize":"1280x1024"},{"@id":"https://rubenvandeven.com/diede","@type":"https://schema.org/MediaObject","http://www.w3.org/2000/01/rdf-schema#seeAlso":"https://gitlab.com/rubenvandeven/diede","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2019","https://schema.org/description":"I taught a computer how to write the name of my son. The progression of this machine learning processes is materialised with a pen plotter.","https://schema.org/image":[{"@id":"https://rubenvandeven.com/diede/image/2"},{"@id":"https://rubenvandeven.com/diede/image/1"}],"https://schema.org/name":"Exercises in overfitting"},{"@id":"https://rubenvandeven.com/diede/image/1","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/diede-1.jpg","https://schema.org/thumbnailUrl":"assets/thumb/diede-1.jpg"},{"@id":"https://rubenvandeven.com/diede/image/2","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/diede-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/diede-2.jpg"},{"@id":"https://rubenvandeven.com/emotionhero","@type":"https://schema.org/MediaObject","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2016","https://schema.org/description":"Emotion recognition software is being used both as a tool for ‘objective’ measurements as well as a tool for training one’s facial expressions, eg. for job interviews. Emotion Hero is a literal translation of the paradoxical relation between these applications of the technology.\n\nEmotion Hero is a two-part artwork. On the one hand is a video-game that is freely downloadable for everybody with an Android device (see Google Play). Inspired by Guitar Hero, the user scores points by following given cues. It provides detailed feedback on the mechanics of the face (eg. “You showed on 10% Joy when you had to show 100%, smile 99.32% more.”), revealing that rather than being a window into the brain, the face is a controllable surface.\n\nThe second part is a projection that shows the aggregated scores of the game. In order to substantiate their discourse, companies in facial expression measurement employ a huge amount of data collection and processing. The results are displayed in a fixed grid, recalling historical practices that, trough extensive measurement and administration, also aimed to delineate something which is conceptually undelineated: think of Duchenne de Boulogne, Lombroso, and Charcot.\n\nEmotion Hero is a playful invitation to open up the box of expression analysis to reveal the assumptions that underlie this technology.\nThe game's emotional intelligence is powered by Affectiva (I was also interviewed by them).","https://schema.org/image":[{"@id":"_:b19"},{"@id":"_:b20"}],"https://schema.org/name":"Emotion Hero","https://schema.org/url":"https://emotionhero.com"},{"@id":"https://rubenvandeven.com/event/cqrrelations","@type":"https://schema.org/Event","https://schema.org/attendee":{"@id":"https://rubenvandeven.com/"},"https://schema.org/endDate":"2015-01-23","https://schema.org/name":"Cqrrelations Worksession","https://schema.org/organizer":{"@id":"https://rubenvandeven.com/organisation/constant"},"https://schema.org/startDate":"2015-01-18","https://schema.org/url":"http://cqrrelations.constantvzw.org/"},{"@id":"https://rubenvandeven.com/exhibition/codesandmodes","@type":"https://schema.org/ExhibitionEvent","https://schema.org/about":{"@id":"http://networkcultures.org/longform/2017/01/25/choose-how-you-feel-you-have-seven-options/"},"https://schema.org/endDate":"2017-03-18","https://schema.org/location":{"@id":"_:b16"},"https://schema.org/name":"Codes & Modes II","https://schema.org/organiser":"Integrated Media Arts MFA","https://schema.org/startDate":"2017-03-16","https://schema.org/url":"https://www.hunterintegratedmedia.org/reframe/speaker-lineup/ruben-van-de-ven/","https://schema.org/workFeatured":[{"@id":"https://rubenvandeven.com/emotionhero"},{"@id":"_:b58"}]},{"@id":"https://rubenvandeven.com/exhibition/hello-world","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2018-11-04","https://schema.org/name":"Hello World!","https://schema.org/organizer":"Creative Coding Utrecht","https://schema.org/startDate":"2018-11-02","https://schema.org/url":"https://www.creativecodingutrecht.nl/2018/10/30/hello-world-full-program-online/"},{"@id":"https://rubenvandeven.com/exhibition/kickstart","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2018-03-28","https://schema.org/location":{"@id":"https://rubenvandeven.com/place/in4art"},"https://schema.org/name":"KickstART","https://schema.org/startDate":"2018-03-28","https://schema.org/workFeatured":[{"@id":"https://rubenvandeven.com/mvp1"},{"@id":"https://rubenvandeven.com/mvp2"},{"@id":"https://rubenvandeven.com/mvp3"}]},{"@id":"https://rubenvandeven.com/exhibition/mood_swings","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2017-05-28","https://schema.org/location":{"@id":"https://rubenvandeven.com/place/q21"},"https://schema.org/name":"Mood Swings","https://schema.org/startDate":"2017-03-28","https://schema.org/subEvent":{"@id":"https://rubenvandeven.com/residency/q21"},"https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/emotionhero"}},{"@id":"https://rubenvandeven.com/exhibition/route-du-nord","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2018-05","https://schema.org/location":{"@id":"https://rubenvandeven.com/venue/zoho"},"https://schema.org/name":"Route du Nord","https://schema.org/startDate":"2018-05","https://schema.org/url":"http://routedunord.nl/portfolio-item/ruben-van-de-ven-2/","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/sustaining-gazes"}},{"@id":"https://rubenvandeven.com/exhibition/stateofemotion","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2016-11-05","https://schema.org/name":"STATE of Emotion","https://schema.org/startDate":"2016-11-04","https://schema.org/workFeatured":[{"@id":"https://rubenvandeven.com/emotionhero"},{"@id":"_:b58"}]},{"@id":"https://rubenvandeven.com/exhibition/the-new-current","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2019-02-10","https://schema.org/location":{"@id":"https://rubenvandeven.com/venue/cruise-terminal"},"https://schema.org/name":"The New Current","https://schema.org/startDate":"2019-02-06","https://schema.org/url":"https://www.thenewcurrent.org/artists-artweek","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/sustaining-gazes"}},{"@id":"https://rubenvandeven.com/ghost-worker","@type":"https://schema.org/MediaObject","https://schema.org/author":[{"@id":"https://rubenvandeven.com/person/merijn-van-moll"},{"@id":"https://rubenvandeven.com/"}],"https://schema.org/dateCreated":"2019","https://schema.org/description":"Nowadays, we are not only increasingly delegating and automating tasks and processes, there also new forms of work that are being created for people. With the emergence of services such as Amazon's Mechanical Turk, specific parts of automatic processes are being outsourced to human workers. These Human Intelligence Tasks are tasks that a machine cannot execute, for example retyping a scanned receipt or entering a Captcha code. In this way machines selectively delegate tasks to humans, creating a human working class that remains largely invisible.Merijn van Moll and Ruben van de Ven want to make this feedback loop between man and machine visible. This creates a new ritual in which the spirit of the worker is invoked again and again.\n\nThis project was developed in light of Future scenarios for AI and the job market by Setup and the NSvP (Dutch Foundation for Psycho technology)","https://schema.org/image":[{"@id":"https://rubenvandeven.com/ghost-worker/image/1"},{"@id":"https://rubenvandeven.com/ghost-worker/image/2"},{"@id":"https://rubenvandeven.com/ghost-worker/image/3"},{"@id":"https://rubenvandeven.com/ghost-worker/image/4"}],"https://schema.org/name":"Ghost Worker","https://schema.org/video":{"@id":"https://rubenvandeven.com/ghost-worker/video/1"}},{"@id":"https://rubenvandeven.com/ghost-worker/image/1","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/ghost_worker01.jpg","https://schema.org/thumbnailUrl":"assets/thumb/ghost_worker01.jpg"},{"@id":"https://rubenvandeven.com/ghost-worker/image/2","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/ghost_worker02.jpg","https://schema.org/thumbnailUrl":"assets/thumb/ghost_worker02.jpg"},{"@id":"https://rubenvandeven.com/ghost-worker/image/3","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/ghost_worker03.jpg","https://schema.org/thumbnailUrl":"assets/thumb/ghost_worker03.jpg"},{"@id":"https://rubenvandeven.com/ghost-worker/image/4","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/ghost_worker04.jpg","https://schema.org/thumbnailUrl":"assets/thumb/ghost_worker04.jpg"},{"@id":"https://rubenvandeven.com/ghost-worker/video/1","@type":"https://schema.org/VideoObject","https://schema.org/contentUrl":"http://works.rubenvandeven.com/2019-ghost_worker/ghostworkers-without-credits.mp4","https://schema.org/encodingFormat":"video/mp4","https://schema.org/thumbnailUrl":"http://works.rubenvandeven.com/2019-ghost_worker/ghostworkers-without-credits.mp4","https://schema.org/videoFrameSize":"1280x1024"},{"@id":"https://rubenvandeven.com/in4art-salon","@type":"https://schema.org/ExhibitionEvent","https://schema.org/endDate":"2018-05-27","https://schema.org/location":{"@id":"https://rubenvandeven.com/place/in4art"},"https://schema.org/name":"Salon VI - Innovatism","https://schema.org/startDate":"2018-05-18","https://schema.org/workFeatured":{"@id":"https://rubenvandeven.com/mvp1"}},{"@id":"https://rubenvandeven.com/mvp1","@type":"https://schema.org/MediaObject","https://schema.org/artworkSurface":"Oilpaint on wood + WiFi-connected RaspberryPi in ABS enclosure","https://schema.org/contributor":{"@id":"https://rubenvandeven.com/person/donald-schenkel"},"https://schema.org/dateCreated":"2018","https://schema.org/description":"The work of Donald Schenkel is augmented with a camera which keeps track of how long people look at the work. Like an electricity meter, the value increases the more the work is used. This data is then sent to a server, and provides the artist —in this case Donald Schenkel— with live statistics. But who owns this data generated by the work? The buyer, the artist, or the mediator — which is me?\n\nThis is the first augmentation that is part of the MVP series created for KickstART.","https://schema.org/height":"55cm + 8cm","https://schema.org/image":[{"@id":"https://rubenvandeven.com/mvp1/image/1"},{"@id":"https://rubenvandeven.com/mvp1/image/2"},{"@id":"https://rubenvandeven.com/mvp1/image/3"}],"https://schema.org/name":"MVP#1 Gathering viewing statistics for Donald Schenkel","https://schema.org/width":"45cm + 15cm"},{"@id":"https://rubenvandeven.com/mvp1/image/1","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/mvp1-1.jpg","https://schema.org/thumbnailUrl":"assets/thumb/mvp1-1.jpg"},{"@id":"https://rubenvandeven.com/mvp1/image/2","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/mvp1-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/mvp1-2.jpg"},{"@id":"https://rubenvandeven.com/mvp1/image/3","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/mvp1-4.jpg","https://schema.org/thumbnailUrl":"assets/thumb/mvp1-4.jpg"},{"@id":"https://rubenvandeven.com/mvp2","@type":"https://schema.org/MediaObject","https://schema.org/artworkSurface":"Pencil drawing in metal LCD enclosure","https://schema.org/contributor":{"@id":"https://rubenvandeven.com/person/joseph-huot"},"https://schema.org/dateCreated":"2018","https://schema.org/description":"In September 2017, huricane Irma was racing towards Florida. Everybody tried to get away from there as quick as possible. Stuck were those with the electric Tesla Model S with the cheaper battery option (60kWh). Then, all of a sudden, Tesla send a software update to these cars. The cars could drive futher then ever before. Until 72 hours later, Tesla reversed the software update. Exactly the same car could drive shorter distances.\n\nThis is the business of software companies: just like Apple limits performance on old iPhones, Tesla used softare to limit the reach of its cars in order to make more money. While physically the exact same product, it can do less.\n\nMinimum Viable Product #2 brings this business model to the art world. It provides a limited edition of a drawing by Joseph Huot.","https://schema.org/height":"50cm","https://schema.org/image":[{"@id":"https://rubenvandeven.com/mvp2/image/1"},{"@id":"https://rubenvandeven.com/mvp2/image/2"},{"@id":"https://rubenvandeven.com/mvp2/image/3"}],"https://schema.org/name":"MVP#2 Joseph Huot's Limited Edition","https://schema.org/width":"32.5cm"},{"@id":"https://rubenvandeven.com/mvp2/image/1","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/mvp2-01.jpg","https://schema.org/thumbnailUrl":"assets/thumb/mvp2-01.jpg"},{"@id":"https://rubenvandeven.com/mvp2/image/2","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/mvp2-02.jpg","https://schema.org/thumbnailUrl":"assets/thumb/mvp2-02.jpg"},{"@id":"https://rubenvandeven.com/mvp2/image/3","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/mvp2-5.jpg","https://schema.org/thumbnailUrl":"assets/thumb/mvp2-5.jpg"},{"@id":"https://rubenvandeven.com/mvp3","@type":"https://schema.org/MediaObject","https://schema.org/artworkSurface":"Personalised print on perspex","https://schema.org/contributor":{"@id":"https://rubenvandeven.com/person/mikel-folgerts"},"https://schema.org/dateCreated":"2018","https://schema.org/description":"On YouTube, Instagram, and many other online platforms, celebrities get paid to place consumer products in their videos. Not prominently, but hidden in plain sight. Can precarious artists finance their work by employing this product placement?\n\nMinimum Viable Product #3 allows buyers to customise the work they bought —Rotterdam, by Mikel Folgerts—, linking the status of the artist to their product.","https://schema.org/height":"40cm","https://schema.org/image":{"@id":"https://rubenvandeven.com/mvp3/image/1"},"https://schema.org/name":"MVP#3 Customiseyour.art - Mikel Folgerts 1/3","https://schema.org/width":"40cm"},{"@id":"https://rubenvandeven.com/mvp3/image/1","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/mvp3-1.jpg","https://schema.org/thumbnailUrl":"assets/thumb/mvp3-1.jpg"},{"@id":"https://rubenvandeven.com/mvps","@type":"https://schema.org/CreativeWorkSeries","https://schema.org/about":"November 2017 In4Art approached me to be part of their KickstART project. They commissioned three works to be part of their auction. The goal was to explicitly develop works that fitted the commercial art scene.\n\nRather than creating 'sellable' works myself, I took the start-up culture that forms the foundation of In4Art, as my object of my series. I augmented the works of three other participants with business models taken from the online/digital realm. This resulted in three Minimum Viable Products. Recontextualising these business models towards an arts context, results in new perspectives on both the arts as well as start-up culture.","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2018","https://schema.org/hasPart":[{"@id":"https://rubenvandeven.com/mvp1"},{"@id":"https://rubenvandeven.com/mvp2"},{"@id":"https://rubenvandeven.com/mvp3"}],"https://schema.org/name":"MVPs"},{"@id":"https://rubenvandeven.com/organisation/constant","@type":"https://schema.org/Organization","https://schema.org/name":"Constant","https://schema.org/url":"http://constantvzw.org"},{"@id":"https://rubenvandeven.com/organisation/v2_","@type":"https://schema.org/Place","https://schema.org/address":"Rotterdam","https://schema.org/name":"V2_"},{"@id":"https://rubenvandeven.com/person/cristina-cochior","@type":"https://schema.org/Person","https://schema.org/name":"Cristina Cochior","https://schema.org/url":"http://randomizer.info"},{"@id":"https://rubenvandeven.com/person/donald-schenkel","@type":"https://schema.org/Person","https://schema.org/name":"Donald Schenkel","https://schema.org/url":"http://www.donaldschenkel.nl/"},{"@id":"https://rubenvandeven.com/person/joseph-huot","@type":"https://schema.org/Person","https://schema.org/name":"Joseph Huot","https://schema.org/url":"http://www.joseph-huot.com/"},{"@id":"https://rubenvandeven.com/person/merijn-van-moll","@type":"https://schema.org/Person","https://schema.org/name":"Merijn van Moll","https://schema.org/url":"https://merijnvanmoll.nl/"},{"@id":"https://rubenvandeven.com/person/mikel-folgerts","@type":"https://schema.org/Person","https://schema.org/name":"Mikel Folgerts","https://schema.org/url":"https://www.instagram.com/mikelfolgerts/"},{"@id":"https://rubenvandeven.com/person/ward-goes","@type":"https://schema.org/Person","https://schema.org/name":"Ward Goes","https://schema.org/url":"http://www.wardgoes.nl"},{"@id":"https://rubenvandeven.com/place/in4art","@type":"https://schema.org/EventVenue","https://schema.org/address":"Amsterdam","https://schema.org/name":"In4Art Project Space"},{"@id":"https://rubenvandeven.com/place/q21","@type":"https://schema.org/Museum","https://schema.org/address":"MuseumsQuartier /Vienna","https://schema.org/name":"frei_raum / Q21"},{"@id":"https://rubenvandeven.com/plottingdata","@type":"https://schema.org/PerformingGroup","https://schema.org/description":"A research into Data Dramatisation as a tactic to make data visualisations more transparent in their underlying procedures of data collection. We advocate a form of data literacy that is not so much focussed on programming skill, but rather one that brings an understanding of data infrastructures: allowing for restistance against data driven governance.","https://schema.org/foundingDate":"2018","https://schema.org/member":[{"@id":"https://rubenvandeven.com/person/cristina-cochior"},{"@id":"https://rubenvandeven.com/"}],"https://schema.org/name":"Plotting Data: dramatisation as tactic","https://schema.org/url":"http://plottingd.at/a"},{"@id":"https://rubenvandeven.com/residency/q21","@type":"https://schema.org/VisualArtsEvent","https://schema.org/attendee":{"@id":"https://rubenvandeven.com/"},"https://schema.org/endDate":"2017-02","https://schema.org/location":{"@id":"https://rubenvandeven.com/place/q21"},"https://schema.org/name":"Residency: Q21","https://schema.org/startDate":"2017-01"},{"@id":"https://rubenvandeven.com/residency/summer-sessions","@type":"https://schema.org/VisualArtsEvent","https://schema.org/about":{"@id":"https://rubenvandeven.com/emotionhero"},"https://schema.org/attendee":{"@id":"https://rubenvandeven.com/"},"https://schema.org/description":"I was part of the Summer Sessions Network for Talent Development in a co-production of Arquivo 237 and V2_ Lab for the Unstable Media, with support of the Creative Industries Fund NL.","https://schema.org/endDate":"2016-10","https://schema.org/name":"Residency: Summer Sessions 2016","https://schema.org/startDate":"2016-09","https://schema.org/url":"http://summersessions.net/17-projects/projects-2016/55-emotion-hero"},{"@id":"https://rubenvandeven.com/spectacular-spectator-mood-meter","@type":"https://schema.org/MediaObject","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2017","https://schema.org/description":"Commisioned by V2_ for Evening of the Black Box Concern. Researchers of digital culture often regard artificial intelligence as black boxes. However, developers of these systems often regard the humans that are analysed as black boxes.\n\nWhat happens when we use black boxes (AI) to analyse black boxes (humans) and present these back to black boxes (humans)?\n\nThis prototype used emotion recognition software by Affectiva to analyse the audience of the talks. It then highlighted the moments in the talks for which outliers in the data were found. Can we use this data to analyse either of the black boxes?","https://schema.org/image":[{"@id":"_:b1"},{"@id":"_:b2"},{"@id":"_:b3"},{"@id":"_:b4"}],"https://schema.org/name":"Spectacular Spectator Mood Meter","https://schema.org/producer":{"@id":"https://rubenvandeven.com/organisation/v2_"}},{"@id":"https://rubenvandeven.com/sustaining-gazes","@type":"https://schema.org/MediaObject","https://schema.org/author":{"@id":"https://rubenvandeven.com/"},"https://schema.org/dateCreated":"2018","https://schema.org/description":"In analytics and statistics, data visualisations, such as the heatmap, are used as tools bring forward patterns in data. These data visualisations, are often presented as objective tools of knowledge: data supposedly do not lie. What is often neglected however, are the subjective and political intricacies embedded within the datasets, and its method of visualisation. In collaboration with Cristina Cochior I am exploring the concept of data dramatisations, as an opposite to data visualisations. Rather than aiming for the objective, we explore the affective & performative aspects of data gathering and processing.\n\nSustaining Gazes is a data dramatisation which looks at the role between the visualisation and its subject. A heatmap is generated by looking at it, which directly influences the looking. Areas which are looked at turn into interesting shapes, inviting even more observation. The pattern of looking is reinforced though its visualisation. This cycle reveals both a hierarchy of power between algorithm and subject, as well as a point for intervention.","https://schema.org/image":[{"@id":"https://rubenvandeven.com/sustaining-gazes/image/1"},{"@id":"https://rubenvandeven.com/sustaining-gazes/image/2"}],"https://schema.org/name":"Sustaining Gazes"},{"@id":"https://rubenvandeven.com/sustaining-gazes/image/1","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/sustaining-gazes-2.jpg","https://schema.org/thumbnailUrl":"assets/thumb/sustaining-gazes-2.jpg"},{"@id":"https://rubenvandeven.com/sustaining-gazes/image/2","@type":"https://schema.org/ImageObject","https://schema.org/contentUrl":"assets/image/sustaining-gazes-1.jpg","https://schema.org/thumbnailUrl":"assets/thumb/sustaining-gazes-1.jpg"},{"@id":"https://rubenvandeven.com/venue/cruise-terminal","@type":"https://schema.org/EventVenue","https://schema.org/address":"Rotterdam","https://schema.org/name":"Cruise Terminal"},{"@id":"https://rubenvandeven.com/venue/zoho","@type":"https://schema.org/EventVenue","https://schema.org/address":"Rotterdam","https://schema.org/name":"ZOHO"},{"@id":"https://www.statefestival.org/","@type":"https://schema.org/Festival","https://schema.org/address":"Berlin","https://schema.org/name":"STATE Festival","https://schema.org/subEvent":[{"@id":"https://rubenvandeven.com/exhibition/stateofemotion"},{"@id":"_:b52"}]},{"@id":"https://www.youtube.com/watch?v=L2O6ecO-8PM&feature=youtu.be&t=2062","@type":"https://schema.org/VideoObject","https://schema.org/embedUrl":"https://www.youtube.com/embed/L2O6ecO-8PM?start=2062","https://schema.org/thumbnailUrl":"https://i.ytimg.com/vi/L2O6ecO-8PM/maxresdefault.jpg"}]} \ No newline at end of file diff --git a/assets/js/textures.min.js b/assets/js/textures.min.js new file mode 100644 index 0000000..300f12f --- /dev/null +++ b/assets/js/textures.min.js @@ -0,0 +1 @@ +!function(t,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):t.textures=r()}(this,function(){"use strict";var t=function(){return(Math.random().toString(36)+"00000000000000000").replace(/[^a-z]+/g,"").slice(0,5)};return{circles:function(){var r=20,n="",e=2,a=!1,u="#343434",i="#343434",c=0,l=t(),o=function(t){var o=t.append("defs").append("pattern").attr("id",l).attr("patternUnits","userSpaceOnUse").attr("width",r).attr("height",r);n&&o.append("rect").attr("width",r).attr("height",r).attr("fill",n),o.append("circle").attr("cx",r/2).attr("cy",r/2).attr("r",e).attr("fill",u).attr("stroke",i).attr("stroke-width",c),a&&[[0,0],[0,r],[r,0],[r,r]].forEach(function(t){o.append("circle").attr("cx",t[0]).attr("cy",t[1]).attr("r",e).attr("fill",u).attr("stroke",i).attr("stroke-width",c)})};return o.heavier=function(t){return 0===arguments.length?e*=2:e*=2*t,o},o.lighter=function(t){return 0===arguments.length?e/=2:e/=2*t,o},o.thinner=function(t){return 0===arguments.length?r*=2:r*=2*t,o},o.thicker=function(t){return 0===arguments.length?r/=2:r/=2*t,o},o.background=function(t){return n=t,o},o.size=function(t){return r=t,o},o.complement=function(t){return a=0===arguments.length||t,o},o.radius=function(t){return e=t,o},o.fill=function(t){return u=t,o},o.stroke=function(t){return i=t,o},o.strokeWidth=function(t){return c=t,o},o.id=function(t){return 0===arguments.length?l:(l=t,o)},o.url=function(){return"url(#"+l+")"},o},lines:function(){var r=20,n="#343434",e=2,a="",u=t(),i=["diagonal"],c="auto",l=function(t){var n=r;switch(t){case"0/8":case"vertical":return"M "+n/2+", 0 l 0, "+n;case"1/8":return"M "+n/4+",0 l "+n/2+","+n+" M "+-n/4+",0 l "+n/2+","+n+" M "+3*n/4+",0 l "+n/2+","+n;case"2/8":case"diagonal":return"M 0,"+n+" l "+n+","+-n+" M "+-n/4+","+n/4+" l "+n/2+","+-n/2+" M "+.75*n+","+5/4*n+" l "+n/2+","+-n/2;case"3/8":return"M 0,"+.75*n+" l "+n+","+-n/2+" M 0,"+n/4+" l "+n+","+-n/2+" M 0,"+5*n/4+" l "+n+","+-n/2;case"4/8":case"horizontal":return"M 0,"+n/2+" l "+n+",0";case"5/8":return"M 0,"+-n/4+" l "+n+","+n/2+"M 0,"+n/4+" l "+n+","+n/2+" M 0,"+3*n/4+" l "+n+","+n/2;case"6/8":return"M 0,0 l "+n+","+n+" M "+-n/4+","+.75*n+" l "+n/2+","+n/2+" M "+3*n/4+","+-n/4+" l "+n/2+","+n/2;case"7/8":return"M "+-n/4+",0 l "+n/2+","+n+" M "+n/4+",0 l "+n/2+","+n+" M "+3*n/4+",0 l "+n/2+","+n;default:return"M "+n/2+", 0 l 0, "+n}},o=function(t){var o=t.append("defs").append("pattern").attr("id",u).attr("patternUnits","userSpaceOnUse").attr("width",r).attr("height",r);a&&o.append("rect").attr("width",r).attr("height",r).attr("fill",a),i.forEach(function(t){o.append("path").attr("d",l(t)).attr("stroke-width",e).attr("shape-rendering",c).attr("stroke",n).attr("stroke-linecap","square")})};return o.heavier=function(t){return 0===arguments.length?e*=2:e*=2*t,o},o.lighter=function(t){return 0===arguments.length?e/=2:e/=2*t,o},o.thinner=function(t){return 0===arguments.length?r*=2:r*=2*t,o},o.thicker=function(t){return 0===arguments.length?r/=2:r/=2*t,o},o.background=function(t){return a=t,o},o.size=function(t){return r=t,o},o.orientation=function(){for(var t=arguments.length,r=Array(t),n=0;n + + + + + + + + + image/svg+xml + + + + + + + I link to a website, and all I get is this lousy image + diff --git a/assets/og_image2.png b/assets/og_image2.png new file mode 100644 index 0000000..78e6d53 --- /dev/null +++ b/assets/og_image2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8c2b8fda88e896b9fb6dbe96ab48d00fb6aff2f5ce8e5f0fbe2601d4ec19edc +size 31048 diff --git a/assets/video/guerilla_project.mp4 b/assets/video/guerilla_project.mp4 new file mode 100644 index 0000000..a31a7dd --- /dev/null +++ b/assets/video/guerilla_project.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0f276436ee404bd79abe10221179e3a0aa12414b8c23a8ef8001b2026f20361 +size 21586113 diff --git a/assets/video/samawati.mp4 b/assets/video/samawati.mp4 new file mode 100644 index 0000000..690125b --- /dev/null +++ b/assets/video/samawati.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f8f0228529063b54be4beee690f0efa7aae6ef72b700b65f98118e14a0cc817 +size 289118426