fixes for mobile, and stricter use of jsonld context

This commit is contained in:
Ruben van de Ven 2018-10-16 09:55:27 +02:00
parent 6d3d7be124
commit 418fc4df6e
4 changed files with 421 additions and 138 deletions

View File

@ -57,7 +57,7 @@ gulp.task('jsonld', function(){
let data = JSON.parse(file.contents.toString(enc));
var transformedFile = file.clone();
// console.log(data);
jsonld.flatten(data, {"@context": "https://schema.org/"}, (err, flattened)=> {
jsonld.flatten(data, {}, (err, flattened)=> {
transformedFile.contents = Buffer.from(JSON.stringify(flattened), enc);
callback(null, transformedFile)
});

View File

@ -1,5 +1,8 @@
{
"@context": "https://schema.org/",
"@context": {
"@vocab": "https://schema.org/",
"s": "http://www.w3.org/2000/01/rdf-schema#"
},
"@type": "WebSite",
"url": "https://rubenvandeven.com",
"author":
@ -7,9 +10,10 @@
"@id": "https://rubenvandeven.com/ruben",
"@type": "Person",
"name": "Ruben van de Ven",
"jobTitle": "digital artist / researcher of software culture",
"email": "info@rubenvandeven.com",
"nationality": "The Netherlands",
"jobTitle": "digital artist / researcher of software culture",
"s:seeAlso": "https://rubenvandeven.com/foaf.rdf#me",
"@reverse": {
"member": [
{
@ -21,7 +25,46 @@
"description": "A research into <em>Data Dramatisation</em> 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.",
"member": [
{"@id": "http://randomizer.info"}
]
],
"@reverse": {
"about": [
{
"@type": "Event",
"name": "Digital Cultures: Knowledge / Culture / Technology",
"url": "https://digitalculturesconference.org/",
"startDate": "2018-09-19",
"endDate": "2018-09-22",
"organizer": "Centre for Digital Cultures & Institute for Culture and Society",
"location": "Leuphana University, Lüneburg"
}
]
}
}
],
"attendee": [
{
"@id": "http://summersessions.net/17-projects/projects-2016/55-emotion-hero",
"@type": "VisualArtsEvent",
"name": "Residency: Summer Sessions 2016",
"url": "http://summersessions.net/17-projects/projects-2016/55-emotion-hero",
"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.",
"startDate": "2016-09",
"endDate": "2016-10",
"about": {
"@id": "https://rubenvandeven.com/emotionhero"
}
},
{
"@type": "VisualArtsEvent",
"name": "Residency: Q21",
"startDate": "2017-01",
"endDate": "2017-02",
"isPartOf": {
"@id": "https://rubenvandeven.com/exhibition/mood_swings"
},
"location": {
"@id": "https://rubenvandeven.com/place/q21"
}
}
],
"author": [
@ -37,7 +80,7 @@
"name": "Route du Nord",
"url": "http://routedunord.nl/portfolio-item/ruben-van-de-ven-2/",
"location": {
"@type": "Place",
"@type": "EventVenue",
"name": "ZOHO",
"address": "Rotterdam"
},
@ -73,6 +116,7 @@
"height": "55cm + 8cm",
"artworkSurface": "Oilpaint on wood + WiFi-connected RaspberryPi in ABS enclosure",
"contributor": {
"@id": "http://www.donaldschenkel.nl/",
"@type": "Person",
"name": "Donald Schenkel",
"url": "http://www.donaldschenkel.nl/"
@ -84,7 +128,8 @@
"@type": "ExhibitionEvent",
"name": "KickstART",
"location": {
"@type": "Place",
"@id": "https://rubenvandeven.com/place/in4art",
"@type": "EventVenue",
"name": "In4Art Project Space",
"address": "Amsterdam"
},
@ -96,9 +141,7 @@
"@type": "ExhibitionEvent",
"name": "Salon VI - Innovatism",
"location": {
"@type": "Place",
"name": "In4Art Project Space",
"address": "Amsterdam"
"@id": "https://rubenvandeven.com/place/in4art"
},
"startDate": "2018-05-18",
"endDate": "2018-05-27"
@ -129,6 +172,7 @@
"height":"50cm",
"artworkSurface": "Pencil drawing in metal LCD enclosure",
"contributor": {
"@id": "http://www.joseph-huot.com/",
"@type": "Person",
"name": "Joseph Huot",
"url": "http://www.joseph-huot.com/"
@ -224,10 +268,11 @@
]
},
{
"@id": "https://rubenvandeven.com/emotionhero",
"@type": "MediaObject",
"dateCreated": "2016",
"name": "Emotion Hero",
"description": "Emotion recognition software is being used both as a tool for \u2018objective\u2019 measurements as well as a tool for training one\u2019s 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 <a href=\"https://play.google.com\/store\/apps\/details?id=com.rubenvandeven.emotion_hero\">Google Play<\/a>). Inspired by Guitar Hero, the user scores points by following given cues. It provides detailed feedback on the mechanics of the face (eg. \u201cYou showed on 10% Joy when you had to show 100%, smile 99.32% more.\u201d), 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 <a href=\"http://blog.affectiva.com\/sdk-on-the-spot-emotion-hero-app-encourages-play-with-facial-expressions\">interviewed<\/a> by them). This project is produced as part of the <a href=\"http://summersessions.net\/17-projects\/projects-2016\/55-emotion-hero\">Summer Sessions Network for Talent Development<\/a> in a co-production of Arquivo 237 and V2_ Lab for the Unstable Media, with support of the Creative Industries Fund NL.\nIt has been exhibited at the <a href=\"http://www.statefestival.org\/2016\/program-entry\/2016\/emotion-hero-2016\">State Festival 2016<\/a> (Berlin, DE) and Digital &lt;Dis&gt;orders (Frankfurt, DE).",
"description": "Emotion recognition software is being used both as a tool for \u2018objective\u2019 measurements as well as a tool for training one\u2019s 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 <a href=\"https://play.google.com\/store\/apps\/details?id=com.rubenvandeven.emotion_hero\">Google Play<\/a>). Inspired by Guitar Hero, the user scores points by following given cues. It provides detailed feedback on the mechanics of the face (eg. \u201cYou showed on 10% Joy when you had to show 100%, smile 99.32% more.\u201d), 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 <a href=\"http://blog.affectiva.com\/sdk-on-the-spot-emotion-hero-app-encourages-play-with-facial-expressions\">interviewed<\/a> by them).",
"url": "https://emotionhero.com",
"@reverse": {
"workFeatured": [
@ -235,7 +280,7 @@
"@type": "ExhibitionEvent",
"name": "ECP Conference",
"location": {
"@type": "Place",
"@type": "EventVenue",
"name": "Fokker Terminal"
},
"startDate": "2018-11-15",
@ -243,10 +288,12 @@
"workFeatured": []
},
{
"@id": "https://rubenvandeven.com/exhibition/mood_swings",
"@type": "ExhibitionEvent",
"name": "Mood Swings",
"location": {
"@type": "Place",
"@id": "https://rubenvandeven.com/place/q21",
"@type": "Museum",
"name": "frei_raum / Q21",
"address": "MuseumsQuartier /Vienna"
},
@ -259,7 +306,7 @@
"name": "Open Codes",
"url": "https://open-codes.zkm.de/",
"location": {
"@type": "Place",
"@type": "Museum",
"name": "ZKM",
"address": "Karlsrue"
},
@ -282,7 +329,7 @@
"name": "Ars Electronica",
"url": "http://v2.nl/events/summer-sessions-at-ars-electronica-festival-2017",
"location": {
"@type": "Place",
"@type": "Festival",
"name": "Ars Electronica",
"address": "Linz"
},
@ -298,7 +345,7 @@
"url": "http://microbites.me/",
"organiser": "ACM Creativity & Cognition",
"location": {
"@type": "Place",
"@type": "Museum",
"name": "Museum of Arts & Sciences",
"address": "Singapore"
},
@ -309,28 +356,33 @@
{
"@id": "https://rubenvandeven.com/#codesandmodes",
"@type": "ExhibitionEvent",
"url": "https://www.hunterintegratedmedia.org/reframe/speaker-lineup/ruben-van-de-ven/",
"name": "Codes & Modes II",
"organiser": "Integrated Media Arts MFA",
"location": {
"@type": "Place",
"@type": "CollegeOrUniversity",
"name": "Hunter College",
"address": "New York"
"address": "New York City"
},
"startDate": "2017-03",
"endDate": "2017-03",
"workFeatured": []
"startDate": "2017-03-16",
"endDate": "2017-03-18",
"workFeatured": [],
"about": {
"@id": "http://networkcultures.org/longform/2017/01/25/choose-how-you-feel-you-have-seven-options/"
}
},
{
"@id": "https://rubenvandeven.com/#stateofemotion",
"@type": "ExhibitionEvent",
"name": "STATE of Emotion",
"location": {
"@type": "Place",
"isPartOf": {
"@id": "https://www.statefestival.org/",
"@type": "Event",
"name": "STATE Festival",
"address": "Berlin"
},
"startDate": "2016-11",
"endDate": "2016-11",
"startDate": "2016-11-04",
"endDate": "2016-11-05",
"workFeatured": []
},
{
@ -338,13 +390,16 @@
"name": "Manipulation: Emotion Hero",
"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.",
"location": {
"@type": "Place",
"@type": "EventVenue",
"name": "Arquivo237",
"address": "Lisbon"
},
"startDate": "2016-09",
"endDate": "2016-09",
"workFeatured": []
"workFeatured": [],
"isPartOf": {
"@id": "http://summersessions.net/17-projects/projects-2016/55-emotion-hero"
}
}
]
},
@ -372,7 +427,7 @@
"@type": "ExhibitionEvent",
"name": "Big Stories Need Human Stakes",
"location": {
"@type": "Place",
"@type": "Museum",
"name": "Nieuwe Vide",
"address": "Haarlem"
},
@ -399,7 +454,7 @@
"name": "Fuzzy Logic - Graduation Show",
"url": "https://pzimediadesign.nl/2016.html",
"location": {
"@type": "Place",
"@type": "CollegeOrUniversity",
"name": "Piet Zwart Institute",
"address": "Rotterdam"
},
@ -463,7 +518,7 @@
"@type": "ExhibitionEvent",
"name": "Grand Instant Fiction",
"location": {
"@type": "Place",
"@type": "EventVenue",
"name": "Umakart Gallery",
"address": "Brno"
},
@ -475,7 +530,7 @@
"name": "Boundaries of the Archive",
"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",
"location": {
"@type": "Place",
"@type": "Museum",
"name": "EYE Film Museum",
"address": "Amsterdam"
},
@ -497,6 +552,7 @@
"datePublished": "2017-01-27"
},
{
"@id": "http://networkcultures.org/longform/2017/01/25/choose-how-you-feel-you-have-seven-options/",
"@type": "Report",
"name": "Longform - Choose How You Feel; You Have Seven Options",
"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.",
@ -506,6 +562,151 @@
"@type": "Organization",
"name": "Institute of Network Cultures",
"url": "http://networkcultures.org"
},
"@reverse": {
"about": [
{
"@type": "Event",
"name": "Presentation @ Media Lab UFRJ",
"url": "https://www.ied.edu/ied-locations/sao-paulo",
"startDate": "2018-05-25",
"endDate": "2018-05-25",
"organizer": "Media Lab UFRJ & Dutch Consulate",
"location": "Rio de Janeiro"
},
{
"@type": "Event",
"name": "Presentation @ Instituto Europeo di Design",
"url": "https://www.ied.edu/ied-locations/sao-paulo",
"startDate": "2018-05-22",
"endDate": "2018-05-22",
"organizer": "Instituto Europeo di Design & Dutch Consulate",
"location": "São Paulo"
},
{
"@type": "Event",
"name": "Presentation @ Nuffic Neso",
"url": "https://www.nuffic.nl/onderwerpen/kantoren-het-buitenland/",
"startDate": "2018-05-21",
"endDate": "2018-05-21",
"organizer": "Nuffic Neso & Dutch Consulate",
"location": "São Paulo"
},
{
"@type": "Event",
"name": "Presentation @ Festival PATH",
"url": "https://www.festivalpath.com.br/palestras/#palestrante-ruben-van-de-ven",
"startDate": "2018-05-19",
"endDate": "2018-05-19",
"organizer": "Dutch Consulate",
"location": "São Paulo"
},
{
"@type": "Event",
"name": "Presentation @ The Hmm",
"url": "https://thehmm.nl/the-hmm-x-baut/",
"startDate": "2018-03-28",
"endDate": "2018-03-28",
"organizer": "The Hmm",
"location": "Baut, Amsterdam"
},
{
"@type": "EducationEvent",
"name": "Workshop: Urban Frictions",
"url": "https://urbaninterfaces.sites.uu.nl",
"startDate": "2018-03-06",
"endDate": "2018-03-06",
"organizer": "Urban Interfaces, Utrecht University",
"location": "Utrecht",
"about": {
"@id" : "http://plottingd.at/a"
}
},
{
"@type": "EducationEvent",
"name": "Presentation @ Industrial Design",
"startDate": "2018-03-06",
"endDate": "2018-03-06",
"organizer": "Felienne Hermans, TU Delft",
"location": "TU Delft"
},
{
"@type": "Event",
"name": "Presentation @ Worlding the Brain II",
"url": "https://worldingthebrain2017.com",
"startDate": "2017-11-02",
"endDate": "2017-11-04",
"organizer": "ASCA Research Group Neuroaesthetics and Neurocultures, University of Amsterdam",
"location": "CREA, Amsterdam"
},
{
"@type": "EducationEvent",
"name": "Workshop: Artistic Point of Interference",
"url": "https://caradt.com/2017/03/07/ekv-13-march-lecture-by-human-index/",
"startDate": "2017-03-13",
"endDate": "2017-03-14",
"organizer": "St. Joost Academy",
"location": "Breda"
},
{
"@type": "Event",
"name": "Presentation @ Cqrrelations publication launch",
"url": "http://cqrrelations.constantvzw.org/0x0/",
"startDate": "2016-06-11",
"endDate": "2016-06-11",
"organizer": "Constant VZW.",
"location": "FoAM, Brussels"
},
{
"@type": "Event",
"name": "Panel: Tracking Emotions",
"url": "https://state-studio.com/participant/2016/ruben-van-de-ven",
"startDate": "2018-11-04",
"endDate": "2018-11-04",
"isPartOf": {
"@id": "https://www.statefestival.org/"
},
"recordedIn": {
"@id": "https://www.youtube.com/watch?v=L2O6ecO-8PM&t=2042s",
"@type": "VideoObject"
}
},
{
"@type": "Event",
"name": "Presentation @ Digital<Dis>orders",
"url": "https://www.normativeorders.net/en/events/young-researchers-conferences/34-veranstaltungen/nachwuchskonferenz/5114-seventh-international-young-researchers-conference",
"startDate": "2016-11-17",
"endDate": "2016-11-19",
"organizer": "Goethe University",
"location": "Frankfurt am Main"
},
{
"@type": "Event",
"name": "Presentation @ Winter school: What happens to the data in 'Big Data' ?",
"url": "www.bsts.be/wp-content/uploads/winter_school_2016-1.pdf",
"startDate": "2016-02",
"endDate": "016-02",
"organizer": "Workgroup: algorithmic governmentality",
"location": "University of Namur"
},
{
"@type": "Event",
"name": "Presentation @ Art Meets Radical Openness",
"url": "https://www.radical-openness.org/en/festival/2016",
"startDate": "2018-05-25",
"endDate": "2018-05-28",
"location": "Linz"
},
{
"@type": "Event",
"name": "Presentation @ Digital Emotions Workgroup",
"url": "http://asca.uva.nl/content/research-groups/digital-emotions/digital-emotions.html",
"startDate": "2016",
"endDate": "2016",
"organizer": "Amsterdam School for Cultral Analysis",
"location": "University of Amsterdam"
}
]
}
},
{
@ -593,7 +794,7 @@
"name": "High Desert International Film Festival ",
"url": "http://routedunord.nl/portfolio-item/ruben-van-de-ven-2/",
"location": {
"@type": "Place",
"@type": "Festival",
"name": "High Desert International Film Festival ",
"address": "Pahrump, Nevada"
},
@ -622,7 +823,7 @@
"@type": "ExhibitionEvent",
"name": "Crossing Border",
"location": {
"@type": "Place",
"@type": "PerformingArtsTheater",
"name": "Leidse Schouwburg",
"address": "Leiden"
},
@ -648,7 +849,10 @@
"contentUrl": "assets\/image\/samawati-prev1.jpg"
}
],
"video": "http://www.youtube.com/watch?v=D8pMy7xOZnI"
"video": {
"@id": "http://www.youtube.com/watch?v=D8pMy7xOZnI",
"@type": "VideoObject"
}
},
{
"@type": "MediaObject",
@ -661,7 +865,10 @@
"contentUrl": "assets\/image\/guerilla2.jpg"
}
],
"video": "http://www.youtube.com/watch?v=7Nk24Mh6uMc"
"video": {
"@id": "http://www.youtube.com/watch?v=7Nk24Mh6uMc",
"@type": "VideoObject"
}
}
],
"contributor": [
@ -670,6 +877,7 @@
"name": "The Spectacular Times",
"dateCreated": "2013-12-04",
"author": {
"@id": "http://www.wardgoes.nl",
"@type": "Person",
"name": "Ward Goes",
"url": "http://www.wardgoes.nl"

View File

@ -1,44 +1,51 @@
var data;
function getTitleAttribute(node) {
if(typeof node['name'] !== "undefined"){
return 'name';
function getLabelAttribute(node) {
if(typeof node['https://schema.org/name'] !== "undefined"){
return 'https://schema.org/name';
}
switch (node['type']) {
case "WebSite":
if(typeof node['url'] !== "undefined") {return 'url';}
switch (node['@type']) {
case "https://schema.org/WebSite":
if(typeof node['https://schema.org/url'] !== "undefined") {return 'https://schema.org/url';}
break;
case "ImageObject":
if(typeof node['caption'] !== "undefined") {return 'caption';}
if(typeof node['contentUrl'] !== "undefined") {return 'contentUrl';}
case "https://schema.org/ImageObject":
if(typeof node['https://schema.org/caption'] !== "undefined") {return 'https://schema.org/caption';}
if(typeof node['https://schema.org/contentUrl'] !== "undefined") {return 'https://schema.org/contentUrl';}
break;
case "PostalAddress":
if(typeof node['addressLocality'] !== "undefined") {return 'addressLocality';}
case "https://schema.org/PostalAddress":
if(typeof node['https://schema.org/addressLocality'] !== "undefined") {return 'https://schema.org/addressLocality';}
break;
}
return 'id';
return '@id';
}
function getNodeTitle(node){
return node[getTitleAttribute(node)];
function getNodeLabel(node){
let labelAttr = getLabelAttribute(node);
let label = node[labelAttr];
if(typeof label == "undefined") label = node["@id"];
if(typeof label == "undefined") label = "";
return label;
}
function getNodeYear(n){
if(typeof n['dateCreated'] !== 'undefined') {
return n['dateCreated'].substr(0,4);
if(typeof n['http://schema.org/dateCreated'] !== 'undefined') {
return n['http://schema.org/dateCreated'].substr(0,4);
}
if(typeof n['datePublished'] !== 'undefined') {
return n['datePublished'].substr(0,4);
if(typeof n['http://schema.org/datePublished'] !== 'undefined') {
return n['http://schema.org/datePublished'].substr(0,4);
}
if(typeof n['startDate'] !== 'undefined') {
return n['startDate'].substr(0,4);
if(typeof n['http://schema.org/startDate'] !== 'undefined') {
return n['http://schema.org/startDate'].substr(0,4);
}
if(typeof n['endDate'] !== 'undefined') {
return n['endDate'].substr(0,4);
if(typeof n['http://schema.org/endDate'] !== 'undefined') {
return n['http://schema.org/endDate'].substr(0,4);
}
if(typeof n['foundingDate'] !== 'undefined') {
return n['foundingDate'].substr(0,4);
if(typeof n['http://schema.org/foundingDate'] !== 'undefined') {
return n['http://schema.org/foundingDate'].substr(0,4);
}
return null;
}
function getDisplayAttr(attr) {
return attr.replace(/.*[#|\/]/, "");
}
/**
Transform a flattened jsonld into a d3 compatible graph
@param Object data flattened jsonld data
@ -50,14 +57,14 @@ function jsonLdToGraph(data){
// collect all nodes
for(let nodeId in data){
// data[nodeId]["type"][0] = data[nodeId]["type"][0];
nodes[data[nodeId]["id"]] = data[nodeId];
// data[nodeId]["@type"][0] = data[nodeId]["@type"][0];
nodes[data[nodeId]["@id"]] = data[nodeId];
}
// collect all links (separate loop as we need to check nodes)
for(let nodeId in data) {
let node = data[nodeId];
let currentId = node["id"];
let currentId = node["@id"];
for(let key in node){
let nodeAttr = Array.isArray(node[key]) ? node[key] : [node[key]];
// // relations should always be lists (eases assumptions)
@ -66,23 +73,23 @@ function jsonLdToGraph(data){
// }
// every attribute is an Array after flatten(), loop them
for(let i in nodeAttr) {
if(key !== "id" && typeof nodeAttr[i] === "string" && nodes[nodeAttr[i]]) {
if(key !== "@id" && typeof nodeAttr[i] === "string" && nodes[nodeAttr[i]]) {
links[links.length] = {
"source": currentId,
"target": nodeAttr[i],
"name": key
};
}
else if(typeof nodeAttr[i]["id"] !== "undefined") {
else if(typeof nodeAttr[i]["@id"] !== "undefined") {
// if there is just one item, flatten/expand has turned urls in objects with just an id
// reverse this, as we don't want these separate for this project
if (Object.keys(nodeAttr[i]).length == 1 && typeof nodes[nodeAttr[i]["id"]] === "undefined") {
if (Object.keys(nodeAttr[i]).length == 1 && typeof nodes[nodeAttr[i]["@id"]] === "undefined") {
// skip
// nodeAttr = nodeAttr[i]["id"];
} else {
links[links.length] = {
"source": currentId,
"target": nodeAttr[i]["id"],
"target": nodeAttr[i]["@id"],
"name": key
};
}
@ -108,7 +115,7 @@ const requestPromise = fetch('/assets/js/rubenvandeven.jsonld')
graph = jsonLdToGraph(data['@graph']);
// create a map of nodes by id.
for(let i in graph.nodes) {
nodeMap[graph.nodes[i]['id']] = graph.nodes[i];
nodeMap[graph.nodes[i]['@id']] = graph.nodes[i];
}
startGraph(graph);
});
@ -156,7 +163,6 @@ function createBreadcrumbs(linkMap, srcId) {
newPath.push(srcId);
let nextSrcIds = [];
for (let link of linkMap[srcId]) {
if(typeof crumbs[link['id']] !== 'undefined') continue;
crumbs[link['id']] = newPath;
@ -196,7 +202,7 @@ linkMap = createLinkMap(graph);
breadcrumbs = createBreadcrumbs(linkMap, firstNodeId);
for (let nodeIdx in graph['nodes']) {
let type = graph['nodes'][nodeIdx]["type"];
let type = graph['nodes'][nodeIdx]["@type"];
if(typeof types[type] == 'undefined') {
types[type] = [];
}
@ -225,7 +231,8 @@ for (let typeCountIdx in typeCounts) {
let typeLinkCountEl = document.createElement("span");
typeLinkCountEl.innerHTML = typeCounts[typeCountIdx][1];
typeLinkCountEl.classList.add('typeCount');
typeLinkAEl.innerHTML = typeName;
typeLinkAEl.innerHTML = getDisplayAttr(typeName);
typeLinkAEl.title = typeName;
typeLinkAEl.addEventListener('click', function(){
centerByType(typeName);
// positionNodesInCenter(types[typeName]);
@ -250,10 +257,8 @@ for (let typeCountIdx in typeCounts) {
}
showMoreTypeLinksEl.addEventListener('click', function () {
console.log('showMore');
document.body.classList.add('showMoreLinks');
var hideMoreTypeLinks = function(e) {
console.log('removeMore');
e.preventDefault();
e.stopPropagation();
document.body.removeEventListener('mouseup', hideMoreTypeLinks, true);
@ -272,7 +277,7 @@ var container = svg.append("g")
;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d["id"]; }).strength(.005))
.force("link", d3.forceLink().id(function(d) { return d["@id"]; }).strength(.005))
.force("charge", d3.forceManyBody()) // doesn't seem necessary?
.force("collision", d3.forceCollide(nodeSize * 1.1)) // avoid overlapping nodes
// .force("center", d3.forceCenter(width / 2, height / 2)) // position around center
@ -288,7 +293,7 @@ var link = container.append("g")
.selectAll(".relationship")
.data(graph['links'])
.enter().append("g")
.attr("class", function(d){return "relationship "+d.name;})
.attr("class", function(l){return "relationship "+l.name;})
;
var linkLine = link
// .append("line");
@ -296,11 +301,10 @@ var linkLine = link
;
var linkText = link
.append("text")
.text(function(d){
return d.name;
// snake_case: return d.name.replace(/(?:^|\.?)([A-Z])/g, function (x,y){return "_" + y.toLowerCase()}).replace(/^_/, "");
.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")
@ -308,14 +312,13 @@ var linkText = link
.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", function(d) { return 'node ' + d['type']; })
.attr("class", function(d) { return 'node ' + d['@type']; })
;
var getViewbox = function() {
return svg.attr("viewBox").split(" ").map(parseFloat);
}
var positionNodesInCenter = function(idxs) {
setViewboxForceCenter(); // sets forceCx & forceCy
if(typeof idxs == "object" && idxs !== null && idxs.length == 1) {
idxs = idxs[0];
}
@ -340,13 +343,14 @@ var positionNodesInCenter = function(idxs) {
// ];
// }
positionNodesInCircle(idxs);
console.log(nodePositions);
// console.log(nodePositions);
}
else{
nodePositions[idxs] = [
forceCx,
forceCy
];
console.log("singleNode", idxs, nodePositions);
}
node.each(function(d,nIdx,nodeEls){
@ -378,6 +382,7 @@ var positionNodesInCircle = function(idxs, r) {
let forceCy = viewBox[1] + viewBox[3]/2;
let stepSize = 2*Math.PI / idxs.length;
for (var i = 0; i < idxs.length; i++) {
nodePositions[idxs[i]] = [
forceCx + Math.sin(stepSize * i) * r,
@ -386,7 +391,6 @@ var positionNodesInCircle = function(idxs, r) {
}
// restart animation (they call that 'alpha' in d3 force)
console.log("reset alpha");
simulation.alpha(1);
simulation.restart();
}
@ -396,7 +400,7 @@ var centerByType = function(types) {
}
let idxs = [];
for(let idx in graph.nodes) {
if(types.indexOf(graph.nodes[idx]['type']) > -1) {
if(types.indexOf(graph.nodes[idx]['@type']) > -1) {
idxs[idxs.length] = idx;
}
}
@ -414,7 +418,7 @@ var createRelationshipEl = function(relNode, i) {
let el = document.createElement("dd");
el.classList.add('relLink');
let titleEl = document.createElement('a');
titleEl.innerHTML = getNodeTitle(relNode)
titleEl.innerHTML = getNodeLabel(relNode)
let year = getNodeYear(relNode);
if(year !== null) {
titleEl.innerHTML += `<span class='nodeYear'>${getNodeYear(relNode)}</span>`;
@ -426,19 +430,20 @@ var createRelationshipEl = function(relNode, i) {
selectNode(idx);
});
let typeEl = document.createElement('a');
typeEl.classList.add('nodeType')
typeEl.innerHTML = relNode['type']
typeEl.classList.add('nodeType');
typeEl.innerHTML = getDisplayAttr(relNode['@type']);
typeEl.title = relNode['@type'];
typeEl.addEventListener('click',function(e){
centerByType(relNode['type']);
centerByType(relNode['@type']);
});
el.appendChild(titleEl);
el.appendChild(typeEl);
// el.innerHTML = `${getNodeTitle(relNode)} (${relNode['type']})`;
return el;
}
var setDetails = function(nodeDatum, nodeIdx) {
document.body.classList.add("detailsOpen");
scrollToY(0, 4000);
while (nodeDetailEl.hasChildNodes()) {
nodeDetailEl.removeChild(nodeDetailEl.lastChild);
}
@ -465,7 +470,7 @@ var setDetails = function(nodeDatum, nodeIdx) {
let breadcrumbsEl = document.createElement('ul');
breadcrumbsEl.classList.add('breadcrumbs');
for(let crumbNodeId of breadcrumbs[nodeDatum['id']]) {
for(let crumbNodeId of breadcrumbs[nodeDatum['@id']]) {
let crumbWrapEl = document.createElement('li');
let crumbEl = document.createElement('span');
crumbEl.classList.add('crumb');
@ -473,38 +478,39 @@ var setDetails = function(nodeDatum, nodeIdx) {
let idx = graph.nodes.indexOf(nodeMap[crumbNodeId]);
selectNode(idx);
});
crumbEl.innerHTML = `${getNodeTitle(nodeMap[crumbNodeId])}`;
crumbEl.innerHTML = `${getNodeLabel(nodeMap[crumbNodeId])}`;
let nodeYear = getNodeYear(nodeMap[crumbNodeId]);
if(nodeYear !== null) {
crumbEl.innerHTML += `<span class='nodeYear'>${nodeYear}</span>`;
}
crumbWrapEl.appendChild(crumbEl);
breadcrumbsEl.appendChild(crumbWrapEl);
pageTitles.push(getNodeTitle(nodeMap[crumbNodeId]));
pageTitles.push(getNodeLabel(nodeMap[crumbNodeId]));
}
nodeDetailEl.appendChild(breadcrumbsEl);
pageTitles.push(getNodeTitle(nodeDatum));
pageTitles.push(getNodeLabel(nodeDatum));
let titleAttr = getTitleAttribute(nodeDatum);
let titleAttr = getLabelAttribute(nodeDatum);
let titleEl = document.createElement('h2');
titleEl.innerHTML = getNodeTitle(nodeDatum);
titleEl.innerHTML = getNodeLabel(nodeDatum);
let typeEl = document.createElement('span');
typeEl.classList.add('nodeType')
typeEl.innerHTML = nodeDatum['type']
typeEl.innerHTML = getDisplayAttr(nodeDatum['@type']);
typeEl.title = nodeDatum['@type']
typeEl.addEventListener('click',function(e){
centerByType(nodeDatum['type']);
centerByType(nodeDatum['@type']);
});
titleEl.appendChild(typeEl);
nodeDetailEl.appendChild(titleEl);
let listEl = document.createElement("dl");
// listEl.innerHTML += `<dt>type</dt><dd>${nodeDatum['type']}</dd>`;
// listEl.innerHTML += `<dt>type</dt><dd>${nodeDatum['@type']}</dd>`;
let skipNodeAttributes = [
'id','x','y','index','type','vy','vx','fx','fy','leftX','rightX'
'@id','x','y','index','@type','vy','vx','fx','fy','leftX','rightX'
];
if(titleAttr !== 'contentUrl') {
if(titleAttr !== 'https://schema.org/contentUrl') {
skipNodeAttributes[skipNodeAttributes.length] = titleAttr;
}
for (let attr in nodeDatum) {
@ -518,39 +524,39 @@ var setDetails = function(nodeDatum, nodeIdx) {
// check if relationship:
if(typeof nodeAttr[i] === "string" && nodeMap[nodeAttr[i]]) {
continue;
} else if(typeof nodeAttr[i]['id'] !== 'undefined') {
} else if(typeof nodeAttr[i]['@id'] !== 'undefined') {
continue;
}
if(attr == 'url') {
listEl.innerHTML += `<dt class='dt-${attr}'>${attr}</dt><dd class='dd-${attr}'><a href='${nodeAttr[i]}'>${nodeAttr[i]}</a></dd>`;
} else if(attr == 'contentUrl') {
console.log('test', attr);
listEl.innerHTML += `<dt class='dt-${attr}'>${attr}</dt><dd class='dd-${attr}'><a href='${nodeAttr[i]}'>${nodeAttr[i]}</a></dd>`;
if(attr == 'https://schema.org/url') {
listEl.innerHTML += `<dt class='dt-${getDisplayAttr(attr)}' title='${attr}'>${getDisplayAttr(attr)}</dt><dd class='dd-${getDisplayAttr(attr)}'><a href='${nodeAttr[i]}'>${nodeAttr[i]}</a></dd>`;
} else if(attr == 'https://schema.org/contentUrl') {
// console.log('test', attr);
listEl.innerHTML += `<dt class='dt-${getDisplayAttr(attr)}' title='${attr}'>${getDisplayAttr(attr)}</dt><dd class='dd-${getDisplayAttr(attr)}'><a href='${nodeAttr[i]}'>${nodeAttr[i]}</a></dd>`;
listEl.innerHTML += `<dd class='dd-contentobject'><object data='${nodeAttr[i]}'></object></dd>`;
} else {
let valueHtml = nodeAttr[i].replace(/\n/g,"<br>");
listEl.innerHTML += `<dt class='dt-${attr}'>${attr}</dt><dd class='dd-${attr}'>${valueHtml}</dd>`;
listEl.innerHTML += `<dt class='dt-${getDisplayAttr(attr)}' title='${attr}'>${getDisplayAttr(attr)}</dt><dd class='dd-${getDisplayAttr(attr)}'>${valueHtml}</dd>`;
}
}
}
nodeDetailEl.appendChild(listEl);
let relTitleEl = document.createElement("h4");
relTitleEl.classList.add('linkTitle');
relTitleEl.innerHTML = "links";
nodeDetailEl.appendChild(relTitleEl);
// let relTitleEl = document.createElement("h4");
// relTitleEl.classList.add('linkTitle');
// relTitleEl.innerHTML = "links";
// nodeDetailEl.appendChild(relTitleEl);
let relsEl = document.createElement("dl");
// collect relationships
for (var i = 0; i < graph.links.length; i++) {
let link = graph.links[i];
if(link['source']['id'] == nodeDatum['id']) {
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(link['target']['@id'] == nodeDatum['@id']) {
if(typeof relUp[link['name']] == "undefined") {
relUp[link['name']] = [];
}
@ -561,15 +567,15 @@ var setDetails = function(nodeDatum, nodeIdx) {
// relationships / links in <dl>
for(let attr in relDown) {
let attrEl = document.createElement("dt");
attrEl.innerHTML = attr;
attrEl.innerHTML = getDisplayAttr(attr);
relsEl.appendChild(attrEl);
for(let i in relDown[attr]) {
let rel = relDown[attr][i];
relsEl.appendChild(createRelationshipEl(rel));
if(typeof rel['contentUrl'] != 'undefined') {
if(typeof rel['https://schema.org/contentUrl'] != 'undefined') {
let ddEl = document.createElement('dd')
ddEl.classList.add('dd-contentobject');
ddEl.innerHTML = `<object data='${rel['contentUrl']}'></object>`
ddEl.innerHTML = `<object data='${rel['https://schema.org/contentUrl']}'></object>`
relsEl.appendChild(ddEl);
}
}
@ -577,15 +583,15 @@ var setDetails = function(nodeDatum, nodeIdx) {
for(let attr in relUp) {
let attrEl = document.createElement("dt");
attrEl.innerHTML = attr;
attrEl.innerHTML = getDisplayAttr(attr);
relsEl.appendChild(attrEl);
for(let i in relUp[attr]) {
let rel = relUp[attr][i];
relsEl.appendChild(createRelationshipEl(rel, i));
if(typeof rel['contentUrl'] != 'undefined') {
if(typeof rel['https://schema.org/contentUrl'] != 'undefined') {
let ddEl = document.createElement('dd')
ddEl.classList.add('dd-contentobject');
ddEl.innerHTML = `<object data='${rel['contentUrl']}'></object>`
ddEl.innerHTML = `<object data='${rel['https://schema.org/contentUrl']}'></object>`
relsEl.appendChild(ddEl);
}
}
@ -606,6 +612,7 @@ var setDetails = function(nodeDatum, nodeIdx) {
};
var closeDetails = function() {
document.body.classList.remove("detailsOpen");
scrollToY(0, 4000); // for mobile
}
/**
@ -630,9 +637,8 @@ var selectNode = function(idx){
// set global var
positionNodesInCenter(idx);
let currentCrumbs = breadcrumbs[nodeDatum['id']].slice();
currentCrumbs[currentCrumbs.length] = nodeDatum['id'];
console.log(currentCrumbs);
let currentCrumbs = breadcrumbs[nodeDatum['@id']].slice();
currentCrumbs[currentCrumbs.length] = nodeDatum['@id'];
// set active links.
let linkedIdxs = [];
@ -642,7 +648,7 @@ var selectNode = function(idx){
linkEls[idx].classList.add('activeLink','visibleLink');
linkEls[idx].getElementsByTagName("line")[0].setAttribute("marker-end", "url(#arrowHeadSelected)");
node.filter(function(a, fnodeIdx){
let r = a.id == d.source.id || a.id == d.target.id; //connected node: true/false
let 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;
}
@ -653,8 +659,8 @@ var selectNode = function(idx){
linkEls[idx].getElementsByTagName("line")[0].setAttribute("marker-end", "url(#arrowHead)");
}
// check if link is part of breadcrumb trail
let posSrc = currentCrumbs.indexOf(d.source['id']);
let posTrg = currentCrumbs.indexOf(d.target['id']);
let posSrc = currentCrumbs.indexOf(d.source['@id']);
let 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)");
@ -677,6 +683,7 @@ var deselectNode = function() {
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();
@ -810,7 +817,7 @@ node.append('circle')
node.append('text')
.attr("class", "nodeType")
.text(function(n){
return n['type'];
return n['@type'];
})
node.append('text')
@ -827,11 +834,11 @@ let nodeTitle = node.append('text')
nodeTitle
// .append("textPath")
// .attr( "xlink:href",function(d, idx){return '#nodePath'+idx;})
// .text(getNodeTitle)
// .text(getNodeLabel)
.each(function(node, nodes){
let textLength;
let self = d3.select(this);
let titleText = getNodeTitle(node);
let titleText = getNodeLabel(node);
if(titleText.length > 20 && titleText.indexOf(" ") > -1) {
let mid = Math.floor(titleText.length / 2);
mid = titleText.substr(0,mid).lastIndexOf(" ");
@ -864,11 +871,11 @@ nodeTitle
;
node.each(function(d) {
if(!d.contentUrl) {
if(!d['https://schema.org/contentUrl']) {
return;
}
d3.select(this).append('svg:image')
.attr("xlink:href", d.contentUrl.replace('image', 'thumb'))
.attr("xlink:href", d['https://schema.org/contentUrl'].replace('image', 'thumb'))
.attr("width", nodeSize*2)
.attr("height", nodeSize*2)
.attr("transform","translate(-"+nodeSize+" -"+nodeSize+")")
@ -877,9 +884,6 @@ node.each(function(d) {
;
});
// node.append("title")
// .text(function(d) { return d.id; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
@ -1017,7 +1021,8 @@ function moveViewboxPx(dx, dy){
// start by selecting the first node :-)
// selectNode(currentNodeIdx+1);
// positionNodesInCenter(currentNodeIdx);
selectNode(graph['nodes'].length - 1);
var firstNode = graph['nodes'].find(n => n['@id'] === "https://rubenvandeven.com/ruben");
selectNode(graph['nodes'].indexOf(firstNode));
// closeDetails(); // hide details at first
// positionNodesInCenter(currentNodeIdx+1);
@ -1033,3 +1038,68 @@ setTimeout(function(){
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
let 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
let PI_D2 = Math.PI / 2,
easingEquations = {
easeOutSine: function (pos) {
return Math.sin(pos * (Math.PI / 2));
},
easeInOutSine: function (pos) {
return (-0.5 * (Math.cos(Math.PI * pos) - 1));
},
easeInOutQuint: function (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();
}

View File

@ -131,7 +131,8 @@ g.node{
opacity: 1;
}
&.visibleLink{
// &.visibleLink{
&.activeLink{
display:block;
// opacity: 1;
@ -310,16 +311,17 @@ text{
}
dt{
float:left;
width: 120px;
width: 170px;
font-weight:bold;
min-height:25px;
clear:both;
}
dd{
min-height:30px;
margin-top:5px;
}
dd:not(.nodeTitleNr1) {
margin-left: 130px;
margin-left: 170px;
}
dt.dt-description{
float:none;
@ -424,11 +426,14 @@ svg#portfolioGraph {
}
@media (max-width: 1000px) {
@media (max-width: 1496px) {
body{
overflow-y: auto;
overflow-y: hidden;
overflow-x: hidden;
font-size: 16pt;
&.detailsOpen{
overflow-y: auto;
}
}
svg#portfolioGraph{
@ -457,7 +462,7 @@ svg#portfolioGraph {
body.detailsOpen{
#nodeDetails{
displaY:block;
margin-top: calc( 100vh + $detailSlideMobile);
margin-top: calc( 100vh + #{$detailSlideMobile} );
position: relative;
z-index: 1000;
// min-height:$detailSlideMobile * -1;