/*global define */
define(['jquery', 'underscore', 'backbone', 'd3', 'LineChart', 'BarChart', 'DonutChart', 'CircleBadge', 'text!templates/profile.html', 'text!templates/alert.html', 'text!templates/loading.html'],
function($, _, Backbone, d3, LineChart, BarChart, DonutChart, CircleBadge, profileTemplate, AlertTemplate, LoadingTemplate) {
'use strict';
var StatsView = Backbone.View.extend({
el: '#Content',
hideUpdatesChart: false,
template: _.template(profileTemplate),
alertTemplate: _.template(AlertTemplate),
loadingTemplate: _.template(LoadingTemplate),
initialize: function(options){
if(!options) options = {};
this.title = (typeof options.title === "undefined") ? "Summary of Holdings" : options.title;
this.description = (typeof options.description === "undefined") ?
"A summary of all datasets in our catalog." : options.description;
if(typeof options.el === "undefined")
this.el = options.el;
this.hideUpdatesChart = (options.hideUpdatesChart === true)? true : false;
this.model = options.model || MetacatUI.statsModel;
},
render: function () {
//Clear the page
this.$el.html("");
//Only trigger the functions that draw SVG charts if d3 loaded correctly
if(d3){
//this.listenTo(this.model, 'change:dataUploadDates', this.drawUploadChart);
this.listenTo(this.model, 'change:temporalCoverage', this.drawCoverageChart);
this.listenTo(this.model, 'change:metadataDownloadDates', this.drawDownloadsChart);
this.listenTo(this.model, 'change:dataDownloadDates', this.drawDownloadsChart);
this.listenTo(this.model, 'change:downloadDates', this.drawDownloadsChart);
this.listenTo(this.model, "change:dataUpdateDates", this.drawUpdatesChart);
this.listenTo(this.model, "change:totalSize", this.drawTotalSize);
this.listenTo(this.model, 'change:metadataCount', this.drawTotalCount);
this.listenTo(this.model, 'change:dataFormatIDs', this.drawDataCountChart);
this.listenTo(this.model, 'change:metadataFormatIDs', this.drawMetadataCountChart);
//this.listenTo(this.model, 'change:dataUploads', this.drawUploadTitle);
}
this.listenTo(this.model, 'change:downloads', this.drawDownloadTitle);
this.listenTo(this.model, 'change:lastEndDate', this.drawCoverageChartTitle);
// mdq
this.listenTo(this.model, 'change:mdqStats', this.drawMdqStats);
this.listenTo(this.model, "change:totalCount", this.showNoActivity);
// set the header type
MetacatUI.appModel.set('headerType', 'default');
//Insert the template
this.$el.html(this.template({
query: this.model.get('query'),
title: this.title,
description: this.description
}));
//Insert the loading template into the space where the charts will go
if(d3){
this.$(".chart").html(this.loadingTemplate);
this.$(".show-loading").html(this.loadingTemplate);
}
//If SVG isn't supported, insert an info warning
else{
this.$el.prepend(this.alertTemplate({
classes: "alert-info",
msg: "Please upgrade your browser or use a different browser to view graphs of these statistics.",
email: false
}));
}
if(!this.model.get("supportDownloads"))
this.$(".stripe.downloads").remove();
//Hide the Latest Updates chart if it's turned off
if( this.hideUpdatesChart ){
this.$(".stripe.updates").remove();
}
//Start retrieving data from Solr
this.model.getAll();
return this;
},
drawDataCountChart: function(){
var dataCount = this.model.get('dataCount');
var data = this.model.get('dataFormatIDs');
if(dataCount){
var svgClass = "data";
}
else if(!this.model.get('dataCount') && this.model.get('metadataCount')){ //Are we drawing a blank chart (i.e. 0 data objects found)?
var svgClass = "data default";
}
else if(!this.model.get('metadataCount') && !this.model.get('dataCount'))
var svgClass = "data no-activity";
//If d3 isn't supported in this browser or didn't load correctly, insert a text title instead
if(!d3){
this.$('.format-charts-data').html("
" + MetacatUI.appView.commaSeparateNumber(dataCount) + " data files
");
return;
}
//Draw a donut chart
var donut = new DonutChart({
id: "data-chart",
data: data,
total: this.model.get('dataCount'),
titleText: "data files",
titleCount: dataCount,
svgClass: svgClass,
countClass: "data",
height: 300,
formatLabel: function(name){
//If this is the application/vnd.ms-excel formatID - let's just display "MS Excel"
if((name !== undefined) && (name.indexOf("ms-excel") > -1)) name = "MS Excel";
else if((name != undefined) && (name == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) name= "MS Excel OpenXML"
else if((name != undefined) && (name == "application/vnd.openxmlformats-officedocument.wordprocessingml.document")) name= "MS Word OpenXML"
//Application/octet-stream - shorten it
else if((name !== undefined) && (name == "application/octet-stream")) name = "Application file";
if(name === undefined) name = "";
return name;
}
});
this.$('.format-charts-data').html(donut.render().el);
},
drawMetadataCountChart: function(){
var metadataCount = this.model.get("metadataCount");
var data = this.model.get('metadataFormatIDs');
if(metadataCount){
var svgClass = "metadata";
}
else if(!this.model.get('metadataCount') && this.model.get('dataCount')){ //Are we drawing a blank chart (i.e. 0 data objects found)?
var svgClass = "metadata default";
}
else if(!this.model.get('metadataCount') && !this.model.get('dataCount'))
var svgClass = "metadata no-activity";
//If d3 isn't supported in this browser or didn't load correctly, insert a text title instead
if(!d3){
this.$('.format-charts-metadata').html("" + MetacatUI.appView.commaSeparateNumber(metadataCount) + " metadata files
");
return;
}
//Draw a donut chart
var donut = new DonutChart({
id: "metadata-chart",
data: data,
total: this.model.get('metadataCount'),
titleText: "metadata files",
titleCount: metadataCount,
svgClass: svgClass,
countClass: "metadata",
height: 300,
formatLabel: function(name){
if((name !== undefined) && (name.indexOf("//ecoinformatics.org") > -1)){
//EML - extract the version only
if(name.substring(0,4) == "eml:") name = name.substr(name.lastIndexOf("/")+1).toUpperCase().replace('-', ' ');
//EML modules
if(name.indexOf("-//ecoinformatics.org//eml-") > -1) name = "EML " + name.substring(name.indexOf("//eml-")+6, name.lastIndexOf("-")) + " " + name.substr(name.lastIndexOf("-")+1, 5);
}
//Dryad - shorten it
else if((name !== undefined) && (name == "http://datadryad.org/profile/v3.1")) name = "Dryad 3.1";
//FGDC - just display "FGDC {year}"
else if((name !== undefined) && (name.indexOf("FGDC") > -1)) name = "FGDC " + name.substring(name.length-4);
if(name === undefined) name = "";
return name;
}
});
this.$('.format-charts-metadata').html(donut.render().el);
},
drawFirstUpload: function(){
var className = "";
if( !this.model.get("firstUpload") ){
var chartData = [{
count: "N/A",
className: "packages no-activity"
}];
}
else{
var firstUpload = new Date(this.model.get("firstUpload")),
readableDate = firstUpload.toDateString();
readableDate = readableDate.substring(readableDate.indexOf(" ") + 1);
var chartData = [{
count: readableDate,
className: "packages"
}];
}
//Create the circle badge
var dateBadge = new CircleBadge({
id: "first-upload-badge",
data: chartData,
title: "first upload",
titlePlacement: "inside",
useGlobalR: true,
globalR: 100
});
this.$("#first-upload").html(dateBadge.render().el);
},
//drawUploadChart will get the upload stats from the stats model and draw a time series cumulative chart
drawUploadChart: function(){
//Get the width of the chart by using the parent container width
var parentEl = this.$('.upload-chart');
var width = parentEl.width() || null;
//If there was no first upload, draw a blank chart and exit
if(!this.model.get('firstUpload')){
var lineChartView = new LineChart(
{ id: "upload-chart",
yLabel: "files uploaded",
frequency: 0,
width: width
});
this.$('.upload-chart').html(lineChartView.render().el);
return;
}
//Set the frequency of our points
var frequency = 12;
//Check which line we should draw first since the scale will be based off the first line
if(this.model.get("metadataUploads") > this.model.get("dataUploads") ){
//If there isn't a lot of point to graph, draw points more frequently on the line
if(this.model.get("metadataUploadDates").length < 40) frequency = 1;
//Create the line chart and draw the metadata line
var lineChartView = new LineChart(
{ data: this.model.get('metadataUploadDates'),
formatFromSolrFacets: true,
cumulative: true,
id: "upload-chart",
className: "metadata",
yLabel: "files uploaded",
labelValue: "Metadata: ",
// frequency: frequency,
radius: 2,
width: width,
labelDate: "M-y"
});
this.$('.upload-chart').html(lineChartView.render().el);
//Only draw the data file line if there was at least one uploaded
if(this.model.get("dataUploads")){
//Add a line to our chart for data uploads
lineChartView.className = "data";
lineChartView.labelValue ="Data: ";
lineChartView.addLine(this.model.get('dataUploadDates'));
}
}
else{
var lineChartView = new LineChart(
{ data: this.model.get('dataUploadDates'),
formatFromSolrFacets: true,
cumulative: true,
id: "upload-chart",
className: "data",
yLabel: "files uploaded",
labelValue: "Data: ",
// frequency: frequency,
radius: 2,
width: width,
labelDate: "M-y"
});
this.$('.upload-chart').html(lineChartView.render().el);
//If no metadata files were uploaded, we don't want to draw the data file line
if(this.model.get("metadataUploads")){
//Add a line to our chart for metadata uploads
lineChartView.className = "metadata";
lineChartView.labelValue = "Metadata: ";
lineChartView.addLine(this.model.get('metadataUploadDates'));
}
}
},
//drawUploadTitle will draw a circle badge title for the uploads time series chart
drawUploadTitle: function(){
//If d3 isn't supported in this browser or didn't load correctly, insert a text title instead
if(!d3){
this.$('#uploads-title').html("" + MetacatUI.appView.commaSeparateNumber(this.model.get('totalUploads')) + "
");
return;
}
if(!this.model.get('dataUploads') && !this.model.get('metadataUploads')){
//Draw the upload chart title
var uploadChartTitle = new CircleBadge({
id: "upload-chart-title",
className: "no-activity",
globalR: 60,
data: [{ count: 0, label: "uploads" }]
});
this.$('#uploads-title').prepend(uploadChartTitle.render().el);
return;
}
//Get information for our upload chart title
var titleChartData = [],
metadataUploads = this.model.get("metadataUploads"),
dataUploads = this.model.get("dataUploads"),
metadataClass = "metadata",
dataClass = "data";
if(metadataUploads == 0) metadataClass = "default";
if(dataUploads == 0) dataClass = "default";
var titleChartData = [
{count: this.model.get("metadataUploads"), label: "metadata", className: metadataClass},
{count: this.model.get("dataUploads"), label: "data", className: dataClass}
];
//Draw the upload chart title
var uploadChartTitle = new CircleBadge({
id: "upload-chart-title",
data: titleChartData,
className: "chart-title",
useGlobalR: true,
globalR: 60
});
this.$('#uploads-title').prepend(uploadChartTitle.render().el);
},
/*
* drawTotalCount - draws a simple count of total metadata files/datasets
*/
drawTotalCount: function(){
var className = "";
if( !this.model.get("metadataCount") && !this.model.get("dataCount") )
className += " no-activity";
var chartData = [{
count: this.model.get("metadataCount"),
className: "packages" + className
}];
//Create the circle badge
var countBadge = new CircleBadge({
id: "total-datasets-title",
data: chartData,
title: "datasets",
titlePlacement: "inside",
useGlobalR: true,
globalR: 100,
height: 220
});
this.$('#total-datasets').html(countBadge.render().el);
},
/*
* drawTotalSize draws a CircleBadgeView with the total file size of
* all current metadata and data files
*/
drawTotalSize: function(){
if( !this.model.get("totalSize") ){
var chartData = [{
count: "0 bytes",
className: "packages no-activity"
}];
}
else{
var chartData = [{
count: this.bytesToSize( this.model.get("totalSize") ),
className: "packages"
}];
}
//Create the circle badge
var sizeBadge = new CircleBadge({
id: "total-size-title",
data: chartData,
title: "of content",
titlePlacement: "inside",
useGlobalR: true,
globalR: 100,
height: 220
});
this.$('#total-size').html(sizeBadge.render().el);
},
/*
* drawUpdatesChart - draws a line chart representing the latest updates over time
*/
drawUpdatesChart: function(){
//If there was no first upload, draw a blank chart and exit
if(!this.model.get('firstUpdate')){
var lineChartView = new LineChart(
{ id: "updates-chart",
yLabel: "files updated",
frequency: 0,
cumulative: false,
width: this.$('.metadata-updates-chart').width()
});
this.$('.metadata-updates-chart').html(lineChartView.render().el);
return;
}
//Set the frequency of our points
var frequency = 12;
//If there isn't a lot of points to graph, draw points more frequently on the line
if(this.model.get("metadataUpdateDates").length < 40) frequency = 1;
//Create the line chart for metadata updates
var metadataLineChart = new LineChart(
{ data: this.model.get('metadataUpdateDates'),
formatFromSolrFacets: true,
cumulative: false,
id: "updates-chart",
className: "metadata",
yLabel: "metadata files updated",
// frequency: frequency,
radius: 2,
width: this.$('.metadata-updates-chart').width(),
labelDate: "M-y"
});
this.$('.metadata-updates-chart').html(metadataLineChart.render().el);
//Only draw the data updates chart if there was at least one uploaded
if(this.model.get("dataCount")){
//Create the line chart for data updates
var dataLineChart = new LineChart(
{ data: this.model.get('dataUpdateDates'),
formatFromSolrFacets: true,
cumulative: false,
id: "updates-chart",
className: "data",
yLabel: "data files updated",
// frequency: frequency,
radius: 2,
width: this.$('.data-updates-chart').width(),
labelDate: "M-y"
});
this.$('.data-updates-chart').html(dataLineChart.render().el);
}
else{
//Create the line chart for data updates
var dataLineChart = new LineChart(
{ data: null,
formatFromSolrFacets: true,
cumulative: false,
id: "updates-chart",
className: "data no-activity",
yLabel: "data files updated",
// frequency: frequency,
radius: 2,
width: this.$('.data-updates-chart').width(),
labelDate: "M-y"
});
this.$('.data-updates-chart').html(dataLineChart.render().el);
}
},
/*
* drawDownloadsChart - draws a line chart representing the downloads over time
*/
drawDownloadsChart: function(){
//Only draw the chart once both metadata and data dates have been retrieved
//if(!this.model.get("metadataDownloadDates") || !this.model.get("dataDownloadDates")) return;
if(!this.model.get("downloadDates")) return;
//Get the width of the chart by using the parent container width
var parentEl = this.$('.download-chart');
var width = parentEl.width() || null;
//If there are no download stats, show a message and exit
if(!this.model.get('downloads')){
var msg = "No one has downloaded any of this data or download statistics are not being reported";
parentEl.html("" + msg + ".
");
return;
}
//Set the frequency of our points
var frequency = 6;
//Check which line we should draw first since the scale will be based off the first line
var options = {
data: this.model.get('downloadDates'),
formatFromSolrFacetRanges: true,
id: "download-chart",
yLabel: "all downloads",
barClass: "packages",
roundedRect: true,
roundedRadius: 10,
barLabelClass: "packages",
width: width
};
var barChart = new BarChart(options);
parentEl.html(barChart.render().el);
},
//drawDownloadTitle will draw a circle badge title for the downloads time series chart
drawDownloadTitle: function(){
//If d3 isn't supported in this browser or didn't load correctly, insert a text title instead
if(!d3){
this.$('#downloads-title').html("" + MetacatUI.appView.commaSeparateNumber(this.model.get('downloads')) + "
");
return;
}
//If there are 0 downloads, draw a default/blank chart title
if(!this.model.get('downloads')){
var downloadChartTitle = new CircleBadge({
id: "download-chart-title",
className: this.model.get("totalUploads") ? "default" : "no-activity",
globalR: 60,
data: [{ count: 0, label: "downloads" }]
});
this.$('#downloads-title').html(downloadChartTitle.render().el);
this.listenToOnce(this.model, "change:totalUploads", this.drawDownloadTitle);
return;
}
//Get information for our download chart title
var titleChartData = [],
metadataDownloads = this.model.get("metadataDownloads"),
dataDownloads = this.model.get("dataDownloads"),
metadataClass = "metadata",
dataClass = "data";
if(metadataDownloads == 0) metadataClass = "default";
if(dataDownloads == 0) dataClass = "default";
var titleChartData = [
{count: this.model.get("metadataDownloads"), label: "metadata", className: metadataClass},
{count: this.model.get("dataDownloads"), label: "data", className: dataClass}
];
//Draw the download chart title
var downloadChartTitle = new CircleBadge({
id: "download-chart-title",
data: titleChartData,
className: "chart-title",
useGlobalR: true,
globalR: 60
});
this.$('#downloads-title').html(downloadChartTitle.render().el);
},
//Draw a bar chart for the temporal coverage
drawCoverageChart: function(e, data){
//Get the width of the chart by using the parent container width
var parentEl = this.$('.temporal-coverage-chart');
var width = parentEl.width() || null;
// If results were found but none have temporal coverage, draw a default chart
if(!this.model.get('firstBeginDate')){
parentEl.html("There are no metadata documents that describe temporal coverage.
");
return;
}
var options = {
data: data,
formatFromSolrFacets: true,
id: "temporal-coverage-chart",
yLabel: "data packages",
yFormat: d3.format(",d"),
barClass: "packages",
roundedRect: true,
roundedRadius: 10,
barLabelClass: "packages",
width: width
};
var barChart = new BarChart(options);
parentEl.html(barChart.render().el);
},
drawCoverageChartTitle: function(){
if((!this.model.get('firstBeginDate')) || (!this.model.get('lastEndDate'))) return;
//Create the range query
var yearRange = this.model.get('firstBeginDate').getUTCFullYear() + " - " + this.model.get('lastEndDate').getUTCFullYear();
//Find the year range element
this.$('#data-coverage-year-range').text(yearRange);
},
drawMdqStats: function() {
if (!this.model.get("mdqStats")) {
return;
}
if (!this.model.get("mdqStatsTotal")) {
return;
}
var mdqCompositeStats= this.model.get("mdqStats").mdq_composite_d;
var mdqTotalStats = this.model.get("mdqStatsTotal").mdq_composite_d;
if (mdqTotalStats && mdqTotalStats.mean && mdqCompositeStats && mdqCompositeStats.mean) {
var diff = mdqCompositeStats.mean - mdqTotalStats.mean;
var repoAvg = (mdqTotalStats.mean*100).toFixed(0) + "%";
if (diff < 0) {
$("#mdq-percentile-container").text("Below repository average");
$("#mdq-percentile-icon").addClass("icon-thumbs-down");
}
if (diff > 0) {
$("#mdq-percentile-container").text("Above repository average");
$("#mdq-percentile-icon").addClass("icon-thumbs-up");
}
if (diff == 0) {
$("#mdq-percentile-container").text("At repository average");
$("#mdq-percentile-icon").addClass("icon-star");
}
// for the box plot
// top arrow for this view
$("#mdq-score-num").text((mdqCompositeStats.mean*100).toFixed(0) + "%");
$("#mdq-score").css(
{
"margin-left": (mdqCompositeStats.mean*100).toFixed(0) + "%"
});
// the range
$("#mdq-box").css(
{
"width": ((mdqCompositeStats.max - mdqCompositeStats.min) * 100).toFixed(0) + "%",
"margin-left": (mdqCompositeStats.min*100).toFixed(0) + "%"
});
$("#mdq-box").attr("data-content", mdqCompositeStats.count + " scores range from " + (mdqCompositeStats.min*100).toFixed(0) + "%" + " to " + (mdqCompositeStats.max*100).toFixed(0) + "%");
// the bottom arrow for repo
$("#mdq-repo-score-num").text((mdqTotalStats.mean*100).toFixed(0) + "%");
$("#mdq-repo-score").css(
{
"margin-left": (mdqTotalStats.mean*100).toFixed(0) + "%"
});
}
// now draw the chart
this.drawMdqFacets();
},
drawMdqFacets: function() {
var mdqCompositeStats= this.model.get("mdqStats").mdq_composite_d;
if (mdqCompositeStats) {
// keys are the facet values, values are the stats (min, max, mean, etc...)
var datasourceFacets = mdqCompositeStats.facets.mdq_metadata_datasource_s || {};
var formatIdFacets = mdqCompositeStats.facets.mdq_metadata_formatId_s || {};
var rightsHolderFacets = mdqCompositeStats.facets.mdq_metadata_rightsHolder_s || {};
var suiteIdFacets = mdqCompositeStats.facets.mdq_suiteId_s || {};
var funderFacets = mdqCompositeStats.facets.mdq_metadata_funder_sm || {};
var groupFacets = mdqCompositeStats.facets.mdq_metadata_group_sm || {};
if(!Object.keys(datasourceFacets).length &&
!Object.keys(formatIdFacets).length &&
!Object.keys(rightsHolderFacets).length &&
!Object.keys(suiteIdFacets).length &&
!Object.keys(funderFacets).length &&
!Object.keys(groupFacets).length)
return;
//this.drawMdqChart(datasourceFacets);
//this.drawMdqChart(rightsHolderFacets);
this.drawMdqChart(_.extend(formatIdFacets, datasourceFacets, suiteIdFacets, funderFacets, groupFacets));
//Unhide the quality chart
$("#quality-chart").show();
}
},
//Draw a bar chart for the slice
drawMdqChart: function(data){
//Get the width of the chart by using the parent container width
var parentEl = this.$('.mdq-chart');
var width = parentEl.width() || null;
var options = {
data: data,
formatFromSolrFacets: true,
solrFacetField: "mean",
id: "mdq-slice-chart",
yLabel: "mean score",
yFormat: d3.format(",%"),
barClass: "packages",
roundedRect: true,
roundedRadius: 10,
barLabelClass: "packages",
width: width
};
var barChart = new BarChart(options);
parentEl.html(barChart.render().el);
},
/*
* Shows that this person/group/node has no activity
*/
showNoActivity: function(){
this.$(".show-loading .loading").remove();
this.$el.addClass("no-activity");
},
/**
* Convert number of bytes into human readable format
*
* @param integer bytes Number of bytes to convert
* @param integer precision Number of digits after the decimal separator
* @return string
*/
bytesToSize: function(bytes, precision){
var kilobyte = 1024;
var megabyte = kilobyte * 1024;
var gigabyte = megabyte * 1024;
var terabyte = gigabyte * 1024;
if(typeof bytes === "undefined") var bytes = this.get("size");
if ((bytes >= 0) && (bytes < kilobyte)) {
return bytes + ' B';
} else if ((bytes >= kilobyte) && (bytes < megabyte)) {
return (bytes / kilobyte).toFixed(precision) + ' KB';
} else if ((bytes >= megabyte) && (bytes < gigabyte)) {
return (bytes / megabyte).toFixed(precision) + ' MB';
} else if ((bytes >= gigabyte) && (bytes < terabyte)) {
return (bytes / gigabyte).toFixed(precision) + ' GB';
} else if (bytes >= terabyte) {
return (bytes / terabyte).toFixed(precision) + ' TB';
} else {
return bytes + ' B';
}
},
onClose: function () {
//Clear the template
this.$el.html("");
//Stop listening to changes in the model
this.stopListening(this.model);
//Reset the stats model
this.model.clear().set(this.model.defaults);
}
});
return StatsView;
});