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)); let data = JSON.parse(file.contents.toString(enc));
var transformedFile = file.clone(); var transformedFile = file.clone();
// console.log(data); // 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); transformedFile.contents = Buffer.from(JSON.stringify(flattened), enc);
callback(null, transformedFile) 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", "@type": "WebSite",
"url": "https://rubenvandeven.com", "url": "https://rubenvandeven.com",
"author": "author":
@ -7,9 +10,10 @@
"@id": "https://rubenvandeven.com/ruben", "@id": "https://rubenvandeven.com/ruben",
"@type": "Person", "@type": "Person",
"name": "Ruben van de Ven", "name": "Ruben van de Ven",
"jobTitle": "digital artist / researcher of software culture",
"email": "info@rubenvandeven.com", "email": "info@rubenvandeven.com",
"nationality": "The Netherlands", "nationality": "The Netherlands",
"jobTitle": "digital artist / researcher of software culture", "s:seeAlso": "https://rubenvandeven.com/foaf.rdf#me",
"@reverse": { "@reverse": {
"member": [ "member": [
{ {
@ -21,8 +25,47 @@
"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.", "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": [ "member": [
{"@id": "http://randomizer.info"} {"@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": [ "author": [
{ {
@ -37,7 +80,7 @@
"name": "Route du Nord", "name": "Route du Nord",
"url": "http://routedunord.nl/portfolio-item/ruben-van-de-ven-2/", "url": "http://routedunord.nl/portfolio-item/ruben-van-de-ven-2/",
"location": { "location": {
"@type": "Place", "@type": "EventVenue",
"name": "ZOHO", "name": "ZOHO",
"address": "Rotterdam" "address": "Rotterdam"
}, },
@ -73,6 +116,7 @@
"height": "55cm + 8cm", "height": "55cm + 8cm",
"artworkSurface": "Oilpaint on wood + WiFi-connected RaspberryPi in ABS enclosure", "artworkSurface": "Oilpaint on wood + WiFi-connected RaspberryPi in ABS enclosure",
"contributor": { "contributor": {
"@id": "http://www.donaldschenkel.nl/",
"@type": "Person", "@type": "Person",
"name": "Donald Schenkel", "name": "Donald Schenkel",
"url": "http://www.donaldschenkel.nl/" "url": "http://www.donaldschenkel.nl/"
@ -84,7 +128,8 @@
"@type": "ExhibitionEvent", "@type": "ExhibitionEvent",
"name": "KickstART", "name": "KickstART",
"location": { "location": {
"@type": "Place", "@id": "https://rubenvandeven.com/place/in4art",
"@type": "EventVenue",
"name": "In4Art Project Space", "name": "In4Art Project Space",
"address": "Amsterdam" "address": "Amsterdam"
}, },
@ -96,9 +141,7 @@
"@type": "ExhibitionEvent", "@type": "ExhibitionEvent",
"name": "Salon VI - Innovatism", "name": "Salon VI - Innovatism",
"location": { "location": {
"@type": "Place", "@id": "https://rubenvandeven.com/place/in4art"
"name": "In4Art Project Space",
"address": "Amsterdam"
}, },
"startDate": "2018-05-18", "startDate": "2018-05-18",
"endDate": "2018-05-27" "endDate": "2018-05-27"
@ -129,6 +172,7 @@
"height":"50cm", "height":"50cm",
"artworkSurface": "Pencil drawing in metal LCD enclosure", "artworkSurface": "Pencil drawing in metal LCD enclosure",
"contributor": { "contributor": {
"@id": "http://www.joseph-huot.com/",
"@type": "Person", "@type": "Person",
"name": "Joseph Huot", "name": "Joseph Huot",
"url": "http://www.joseph-huot.com/" "url": "http://www.joseph-huot.com/"
@ -224,10 +268,11 @@
] ]
}, },
{ {
"@id": "https://rubenvandeven.com/emotionhero",
"@type": "MediaObject", "@type": "MediaObject",
"dateCreated": "2016", "dateCreated": "2016",
"name": "Emotion Hero", "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", "url": "https://emotionhero.com",
"@reverse": { "@reverse": {
"workFeatured": [ "workFeatured": [
@ -235,7 +280,7 @@
"@type": "ExhibitionEvent", "@type": "ExhibitionEvent",
"name": "ECP Conference", "name": "ECP Conference",
"location": { "location": {
"@type": "Place", "@type": "EventVenue",
"name": "Fokker Terminal" "name": "Fokker Terminal"
}, },
"startDate": "2018-11-15", "startDate": "2018-11-15",
@ -243,10 +288,12 @@
"workFeatured": [] "workFeatured": []
}, },
{ {
"@id": "https://rubenvandeven.com/exhibition/mood_swings",
"@type": "ExhibitionEvent", "@type": "ExhibitionEvent",
"name": "Mood Swings", "name": "Mood Swings",
"location": { "location": {
"@type": "Place", "@id": "https://rubenvandeven.com/place/q21",
"@type": "Museum",
"name": "frei_raum / Q21", "name": "frei_raum / Q21",
"address": "MuseumsQuartier /Vienna" "address": "MuseumsQuartier /Vienna"
}, },
@ -259,7 +306,7 @@
"name": "Open Codes", "name": "Open Codes",
"url": "https://open-codes.zkm.de/", "url": "https://open-codes.zkm.de/",
"location": { "location": {
"@type": "Place", "@type": "Museum",
"name": "ZKM", "name": "ZKM",
"address": "Karlsrue" "address": "Karlsrue"
}, },
@ -282,7 +329,7 @@
"name": "Ars Electronica", "name": "Ars Electronica",
"url": "http://v2.nl/events/summer-sessions-at-ars-electronica-festival-2017", "url": "http://v2.nl/events/summer-sessions-at-ars-electronica-festival-2017",
"location": { "location": {
"@type": "Place", "@type": "Festival",
"name": "Ars Electronica", "name": "Ars Electronica",
"address": "Linz" "address": "Linz"
}, },
@ -298,7 +345,7 @@
"url": "http://microbites.me/", "url": "http://microbites.me/",
"organiser": "ACM Creativity & Cognition", "organiser": "ACM Creativity & Cognition",
"location": { "location": {
"@type": "Place", "@type": "Museum",
"name": "Museum of Arts & Sciences", "name": "Museum of Arts & Sciences",
"address": "Singapore" "address": "Singapore"
}, },
@ -309,28 +356,33 @@
{ {
"@id": "https://rubenvandeven.com/#codesandmodes", "@id": "https://rubenvandeven.com/#codesandmodes",
"@type": "ExhibitionEvent", "@type": "ExhibitionEvent",
"url": "https://www.hunterintegratedmedia.org/reframe/speaker-lineup/ruben-van-de-ven/",
"name": "Codes & Modes II", "name": "Codes & Modes II",
"organiser": "Integrated Media Arts MFA", "organiser": "Integrated Media Arts MFA",
"location": { "location": {
"@type": "Place", "@type": "CollegeOrUniversity",
"name": "Hunter College", "name": "Hunter College",
"address": "New York" "address": "New York City"
}, },
"startDate": "2017-03", "startDate": "2017-03-16",
"endDate": "2017-03", "endDate": "2017-03-18",
"workFeatured": [] "workFeatured": [],
"about": {
"@id": "http://networkcultures.org/longform/2017/01/25/choose-how-you-feel-you-have-seven-options/"
}
}, },
{ {
"@id": "https://rubenvandeven.com/#stateofemotion", "@id": "https://rubenvandeven.com/#stateofemotion",
"@type": "ExhibitionEvent", "@type": "ExhibitionEvent",
"name": "STATE of Emotion", "name": "STATE of Emotion",
"location": { "isPartOf": {
"@type": "Place", "@id": "https://www.statefestival.org/",
"@type": "Event",
"name": "STATE Festival", "name": "STATE Festival",
"address": "Berlin" "address": "Berlin"
}, },
"startDate": "2016-11", "startDate": "2016-11-04",
"endDate": "2016-11", "endDate": "2016-11-05",
"workFeatured": [] "workFeatured": []
}, },
{ {
@ -338,13 +390,16 @@
"name": "Manipulation: Emotion Hero", "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.", "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": { "location": {
"@type": "Place", "@type": "EventVenue",
"name": "Arquivo237", "name": "Arquivo237",
"address": "Lisbon" "address": "Lisbon"
}, },
"startDate": "2016-09", "startDate": "2016-09",
"endDate": "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", "@type": "ExhibitionEvent",
"name": "Big Stories Need Human Stakes", "name": "Big Stories Need Human Stakes",
"location": { "location": {
"@type": "Place", "@type": "Museum",
"name": "Nieuwe Vide", "name": "Nieuwe Vide",
"address": "Haarlem" "address": "Haarlem"
}, },
@ -399,7 +454,7 @@
"name": "Fuzzy Logic - Graduation Show", "name": "Fuzzy Logic - Graduation Show",
"url": "https://pzimediadesign.nl/2016.html", "url": "https://pzimediadesign.nl/2016.html",
"location": { "location": {
"@type": "Place", "@type": "CollegeOrUniversity",
"name": "Piet Zwart Institute", "name": "Piet Zwart Institute",
"address": "Rotterdam" "address": "Rotterdam"
}, },
@ -463,7 +518,7 @@
"@type": "ExhibitionEvent", "@type": "ExhibitionEvent",
"name": "Grand Instant Fiction", "name": "Grand Instant Fiction",
"location": { "location": {
"@type": "Place", "@type": "EventVenue",
"name": "Umakart Gallery", "name": "Umakart Gallery",
"address": "Brno" "address": "Brno"
}, },
@ -475,7 +530,7 @@
"name": "Boundaries of the Archive", "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", "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": { "location": {
"@type": "Place", "@type": "Museum",
"name": "EYE Film Museum", "name": "EYE Film Museum",
"address": "Amsterdam" "address": "Amsterdam"
}, },
@ -497,6 +552,7 @@
"datePublished": "2017-01-27" "datePublished": "2017-01-27"
}, },
{ {
"@id": "http://networkcultures.org/longform/2017/01/25/choose-how-you-feel-you-have-seven-options/",
"@type": "Report", "@type": "Report",
"name": "Longform - Choose How You Feel; You Have Seven Options", "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.", "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", "@type": "Organization",
"name": "Institute of Network Cultures", "name": "Institute of Network Cultures",
"url": "http://networkcultures.org" "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 ", "name": "High Desert International Film Festival ",
"url": "http://routedunord.nl/portfolio-item/ruben-van-de-ven-2/", "url": "http://routedunord.nl/portfolio-item/ruben-van-de-ven-2/",
"location": { "location": {
"@type": "Place", "@type": "Festival",
"name": "High Desert International Film Festival ", "name": "High Desert International Film Festival ",
"address": "Pahrump, Nevada" "address": "Pahrump, Nevada"
}, },
@ -622,7 +823,7 @@
"@type": "ExhibitionEvent", "@type": "ExhibitionEvent",
"name": "Crossing Border", "name": "Crossing Border",
"location": { "location": {
"@type": "Place", "@type": "PerformingArtsTheater",
"name": "Leidse Schouwburg", "name": "Leidse Schouwburg",
"address": "Leiden" "address": "Leiden"
}, },
@ -648,7 +849,10 @@
"contentUrl": "assets\/image\/samawati-prev1.jpg" "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", "@type": "MediaObject",
@ -661,7 +865,10 @@
"contentUrl": "assets\/image\/guerilla2.jpg" "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": [ "contributor": [
@ -670,6 +877,7 @@
"name": "The Spectacular Times", "name": "The Spectacular Times",
"dateCreated": "2013-12-04", "dateCreated": "2013-12-04",
"author": { "author": {
"@id": "http://www.wardgoes.nl",
"@type": "Person", "@type": "Person",
"name": "Ward Goes", "name": "Ward Goes",
"url": "http://www.wardgoes.nl" "url": "http://www.wardgoes.nl"

View file

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