diff --git a/bower.json b/bower.json index fbdffff8..8b753cee 100644 --- a/bower.json +++ b/bower.json @@ -1,5 +1,5 @@ { - "name": "ng-boilerplate", + "name": "baw-client", "version": "0.0.18", "devDependencies": { "angular": "1.3.x", diff --git a/src/app/app.js b/src/app/app.js index ee712b5a..ae4a25c4 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -93,6 +93,7 @@ var app = angular.module('baw', 'bawApp.d3', /* our d3 integration */ 'bawApp.d3.calendarView', + 'bawApp.d3.timelineView', 'bawApp.accounts', 'bawApp.annotationViewer', diff --git a/src/app/d3Bindings/calendarView/calendarView.js b/src/app/d3Bindings/calendarView/calendarView.js index 0536c01c..2a84060c 100644 --- a/src/app/d3Bindings/calendarView/calendarView.js +++ b/src/app/d3Bindings/calendarView/calendarView.js @@ -3,14 +3,14 @@ * Created by Anthony on 23/08/2014. */ angular.module("bawApp.d3.calendarView", ["bawApp.d3"]) - .directive("bawCalendarView", ["d3", function (d3) { + .directive("bawCalendarView", ["d3", "moment", function (d3, moment) { // d3 functions // private properties - globals, formatters, magic numbers - var day = null, - week = null, - format = null, - month_format = null, + var day = d3.time.format("%w"), + week = d3.time.format("%U"), + format = d3.time.format("%Y-%m-%d"), + month_format = d3.time.format("%b"), width = 960, height = 136, cellSize = 17, // cell size @@ -26,27 +26,33 @@ angular.module("bawApp.d3.calendarView", ["bawApp.d3"]) ]; var updateCatalogueData = function updateCatalogueData(json) { - var data = d3.nest() - .key(function (d) { - return d.extracted_year + "-" + d.extracted_month + "-" + d.extracted_day; - }) - .rollup(function (d) { - var itemYear = parseInt(d[0].extracted_year); - if (firstYear == null || itemYear > firstYear) { - firstYear = itemYear; - } - if (lastYear == null || itemYear < lastYear) { - lastYear = itemYear; - } - var itemCount = parseInt(d[0].count); - if (colourRangeStop == null || itemCount > colourRangeStop) { - colourRangeStop = itemCount; - } - - return itemCount; - }) - .map(json); + var data = {}; + for(var i = 0;i firstYear) { + firstYear = itemYear; + } + if (lastYear == null || itemYear < lastYear) { + lastYear = itemYear; + } + + // add one or set property + if(data[key]){ + data[key] += 1; + } else { + data[key] = 1; + } + + // get the max number of recordings in a day + if (colourRangeStop == null || data[key] > colourRangeStop) { + colourRangeStop = data[key]; + } + } // ensure year doesn't go beyond 2007 if (lastYear < minYear) { @@ -54,7 +60,7 @@ angular.module("bawApp.d3.calendarView", ["bawApp.d3"]) } var elements = createSvgCalendarView(firstYear, lastYear); - addDataToCalendar(elements.rect, data) + addDataToCalendar(elements.rect, data); }; @@ -141,11 +147,11 @@ angular.module("bawApp.d3.calendarView", ["bawApp.d3"]) var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0), d0 = +day(t0), w0 = +week(t0), d1 = +day(t1), w1 = +week(t1); - return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize - + "H" + w0 * cellSize + "V" + 7 * cellSize - + "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize - + "H" + (w1 + 1) * cellSize + "V" + 0 - + "H" + (w0 + 1) * cellSize + "Z"; + return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize + + "H" + w0 * cellSize + "V" + 7 * cellSize + + "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize + + "H" + (w1 + 1) * cellSize + "V" + 0 + + "H" + (w0 + 1) * cellSize + "Z"; }; var addDataToCalendar = function addDataToCalendar(rect, data) { diff --git a/src/app/d3Bindings/calendarView/calenderViewTemplate.tpl.html b/src/app/d3Bindings/calendarView/calenderViewTemplate.tpl.html index 1a7a8af1..5821db07 100644 --- a/src/app/d3Bindings/calendarView/calenderViewTemplate.tpl.html +++ b/src/app/d3Bindings/calendarView/calenderViewTemplate.tpl.html @@ -1 +1 @@ -
+
diff --git a/src/app/d3Bindings/d3TestPage.js b/src/app/d3Bindings/d3TestPage.js index 570a2c81..75ce5e25 100644 --- a/src/app/d3Bindings/d3TestPage.js +++ b/src/app/d3Bindings/d3TestPage.js @@ -6,7 +6,7 @@ bawD3.controller('D3TestPageCtrl', ['$scope', 'conf.paths', '$http', function ($ // assign the resulting data to scope (not great but it will do for now) $scope.basicData = [0, 1, 2, 3, 4, 5]; - $scope.siteId = 399; + $scope.siteId = 895; var request_filter = { "filter": { diff --git a/src/app/d3Bindings/d3TestPage.tpl.html b/src/app/d3Bindings/d3TestPage.tpl.html index 6e4f5183..2f506b3f 100644 --- a/src/app/d3Bindings/d3TestPage.tpl.html +++ b/src/app/d3Bindings/d3TestPage.tpl.html @@ -5,4 +5,8 @@

Calendar view

+

Timeline view

+
+ +
\ No newline at end of file diff --git a/src/app/d3Bindings/timelineView/_timelineView.scss b/src/app/d3Bindings/timelineView/_timelineView.scss new file mode 100644 index 00000000..b53f20e2 --- /dev/null +++ b/src/app/d3Bindings/timelineView/_timelineView.scss @@ -0,0 +1,34 @@ +.chart { + shape-rendering: crispEdges; +} + +.mini text { + font: 9px sans-serif; +} + +.main text { + font: 12px sans-serif; +} + +.miniItem0 { + fill: darksalmon; + stroke-width: 6; +} + +.miniItem1 { + fill: darkolivegreen; + fill-opacity: .7; + stroke-width: 6; +} + +.miniItem2 { + fill: slategray; + fill-opacity: .7; + stroke-width: 6; +} + +.brush .extent { + stroke: gray; + fill: dodgerblue; + fill-opacity: .365; +} \ No newline at end of file diff --git a/src/app/d3Bindings/timelineView/timelineView.js b/src/app/d3Bindings/timelineView/timelineView.js new file mode 100644 index 00000000..3a5eb93c --- /dev/null +++ b/src/app/d3Bindings/timelineView/timelineView.js @@ -0,0 +1,304 @@ +/** + * A d3 Timeline View directive + * Created by Mark on 09/09/2014. + */ +angular.module("bawApp.d3.timelineView", ["bawApp.d3"]) + .directive("bawTimelineView", ["d3", "moment", function (d3, moment) { + + //data + var lanes = ["Chinese", "Japanese", "Korean"], + laneLength = lanes.length, + items = [ + {"lane": 0, "id": "Qin", "start": 5, "end": 205}, + {"lane": 0, "id": "Jin", "start": 265, "end": 420}, + {"lane": 0, "id": "Sui", "start": 580, "end": 615}, + {"lane": 0, "id": "Tang", "start": 620, "end": 900}, + {"lane": 0, "id": "Song", "start": 960, "end": 1265}, + {"lane": 0, "id": "Yuan", "start": 1270, "end": 1365}, + {"lane": 0, "id": "Ming", "start": 1370, "end": 1640}, + {"lane": 0, "id": "Qing", "start": 1645, "end": 1910}, + {"lane": 1, "id": "Yamato", "start": 300, "end": 530}, + {"lane": 1, "id": "Asuka", "start": 550, "end": 700}, + {"lane": 1, "id": "Nara", "start": 710, "end": 790}, + {"lane": 1, "id": "Heian", "start": 800, "end": 1180}, + {"lane": 1, "id": "Kamakura", "start": 1190, "end": 1330}, + {"lane": 1, "id": "Muromachi", "start": 1340, "end": 1560}, + {"lane": 1, "id": "Edo", "start": 1610, "end": 1860}, + {"lane": 1, "id": "Meiji", "start": 1870, "end": 1900}, + {"lane": 1, "id": "Taisho", "start": 1910, "end": 1920}, + {"lane": 1, "id": "Showa", "start": 1925, "end": 1985}, + {"lane": 1, "id": "Heisei", "start": 1990, "end": 1995}, + {"lane": 2, "id": "Three Kingdoms", "start": 10, "end": 670}, + {"lane": 2, "id": "North and South States", "start": 690, "end": 900}, + {"lane": 2, "id": "Goryeo", "start": 920, "end": 1380}, + {"lane": 2, "id": "Joseon", "start": 1390, "end": 1890}, + {"lane": 2, "id": "Korean Empire", "start": 1900, "end": 1945} + ], + timeBegin = 0, + timeEnd = 2000; + + var m = [20, 15, 15, 120], //top right bottom left + w = 960 - m[1] - m[3], + h = 500 - m[0] - m[2], + miniHeight = laneLength * 12 + 50, + mainHeight = h - miniHeight - 50; + + //scales + var x = d3.scale.linear() + .domain([timeBegin, timeEnd]) + .range([0, w]); + var x1 = d3.scale.linear() + .range([0, w]); + var y1 = d3.scale.linear() + .domain([0, laneLength]) + .range([0, mainHeight]); + var y2 = d3.scale.linear() + .domain([0, laneLength]) + .range([0, miniHeight]); + + //brush + var brush = d3.svg.brush() + .x(x) + .on("brush", display); + + var create = function create() { + + var chart = d3.select("#audioRecordingTimelineContainer") + .append("svg") + .attr("width", w + m[1] + m[3]) + .attr("height", h + m[0] + m[2]) + .attr("class", "chart"); + + chart.append("defs").append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", w) + .attr("height", mainHeight); + + var main = chart.append("g") + .attr("transform", "translate(" + m[3] + "," + m[0] + ")") + .attr("width", w) + .attr("height", mainHeight) + .attr("class", "main"); + + var mini = chart.append("g") + .attr("transform", "translate(" + m[3] + "," + (mainHeight + m[0]) + ")") + .attr("width", w) + .attr("height", miniHeight) + .attr("class", "mini"); + + //main lanes and texts + main.append("g").selectAll(".laneLines") + .data(items) + .enter().append("line") + .attr("x1", m[1]) + .attr("y1", function (d) { + return y1(d.lane); + }) + .attr("x2", w) + .attr("y2", function (d) { + return y1(d.lane); + }) + .attr("stroke", "lightgray"); + + main.append("g").selectAll(".laneText") + .data(lanes) + .enter().append("text") + .text(function (d) { + return d; + }) + .attr("x", -m[1]) + .attr("y", function (d, i) { + return y1(i + .5); + }) + .attr("dy", ".5ex") + .attr("text-anchor", "end") + .attr("class", "laneText"); + + //mini lanes and texts + mini.append("g").selectAll(".laneLines") + .data(items) + .enter().append("line") + .attr("x1", m[1]) + .attr("y1", function (d) { + return y2(d.lane); + }) + .attr("x2", w) + .attr("y2", function (d) { + return y2(d.lane); + }) + .attr("stroke", "lightgray"); + + mini.append("g").selectAll(".laneText") + .data(lanes) + .enter().append("text") + .text(function (d) { + return d; + }) + .attr("x", -m[1]) + .attr("y", function (d, i) { + return y2(i + .5); + }) + .attr("dy", ".5ex") + .attr("text-anchor", "end") + .attr("class", "laneText"); + + var itemRects = main.append("g") + .attr("clip-path", "url(#clip)"); + + //mini item rects + mini.append("g").selectAll("miniItems") + .data(items) + .enter().append("rect") + .attr("class", function (d) { + return "miniItem" + d.lane; + }) + .attr("x", function (d) { + return x(d.start); + }) + .attr("y", function (d) { + return y2(d.lane + .5) - 5; + }) + .attr("width", function (d) { + return x(d.end - d.start); + }) + .attr("height", 10); + + //mini labels + mini.append("g").selectAll(".miniLabels") + .data(items) + .enter().append("text") + .text(function (d) { + return d.id; + }) + .attr("x", function (d) { + return x(d.start); + }) + .attr("y", function (d) { + return y2(d.lane + .5); + }) + .attr("dy", ".5ex"); + + mini.append("g") + .attr("class", "x brush") + .call(brush) + .selectAll("rect") + .attr("y", 1) + .attr("height", miniHeight - 1); + + }; + + var display = function display() { + var rects, labels, + minExtent = brush.extent()[0], + maxExtent = brush.extent()[1], + visItems = items.filter(function (d) { + return d.start < maxExtent && d.end > minExtent; + }); + + mini.select(".brush") + .call(brush.extent([minExtent, maxExtent])); + + x1.domain([minExtent, maxExtent]); + + //update main item rects + rects = itemRects.selectAll("rect") + .data(visItems, function (d) { + return d.id; + }) + .attr("x", function (d) { + return x1(d.start); + }) + .attr("width", function (d) { + return x1(d.end) - x1(d.start); + }); + + rects.enter().append("rect") + .attr("class", function (d) { + return "miniItem" + d.lane; + }) + .attr("x", function (d) { + return x1(d.start); + }) + .attr("y", function (d) { + return y1(d.lane) + 10; + }) + .attr("width", function (d) { + return x1(d.end) - x1(d.start); + }) + .attr("height", function (d) { + return .8 * y1(1); + }); + + rects.exit().remove(); + + //update the item labels + labels = itemRects.selectAll("text") + .data(visItems, function (d) { + return d.id; + }) + .attr("x", function (d) { + return x1(Math.max(d.start, minExtent) + 2); + }); + + labels.enter().append("text") + .text(function (d) { + return d.id; + }) + .attr("x", function (d) { + return x1(Math.max(d.start, minExtent)); + }) + .attr("y", function (d) { + return y1(d.lane + .5); + }) + .attr("text-anchor", "start"); + + labels.exit().remove(); + + }; + + return { + restrict: "EA", + scope: { + data: "=" + }, + templateUrl: "d3Bindings/timelineView/timelineViewTemplate.tpl.html", + link: function ($scope, $element, attributes, controller, transcludeFunction) { + + // use this function to bind DOM events to angular scope + // or d3 events to angular scope. + // you can use the jQuery / d3 objects here (use the injected d3 instance) + + // where possible avoid jQuery + var element = $element[0]; + + // watch for changes on scope data + $scope.$watch( + function () { + return $scope.data; + }, + function (newValue, oldValue) { + if (newValue) { + create(); + display(); + //updateCatalogueData(newValue.data); + } + }); + + }, + controller: "bawTimelineViewController" + } + }]) + .controller("bawTimelineViewController", ["$scope", "$element", "$attrs", + function ($scope, $element, $attrs) { + // The controller should host functionality native to angular + // e.g. + // - functions for button clicks + // - API calls (not relevant in this case) + // - scope modification + // - iteraction with other services/providers + // IT SHOULD NOT contain any reference to the d3 or jQuery objects + + //$scope.example = "Hello world!"; + + }]); \ No newline at end of file diff --git a/src/app/d3Bindings/timelineView/timelineViewTemplate.tpl.html b/src/app/d3Bindings/timelineView/timelineViewTemplate.tpl.html new file mode 100644 index 00000000..7e6898c2 --- /dev/null +++ b/src/app/d3Bindings/timelineView/timelineViewTemplate.tpl.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/sass/application.tpl.scss b/src/sass/application.tpl.scss index 7c26cb63..496072f4 100644 --- a/src/sass/application.tpl.scss +++ b/src/sass/application.tpl.scss @@ -37,6 +37,7 @@ $DEBUG: '<%= build_configs.current.key === "development" %>' == 'true'; @import "../app/annotationLibrary/annotationLibrary"; @import "../app/bookmarks/bookmarks"; @import "../app/d3Bindings/calendarView/calendarView"; +@import "../app/d3Bindings/timelineView/timelineView"; @import "../app/home/home"; @import "../app/listen/listen"; @import "../app/login/login_control";