diff --git a/README.md b/README.md
index 56817b5..477feee 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ An example can be found on the [github page](http://jalperin.github.io/almviz/)
These visualizations were inspired by conversations at the Alt-Viz hackathon group hosted by PLOS in November 2012. [More info](http://article-level-metrics.plos.org/alm-workshop-2012/hackathon/#altviz)
license
--------
+ - -
almviz is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,4 +18,55 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
-See .
\ No newline at end of file
+See .
+
+# How to use #
+- import alm.js
+- import almviz.css and jqueryUi.css (if not already using jqueryUI)
+- declare options object
+ - almStatsJson: The JSON response from the ALM app
+ - additionalStatsJson (optional): an additional source (for appending to ALM app response)
+ - baseUrl: URL of ALM installation for pointing users back
+ - minItemsToShowGraph*: assoc array with the following keys, declaring conditions for when to show graph
+ - minEventsForYearly
+ - minEventsForMonthly
+ - minEventsForDaily
+ - minYearsForYearly
+ - minMonthsForMonthly
+ - minDaysForDaily
+ - hasIcon: array of sources that have icons on the ALM server
+ - showTitle: boolean to display the title of the article at the top of the visualization
+ - categories: array of objects with the following keys: name, display_name, tooltip_text
+ - i.e., [{ name: "html", display_name: "HTML Views", tooltip_text: "Total number of HTML page views for this article. "}, { name ... }]
+ - vizDiv (optional): a selector where to place the whole thing (defaults to #alm)
+- declare AlmViz object: var almviz = new AlmViz(options);
+- initialize the Viz: almviz.initViz();
+
+## Example ##
+ options = {
+ baseUrl: 'http://pkp-alm.lib.sfu.ca',
+ minItemsToShowGraph: {
+ minEventsForYearly: 6,
+ minEventsForMonthly: 6,
+ minEventsForDaily: 6,
+ minYearsForYearly: 6,
+ minMonthsForMonthly: 6,
+ minDaysForDaily: 6
+ },
+ hasIcon: ['wikipedia', 'scienceseeker', 'researchblogging', 'pubmed', 'nature', 'mendeley', 'facebook', 'crossref', 'citeulike'],
+ showTitle: true,
+ categories: [{ name: "html", display_name: "HTML Views", tooltip_text: 'Total number of HTML page views for this article. These views are recorded directly within the system itself. Overall monthly view counts may also be available.' },
+ { name: "pdf", display_name: "PDF Downloads", tooltip_text: 'Total number of PDF views and downloads for this article. These views are recorded directly within the system itself. Overall monthly view counts may also be available.' },
+ { name: "likes", display_name: "Likes", tooltip_text: 'Likes found in social networks such as Facebook.' },
+ { name: "shares", display_name: "Shares", tooltip_text: 'Shares or bookmarks in social networks such as Facebook, CiteULike and Mendeley. In most cases, clicking on the number of shares will take you to a listing in the network itself.' },
+ { name: "comments", display_name: "Comments", tooltip_text: 'Comments are .' },
+ { name: "citations", display_name: "Citations", tooltip_text: 'Citations of this article found in CrossRef, PubMed and Wikipedia. In most cases, clicking on the citation count will take you to a listing in the referencing service itself.' }],
+ }
+
+ d3.json('alm.json', function(data) {
+ options.almStatsJson = data
+
+ var almviz = new AlmViz(options);
+ almviz.initViz();
+ });
+
diff --git a/alm.js b/alm.js
index e009dfb..b5ce3a5 100644
--- a/alm.js
+++ b/alm.js
@@ -1,488 +1,561 @@
-// var doi = d3.select("dd#doi").attr('data-doi');
-
-var baseUrl = 'http://alm.plos.org';
-// var baseUrl = '';
-
-var doi = 'doi/10.1371/journal.pone.0035869';
-
-var dataUrl = 'alm.json'
-// var dataUrl = "/api/v3/articles/info:doi/" + doi + "?info=history";
-
-var hasSVG = document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1");
-
-//
-// Configuration for when to show graphs
-//
-var minEventsForYearly, minEventsForMonthly, minEventsForDaily;
-var minYearsForYearly, minMonthsForMonthly, minDaysForDaily;
-
-minEventsForYearly = minEventsForMonthly = minEventsForDaily = 6;
-minYearsForYearly = minMonthsForMonthly = minDaysForDaily = 6;
-
-var hasIcon = Array('wikipedia', 'scienceseeker', 'researchblogging', 'pubmed', 'nature', 'mendeley', 'facebook', 'crossref', 'citeulike');
-
/**
- * Extract the date from the source
- * @param level (day|month|year)
- * @param d the datum
- * @return {Date}
+ * ALMViz
+ * See https://github.com/jalperin/almviz for more details
+ * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
+ *
+ * @brief Article level metrics visualization controller.
*/
-function get_date(level, d) {
- switch (level) {
- case 'year':
- return new Date(d.year, 0, 1);
- case 'month':
- // js Date indexes months at 0
- return new Date(d.year, d.month - 1, 1);
- case 'day':
- // js Date indexes months at 0
- return new Date(d.year, d.month - 1, d.day);
+function AlmViz(options) {
+ // allow jQuery object to be passed in
+ // in case a different version of jQuery is needed from the one globally defined
+ $ = options.jQuery || $;
+
+ // Init data
+ var categories_ = options.categories;
+ var data = options.almStatsJson;
+ var additionalStats = options.additionalStatsJson;
+ if (additionalStats) {
+ data[0].sources.push(additionalStats);
}
-}
-/**
- * Format the date for display
- * @param level (day|month|year)
- * @param d the datum
- * @return {String}
- */
-function get_formatted_date(level, d) {
- switch (level) {
- case 'year':
- return d3.time.format("%Y")(get_date(level, d));
- case 'month':
- return d3.time.format("%b %y")(get_date(level, d));
- case 'day':
- return d3.time.format("%d %b %y")(get_date(level, d));
- }
-}
+ // Init basic options
+ var baseUrl_ = options.baseUrl;
+ var hasIcon = options.hasIcon;
+ var minItems_ = options.minItemsToShowGraph;
+ var showTitle = options.showTitle;
+ var formatNumber_ = d3.format(",d");
-/**
- *
- * @param level (day|month|year)
- * @param source (from Json response)
- * @return Array of metrics
- */
-function get_data(level, source) {
- switch (level) {
- case 'year':
- return source.by_year;
- case 'month':
- return source.by_month;
- case 'day':
- return source.by_day;
- }
-}
+ // extract publication date
+ var pub_date = d3.time.format.iso.parse(data[0]["publication_date"]);
-/**
- * Returns a d3 timeInterval for date operations
- * @param level (day|month|year
- * @return d3 ime Interval
- */
-function get_time_interval(level) {
- switch (level) {
- case 'year':
- return d3.time.year.utc;
- case 'month':
- return d3.time.month.utc;
- case 'day':
- return d3.time.day.utc;
+ var vizDiv;
+ // Get the Div where the viz should go (default to one with ID "alm')
+ if (options.vizDiv) {
+ vizDiv = d3.select(options.vizDiv);
+ } else {
+ vizDiv = d3.select("#alm");
}
-}
-
-// map of category keys to labels for display
-var categories = [{ name: "html", display_name: "HTML Views" },
- { name: "pdf", display_name: "PDF Downloads" },
- { name: "likes", display_name: "Likes" },
- { name: "shares", display_name: "Shares" },
- { name: "comments", display_name: "Comments" },
- { name: "citations", display_name: "Citations" }];
-
-var metricsFound = false; // flag
-var format_number = d3.format(",d"); // for formatting numbers for display
-var charts = new Array(); // keep track of AlmViz objects
-
-/* Graph visualization
- * The basic general set up of the graph itself
- * @param chartDiv The div where the chart should go
- * @param data The raw data
- * @param category The category for 86 chart
- */
-function AlmViz(chartDiv, pub_date, source, category) {
- // size parameters
- this.margin = {top: 10, right: 40, bottom: 0, left: 40};
- this.width = 400 - this.margin.left - this.margin.right;
- this.height = 100 - this.margin.top - this.margin.bottom;
-
- // div where everything goes
- this.chartDiv = chartDiv;
-
- // publication date
- this.pub_date = pub_date;
-
- // source data and which category
- this.category = category;
- this.source = source;
-
- // just for record keeping
- this.name = source.name + '-' + category.name;
-
- this.x = d3.time.scale();
- this.x.range([0, this.width]);
-
- this.y = d3.scale.linear();
- this.y.range([this.height, 0]);
-
- this.z = d3.scale.ordinal();
- this.z.range(['main', 'alt']);
- // the chart
- this.svg = this.chartDiv.append("svg")
- .attr("width", this.width + this.margin.left + this.margin.right)
- .attr("height", this.height + this.margin.top + this.margin.bottom)
- .append("g")
- .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
+ // look to make sure browser support SVG
+ var hasSVG_ = document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1");
+ // to track if any metrics have been found
+ var metricsFound_;
- // draw the bars g first so it ends up underneath the axes
- this.bars = this.svg.append("g");
+ /**
+ * Initialize the visualization.
+ * NB: needs to be accessible from the outside for initialization
+ */
+ this.initViz = function() {
+ vizDiv.select("#loading").remove();
- // and the shadow bars on top for the tooltips
- this.barsForTooltips = this.svg.append("g");
-
- this.svg.append("g")
- .attr("class", "x axis")
- .attr("transform", "translate(0," + (this.height - 1) + ")");
- this.svg.append("g")
- .attr("class", "y axis");
-
-}
-
-/**
- * Takes in the basic set up of a graph and loads the data itself
- * @param viz AlmViz object
- * @param level string (day|month|year)
- */
-function loadData(viz, level) {
- d3.select("#alm > #loading").remove();
-
- var pub_date = viz.pub_date
- var category = viz.category;
- var level_data = get_data(level, viz.source);
- var timeInterval = get_time_interval(level);
-
- var end_date = new Date();
- // use only first 29 days if using day view
- // close out the year otherwise
- if ( level == 'day' ) {
- end_date = timeInterval.offset(pub_date, 29);
- } else {
- end_date = d3.time.year.utc.ceil(end_date);
- }
-
- //
- // Domains for x and y
- //
-
- // a time x axis, between pub_date and end_date
- viz.x.domain([timeInterval.floor(pub_date), end_date]);
-
- // a linear axis from 0 to max value found
- viz.y.domain([0, d3.max(level_data, function(d) { return d[category.name]; })]);
-
- //
- // Axis
- //
- // a linear axis between publication date and current date
- viz.xAxis = d3.svg.axis()
- .scale(viz.x)
- .tickSize(0)
- .ticks(0);
-
- // a linear y axis between 0 and max value found in data
- viz.yAxis = d3.svg.axis()
- .scale(viz.y)
- .orient("left")
- .tickSize(0)
- .tickValues([d3.max(viz.y.domain())]) // only one tick at max
- .tickFormat(d3.format(",d"));
-
- //
- // The chart itself
- //
- // TODO: these transitions could use a little work
- var barWidth = Math.max((viz.width/(timeInterval.range(pub_date, end_date).length + 1)) - 2, 1);
-
- var barsForTooltips = viz.barsForTooltips.selectAll(".barsForTooltip")
- .data(level_data, function(d) { return get_date(level, d); });
-
- barsForTooltips
- .exit()
- .remove();
-
- var bars = viz.bars.selectAll(".bar")
- .data(level_data, function(d) { return get_date(level, d); });
-
- bars
- .enter().append("rect")
- .attr("class", function(d) { return "bar " + viz.z((level == 'day' ? d3.time.weekOfYear(get_date(level, d)) : d.year)); })
- .attr("y", viz.height)
- .attr("height", 0);
-
- bars
- .attr("x", function(d) { return viz.x(get_date(level, d)) + 2; }) // padding of 2, 1 each side
- .attr("width", barWidth);
-
- bars.transition()
- .duration(1000)
- .attr("width", barWidth)
- .attr("y", function(d) { return viz.y(d[category.name]); })
- .attr("height", function(d) { return viz.height - viz.y(d[category.name]); });
-
- bars
- .exit().transition()
- .attr("y", viz.height)
- .attr("height", 0);
-
- bars
- .exit() // .transition().delay(1000)
- .remove();
-
- viz.svg
- .select(".x.axis")
- .call(viz.xAxis);
-
- viz.svg
- .transition().duration(1000)
- .select(".y.axis")
- .call(viz.yAxis);
-
- barsForTooltips
- .enter().append("rect")
- .attr("class", function(d) { return "barsForTooltip " + viz.z((level == 'day' ? d3.time.weekOfYear(get_date(level, d)) : d.year)); });
-
- barsForTooltips
- .attr("width", barWidth + 2)
- .attr("x", function(d) { return viz.x(get_date(level, d)) + 1; })
- .attr("y", function(d) { return viz.y(d[category.name]) - 1; })
- .attr("height", function(d) { return viz.height - viz.y(d[category.name]) + 1; });
-
-
- // add in some tool tips
- viz.barsForTooltips.selectAll("rect").each(
- function(d,i){
- $(this).tooltip('destroy'); // need to destroy so all bars get updated
- $(this).tooltip({title: format_number(d[category.name]) + " in " + get_formatted_date(level, d), container: "body"});
+ if (showTitle) {
+ vizDiv.append("a")
+ .attr('href', 'http://dx.doi.org/' + data[0].doi)
+ .attr("class", "title")
+ .text(data[0].title);
}
- );
-}
-d3.json(dataUrl, function(data) {
- // extract publication date
- var pub_date = d3.time.format.iso.parse(data[0]["publication_date"]);
+ // loop through categories
+ categories_.forEach(function(category) {
+ addCategory_(vizDiv, category, data);
+ });
- var canvas = d3.select("#alm");
- canvas.append("a")
- .attr('href', 'http://dx.doi.org/' + data[0].doi)
- .attr("class", "title")
- .text(data[0].title);
+ if (!metricsFound_) {
+ vizDiv.append("p")
+ .attr("class", "muted")
+ .text("No metrics found.");
+ }
+ };
- // loop through categories
- categories.forEach(function(category) {
- canvas.append("div")
- .attr("class", "alm");
- var categoryRow = false;
+ /**
+ * Build each article level statistics category.
+ * @param {Object} canvas d3 element
+ * @param {Array} category Information about the category.
+ * @param {Object} data Statistics.
+ * @return {JQueryObject|boolean}
+ */
+ var addCategory_ = function(canvas, category, data) {
+ var $categoryRow = false;
- // loop through sources
+ // Loop through sources to add statistics data to the category.
data[0]["sources"].forEach(function(source) {
var total = source.metrics[category.name];
-
if (total > 0) {
- if (!categoryRow) {
- categoryRow = canvas.append("div")
- .attr("class", "alm-category-row")
- .attr("style", "width: 100%; overflow: hidden;")
- .attr("id", "category-" + category.name);
-
- categoryRow.append("h2", "div.alm-category-row-heading" + category.name)
- .attr("class", "border-bottom")
- .attr("id", "month-" + category.name)
- .text(category.display_name);
-
- // flag that there is at least one metric
- metricsFound = true;
+ // Only add the category row the first time
+ if (!$categoryRow) {
+ $categoryRow = getCategoryRow_(canvas, category);
}
- var row = categoryRow
- .append("div")
- .attr("class", "alm-row")
- .attr("style", "float: left")
- .attr("id", "alm-row-" + source.name + "-" + category.name);
-
- var countLabel = row.append("div")
- .attr("class", "alm-count-label");
-
- if (hasIcon.indexOf(source.name) >= 0) {
- countLabel.append("img")
- .attr("src", baseUrl + '/assets/' + source.name + '.png')
- .attr("alt", 'a description of the source')
- .attr("class", "label-img");
- }
+ // Flag that there is at least one metric
+ metricsFound_ = true;
+ addSource_(source, total, category, $categoryRow);
+ }
+ });
+ };
+
+
+ /**
+ * Get category row d3 HTML element. It will automatically
+ * add the element to the passed canvas.
+ * @param {d3Object} canvas d3 HTML element
+ * @param {Array} category Category information.
+ * @param {d3Object}
+ */
+ var getCategoryRow_ = function(canvas, category) {
+ var categoryRow, categoryTitle, tooltip;
+
+ // Build category html objects.
+ categoryRow = canvas.append("div")
+ .attr("class", "alm-category-row")
+ .attr("style", "width: 100%; overflow: hidden;")
+ .attr("id", "category-" + category.name);
+
+ categoryTitle = categoryRow.append("h2")
+ .attr("class", "alm-category-row-heading")
+ .attr("id", "month-" + category.name)
+ .text(category.display_name);
+
+ tooltip = categoryTitle.append("div")
+ .attr("class", "alm-category-row-info").append("span")
+ .attr("class", "ui-icon ui-icon-info");
+
+ $(tooltip).tooltip({title: category.tooltip_text, container: 'body'});
+
+ return categoryRow;
+ };
+
+
+ /**
+ * Add source information to the passed category row element.
+ * @param {Object} source
+ * @param {integer} sourceTotalValue
+ * @param {Object} category
+ * @param {JQueryObject} $categoryRow
+ * @return {JQueryObject}
+ */
+ var addSource_ = function(source, sourceTotalValue, category, $categoryRow) {
+ var $row, $countLabel, $count,
+ total = sourceTotalValue;
+
+ $row = $categoryRow
+ .append("div")
+ .attr("class", "alm-row")
+ .attr("style", "float: left")
+ .attr("id", "alm-row-" + source.name + "-" + category.name);
+
+ $countLabel = $row.append("div")
+ .attr("class", "alm-count-label");
+
+ if (hasIcon.indexOf(source.name) >= 0) {
+ $countLabel.append("img")
+ .attr("src", baseUrl_ + '/assets/' + source.name + '.png')
+ .attr("alt", 'a description of the source')
+ .attr("class", "label-img");
+ }
- var count;
- if (source.events_url) {
- // if there is an events_url, we can link to it from the count
- count = countLabel.append("a")
- .attr("href", function(d) { return source.events_url; });
- } else {
- // if no events_url, we just put in the count
- count = countLabel.append("span");
- }
+ if (source.events_url) {
+ // if there is an events_url, we can link to it from the count
+ $count = $countLabel.append("a")
+ .attr("href", function(d) { return source.events_url; });
+ } else {
+ // if no events_url, we just put in the count
+ $count = $countLabel.append("span");
+ }
- count
- .attr("class", "alm-count")
- .attr("id", "alm-count-" + source.name + "-" + category.name)
- .text(function(d) { return format_number(total); });
+ $count
+ .attr("class", "alm-count")
+ .attr("id", "alm-count-" + source.name + "-" + category.name)
+ .text(formatNumber_(total));
+
+ $countLabel.append("br");
+
+ if (source.name == 'pkpTimedViews') {
+ $countLabel.append("span")
+ .text(source.display_name);
+ } else {
+ // link the source name
+ $countLabel.append("a")
+ .attr("href", baseUrl_ + "/sources/" + source.name)
+ .text(source.display_name);
+ }
+ // Only add a chart if the browser supports SVG
+ if (hasSVG_) {
+ var level = false;
+
+ // check what levels we can show
+ var showDaily = false;
+ var showMonthly = false;
+ var showYearly = false;
+
+ if (source.by_year) {
+ level_data = getData_('year', source);
+ var yearTotal = level_data.reduce(function(i, d) { return i + d[category.name]; }, 0);
+ var numYears = d3.time.year.utc.range(pub_date, new Date()).length;
+
+ if (yearTotal >= minItems_.minEventsForYearly &&
+ numYears >= minItems_.minYearsForYearly) {
+ showYearly = true;
+ level = 'year';
+ };
+ }
- countLabel.append("br");
+ if (source.by_month) {
+ level_data = getData_('month', source);
+ var monthTotal = level_data.reduce(function(i, d) { return i + d[category.name]; }, 0);
+ var numMonths = d3.time.month.utc.range(pub_date, new Date()).length;
- // link the source name
- countLabel.append("a")
- .attr("href", function(d) { return baseUrl + "/sources/" + source.name; })
- .text(function(d) { return source.display_name; });
+ if (monthTotal >= minItems_.minEventsForMonthly &&
+ numMonths >= minItems_.minMonthsForMonthly) {
+ showMonthly = true;
+ level = 'month';
+ };
}
- // If there is not SVG, do not even try the charts
- if ( hasSVG ) {
- var level = false;
+ if (source.by_day){
+ level_data = getData_('day', source);
+ var dayTotal = level_data.reduce(function(i, d) { return i + d[category.name]; }, 0);
+ var numDays = d3.time.day.utc.range(pub_date, new Date()).length;
- // check what levels we can show
- var showDaily = false;
- var showMonthly = false;
- var showYearly = false;
+ if (dayTotal >= minItems_.minEventsForDaily && numDays >= minItems_.minDaysForDaily) {
+ showDaily = true;
+ level = 'day';
+ };
+ }
- if (source.by_year) {
- level_data = get_data('year', source);
- var yearTotal = level_data.reduce(function(i, d) { return i + d[category.name]; }, 0);
- var numYears = d3.time.year.utc.range(pub_date, new Date()).length
+ // The level and level_data should be set to the finest level
+ // of granularity that we can show
+ timeInterval = getTimeInterval_(level);
- if (yearTotal >= minEventsForYearly && numYears >= minYearsForYearly) {
- showYearly = true;
- level = 'year';
- }
- }
+ // check there is data for
+ if (showDaily || showMonthly || showYearly) {
+ var $chartDiv = $row.append("div")
+ .attr("style", "width: 70%; float:left;")
+ .attr("class", "alm-chart-area");
- if (source.by_month) {
- level_data = get_data('month', source);
- var monthTotal = level_data.reduce(function(i, d) { return i + d[category.name]; }, 0);
- var numMonths = d3.time.month.utc.range(pub_date, new Date()).length
+ var viz = getViz_($chartDiv, source, category);
+ loadData_(viz, level);
- if (monthTotal >= minEventsForMonthly && numMonths >= minMonthsForMonthly) {
- showMonthly = true;
- level = 'month';
- }
- }
+ var update_controls = function(control) {
+ control.siblings('.alm-control').removeClass('active');
+ control.addClass('active');
+ };
- if (source.by_day){
- level_data = get_data('day', source);
- var dayTotal = level_data.reduce(function(i, d) { return i + d[category.name]; }, 0);
- var numDays = d3.time.day.utc.range(pub_date, new Date()).length
+ var $levelControlsDiv = $chartDiv.append("div")
+ .attr("style", "width: " + (viz.margin.left + viz.width) + "px;")
+ .append("div")
+ .attr("style", "float:right;");
+
+ if (showDaily) {
+ $levelControlsDiv.append("a")
+ .attr("href", "javascript:void(0)")
+ .classed("alm-control", true)
+ .classed("disabled", !showDaily)
+ .classed("active", (level == 'day'))
+ .text("daily (first 30)")
+ .on("click", function() {
+ if (showDaily && !$(this).hasClass('active')) {
+ loadData_(viz, 'day');
+ update_controls($(this));
+ }
+ }
+ );
- if (dayTotal >= minEventsForDaily && numDays >= minDaysForDaily) {
- showDaily = true;
- level = 'day';
- }
+ $levelControlsDiv.append("text").text(" | ");
}
- // The level level_data should be set to the finest level
- // of granularity that we can show
- timeInterval = get_time_interval(level);
-
- // check there is data for
- if ( showDaily || showMonthly || showYearly ) {
- var chartDiv = row.append("div")
- .attr("style", "width: 70%; float:left;")
- .attr("class", "alm-chart-area");
-
- var viz = new AlmViz(chartDiv, pub_date, source, category);
- loadData(viz, level);
-
- var update_controls = function(control) {
- control.siblings('.alm-control').removeClass('active');
- control.addClass('active')
- }
- var levelControlsDiv = chartDiv.append("div")
- .attr("style", "width: " + (viz.margin.left + viz.width) + "px;")
- .append("div")
- .attr("style", "float:right;");
-
- if (showDaily) {
- levelControlsDiv.append("a")
- .attr("href", "javascript:void(0)")
- .classed("alm-control", true)
- .classed("disabled", !showDaily)
- .classed("active", (level == 'day'))
- .text("daily (first 30)")
- .on("click", function() { if (showDaily && !$(this).hasClass('active')) {
- loadData(viz, 'day');
- update_controls($(this));
- } });
-
- levelControlsDiv.append("text")
- .text(" | ");
- }
-
- if (showMonthly) {
- levelControlsDiv.append("a")
- .attr("href", "javascript:void(0)")
- .classed("alm-control", true)
- .classed("disabled", !showMonthly || !showYearly)
- .classed("active", (level == 'month'))
- .text("monthly")
- .on("click", function() { if (showMonthly && !$(this).hasClass('active')) {
- loadData(viz, 'month');
- update_controls($(this));
- } });
-
- if (showYearly) {
- levelControlsDiv.append("text")
- .text(" | ");
- }
-
- }
+ if (showMonthly) {
+ $levelControlsDiv.append("a")
+ .attr("href", "javascript:void(0)")
+ .classed("alm-control", true)
+ .classed("disabled", !showMonthly || !showYearly)
+ .classed("active", (level == 'month'))
+ .text("monthly")
+ .on("click", function() { if (showMonthly && !$(this).hasClass('active')) {
+ loadData_(viz, 'month');
+ update_controls($(this));
+ } });
if (showYearly) {
- levelControlsDiv.append("a")
- .attr("href", "javascript:void(0)")
- .classed("alm-control", true)
- .classed("disabled", !showYearly || !showMonthly)
- .classed("active", (level == 'year'))
- .text("yearly")
- .on("click", function() { if (showYearly && !$(this).hasClass('active')) {
- loadData(viz, 'year');
- update_controls($(this));
- } });
+ $levelControlsDiv.append("text")
+ .text(" | ");
}
- // keep track of all instances (mostly for debugging at this point)
- charts[source.name + '-' + category.name] = viz;
-
- // add a clearer and styles to ensure graphs on their own line
- row.insert("div", ":first-child")
- .attr('style', 'clear:both')
- row.attr('style', "width: 100%")
+ }
+ if (showYearly) {
+ $levelControlsDiv.append("a")
+ .attr("href", "javascript:void(0)")
+ .classed("alm-control", true)
+ .classed("disabled", !showYearly || !showMonthly)
+ .classed("active", (level == 'year'))
+ .text("yearly")
+ .on("click", function() {
+ if (showYearly && !$(this).hasClass('active')) {
+ loadData_(viz, 'year');
+ update_controls($(this));
+ }
+ }
+ );
}
- }
- });
- });
- if (!metricsFound) {
- canvas.append("p")
- .attr("class", "muted")
- .text("No metrics found.");
+ // add a clearer and styles to ensure graphs on their own line
+ $row.insert("div", ":first-child")
+ .attr('style', 'clear:both');
+ $row.attr('style', "width: 100%");
+ };
+ };
+
+ return $row;
+ };
+
+
+ /**
+ * Extract the date from the source
+ * @param level (day|month|year)
+ * @param d the datum
+ * @return {Date}
+ */
+ var getDate_ = function(level, d) {
+ switch (level) {
+ case 'year':
+ return new Date(d.year, 0, 1);
+ case 'month':
+ // js Date indexes months at 0
+ return new Date(d.year, d.month - 1, 1);
+ case 'day':
+ // js Date indexes months at 0
+ return new Date(d.year, d.month - 1, d.day);
+ }
+ };
+
+
+ /**
+ * Format the date for display
+ * @param level (day|month|year)
+ * @param d the datum
+ * @return {String}
+ */
+ var getFormattedDate_ = function(level, d) {
+ switch (level) {
+ case 'year':
+ return d3.time.format("%Y")(getDate_(level, d));
+ case 'month':
+ return d3.time.format("%b %y")(getDate_(level, d));
+ case 'day':
+ return d3.time.format("%d %b %y")(getDate_(level, d));
+ }
+ };
+
+
+ /**
+ * Extract the data from the source.
+ * @param {string} level (day|month|year)
+ * @param {Object} source
+ * @return {Array} Metrics
+ */
+ var getData_ = function(level, source) {
+ switch (level) {
+ case 'year':
+ return source.by_year;
+ case 'month':
+ return source.by_month;
+ case 'day':
+ return source.by_day;
+ }
+ };
+
+ /**
+ * Returns a d3 timeInterval for date operations.
+ * @param {string} level (day|month|year
+ * @return {Object} d3 time Interval
+ */
+ var getTimeInterval_ = function(level) {
+ switch (level) {
+ case 'year':
+ return d3.time.year.utc;
+ case 'month':
+ return d3.time.month.utc;
+ case 'day':
+ return d3.time.day.utc;
+ }
+ };
+
+ /**
+ * The basic general set up of the graph itself
+ * @param {JQueryElement} chartDiv The div where the chart should go
+ * @param {Object} source
+ * @param {Array} category The category for 86 chart
+ * @return {Object}
+ */
+ var getViz_ = function(chartDiv, source, category) {
+ var viz = {};
+
+ // size parameters
+ viz.margin = {top: 10, right: 40, bottom: 0, left: 40};
+ viz.width = 400 - viz.margin.left - viz.margin.right;
+ viz.height = 100 - viz.margin.top - viz.margin.bottom;
+
+ // div where everything goes
+ viz.chartDiv = chartDiv;
+
+ // source data and which category
+ viz.category = category;
+ viz.source = source;
+
+ // just for record keeping
+ viz.name = source.name + '-' + category.name;
+
+ viz.x = d3.time.scale();
+ viz.x.range([0, viz.width]);
+
+ viz.y = d3.scale.linear();
+ viz.y.range([viz.height, 0]);
+
+ viz.z = d3.scale.ordinal();
+ viz.z.range(['main', 'alt']);
+
+ // the chart
+ viz.svg = viz.chartDiv.append("svg")
+ .attr("width", viz.width + viz.margin.left + viz.margin.right)
+ .attr("height", viz.height + viz.margin.top + viz.margin.bottom)
+ .append("g")
+ .attr("transform", "translate(" + viz.margin.left + "," + viz.margin.top + ")");
+
+
+ // draw the bars g first so it ends up underneath the axes
+ viz.bars = viz.svg.append("g");
+
+ // and the shadow bars on top for the tooltips
+ viz.barsForTooltips = viz.svg.append("g");
+
+ viz.svg.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + (viz.height - 1) + ")");
+
+ viz.svg.append("g")
+ .attr("class", "y axis");
+
+ return viz;
+ };
+
+
+ /**
+ * Takes in the basic set up of a graph and loads the data itself
+ * @param {Object} viz AlmViz object
+ * @param {string} level (day|month|year)
+ */
+ var loadData_ = function(viz, level) {
+ var category = viz.category;
+ var level_data = getData_(level, viz.source);
+ var timeInterval = getTimeInterval_(level);
+
+ var end_date = new Date();
+ // use only first 29 days if using day view
+ // close out the year otherwise
+ if (level == 'day') {
+ end_date = timeInterval.offset(pub_date, 29);
+ } else {
+ end_date = d3.time.year.utc.ceil(end_date);
+ }
+
+ //
+ // Domains for x and y
+ //
+ // a time x axis, between pub_date and end_date
+ viz.x.domain([timeInterval.floor(pub_date), end_date]);
+
+ // a linear axis from 0 to max value found
+ viz.y.domain([0, d3.max(level_data, function(d) { return d[category.name]; })]);
+
+ //
+ // Axis
+ //
+ // a linear axis between publication date and current date
+ viz.xAxis = d3.svg.axis()
+ .scale(viz.x)
+ .tickSize(0)
+ .ticks(0);
+
+ // a linear y axis between 0 and max value found in data
+ viz.yAxis = d3.svg.axis()
+ .scale(viz.y)
+ .orient("left")
+ .tickSize(0)
+ .tickValues([d3.max(viz.y.domain())]) // only one tick at max
+ .tickFormat(d3.format(",d"));
+
+ //
+ // The chart itself
+ //
+
+ // TODO: these transitions could use a little work
+ var barWidth = Math.max((viz.width/(timeInterval.range(pub_date, end_date).length + 1)) - 2, 1);
+
+ var barsForTooltips = viz.barsForTooltips.selectAll(".barsForTooltip")
+ .data(level_data, function(d) { return getDate_(level, d); });
+
+ barsForTooltips
+ .exit()
+ .remove();
+
+ var bars = viz.bars.selectAll(".bar")
+ .data(level_data, function(d) { return getDate_(level, d); });
+
+ bars
+ .enter().append("rect")
+ .attr("class", function(d) { return "bar " + viz.z((level == 'day' ? d3.time.weekOfYear(getDate_(level, d)) : d.year)); })
+ .attr("y", viz.height)
+ .attr("height", 0);
+
+ bars
+ .attr("x", function(d) { return viz.x(getDate_(level, d)) + 2; }) // padding of 2, 1 each side
+ .attr("width", barWidth);
+
+ bars.transition()
+ .duration(1000)
+ .attr("width", barWidth)
+ .attr("y", function(d) { return viz.y(d[category.name]); })
+ .attr("height", function(d) { return viz.height - viz.y(d[category.name]); });
+
+ bars
+ .exit().transition()
+ .attr("y", viz.height)
+ .attr("height", 0);
+
+ bars
+ .exit()
+ .remove();
+
+ viz.svg
+ .select(".x.axis")
+ .call(viz.xAxis);
+
+ viz.svg
+ .transition().duration(1000)
+ .select(".y.axis")
+ .call(viz.yAxis);
+
+ barsForTooltips
+ .enter().append("rect")
+ .attr("class", function(d) { return "barsForTooltip " + viz.z((level == 'day' ? d3.time.weekOfYear(getDate_(level, d)) : d.year)); });
+
+ barsForTooltips
+ .attr("width", barWidth + 2)
+ .attr("x", function(d) { return viz.x(getDate_(level, d)) + 1; })
+ .attr("y", function(d) { return viz.y(d[category.name]) - 1; })
+ .attr("height", function(d) { return viz.height - viz.y(d[category.name]) + 1; });
+
+
+ // add in some tool tips
+ viz.barsForTooltips.selectAll("rect").each(
+ function(d,i){
+ $(this).tooltip('destroy'); // need to destroy so all bars get updated
+ $(this).tooltip({title: formatNumber_(d[category.name]) + " in " + getFormattedDate_(level, d), container: "body"});
+ }
+ );
}
-});
+}
\ No newline at end of file
diff --git a/assets/ui-icons_469bdd_256x240.png b/assets/ui-icons_469bdd_256x240.png
new file mode 100644
index 0000000..bd2cf07
Binary files /dev/null and b/assets/ui-icons_469bdd_256x240.png differ
diff --git a/css/almviz.css b/css/almviz.css
index f23d4bc..a487371 100644
--- a/css/almviz.css
+++ b/css/almviz.css
@@ -1,11 +1,6 @@
-body {
- font-family: sans-serif;
- width: 800px;
-}
-
.alm {
- font-size: 11pt;
- font-family: sans-serif;
+ font-size: 1em;
+
margin-bottom: 15px;
}
@@ -43,6 +38,10 @@ body {
margin-bottom: 1em;
}
+.alm-category-row-heading {
+ font-size: 1.2em;
+}
+
.alm-category-row-info {
display: inline-block;
width: 14px;
@@ -62,17 +61,17 @@ body {
margin-right: 2em;
padding: 0.8em;
text-align: center;
- background: #dfeffc;
- font-size: .75em;
- color: #555;
- border: 1px solid #c5dbec;
- -moz-border-radius: 5px;
- -webkit-border-radius: 5px;
- border-radius: 5px;
+ border: 1px solid #c5dbec;
+ background: #c8d7d7;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ color: #304345;
+
}
.alm-count-label a {
- color: #2e6e9e;
+ text-decoration: none;
}
.alm-count-label a:hover {
@@ -80,16 +79,12 @@ body {
color: #555555 !important;
}
-.alm-count-label a {
- text-decoration: none;
-}
-
.alm-count-label img {
padding-right:0.5em;
}
.alm-count {
- font-size: 1.25em;
+ font-size: 1.15em;
padding-bottom: 0.4em;
padding-top: 0.4em;
font-weight: bold;
@@ -103,7 +98,6 @@ body {
.alm-control.active {
text-decoration: none;
- color: blue;
}
.alm-control.disabled {
@@ -112,7 +106,7 @@ body {
color: grey;
}
-.alm-control.disabled:hover {
+.alm-control:hover {
background-color: transparent;
}
@@ -123,4 +117,4 @@ body {
#built-with {
float: right;
font-size: .75em;
-}
\ No newline at end of file
+}
diff --git a/css/jqueryUi.css b/css/jqueryUi.css
new file mode 100644
index 0000000..503857a
--- /dev/null
+++ b/css/jqueryUi.css
@@ -0,0 +1,15 @@
+/*
+ * jQuery UI CSS Framework 1.8.6
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+Only bringing in these two needed classes and image file
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+.ui-icon { width: 16px; height: 16px; background-image: url(../assets/ui-icons_469bdd_256x240.png); }
+
+.ui-icon-info { background-position: -16px -144px; }
\ No newline at end of file
diff --git a/index.html b/index.html
index 66b088e..1c2143e 100755
--- a/index.html
+++ b/index.html
@@ -6,9 +6,16 @@
+
+
ALM-VIZ
@@ -16,7 +23,36 @@ ALM-VIZ
+
+