├── .gitignore
├── LICENSE.txt
├── README.md
├── app
├── assets
│ ├── javascripts
│ │ ├── admin-utils.js
│ │ ├── build.js
│ │ ├── common
│ │ │ ├── autocomplete.js
│ │ │ ├── densityGrid.js
│ │ │ ├── draggable.js
│ │ │ ├── formatting.js
│ │ │ ├── hasEvents.js
│ │ │ └── heatmapLayer.js
│ │ ├── heatmap.js
│ │ ├── map.js
│ │ ├── page-utils.js
│ │ ├── peripleo-ui.js
│ │ ├── peripleo-ui
│ │ │ ├── api.js
│ │ │ ├── controls
│ │ │ │ ├── autoSuggest.js
│ │ │ │ ├── filter
│ │ │ │ │ ├── facetChart.js
│ │ │ │ │ ├── facetFilterParser.js
│ │ │ │ │ ├── filterEditor.js
│ │ │ │ │ ├── filterPanel.js
│ │ │ │ │ └── timeHistogram.js
│ │ │ │ ├── imageControl.js
│ │ │ │ ├── resultList.js
│ │ │ │ ├── searchAtButton.js
│ │ │ │ ├── searchPanel.js
│ │ │ │ ├── selection
│ │ │ │ │ ├── selectedItem.js
│ │ │ │ │ ├── selectedPlace.js
│ │ │ │ │ └── selectionInfo.js
│ │ │ │ ├── settings
│ │ │ │ │ └── settingsEditor.js
│ │ │ │ └── toolbar.js
│ │ │ ├── events
│ │ │ │ ├── eventBroker.js
│ │ │ │ ├── events.js
│ │ │ │ └── lifecycleWatcher.js
│ │ │ ├── map
│ │ │ │ ├── densityGrid.js
│ │ │ │ ├── map.js
│ │ │ │ └── objectLayer.js
│ │ │ └── urlBar.js
│ │ ├── place-map.js
│ │ ├── place-network.js
│ │ ├── placeAdjacencyNetwork.js
│ │ ├── temporal-profile.js
│ │ └── time-histogram.js
│ └── stylesheets
│ │ ├── admin
│ │ └── main.less
│ │ ├── base-layout.less
│ │ ├── common
│ │ ├── autocomplete.less
│ │ ├── paginate.less
│ │ ├── pagination.less
│ │ └── table.less
│ │ ├── datasets
│ │ ├── dataset-details.less
│ │ └── dataset-list.less
│ │ ├── gazetteer-uri.less
│ │ ├── gazetteer
│ │ └── main.less
│ │ ├── globals-new.less
│ │ ├── globals.less
│ │ ├── home
│ │ └── index.less
│ │ ├── items
│ │ └── item-details.less
│ │ ├── map-new
│ │ ├── _autoComplete.less
│ │ ├── _facetChart.less
│ │ ├── _filterEditor.less
│ │ ├── _filterPanel.less
│ │ ├── _gazetteerUri.less
│ │ ├── _gazetteerUriEARK.less
│ │ ├── _imageControl.less
│ │ ├── _modalEditor.less
│ │ ├── _searchPanel.less
│ │ ├── _searchResults.less
│ │ ├── _selectionInfo.less
│ │ ├── _settingsEditor.less
│ │ ├── _timeHistogram.less
│ │ ├── _toolbar.less
│ │ └── main.less
│ │ ├── map
│ │ ├── _autocomplete.less
│ │ ├── _facet-graph.less
│ │ └── main.less
│ │ ├── places
│ │ └── place-details.less
│ │ └── search
│ │ ├── _filterpanel.less
│ │ ├── _map.less
│ │ ├── _pagination.less
│ │ ├── _searchresults.less
│ │ └── main.less
├── controllers
│ ├── AbstractController.scala
│ ├── AnnotatedThingController.scala
│ ├── AnnotationController.scala
│ ├── DatasetController.scala
│ ├── PlaceController.scala
│ ├── SearchController.scala
│ ├── admin
│ │ ├── AnalyticsController.scala
│ │ ├── AuthController.scala
│ │ ├── BaseUploadController.scala
│ │ ├── DatasetAdminController.scala
│ │ └── GazetteerAdminController.scala
│ ├── common
│ │ └── JSONWrites.scala
│ ├── experimental
│ │ └── ExperimentalPagesController.scala
│ └── pages
│ │ ├── AnnotatedThingPagesController.scala
│ │ ├── DatasetPagesController.scala
│ │ ├── LandingPageController.scala
│ │ └── PlacePagesController.scala
├── global
│ ├── Global.scala
│ └── housekeeping
│ │ └── AcessLogArchiver.scala
├── index
│ ├── FacetTree.scala
│ ├── Heatmap.scala
│ ├── Index.scala
│ ├── IndexFields.scala
│ ├── NGramAnalyzer.scala
│ ├── NumberRangePrefixTreeStrategy.java
│ ├── SearchParameters.scala
│ ├── TimeHistogram.scala
│ ├── annotations
│ │ ├── AnnotationReader.scala
│ │ ├── AnnotationWriter.scala
│ │ └── IndexedAnnotation.scala
│ ├── objects
│ │ ├── IndexedObject.scala
│ │ ├── ObjectReader.scala
│ │ └── ObjectWriter.scala
│ ├── places
│ │ ├── IndexedPlace.scala
│ │ ├── IndexedPlaceNetwork.scala
│ │ ├── PlaceReader.scala
│ │ └── PlaceWriter.scala
│ └── suggest
│ │ └── SuggestIndex.scala
├── ingest
│ ├── AbstractImporter.scala
│ ├── CSVImporter.scala
│ ├── PelagiosOAImporter.scala
│ ├── TEImporter.scala
│ ├── VoIDImporter.scala
│ └── harvest
│ │ ├── DataHarvestWorker.scala
│ │ ├── DataHarvester.scala
│ │ ├── GazetteerImporter.scala
│ │ └── Messages.scala
├── models
│ ├── AccessLog.scala
│ ├── Associations.scala
│ ├── HarvestLog.scala
│ ├── ImportStatus.scala
│ ├── Page.scala
│ ├── Taxonomy.scala
│ ├── adjacency
│ │ ├── PlaceAdjacency.scala
│ │ └── PlaceAdjacencyGraph.scala
│ ├── core
│ │ ├── AnnotatedThing.scala
│ │ ├── Annotation.scala
│ │ ├── Dataset.scala
│ │ ├── Image.scala
│ │ ├── Tag.scala
│ │ └── TemporalProfile.scala
│ └── geo
│ │ ├── BoundingBox.scala
│ │ ├── Gazetteer.scala
│ │ ├── GazetteerReference.scala
│ │ └── Hull.scala
└── views
│ ├── admin
│ ├── accessLog.scala.html
│ ├── datasets.scala.html
│ ├── gazetteers.scala.html
│ └── login.scala.html
│ ├── annotatedThingDetails.scala.html
│ ├── datasetDetails.scala.html
│ ├── datasetList.scala.html
│ ├── home.scala.html
│ ├── landingPage.scala.html
│ ├── map.scala.html
│ ├── placeAdjacencyHack.scala.html
│ ├── placeDetails.scala.html
│ ├── showGazetteer.scala.html
│ └── tags
│ ├── gazetteerURI.scala.html
│ └── timespan.scala.html
├── build.sbt
├── conf
├── .gitignore
├── application.conf.template
└── routes
├── db
└── .gitignore
├── gazetteer
├── .gitignore
├── dare-2015-1014.ttl.gz
└── pleiades-201207-migrated.ttl.gz
├── lib
└── concave_hull.jar
├── project
├── build.properties
└── plugins.sbt
├── public
├── images
│ ├── cc-by-nc.png
│ ├── cc-zero.png
│ ├── favicon.png
│ ├── open-data-generic.png
│ ├── wait-anim-fb.gif
│ └── wait-circle.gif
├── javascripts
│ └── lib
│ │ ├── chart
│ │ └── chart.min.js
│ │ ├── d3.v3.min.js
│ │ ├── heatmap
│ │ ├── heatmap.min.js
│ │ └── leaflet-heatmap.js
│ │ ├── jquery-1.9.0.min.js
│ │ ├── jquery-ui.min.js
│ │ ├── jquery.twbsPagination.min.js
│ │ ├── jquery.ui.touch-punch.min.js
│ │ ├── leaflet
│ │ ├── images
│ │ │ ├── layers-2x.png
│ │ │ ├── layers.png
│ │ │ ├── marker-icon-2x.png
│ │ │ ├── marker-icon-blue-2x.png
│ │ │ ├── marker-icon-blue.png
│ │ │ ├── marker-icon.png
│ │ │ └── marker-shadow.png
│ │ ├── leaflet-canvasOverlay.js
│ │ ├── leaflet-heat.js
│ │ ├── leaflet.css
│ │ └── leaflet.js
│ │ ├── moment.min.js
│ │ ├── numeral.min.js
│ │ ├── tinycolor.js
│ │ ├── typeahead
│ │ └── typeahead.jquery.min.js
│ │ └── velocity.min.js
└── stylesheets
│ ├── Cinzel-Regular.ttf
│ ├── Roboto-Light.ttf
│ ├── Roboto-Regular.ttf
│ ├── fontawesome-webfont.ttf
│ ├── fontello-icons.ttf
│ └── peripleo.ttf
├── test-scenarios.md
├── test
├── ApplicationSpec.scala
├── IntegrationSpec.scala
├── index
│ └── places
│ │ └── IndexedPlaceNetworkSpec.scala
├── ingest
│ └── TEIImporterSpec.scala
├── models
│ └── TaxonomySpec.scala
└── resources
│ ├── sample-short.tei.xml
│ └── sample.tei.xml
└── todo.md
/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | project/project
3 | project/target
4 | target
5 | tmp
6 | .history
7 | dist
8 | /.idea
9 | /*.iml
10 | /out
11 | /.idea_modules
12 | /.classpath
13 | /.project
14 | /RUNNING_PID
15 | /.settings
16 | /.target
17 | /bin
18 | /.cache
19 | /lib/scalagios-*
20 | /index
21 | /rebuild-scalagios.sh
22 | .cache-*
23 |
--------------------------------------------------------------------------------
/app/assets/javascripts/admin-utils.js:
--------------------------------------------------------------------------------
1 | var util = util || {};
2 |
3 | /** Enables the file upload buttons **/
4 | util.enableUploads = function() {
5 | // Wire CSS-styled upload button to hidden file input
6 | $(document).on('click', '.upload', function(e) {
7 | var inputId = e.target.getAttribute('data-input');
8 | $('#' + inputId).click();
9 | });
10 |
11 | // Set the hidden inputs to auto-submit
12 | $(document).on('change', 'input:file', function(e) {
13 | e.target.parentNode.submit();
14 | });
15 | }
--------------------------------------------------------------------------------
/app/assets/javascripts/build.js:
--------------------------------------------------------------------------------
1 | require.config({
2 | baseUrl: "/assets/javascripts",
3 | fileExclusionRegExp: /^lib$/
4 | });
5 |
--------------------------------------------------------------------------------
/app/assets/javascripts/common/autocomplete.js:
--------------------------------------------------------------------------------
1 | define(function() {
2 |
3 | var AutoComplete = function(form, input) {
4 | input.typeahead({
5 | hint: true,
6 | highlight: true,
7 | minLength: 1
8 | },{
9 | displayKey: 'key',
10 | source: function(query, callback) {
11 | jQuery.getJSON('/api-v3/new/autosuggest?q=' + query, function(results) {
12 | callback(results);
13 | });
14 | }
15 | });
16 |
17 | input.on('typeahead:selected', function(e) {
18 | form.submit();
19 | });
20 | };
21 |
22 | return AutoComplete;
23 |
24 | });
25 |
--------------------------------------------------------------------------------
/app/assets/javascripts/common/densityGrid.js:
--------------------------------------------------------------------------------
1 | define(function() {
2 |
3 | var densityGridLayer = function() {
4 |
5 | var isHidden = false,
6 |
7 | render = function(canvasOverlay, params) {
8 | if (!params.options.heatmap)
9 | return;
10 |
11 | var ctx = params.canvas.getContext('2d');
12 | ctx.clearRect(0, 0, params.canvas.width, params.canvas.height);
13 | ctx.fillStyle = '#5254a3';
14 | ctx.strokeStyle = '#5254a3';
15 |
16 | // Hack!
17 | var heatmap = params.options.heatmap,
18 | weights = jQuery.map(heatmap.cells, function(val) { return val.weight; });
19 | mean = weights.reduce(function(a, b) { return a + b; }, 0) / weights.length,
20 | maxWeight = heatmap.max_value,
21 |
22 | xyOrigin = canvasOverlay._map.latLngToContainerPoint([0, 0]),
23 | xyOneCell = canvasOverlay._map.latLngToContainerPoint([1.40625, 1.40625]), // TODO grab info from heatmap JSON
24 | cellHalfDimensions = { x: (xyOneCell.x - xyOrigin.x) / 2, y: (xyOrigin.y - xyOneCell.y) / 2 };
25 |
26 | var classified = jQuery.map(heatmap.cells, function(val) {
27 | return { c: val, is_outlier: val.weight > 5 * mean };
28 | });
29 |
30 | jQuery.each(classified, function(idx, tuple) {
31 | var x = tuple.c.x, y = tuple.c.y,
32 | delta = Math.sqrt(tuple.c.weight / (5 * mean), 2),
33 | bottomLeft = canvasOverlay._map.latLngToContainerPoint([y + heatmap.cell_height / 2, x - heatmap.cell_width / 2]);
34 | topRight = canvasOverlay._map.latLngToContainerPoint([y - heatmap.cell_height / 2, x + heatmap.cell_width / 2]);
35 | width = topRight.x - bottomLeft.x,
36 | height = topRight.y - bottomLeft.y;
37 |
38 | if (tuple.is_outlier)
39 | ctx.globalAlpha = 0.9;
40 | else
41 | ctx.globalAlpha = 0.1 + delta * 0.7;
42 | ctx.fillRect(bottomLeft.x, bottomLeft.y, width, height);
43 | ctx.globalAlpha = 0.2;
44 | ctx.strokeRect(bottomLeft.x, bottomLeft.y, width, height);
45 | });
46 | },
47 |
48 | canvasOverlay = L.canvasOverlay().drawing(render);
49 |
50 | /** Privileged methods **/
51 | this.update = function(heatmap) {
52 | canvasOverlay.params({ heatmap: heatmap });
53 | canvasOverlay.redraw();
54 | };
55 |
56 | this.hideasdf = function() {
57 | if (!isHidden) {
58 | map.removeLayer(canvasOverlay);
59 | isHidden = true;
60 | }
61 | };
62 |
63 | this.showasdf = function() {
64 | if (isHidden) {
65 | map.addLayer(canvasOverlay);
66 | isHidden = false;
67 | }
68 | };
69 |
70 | this.adsaddTo = function(map) {
71 | canvasOverlay.addTo(map);
72 | return this; // Just to mimick with Leaflet's API
73 | };
74 |
75 | };
76 |
77 | return densityGridLayer;
78 |
79 | });
80 |
--------------------------------------------------------------------------------
/app/assets/javascripts/common/draggable.js:
--------------------------------------------------------------------------------
1 | define(function() {
2 |
3 | /** Flag that indicates wether the device supports touch events **/
4 | var hasTouch = ('ontouchstart' in window) || (navigator.MaxTouchPoints > 0),
5 |
6 | makeTouchXDraggable = function(element, onDrag, onStop, opt_containment) {
7 | element.bind('touchmove', function(e) {
8 | console.log(e);
9 | });
10 | };
11 |
12 | return {
13 |
14 | makeXDraggable: function(element, onDrag, onStop, opt_containment) {
15 | if (hasTouch)
16 | makeTouchXDraggable(element, onDrag, onStop, opt_containment);
17 | else // TODO remove jQuery dependency once we have implemented our own touch code
18 | element.draggable({
19 | axis: 'x',
20 | containment: opt_containment,
21 | drag: onDrag,
22 | stop: onStop
23 | });
24 | }
25 |
26 | };
27 |
28 | /*
29 | * TODO mimic jQuery's draggable method:
30 | *
31 | * We need to constrain to X axis, provide onDrag/onStop callbacks and - possibly - a parent containment
32 | *
33 | *
34 | element.draggable({
35 | axis: 'x',
36 | containment: opt_containment,
37 | drag: onDrag,
38 | stop: onStop
39 | });
40 | */
41 |
42 | /*
43 | element.bind('touchmove', function(e) {
44 | console.log(e);
45 | });
46 | */
47 |
48 | });
49 |
--------------------------------------------------------------------------------
/app/assets/javascripts/common/hasEvents.js:
--------------------------------------------------------------------------------
1 | /** A generic pub-sub trait **/
2 | define(function() {
3 |
4 | /**
5 | * A simple base class that takes care of event subcription.
6 | * @constructor
7 | */
8 | var HasEvents = function() {
9 | this.handlers = {}
10 | }
11 |
12 | /**
13 | * Adds an event handler to this component. Refer to the docs of the components
14 | * for information about supported events.
15 | * @param {String} event the event name
16 | * @param {Function} handler the handler function
17 | */
18 | HasEvents.prototype.on = function(event, handler) {
19 | this.handlers[event] = handler;
20 | }
21 |
22 | /**
23 | * Fires an event.
24 | * @param {String} event the event name
25 | * @param {Object} e the event object
26 | * @param {Object} args the event arguments
27 | */
28 | HasEvents.prototype.fireEvent = function(event, e, args) {
29 | if (this.handlers[event])
30 | this.handlers[event](e, args);
31 | }
32 |
33 | return HasEvents;
34 |
35 | });
36 |
--------------------------------------------------------------------------------
/app/assets/javascripts/common/heatmapLayer.js:
--------------------------------------------------------------------------------
1 | define(function() {
2 |
3 | var HeatmapLayer = function() {
4 | var heatmapLayer = L.heatLayer([], { radius: 30 });
5 |
6 |
7 | this.layer = heatmapLayer;
8 |
9 | this.update = function(heatmap) {
10 | var weights = jQuery.map(heatmap, function(val) { return val.weight; }),
11 | maxWeight = Math.max.apply(Math, weights),
12 | points = [];
13 |
14 | jQuery.each(heatmap, function(idx, val) {
15 | points.push([ val.y, val.x, val.weight ]);
16 | });
17 |
18 | heatmapLayer.setLatLngs(points);
19 | };
20 |
21 | };
22 |
23 | return HeatmapLayer;
24 |
25 | });
26 |
--------------------------------------------------------------------------------
/app/assets/javascripts/heatmap.js:
--------------------------------------------------------------------------------
1 | window.Heatmap = function(mapId, places) {
2 | var awmcLayer = L.tileLayer('http://a.tiles.mapbox.com/v3/isawnyu.map-knmctlkh/{z}/{x}/{y}.png', {
3 | attribution: 'Tiles and Data © 2013 AWMC ' +
4 | 'CC-BY-NC 3.0'});
5 |
6 | this.map = new L.Map(mapId, {
7 | center: new L.LatLng(41.893588, 12.488022),
8 | zoom: 3,
9 | layers: [awmcLayer]
10 | });
11 |
12 | var latlngs = [];
13 | $.each(places, function(idx, place) {
14 | if (place.centroid)
15 | latlngs.push([place.centroid.lat, place.centroid.lon]);
16 | });
17 | L.heatLayer(latlngs, { max: 1.5, maxZoom: 0, radius: 5, blur: 6 }).addTo(this.map);
18 | };
19 |
20 | window.Heatmap.prototype.refresh = function() {
21 | this.map.invalidateSize();
22 | };
23 |
--------------------------------------------------------------------------------
/app/assets/javascripts/map.js:
--------------------------------------------------------------------------------
1 | require(['common/autocomplete', 'common/densityGrid', 'common/timeHistogram'], function(AutoComplete, DensityGrid, TimeHistogram) {
2 |
3 | jQuery(document).ready(function() {
4 | var timeHistogram = new TimeHistogram('time-histogram', function(interval) {
5 | queryFilters.timespan = interval;
6 | update();
7 | refreshHeatmap();
8 | }),
9 |
10 | autoComplete = new AutoComplete(searchForm, searchInput);
11 |
12 | /** Helper to parse the source facet label **/
13 | parseSourceFacetLabel = function(labelAndId) {
14 | var separatorIdx = labelAndId.indexOf('#'),
15 | label = labelAndId.substring(0, separatorIdx),
16 | id = labelAndId.substring(separatorIdx);
17 |
18 | return { label: label, id: id };
19 | },
20 |
21 | search = function() {
22 | queryFilters.query = searchInput.val();
23 | searchInput.blur();
24 | update();
25 | refreshHeatmap();
26 | return false; // preventDefault + stopPropagation
27 | };
28 | });
29 |
30 | });
31 |
--------------------------------------------------------------------------------
/app/assets/javascripts/page-utils.js:
--------------------------------------------------------------------------------
1 | var util = util || {};
2 |
3 | /** Loops through all elements with CSS class .number and formats using numeral.js **/
4 | util.formatNumbers = function(opt_parent) {
5 | var elements = (opt_parent) ? $(opt_parent).find('.number') : $('.number');
6 | $.each(elements, function(idx, el) {
7 | var formatted = numeral($(el).text()).format('0,0');
8 | $(el).html(formatted);
9 | });
10 | };
11 |
12 | /** Renders an image icon corresponding to a specific license URL **/
13 | util.licenseIcon = function(url) {
14 | if (url.indexOf('http://opendatacommons.org/licenses/odbl') == 0) {
15 | return '
';
16 | } else if (url.indexOf('http://creativecommons.org/publicdomain/zero/1.0') == 0) {
17 | return '
';
18 | } else if (url.indexOf('http://creativecommons.org/licenses/by-nc') == 0) {
19 | return '
';
20 | } else {
21 | return '' + url + '';
22 | }
23 | };
24 |
25 | util.formatGazetteerURI = function(uri) {
26 | if (uri.indexOf('http://pleiades.stoa.org/places/') > -1) {
27 | return 'pleiades:' + uri.substr(32);
28 | } else if (uri.indexOf('http://dare.ht.lu.se/places/') > -1) {
29 | return 'dare:' + uri.substr(28);
30 | } else if (uri.indexOf('http://gazetteer.dainst.org/place/') > -1) {
31 | return 'dai:' + uri.substr(34);
32 | } else if (uri.indexOf('http://sws.geonames.org/') > -1) {
33 | return 'geonames:' + uri.substr(24);
34 | } else if (uri.indexOf('http://vici.org/vici') > -1) {
35 | return 'vici:' + uri.substr(21);
36 | } else if (uri.indexOf('http://www.trismegistos.org/place/') > -1) {
37 | return 'trismegistos:' + uri.substr(34);
38 | } else if (uri.indexOf('http://nomisma.org/') > -1) {
39 | return 'nomisma:' + uri.substr(22);
40 | } else if (uri.indexOf('http://data.pastplace.org') > -1) {
41 | return 'pastplace:' + uri.substr(35);
42 | } else if (uri.indexOf('http://www.wikidata.org/entity') > -1) {
43 | return 'wikidata:' + uri.substr(32);
44 | } else {
45 | return uri;
46 | }
47 | };
48 |
49 | /** From http://www.samaxes.com/2011/09/change-url-parameters-with-jquery/ **/
50 | util.buildPageRequestURL = function(offset, limit) {
51 | var queryParameters = {},
52 | queryString = location.search.substring(1).replace(/\+/g, ' '),
53 | re = /([^&=]+)=([^&]*)/g,
54 | m;
55 |
56 | while (m = re.exec(queryString)) {
57 | queryParameters[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
58 | }
59 |
60 | queryParameters['offset'] = offset;
61 | queryParameters['limit'] = limit;
62 |
63 | return $.param(queryParameters);
64 | };
65 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peripleo-ui.js:
--------------------------------------------------------------------------------
1 | require(['peripleo-ui/controls/settings/settingsEditor',
2 | 'peripleo-ui/controls/resultList',
3 | 'peripleo-ui/controls/searchPanel',
4 | 'peripleo-ui/controls/toolbar',
5 | 'peripleo-ui/events/events',
6 | 'peripleo-ui/events/eventBroker',
7 | 'peripleo-ui/events/lifecycleWatcher',
8 | 'peripleo-ui/map/map',
9 | 'peripleo-ui/api',
10 | 'peripleo-ui/urlBar'], function(SettingsEditor, ResultList, SearchPanel, Toolbar, Events, EventBroker, LifeCycleWatcher, Map, API, URLBar) {
11 |
12 | jQuery(document).ready(function() {
13 | /** DOM element shorthands **/
14 | var mapDIV = document.getElementById('map'),
15 | controlsDIV = jQuery('#controls'),
16 | toolbarDIV = jQuery('#toolbar'),
17 |
18 | /** Top-level components **/
19 | eventBroker = new EventBroker(),
20 | lifeCycleWatcher = new LifeCycleWatcher(eventBroker),
21 | urlBar = new URLBar(eventBroker),
22 | api = new API(eventBroker),
23 | map = new Map(mapDIV, eventBroker),
24 | toolbar = new Toolbar(toolbarDIV, eventBroker),
25 | searchPanel = new SearchPanel(controlsDIV, eventBroker),
26 | resultList = new ResultList(controlsDIV, eventBroker),
27 | settingsEditor = new SettingsEditor(eventBroker),
28 |
29 | /** Resolve the URL bar **/
30 | initialSettings = urlBar.parseURLHash(window.location.hash),
31 |
32 | /** Is there an initially selected place? Fetch it now! **/
33 | fetchInitiallySelectedPlace = function(encodedURL, zoomTo) {
34 | jQuery.getJSON('/peripleo/places/' + encodedURL, function(response) {
35 | eventBroker.fireEvent(Events.SELECT_RESULT, [ response ]);
36 | if (zoomTo)
37 | map.zoomTo(response.geo_bounds);
38 | });
39 | },
40 |
41 | /** Is there an initial query phrase? Fetch the results now **/
42 | runInitialQuery = function(query) {
43 | eventBroker.fireEvent(Events.SEARCH_CHANGED, { query: query });
44 | },
45 |
46 | /** Initializes map center and zoom **/
47 | initializeMap = function(at) {
48 | map.setView([ at.lat, at.lng ], at.zoom);
49 | };
50 |
51 | eventBroker.fireEvent(Events.LOAD, initialSettings);
52 |
53 | if (initialSettings.at)
54 | initializeMap(initialSettings.at);
55 |
56 | if (initialSettings.query)
57 | runInitialQuery(initialSettings.query);
58 |
59 | if (initialSettings.places)
60 | fetchInitiallySelectedPlace(initialSettings.places, !initialSettings.hasOwnProperty('at'));
61 |
62 | if (initialSettings.ex)
63 | eventBroker.fireEvent(Events.START_EXPLORATION);
64 |
65 | delete initialSettings.at;
66 | });
67 |
68 | });
69 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peripleo-ui/controls/autoSuggest.js:
--------------------------------------------------------------------------------
1 | define(function() {
2 |
3 | var AutoSuggest = function(form, input) {
4 | input.typeahead({
5 | hint: false,
6 | highlight: true,
7 | minLength: 1
8 | },{
9 | displayKey: 'val',
10 | source: function(query, callback) {
11 | jQuery.getJSON('/peripleo/autocomplete?q=' + query, function(results) {
12 | callback(results);
13 | });
14 | }
15 | });
16 |
17 | input.on('typeahead:selected', function(e) {
18 | form.submit();
19 | });
20 |
21 |
22 | this.clear = function() {
23 | input.typeahead('val','');
24 | };
25 | };
26 |
27 | return AutoSuggest;
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peripleo-ui/controls/selection/selectedItem.js:
--------------------------------------------------------------------------------
1 | define(['common/formatting',
2 | 'peripleo-ui/controls/selection/selectionInfo',
3 | 'peripleo-ui/events/events'], function(Formatting, SelectionInfo, Events) {
4 |
5 | var SLIDE_DURATION = 180;
6 |
7 | var SelectedItem = function(container, eventBroker) {
8 | var self = this,
9 |
10 | content = jQuery(
11 | '
' +
12 | '
' +
13 | '
' +
14 | ' ' +
15 | ' ' +
16 | '
' +
17 | '
' +
18 | '
' +
19 | '
' +
20 | '
'),
21 |
22 | /** DOM element shorthands **/
23 | heading = content.find('h3'),
24 | tempBounds = content.find('.temp-bounds'),
25 | names = content.find('.names'),
26 | description = content.find('.description'),
27 | homepage = content.find('.homepage'),
28 | snippets = content.find('.snippets'),
29 |
30 | /** Clears the contents **/
31 | clearContent = function() {
32 | heading.empty();
33 | tempBounds.empty();
34 | tempBounds.hide();
35 | names.empty();
36 | description.empty();
37 | homepage.empty();
38 | snippets.empty();
39 | },
40 |
41 | /** Fills the content **/
42 | fill = function(obj) {
43 | heading.html(obj.title);
44 |
45 | if (obj.temporal_bounds) {
46 | if (obj.temporal_bounds.start === obj.temporal_bounds.end)
47 | tempBounds.html(Formatting.formatYear(obj.temporal_bounds.start));
48 | else
49 | tempBounds.html(Formatting.formatYear(obj.temporal_bounds.start) + ' - ' + Formatting.formatYear(obj.temporal_bounds.end));
50 | tempBounds.show();
51 | }
52 |
53 | if (obj.description)
54 | description.html(obj.description);
55 |
56 | if (obj.homepage)
57 | homepage.append(Formatting.formatSourceURL(obj.homepage));
58 |
59 | if (obj.snippet)
60 | snippets.append(obj.snippet);
61 | };
62 |
63 | container.append(content);
64 | SelectionInfo.apply(this, [ container, eventBroker, fill, clearContent ]);
65 |
66 | eventBroker.addHandler(Events.SELECTION, function(results) {
67 | var firstResultType = (results) ? results[0].object_type : false;
68 | if (firstResultType === 'Item')
69 | self.show(results[0]);
70 | else
71 | self.hide();
72 | });
73 | };
74 |
75 | SelectedItem.prototype = Object.create(SelectionInfo.prototype);
76 |
77 | return SelectedItem;
78 |
79 | });
80 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peripleo-ui/controls/selection/selectedPlace.js:
--------------------------------------------------------------------------------
1 | define(['common/formatting',
2 | 'peripleo-ui/controls/selection/selectionInfo',
3 | 'peripleo-ui/events/events'], function(Formatting, SelectionInfo, Events) {
4 |
5 | var SLIDE_DURATION = 180;
6 |
7 | var SelectedPlace = function(container, eventBroker) {
8 | var self = this,
9 |
10 | content = jQuery(
11 | '' +
12 | '
' +
13 | '
' +
14 | ' ' +
15 | ' ' +
16 | '
' +
17 | '
' +
18 | '
' +
19 | '
' +
20 | '
'),
21 |
22 | /** DOM element shorthands **/
23 | heading = content.find('h3'),
24 | tempBounds = content.find('.temp-bounds'),
25 | names = content.find('.names'),
26 | description = content.find('.description'),
27 | uris = content.find('.uris'),
28 |
29 | /** Clears the contents **/
30 | clearContent = function() {
31 | heading.empty();
32 | tempBounds.empty();
33 | tempBounds.hide();
34 | names.empty();
35 | description.empty();
36 | uris.empty();
37 | },
38 |
39 | /** Fills the content **/
40 | fill = function(obj) {
41 | heading.html(obj.title);
42 |
43 | if (obj.temporal_bounds) {
44 | if (obj.temporal_bounds.start === obj.temporal_bounds.end)
45 | tempBounds.html(Formatting.formatYear(obj.temporal_bounds.start));
46 | else
47 | tempBounds.html(Formatting.formatYear(obj.temporal_bounds.start) + ' - ' + Formatting.formatYear(obj.temporal_bounds.end));
48 | tempBounds.show();
49 | }
50 |
51 | if (obj.names)
52 | names.html(obj.names.slice(0, 8).join(', '));
53 |
54 | if (obj.description)
55 | description.html(obj.description);
56 |
57 | uris.append(jQuery('' + Formatting.formatGazetteerURI(obj.identifier) + ''));
58 | if (obj.matches)
59 | jQuery.each(obj.matches, function(idx, uri) {
60 | uris.append(jQuery('' + Formatting.formatGazetteerURI(uri) + ''));
61 | });
62 | };
63 |
64 | container.append(content);
65 | SelectionInfo.apply(this, [ container, eventBroker, fill, clearContent ]);
66 |
67 | eventBroker.addHandler(Events.SELECT_MARKER, self.show);
68 | eventBroker.addHandler(Events.SELECT_RESULT, function(results) {
69 | var firstResultType = (results) ? results[0].object_type : false;
70 | if (firstResultType === 'Place')
71 | self.show(results[0]);
72 | });
73 | };
74 |
75 | SelectedPlace.prototype = Object.create(SelectionInfo.prototype);
76 |
77 | return SelectedPlace;
78 |
79 | });
80 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peripleo-ui/controls/selection/selectionInfo.js:
--------------------------------------------------------------------------------
1 | /** Common code for SelectedPlace and SelectedItem boxes **/
2 | define(function() {
3 |
4 | var SLIDE_DURATION = 180;
5 |
6 | var SelectionInfo = function(container, eventBroker, fill, clearContent) {
7 |
8 | var currentObject = false,
9 |
10 | slideDown = function() {
11 | container.velocity('slideDown', { duration: SLIDE_DURATION });
12 | },
13 |
14 | slideUp = function(opt_complete) {
15 | container.velocity('slideUp', {
16 | duration: SLIDE_DURATION,
17 | complete: function() {
18 | clearContent();
19 | if (opt_complete)
20 | opt_complete();
21 | }
22 | });
23 | },
24 |
25 | hide = function() {
26 | currentObject = false;
27 | slideUp();
28 | },
29 |
30 | show = function(objects) {
31 | // TODO support display of lists of objects, rather than just single one
32 | var obj = (jQuery.isArray(objects)) ? objects[0] : objects,
33 | currentType = (currentObject) ? currentObject.object_type : false;
34 |
35 | if (currentObject) { // Box is currently open
36 | if (!obj) { // Close it
37 | hide();
38 | } else {
39 | if (currentObject.identifier !== obj.identifier) { // New object - change
40 | currentObject = obj;
41 | slideUp(function() {
42 | fill(obj);
43 | slideDown();
44 | });
45 | }
46 | }
47 | } else { // Currently closed
48 | if (obj) { // Open it
49 | currentObject = obj;
50 | fill(obj);
51 | slideDown();
52 | }
53 | }
54 | };
55 |
56 | this.show = show;
57 | this.hide = hide;
58 | container.hide();
59 | };
60 |
61 | return SelectionInfo;
62 |
63 | });
64 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peripleo-ui/controls/toolbar.js:
--------------------------------------------------------------------------------
1 | define(['peripleo-ui/events/events'], function(Events) {
2 |
3 | var Toolbar = function(container, eventBroker) {
4 | var btnHelp =
5 | jQuery('
'),
6 |
7 | btnSettings =
8 | jQuery('
'),
9 |
10 | btnZoom = jQuery(
11 | ''),
15 |
16 | btnZoomIn = btnZoom.find('#toolbar-zoom-in'),
17 | btnZoomOut = btnZoom.find('#toolbar-zoom-out');
18 |
19 | btnSettings.click(function() { eventBroker.fireEvent(Events.EDIT_MAP_SETTINGS); });
20 | btnZoomIn.click(function() { eventBroker.fireEvent(Events.ZOOM_IN); });
21 | btnZoomOut.click(function() { eventBroker.fireEvent(Events.ZOOM_OUT); });
22 |
23 | container.append(btnHelp);
24 | container.append(btnSettings);
25 | container.append(btnZoom);
26 |
27 | };
28 |
29 | return Toolbar;
30 |
31 | });
32 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peripleo-ui/events/eventBroker.js:
--------------------------------------------------------------------------------
1 | /** A generic event broker implementation **/
2 | define(function() {
3 |
4 | var _handlers = [];
5 |
6 | /** A central event broker for communication between UI components **/
7 | var EventBroker = function(events) {
8 |
9 | this.events = events;
10 |
11 | };
12 |
13 | /** Adds an event handler **/
14 | EventBroker.prototype.addHandler = function(type, handler) {
15 | if (type) {
16 | if (!_handlers[type])
17 | _handlers[type] = [];
18 |
19 | _handlers[type].push(handler);
20 | } else {
21 | throw('Event type is undefined');
22 | }
23 | };
24 |
25 | /** Removes an event handler **/
26 | EventBroker.prototype.removeHandler = function(type, handler) {
27 | var handlers = _handlers[type];
28 | if (handlers) {
29 | var idx = handlers.indexOf(handler);
30 | handlers.splice(idx, 1);
31 | }
32 | };
33 |
34 | /** Fires an event **/
35 | EventBroker.prototype.fireEvent = function(type, opt_event) {
36 | if (!type)
37 | throw('Event type is undefined');
38 |
39 | var handlers = _handlers[type];
40 | if (handlers) {
41 | jQuery.each(handlers, function(idx, handler) {
42 | handler(opt_event);
43 | });
44 | }
45 | }
46 |
47 | return EventBroker;
48 |
49 | });
50 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peripleo-ui/events/lifecycleWatcher.js:
--------------------------------------------------------------------------------
1 | /** A helper that manages 'derivative events' in the Peripleo UI lifecycle **/
2 | define(['peripleo-ui/events/events'], function(Events) {
3 |
4 | var LifecycleWatcher = function(eventBroker) {
5 |
6 | /** Flag recording current search mode state **/
7 | var isStateSubsearch = false;
8 |
9 | /** SELECT_MARKER & SELECT_RESULT trigger parent SELECTION **/
10 | eventBroker.addHandler(Events.SELECT_MARKER, function(selection) {
11 | eventBroker.fireEvent(Events.SELECTION, selection);
12 | });
13 |
14 | eventBroker.addHandler(Events.SELECT_RESULT, function(results) {
15 | eventBroker.fireEvent(Events.SELECTION, results);
16 | });
17 |
18 | /**
19 | * When in subsearch state, selection of a new place or place de-selection
20 | * triggers TO_STATE_SEARCH
21 | */
22 | eventBroker.addHandler(Events.TO_STATE_SUB_SEARCH, function() {
23 | isStateSubsearch = true;
24 | });
25 |
26 | eventBroker.addHandler(Events.SELECT_MARKER, function(places) {
27 | eventBroker.fireEvent(Events.TO_STATE_SEARCH);
28 | });
29 |
30 | };
31 |
32 | return LifecycleWatcher;
33 |
34 | });
35 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peripleo-ui/map/densityGrid.js:
--------------------------------------------------------------------------------
1 | /** Pelagios' 'Density Grid' layer for Leaflet **/
2 | define(['peripleo-ui/events/events'], function(Events) {
3 |
4 | var DensityGrid = function(map, eventBroker) {
5 |
6 | var render = function(canvasOverlay, params) {
7 | var ctx = params.canvas.getContext('2d');
8 | ctx.clearRect(0, 0, params.canvas.width, params.canvas.height);
9 |
10 | if (!params.options.heatmap)
11 | return;
12 |
13 | ctx.fillStyle = '#5254a3';
14 | ctx.strokeStyle = '#5254a3';
15 |
16 | // Hack!
17 | var heatmap = params.options.heatmap,
18 | weights = jQuery.map(heatmap.cells, function(val) { return val.weight; });
19 | mean = weights.reduce(function(a, b) { return a + b; }, 0) / weights.length,
20 | maxWeight = heatmap.max_value,
21 |
22 | xyOrigin = canvasOverlay._map.latLngToContainerPoint([0, 0]),
23 | xyOneCell = canvasOverlay._map.latLngToContainerPoint([1.40625, 1.40625]), // TODO grab info from heatmap JSON
24 | cellHalfDimensions = { x: (xyOneCell.x - xyOrigin.x) / 2, y: (xyOrigin.y - xyOneCell.y) / 2 };
25 |
26 | var classified = jQuery.map(heatmap.cells, function(val) {
27 | return { c: val, is_outlier: val.weight > 5 * mean };
28 | });
29 |
30 | jQuery.each(classified, function(idx, tuple) {
31 | var x = tuple.c.x, y = tuple.c.y,
32 | delta = Math.sqrt(tuple.c.weight / (5 * mean), 2),
33 | bottomLeft = canvasOverlay._map.latLngToContainerPoint([y + heatmap.cell_height / 2, x - heatmap.cell_width / 2]),
34 | topRight = canvasOverlay._map.latLngToContainerPoint([y - heatmap.cell_height / 2, x + heatmap.cell_width / 2]),
35 | width = topRight.x - bottomLeft.x,
36 | height = topRight.y - bottomLeft.y;
37 |
38 | if (tuple.is_outlier) {
39 | ctx.globalAlpha = 1;
40 | } else {
41 | ctx.globalAlpha = 0.3 + delta * 0.5;
42 | }
43 |
44 | ctx.fillRect(bottomLeft.x, bottomLeft.y, width, height);
45 | ctx.globalAlpha = 0.2;
46 | ctx.strokeRect(bottomLeft.x, bottomLeft.y, width, height);
47 | });
48 | },
49 |
50 | canvasOverlay = L.canvasOverlay().drawing(render);
51 |
52 | eventBroker.addHandler(Events.TOGGLE_HEATMAP, function(args) {
53 | if (args.enabled) {
54 | canvasOverlay.addTo(map);
55 | } else {
56 | canvasOverlay.params({ heatmap: false });
57 | canvasOverlay.redraw();
58 | map.removeLayer(canvasOverlay);
59 | }
60 | });
61 |
62 | eventBroker.addHandler(Events.API_VIEW_UPDATE, function(response) {
63 | if (response.heatmap) {
64 | canvasOverlay.params({ heatmap: response.heatmap });
65 | window.setTimeout(function() {
66 | canvasOverlay.redraw();
67 | }, 1);
68 | }
69 | });
70 | };
71 |
72 | return DensityGrid;
73 |
74 | });
75 |
--------------------------------------------------------------------------------
/app/assets/javascripts/place-map.js:
--------------------------------------------------------------------------------
1 | window.PlaceMap = function(mapId, coord) {
2 | var awmcLayer = L.tileLayer('http://a.tiles.mapbox.com/v3/isawnyu.map-knmctlkh/{z}/{x}/{y}.png', {
3 | attribution: 'Tiles and Data © 2013 AWMC ' +
4 | 'CC-BY-NC 3.0'});
5 |
6 | var map = new L.Map(mapId, {
7 | center: coord,
8 | zoom: 7,
9 | layers: [awmcLayer]
10 | });
11 |
12 | L.marker(coord).addTo(map);
13 | };
14 |
--------------------------------------------------------------------------------
/app/assets/javascripts/place-network.js:
--------------------------------------------------------------------------------
1 | window.PlaceNetwork = function(divId, nodes, edges) {
2 | var div = $('#' + divId),
3 | width = div.width(),
4 | height = div.height(),
5 | innerNodes = $.grep(nodes, function(n) {
6 | return n.is_inner_node;
7 | }),
8 | innerEdges = $.grep(edges, function(e) {
9 | return e.is_inner_edge;
10 | });
11 |
12 | var force = d3.layout.force()
13 | .charge(-300)
14 | .linkDistance(80)
15 | .size([width, height])
16 | .nodes(nodes)
17 | .links(edges)
18 | .on('tick', function() {
19 | link
20 | .attr('x1', function(d) { return d.source.x; })
21 | .attr('y1', function(d) { return d.source.y; })
22 | .attr('x2', function(d) { return d.target.x; })
23 | .attr('y2', function(d) { return d.target.y; });
24 |
25 | node.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')' });
26 | });
27 |
28 | var svg = d3.select('#' + divId).append('svg')
29 | .attr('width', width)
30 | .attr('height', height);
31 |
32 | svg.append('defs').selectAll('marker')
33 | .data(['end'])
34 | .enter().append('marker')
35 | .attr('id', String)
36 | .attr('viewBox', '0 -5 10 10')
37 | .attr('refX', 16)
38 | .attr('refY', 0)
39 | .attr('markerWidth', 7)
40 | .attr('markerHeight', 9)
41 | .attr('orient', 'auto')
42 | .append('path')
43 | .attr('d', 'M0,-5L10,0L0,5');
44 |
45 | var link = svg.selectAll('.link')
46 | .data(innerEdges)
47 | .enter().append('line')
48 | .attr('class', function(d) {
49 | var t = nodes[d.target];
50 | if (t.label)
51 | return 'link'
52 | else
53 | return 'link virtual';
54 | })
55 | .attr('marker-end', 'url(#end)')
56 |
57 | var node = svg.selectAll('.node')
58 | .data(innerNodes)
59 | .enter().append('g')
60 | .attr('class', 'node')
61 | .call(force.drag);
62 |
63 | node.append('circle')
64 | .attr('r', 6)
65 | .attr('class', function(d) {
66 | if (d.source_gazetteer)
67 | return d.source_gazetteer.toLowerCase();
68 | else
69 | return 'virtual';
70 | });
71 |
72 | node.append('title')
73 | .text(function(d) { return (d.title) ? d.title : d.uri; });
74 |
75 | node.append('text')
76 | .attr('x', 12)
77 | .attr('dy', '.35em')
78 | .text(function(d) { return util.formatGazetteerURI(d.uri); });
79 |
80 | force.start();
81 | }
82 |
--------------------------------------------------------------------------------
/app/assets/javascripts/placeAdjacencyNetwork.js:
--------------------------------------------------------------------------------
1 | define(function() {
2 |
3 | jQuery(document).ready(function() {
4 | var div = $('#graph'),
5 | width = div.width(),
6 | height = div.height(),
7 |
8 | relatedLinks = jQuery.grep($('link'), function(element) { return $(element).attr('rel') == 'related'; }),
9 | dataURL = $(relatedLinks[0]).attr('href'),
10 |
11 | force = d3.layout.force()
12 | .charge(-100)
13 | .linkDistance(80)
14 | .size([width, height]),
15 |
16 | zoom = d3.behavior.zoom()
17 | .scaleExtent([1, 10])
18 | .on('zoom', function() {
19 | svg.attr('transform', 'translate(' + d3.event.translate + ') scale(' + d3.event.scale + ')'); }),
20 |
21 | svg = d3.select('#graph').append('svg')
22 | .attr('width', width)
23 | .attr('height', height)
24 | .call(zoom)
25 | .append('g'),
26 |
27 | renderGraph = function(nodes, edges) {
28 | var edge, node;
29 |
30 | force
31 | .nodes(nodes)
32 | .links(edges)
33 | .start();
34 |
35 | edge = svg.selectAll('.link')
36 | .data(edges)
37 | .enter().append('line')
38 | .attr('class', 'link')
39 | .style('stroke-width', function(d) { return 1.5 * Math.sqrt(d.weight); });
40 |
41 | node = svg.selectAll('.node')
42 | .data(nodes)
43 | .enter().append('g')
44 | .attr('class', 'node')
45 |
46 | .call(force.drag);
47 |
48 | node.append('circle')
49 | .attr('r', function(d) {
50 | return Math.max(6, 3 * Math.sqrt(d.weight));
51 | });
52 |
53 | node.append('title').text(function(d) { return d.title; });
54 | node.append('text')
55 | .attr('x', 12)
56 | .attr('dy', '.35em')
57 | .text(function(d) { return d.title });
58 |
59 | force.on('tick', function() {
60 | edge
61 | .attr('x1', function(d) { return d.source.x; })
62 | .attr('y1', function(d) { return d.source.y; })
63 | .attr('x2', function(d) { return d.target.x; })
64 | .attr('y2', function(d) { return d.target.y; });
65 |
66 | node.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')' });
67 | });
68 |
69 | };
70 |
71 | loadGraphData = function(url, callback) {
72 | jQuery.getJSON(url, function(data) {
73 | callback(data.nodes, data.links);
74 | });
75 | };
76 |
77 | loadGraphData(dataURL, renderGraph);
78 | });
79 |
80 |
81 |
82 |
83 |
84 | /*
85 | svg.append('defs').selectAll('marker')
86 | .data(['end'])
87 | .enter().append('marker')
88 | .attr('id', String)
89 | .attr('viewBox', '0 -5 10 10')
90 | .attr('refX', 16)
91 | .attr('refY', 0)
92 | .attr('markerWidth', 7)
93 | .attr('markerHeight', 9)
94 | .attr('orient', 'auto')
95 | .append('path')
96 | .attr('d', 'M0,-5L10,0L0,5');
97 |
98 |
99 |
100 | node.append('circle')
101 | .attr('r', 6)
102 | .attr('class', function(d) {
103 | if (d.source_gazetteer)
104 | return d.source_gazetteer.toLowerCase();
105 | else
106 | return 'virtual';
107 | });
108 |
109 | node.append('title')
110 | .text(function(d) { return (d.title) ? d.title : d.uri; });
111 |
112 | node.append('text')
113 | .attr('x', 12)
114 | .attr('dy', '.35em')
115 | .text(function(d) { return util.formatGazetteerURI(d.uri); });
116 |
117 | force.start();
118 | */
119 |
120 | });
121 |
--------------------------------------------------------------------------------
/app/assets/javascripts/temporal-profile.js:
--------------------------------------------------------------------------------
1 | window.TemporalProfile = function(divId, data) {
2 | var div = $('#' + divId),
3 | width = div.width(),
4 | height = div.height(),
5 | canvas = $('