Author: Tomasz Neugebauer, Digital Projects & Systems Development Librarian at Concordia University and e-artexte Researcher in Residence.
First Published: April 2013
Last Updated: July 2013 – Added license information. Updated section on loading EPrints data with new jquery ajax method that allows for displaying a loader.
All of the source code for this example is available at: https://github.com/photomedia/SimileTimelineEPrints
User License Agreement:
Use of this source code implies agreement to the license of its content (i.e.: e-artexte metadata) and open source components, such as: SIMILE, jquery, Yahoo YUI Library.
The visualization documented in this tutorial is available
at:http://www.photographymedia.com/visualizations/artexte/e-artexte-1.html
The following is a technical tutorial on the re-use of e-artexte bibliographic data using SIMILE Timeline (http://www.simile-widgets.org/timeline/). E-artexte uses the EPrints (http://www.eprints.org) open source software, so this method is potentially of interest to anyone wishing to re-use data stored in EPrints repositories.
To improve consistency of display across browsers, a CSS style foundation Yahoo YUI library (http://yuilibrary.com/yui/css/) is used:
<link rel="stylesheet" href="yui3.9.1/cssreset-min.css" type="text/css">
<link rel="stylesheet" href="yui3.9.1/cssbase-min.css" type="text/css">
<link rel="stylesheet" href="yui3.9.1/cssfonts-min.css" type="text/css">
The SIMILE JavaScript libraries can be included in two ways: bundled into compressed files or unbundled series of human readable source files. Making the libraries compatible with the EPrints metadata export into JSON requires custom changes to both timeline-api and simile-ajax-api. Therefore, the unbundled versions of the source code need to be included using the following statements in the head of the main e-artexte-1.html file:
<script type="text/javascript">
var Timeline_ajax_url="timeline_2.3.1/simile-ajax-api.js?bundle=false";
var Timeline_urlPrefix='timeline_2.3.1/';
</script>
<script src="timeline_2.3.1/timeline-api.js?bundle=false" type="text/javascript"></script>
To simplify the implementation, all of the required files are placed in the “timeline_2.3.1” folder. This includes the following:
· timeline-api.js and the dependencies:
o band.js
o compact-painter.js
o decorators.js
o detailed-painter.js
o ether-painters.js
o ethers.js and ethers.css
o event-utils.js and events.css
o labellers.js
o original-painter.js
o overview-painter.js
o sources.js
o themes.js
o timeline.js and timeline.css
· simile-ajax-api.js and the dependencies:
o jquery-1.8.0.min.js
o graphics.js and graphics.css
o platform.js
o xmlhttp.js
o json.js
o dom.js
o date-time.js
o string.js
o html.js
o data-structure.js
o units.js
o ajax.js
o history.js
o window-manager.js
· images folder with required graphic files.
A JSON formatted item in e-artexte contains the following relevant fields to use/display in the Timeline visualization: date, title, abstract/resume, uri. For example:
{
[…]
"date": "2000",
[…]
"uri": "http://e-artexte.ca/id/eprint/14510",
[…]
"abstract": "Catalogue for an exhibition surveying the main trends of Quebec photography since the second half of the 1980s, including self-portraiture, the post-documentary, narrative photography and works concerned with archives, installation and optics. In situating each of the 24 artists involved, the curators note the existence of a group of younger artists whose works, playful and offhanded, make them difficult to categorize. Foreword by L. Michaud in French and Spanish; texts by Campeau and Hakim in French, Spanish and English. List of works. Biographical notes.",
[…]
"title": "Le cadre, la scène, le site : Panorama de la photographie québécoise contemporaine = Le cadre, la scène, le site : Un panorama de la fotografia quebequense contemporánea = Le cadre, la scène, le site : Images of Contemporary Quebec Photography",
[…]
"resume": "Catalogue d’une exposition qui rend compte des principaux axes de la photographie québécoise depuis la seconde moitié des années 1980 – soit le postdocumentaire, l’autoportrait, la photo narrative, la spéculation sur les archives, sur l’installation et sur l’optique. L’analyse des commissaires Campeau et Hakim porte sur chacun des 24 artistes sélectionnés pour constituer ce panorama, et souligne en outre la présence d’un groupe plus jeune et difficile à catégoriser, dont les œuvres sont davantage ludiques et désinvoltes. Préface de L. Michaud en français et en espagnol; textes de Campeau et Hakim en français, en anglais et en espagnol. Liste des œuvres. Notices biographiques.",
[…]
}
Most of the changes that were made to make the Timeline libraries compatible with EPrints/e-artexte JSON were in the sources.js file. To get Timeline to treat uri field the same as the default link, and date field the same as the default start, the following was added:
var evt = new Timeline.DefaultEventSource.Event({
id: ("id" in evnt) ? evnt.id : undefined,
start: parseDateTimeFunction(evnt.start || evnt.s || evnt.date), /*TN - added EPrints Date */
link: this._resolveRelativeURL(evnt.link || evnt.uri, base), /*TN - added EPrints uri */
Add functions to echo the abstract and French language resume fields:
//TN add this custom field "resume"
fillResume:function(A){if (this.getProperty("resume")){A.innerHTML=this.getProperty("resume");}
},
//TN add this custom field
"abstract"
fillAbstract:function(A){if (this.getProperty("abstract")){A.innerHTML=this.getProperty("abstract");}
},
Lastly, to customize the information displayed in the detailed bubble for each event, modify the fillInfoBubble function in sources.js:
fillInfoBubble: function(elmt, theme, labeller) {
var doc = elmt.ownerDocument;
var title = this.getText();
var link = this.getLink();
var image = this.getImage();
if (image != null) {
var img = doc.createElement("img");
img.src = image;
theme.event.bubble.imageStyler(img);
elmt.appendChild(img);
}
var divTitle = doc.createElement("div");
var textTitle = doc.createTextNode(title);
if (link != null) {
var a = doc.createElement("a");
a.href = link;
//TN - open links to e-Artexte in new window
a.target='_blank';
a.appendChild(textTitle);
divTitle.appendChild(a);
} else {
divTitle.appendChild(textTitle);
}
theme.event.bubble.titleStyler(divTitle);
elmt.appendChild(divTitle);
//TN - show abstract
var N=doc.createElement("div");
this.fillAbstract(N);
theme.event.bubble.bodyStyler(N);
N.className="timeline-event-bubble-body-abstract";
elmt.appendChild(N);
//TN - show resume
N=doc.createElement("div");
this.fillResume(N);
N.className="timeline-event-bubble-body-resume";
elmt.appendChild(N);
//TN - show divider in the end
N=doc.createElement("div");
theme.event.bubble.bodyStyler(N);
elmt.appendChild(N);
}
The relevant custom CSS for the formatting was
modified/added to events.css:
.timeline-event-bubble-title {
font-weight: bold;
margin-bottom: 0.5em;
}
.timeline-event-bubble-body-abstract {
margin-bottom:5px;
padding-bottom:4px;
border-bottom:1px solid #c0c0c0;
}
.timeline-event-bubble-body-resume {
margin-bottom:5px;
padding-bottom:4px;
}
.timeline-event-bubble-body {
border-bottom: 1px solid #606060;
}
Accommodating large amounts of concurrent items means that the popup bubble can contain many additional items under the [% more] link. Setting a default 400 px maxHeight of the popup bubbles in graphics.js ensures that the user interface remains proportional:
SimileAjax.Graphics.createBubbleForContentAndPoint = function(
div, pageX, pageY, contentWidth, orientation, maxHeight) {
if (typeof contentWidth != "number") {
contentWidth = 300;
}
if (typeof maxHeight != "number") {
maxHeight = 400; /*TN fix for max height */
}
Using the compact-painter.js, an additional variable for maxLabelWidth is added to the list of parameters passed from the timeline initialization:
Timeline.CompactEventPainter = function(params) {
[…]
this._maxLabelWidth = params.maxLabelWidth; /*TN*/
};
The paintStackedPreciseInstantEvents function in compact-painter.js uses this maxLabelWidth to split the long labels onto multiple lines:
/*TN - for long labels, split to multiple lines when necessary*/
if (labelSize.width>this._maxLabelWidth){
labelSize.height=labelSize.height*Math.ceil(labelSize.width/this._maxLabelWidth);
labelSize.width=this._maxLabelWidth;
}/*end of TN*/
The data is fetched from the EPrints powered e-artexte repository using PHP’s cURL. Copy the URL from the browser address bar, into the $url variable of the PHP script, after performing an advanced search for keywords photography or photographie, date 1960-, and primary item type of exhibition catalogue in e-artexte, exported using JSON format.
The <head> of the main e-artexte-1.html file loads the PHP generated JavaScript file using a jquery ajax request added to the window.onload :
window.onload= function(){
$.ajax({
url: 'EPrintsData.php',
dataType: 'script',
cache: 'true',
error: function(data){
$('#loading').html('Failed to load data for some reason. Please try again by clicking reload on your browser. ');
},
success: function(data) {
onLoad();
$('#loading').html('');
$.ajax({
url:'fileLastUpdate.php',
dataType: 'text',
success: function(data){
$('#fileLastUpdate').html(data);
}
});
}
});
}
This ajax call ensures that upon successful retrieval of the EPrintsData, the custom onLoad() function that generates the timeline is called, and the loader is removed from the display. A subsequent ajax call to update the “data last update” information in the div with id=fileLastUpdate on the bottom of the page is performed at the end. EPrintsData.php file generates a JavaScript variable timeline_data based on the downloaded cached copy of the search results from e-artexte.. The PHP script downloads a fresh copy of the search results if the cached eartextedata.js file is older than 30 days.
<?php
set_time_limit(120);
header('Content-type: text/javascript');
$file = "eartextedata.js";
$offset = 60 * 60 * 24 * 30;
if (file_exists($file)) {
$expires=gmdate("D, d M Y H:i:s", filemtime($file) + $offset) . " GMT";
header("Expires: " . $expires);
$cacheMaxAge=(filemtime($file) + $offset)-time();
header("Cache-Control: max-age=$cacheMaxAge, must-revalidate");
}
echo('
var timeline_data = { // save as a global variable
\'dateTimeFormat\': \'iso8601\',
events :');
$cache_file=$file;
// seconds * minutes * hours * days
if (file_exists($cache_file) && (filemtime($cache_file) > (time() - $offset )) && (filesize($cache_file)!=0)) {
//cache file is OK, do nothing
}
else {
// Our cache is out-of-date or 0, so load the data from our remote server,
$url = 'http://e-artexte.ca/cgi/search/advanced/export_artexte_JSON.js?screen=Public%3A%3AEPrintSearch&_action_export=1&output=JSON&exp=0%7C1%7C-date%2Fcontributors_name%2Ftitle%7Carchive%7C-%7Cdate%3Adate%3AALL%3AEQ%3A1960-%7Ckw%3Akw%3AANY%3AIN%3Aphotographie+photography%7Ctype_pri%3Atype_pri%3AANY%3AEQ%3Atype_7%7C-%7Ceprint_status%3Aeprint_status%3AALL%3AEQ%3Aarchive%7Cmetadata_visibility%3Ametadata_visibility%3AALL%3AEX%3Ashow&n=';
$path = 'eartextedata.js';
$fp = fopen($path, 'w');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_FILE, $fp);
$data = curl_exec($ch);
curl_close($ch);
fclose($fp);
}
//output cached file as JavaScript
if(file_exists($file) && is_readable($file)){
$fileH = fopen($file,'r');
while(!feof($fileH)) {
$name = fgets($fileH);
echo($name);
}
fclose($fileH);
}
else{
echo('error loading data file');
}
echo('}');
?>
The timeline is initialized in e-artexte-1.html file with a series of variables set in the custom onLoad() function that is called after the EPrintsData script is loaded. Each exhibition catalogue has a publication date which results in an “instant” timeline event rather than a time period with both a start and end date. The date format is set to ISO8601, the initial date to display is set to 1967, and the overall timeline is set to cover 1960 to 2020.
var tl;
function onLoad() {
var eventSource
= new Timeline.DefaultEventSource(0);
var theme = Timeline.ClassicTheme.create();
theme.event.instant.icon = "timeline_2.3.1/images/dark-green-circle.png";
theme.event.instant.iconWidth =
10; //
These are for the default stand-alone icon
theme.event.instant.iconHeight =
10;
var d =
Timeline.DateTime.parseIso8601DateTime("1967");
theme.timeline_start
= new Date(Date.UTC(1960, 0, 1));
theme.timeline_stop = new Date(Date.UTC(2020, 0, 1));
The timeline is created with two calls to Timeline.createBandInfo, the first for YEAR, and the second for DECADE. The YEAR band is initialized with a 600 px interval width and a series of parameters for the Timeline.CompactEventPainter, such as the 20 maximum number of concurrent events to display and the 500 px maxLabelWidth. The latter is a custom addition added to improve the display of the long titles in e-artexte. The second band is a much simpler ‘overview’ layout initializing the additional decade browser.
var bandInfos
= [
Timeline.createBandInfo({
width:
"90%",
intervalUnit: Timeline.DateTime.YEAR,
intervalPixels: 600,
eventSource: eventSource,
date:
d,
theme:
theme,
eventPainter: Timeline.CompactEventPainter,
eventPainterParams: {
iconLabelGap: 5,
labelRightMargin: 20,
maxLabelWidth: 500, // TN: MaxLabelWidth
iconWidth: 80, // These are for
per-event custom icons
iconHeight: 80,
stackConcurrentPreciseInstantEvents:
{
limit: 20,
moreMessageTemplate: "[%0
More]",
icon: "timeline_2.3.1/images/dark-green-circle.png", // default icon
in stacks
iconWidth: 10,
iconHeight: 10
}
}
}),
Timeline.createBandInfo({
width:
"10%",
intervalUnit: Timeline.DateTime.DECADE,
intervalPixels: 120,
eventSource: eventSource,
date:
d,
theme:
theme,
layout:
'overview' // original, overview, detailed
})
];
bandInfos[1].syncWith
= 0;
bandInfos[1].highlight = true;
tl = Timeline.create(document.getElementById("tl"), bandInfos, Timeline.HORIZONTAL);
var url
= '.';
// The base url
for image, icon and background image
eventSource.loadJSON(timeline_data,
url);
tl.layout();
}
Using the latest release (2.3.1) of SIMILE libraries, the CompactPainter can handle large amounts of data and display a vertical scrollbar if the amount of data displayed requires it.
The timeline is displayed in the <body> using the following:
<div id="tl" class="timeline-default" style="height: 450px;"></div>
This research was supported by Social Sciences and Humanities Research Council of Canada, the Conseil des arts et des lettres Québec, and Concordia University Libraries.