Adding Freemarker versions of PersonGrantCount (QueryRunner, RequestHandler, VisCodeGenerator) and related ftls (grantCount, grantSparklineContent)

This commit is contained in:
bkoniden 2010-12-15 22:48:42 +00:00
parent 4e4da7d08e
commit 809ae7b162
7 changed files with 1517 additions and 0 deletions

View file

@ -0,0 +1,7 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#assign googleJSAPI = 'http://www.google.com/jsapi?autoload=%7B%22modules%22%3A%5B%7B%22name%22%3A%22visualization%22%2C%22version%22%3A%221%22%2C%22packages%22%3A%5B%22areachart%22%2C%22imagesparkline%22%5D%7D%5D%7D'>
${headScripts.add(googleJSAPI)}
<#include "/visualization/grantSparklineContent.ftl">

View file

@ -0,0 +1,243 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#assign visContainerID = '${sparklineVO.visContainerDivID}'>
<#if sparklineVO.shortVisMode>
<#assign sparklineContainerID = 'grant_count_short_sparkline_vis'>
<#else>
<#assign sparklineContainerID = 'grant_count_full_sparkline_vis'>
</#if>
<#-- This is used to prevent collision between sparkline & visualization container div ids. -->
<#if visContainerID?upper_case == sparklineContainerID?upper_case>
<#assign sparklineContainerID = visContainerID + "_spark">
</#if>
<div class="staticPageBackground">
<div id="${visContainerID}">
<style type='text/css'>
.sparkline_style table {
margin: 0;
padding: 0;
width: auto;
border-collapse: collapse;
border-spacing: 0;
vertical-align: inherit;
}
table.sparkline_wrapper_table
td,th {
vertical-align: bottom;
}
.vis_link a {
padding-top: 5px;
}
td.sparkline_number {
text-align: right;
padding-right: 5px;
}
td.sparkline_text {
text-align: left;
}
.incomplete-data-holder {
}
</style>
<script type="text/javascript">
function drawGrantCountVisualization(providedSparklineImgTD) {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Year');
data.addColumn('number', 'Grants');
data.addRows(${sparklineVO.numOfYearsToBeRendered});
<#list sparklineVO.yearToGrantCountDataTable as yearToGrantCountDataElement>
data.setValue(${yearToGrantCountDataElement.grantCounter}, 0, '${yearToGrantCountDataElement.investigatedYear}');
data.setValue(${yearToGrantCountDataElement.grantCounter}, 1, ${yearToGrantCountDataElement.currentGrants});
</#list>
<#-- Create a view of the data containing only the column pertaining to grant count. -->
var sparklineDataView = new google.visualization.DataView(data);
sparklineDataView.setColumns([1]);
<#if sparklineVO.shortVisMode>
console.log("Yay! Short Vis Mode!");
<#-- For the short view we only want the last 10 year's view of grant count, hence we filter
the data we actually want to use for render. -->
sparklineDataView.setRows(data.getFilteredRows([{
column: 0,
minValue: '${sparklineVO.earliestRenderedGrantYear?c}',
maxValue: '${sparklineVO.latestRenderedGrantYear?c}'
/*minValue: '2001',
maxValue: '2011'*/
}]));
<#else>
console.log("Yay! Full Vis Mode!");
</#if>
<#-- Create the vis object and draw it in the div pertaining to sparkline. -->
var sparkline = new google.visualization.ImageSparkLine(providedSparklineImgTD[0]);
sparkline.draw(sparklineDataView, {
width: 65,
height: 30,
showAxisLines: false,
showValueLabels: false,
labelPosition: 'none'
});
<#if sparklineVO.shortVisMode>
<#-- We want to display how many grant counts were considered, so this is used to calculate this. -->
var shortSparkRows = sparklineDataView.getViewRows();
var renderedShortSparks = 0;
$.each(shortSparkRows, function(index, value) {
renderedShortSparks += data.getValue(value, 1);
});
$('#${sparklineContainerID} td.sparkline_number').text(parseInt(renderedShortSparks) + parseInt(${sparklineVO.unknownYearGrants}));
var sparksText = ' grant(s) within the last 10 years <span class="incomplete-data-holder" title="This information'
+ ' is based solely on grants which have been loaded into the VIVO system. This may only be a small'
+ ' sample of the person\'s total work.">incomplete list</span>';
<#else>
/*
* Sparks that will be rendered will always be the one's which has
* any year associated with it. Hence.
* */
var renderedSparks = ${sparklineVO.renderedSparks};
$('#${sparklineContainerID} td.sparkline_number').text(parseInt(renderedSparks) + parseInt(${sparklineVO.unknownYearGrants}));
var sparksText = ' grant(s) from <span class="sparkline_range">${sparklineVO.earliestYearConsidered?c}'
+ ' to ${sparklineVO.latestRenderedGrantYear?c}</span> '
+ ' <a href="${sparklineVO.downloadDataLink}" class="inline_href">(.CSV File)</a> ';
</#if>
$('#${sparklineContainerID} td.sparkline_text').html(sparksText);
/*
* This will activate the visualization. It takes care of creating
* div elements to hold the actual sparkline image and then calling the
* drawGrantCountVisualization function.
* */
$(document).ready(function() {
var sparklineImgTD;
/*
* This is a nuclear option (creating the container in which everything goes)
* the only reason this will be ever used is the API user never submitted a
* container ID in which everything goes. The alternative was to let the
* vis not appear in the calling page at all. So now atleast vis appears but
* appended at the bottom of the body.
* */
if ($('#${visContainerID}').length === 0) {
$('<div/>', {
'id': '${visContainerID}'
}).appendTo('body');
}
if ($('#${sparklineContainerID}').length === 0) {
$('<div/>', {
'id': '${sparklineContainerID}',
'class': 'sparkline_style'
}).prependTo('#${visContainerID}');
var table = $('<table>');
table.attr('class', 'sparkline_wrapper_table');
var row = $('<tr>');
sparklineImgTD = $('<td>');
sparklineImgTD.attr('id', '${sparklineContainerID}_img');
sparklineImgTD.attr('width', '65');
sparklineImgTD.attr('align', 'right');
sparklineImgTD.attr('class', 'sparkline_style');
row.append(sparklineImgTD);
console.log(sparklineImgTD);
var sparklineNumberTD = $('<td>');
sparklineNumberTD.attr('width', '30');
sparklineNumberTD.attr('align', 'right');
sparklineNumberTD.attr('class', 'sparkline_number');
row.append(sparklineNumberTD);
var sparklineTextTD = $('<td>');
sparklineTextTD.attr('width', '450');
sparklineTextTD.attr('class', 'sparkline_text');
row.append(sparklineTextTD);
table.append(row);
table.prependTo('#${sparklineContainerID}');
}
console.log(sparklineImgTD);
drawGrantCountVisualization(sparklineImgTD);
});
</script>
</div>
<#if sparklineVO.shortVisMode>
<span class="vis_link">
<a href="${sparklineVO.fullTimelineNetworkLink}">View all grants and corresponding co-pi network.</a>
</span>
<#else>
<!-- For Full Sparkline - Print the Table of Grant Counts per Year -->
<p>
<table id='sparkline_data_table'>
<caption>
Grants per year <a href="${sparklineVO.downloadDataLink}">(.CSV File)</a>
</caption>
<thead>
<tr>
<th>
Year
</th>
<th>
Grants
</th>
</tr>
</thead>
<tbody>
<#list sparklineVO.yearToActivityCount?keys as year>
<tr>
<td>
${year}
</td>
<td>
${sparklineVO.yearToActivityCount[year]}
</td>
</tr>
</#list>
</tbody>
</table>
Download data as <a href="${sparklineVO.downloadDataLink}">.csv</a> file.
<br />
</p>
</#if>
</div>

View file

@ -0,0 +1,161 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.visualization.freemarker.persongrantcount;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import com.hp.hpl.jena.iri.IRI;
import com.hp.hpl.jena.iri.IRIFactory;
import com.hp.hpl.jena.iri.Violation;
import com.hp.hpl.jena.query.DataSource;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.query.Syntax;
import com.hp.hpl.jena.rdf.model.RDFNode;
import edu.cornell.mannlib.vitro.webapp.visualization.constants.QueryConstants;
import edu.cornell.mannlib.vitro.webapp.visualization.constants.QueryFieldLabels;
import edu.cornell.mannlib.vitro.webapp.visualization.exceptions.MalformedQueryParametersException;
import edu.cornell.mannlib.vitro.webapp.visualization.freemarker.valueobjects.Grant;
import edu.cornell.mannlib.vitro.webapp.visualization.freemarker.valueobjects.Individual;
import edu.cornell.mannlib.vitro.webapp.visualization.visutils.QueryRunner;
/**
* This query runner is used to execute Sparql query that will fetch all the grants for an individual
* @author bkoniden
* Deepak Konidena
*
*/
public class PersonGrantCountQueryRunner implements QueryRunner<Set<Grant>>{
protected static final Syntax SYNTAX = Syntax.syntaxARQ;
private String personURI;
private DataSource dataSource;
private Individual principalInvestigator;
public Individual getPrincipalInvestigator(){
return principalInvestigator;
}
private Log log;
private static final String SPARQL_QUERY_COMMON_SELECT_CLAUSE = ""
+ "SELECT (str(?PILabel) as ?PILabelLit) "
+ "(str(?Grant) as ?grantLit)"
+ "(str(?GrantLabel) as ?grantLabelLit)"
+ "(str(?GrantStartDate) as ?grantStartDateLit)"
+ "(str(?GrantEndDate) as ?grantEndDateLit)" ;
public PersonGrantCountQueryRunner(String personURI, DataSource dataSource, Log log){
this.personURI = personURI;
this.dataSource = dataSource;
this.log = log;
}
private Set<Grant> createJavaValueObjects(ResultSet resultSet){
Set<Grant> PIGrant = new HashSet<Grant>();
while(resultSet.hasNext()){
QuerySolution solution = resultSet.nextSolution();
Grant grant = new Grant(solution.get(QueryFieldLabels.GRANT_URL).toString());
RDFNode grantLabelNode = solution.get(QueryFieldLabels.GRANT_LABEL);
if(grantLabelNode != null){
grant.setIndividualLabel(grantLabelNode.toString());
}
RDFNode grantStartDateNode = solution.get(QueryFieldLabels.GRANT_START_DATE);
if(grantStartDateNode != null){
grant.setGrantStartDate(grantStartDateNode.toString());
}
RDFNode grantEndDateNode = solution.get(QueryFieldLabels.GRANT_END_DATE);
if(grantEndDateNode != null){
grant.setGrantEndDate(grantEndDateNode.toString());
}
/*
* Since we are getting grant count for just one PI at a time we need
* to create only one "Individual" instance. We test against the null for "PI" to
* make sure that it has not already been instantiated.
* */
RDFNode PIURLNode = solution.get(QueryFieldLabels.PI_URL);
if (PIURLNode != null && principalInvestigator == null) {
principalInvestigator = new Individual(PIURLNode.toString());
RDFNode PILabelNode = solution.get(QueryFieldLabels.PI_LABEL);
if (PILabelNode != null) {
principalInvestigator.setIndividualLabel(PILabelNode.toString());
}
}
PIGrant.add(grant);
}
return PIGrant;
}
private ResultSet executeQuery(String queryURI, DataSource dataSource){
QueryExecution queryExecution = null;
Query query = QueryFactory.create(getSparqlQuery(queryURI), SYNTAX);
queryExecution = QueryExecutionFactory.create(query,dataSource);
return queryExecution.execSelect();
}
private String getSparqlQuery(String queryURI){
String sparqlQuery = QueryConstants.getSparqlPrefixQuery()
+ SPARQL_QUERY_COMMON_SELECT_CLAUSE
+ "(str(<" + queryURI + ">) as ?PILit) "
+ "WHERE {"
+ "<" + queryURI + "> rdfs:label ?PILabel;"
+ "core:hasCo-PrincipalInvestigatorRole ?Role ."
+ "?Role core:roleIn ?Grant ."
+ "?Grant rdfs:label ?GrantLabel ; core:startDate ?GrantStartDate ; core:endDate ?GrantEndDate ."
+ "}";
System.out.println("SPARQL query for person grant count -> \n"+ sparqlQuery);
return sparqlQuery;
}
public Set<Grant> getQueryResult() throws MalformedQueryParametersException{
if(StringUtils.isNotBlank(this.personURI)){
/*
* To test the validity of the URI submitted
*/
IRIFactory iriFactory = IRIFactory.jenaImplementation();
IRI iri = iriFactory.create(this.personURI);
if(iri.hasViolation(false)){
String errorMsg = ((Violation) iri.violations(false).next()).getShortMessage();
log.error("Grant Count vis Query " + errorMsg);
throw new MalformedQueryParametersException(
"URI provided for an individual is malformed.");
}
} else {
throw new MalformedQueryParametersException("URL parameter is either null or empty.");
}
ResultSet resultSet = executeQuery(this.personURI, this.dataSource);
return createJavaValueObjects(resultSet);
}
}

View file

@ -0,0 +1,375 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.visualization.freemarker.persongrantcount;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import com.hp.hpl.jena.query.DataSource;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfWriter;
import edu.cornell.mannlib.vitro.webapp.beans.Portal;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.FileResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.visualization.VisualizationFrameworkConstants;
import edu.cornell.mannlib.vitro.webapp.visualization.exceptions.MalformedQueryParametersException;
import edu.cornell.mannlib.vitro.webapp.visualization.freemarker.visutils.UtilityFunctions;
import edu.cornell.mannlib.vitro.webapp.visualization.freemarker.visutils.VisualizationRequestHandler;
import edu.cornell.mannlib.vitro.webapp.visualization.freemarker.valueobjects.Grant;
import edu.cornell.mannlib.vitro.webapp.visualization.freemarker.valueobjects.Individual;
import edu.cornell.mannlib.vitro.webapp.visualization.freemarker.valueobjects.SparklineData;
import edu.cornell.mannlib.vitro.webapp.visualization.visutils.PDFDocument;
import edu.cornell.mannlib.vitro.webapp.visualization.visutils.QueryRunner;
import edu.cornell.mannlib.vitro.webapp.web.ContentType;
/**
*
* This request handler is used to serve the content related to an individual's
* grants over the years like,
* 1. Sparkline representing this
* 2. An entire page dedicated to the sparkline vis which will also have links to
* download the data using which the sparkline was rendered & its tabular representation etc.
* 3. Downloadable CSV file containing number of grants over the years.
* 4. Downloadable PDf file containing the grant content, among other things.
* Currently this is disabled because the feature is half-baked. We plan to activate this in
* the next major release.
*
* @author bkoniden
* Deepak Konidena
*/
public class PersonGrantCountRequestHandler implements VisualizationRequestHandler {
public ResponseValues generateVisualization(VitroRequest vitroRequest,
Log log, DataSource dataSource) {
String personURI = vitroRequest
.getParameter(VisualizationFrameworkConstants.INDIVIDUAL_URI_KEY);
String renderMode = vitroRequest
.getParameter(VisualizationFrameworkConstants.RENDER_MODE_KEY);
String visMode = vitroRequest
.getParameter(VisualizationFrameworkConstants.VIS_MODE_KEY);
String visContainer = vitroRequest
.getParameter(VisualizationFrameworkConstants.VIS_CONTAINER_KEY);
QueryRunner<Set<Grant>> queryManager = new PersonGrantCountQueryRunner(personURI, dataSource, log );
try{
Set<Grant> piGrants = queryManager.getQueryResult();
/*
* Create a map from the year to number of grants. Use the Grant's
* parsedGrantYear to populate the data.
* */
Map<String, Integer> yearToGrantCount =
UtilityFunctions.getYearToGrantCount(piGrants);
Individual investigator = ((PersonGrantCountQueryRunner) queryManager).getPrincipalInvestigator();
if (VisualizationFrameworkConstants.DATA_RENDER_MODE
.equalsIgnoreCase(renderMode)) {
return prepareDataResponse(investigator,
piGrants,
yearToGrantCount);
}
/*
* For now we are disabling the capability to render pdf file.
* */
/*
if (VisualizationFrameworkConstants.PDF_RENDER_MODE
.equalsIgnoreCase(renderMode)) {
preparePDFResponse(investigator,
piGrants,
yearToGrantCount,
response);
return;
}
*/
/*
* Computations required to generate HTML for the sparkline & related context.
* */
PersonGrantCountVisCodeGenerator visualizationCodeGenerator =
new PersonGrantCountVisCodeGenerator(vitroRequest.getContextPath(),
personURI,
visMode,
visContainer,
piGrants,
yearToGrantCount,
log);
SparklineData sparklineData = visualizationCodeGenerator
.getValueObjectContainer();
/*
* This is side-effecting because the response of this method is just to redirect to
* a page with visualization on it.
* */
RequestDispatcher requestDispatcher = null;
if (VisualizationFrameworkConstants.DYNAMIC_RENDER_MODE
.equalsIgnoreCase(renderMode)) {
return prepareDynamicResponse(vitroRequest,
sparklineData,
yearToGrantCount);
} else {
return prepareStandaloneResponse(vitroRequest,
sparklineData);
}
} catch (MalformedQueryParametersException e) {
return UtilityFunctions.handleMalformedParameters(
"Visualization Query Error - Individual Grant Count",
e.getMessage(),
vitroRequest);
}
}
private String getGrantsOverTimeCSVContent(Map<String, Integer> yearToGrantCount) {
StringBuilder csvFileContent = new StringBuilder();
csvFileContent.append("Year, Grants\n");
for (Entry<String, Integer> currentEntry : yearToGrantCount.entrySet()) {
csvFileContent.append(StringEscapeUtils.escapeCsv(currentEntry.getKey()));
csvFileContent.append(",");
csvFileContent.append(currentEntry.getValue());
csvFileContent.append("\n");
}
return csvFileContent.toString();
}
/**
* Provides response when csv file containing the grant count over the years
* is requested.
* @param investigator
* @param piGrants
* @param yearToGrantCount
* @return
*/
private FileResponseValues prepareDataResponse(
Individual investigator,
Set<Grant> piGrants,
Map<String, Integer> yearToGrantCount) {
String piName = null;
/*
* To protect against cases where there are no PI grants associated with the
* individual.
* */
if (piGrants.size() > 0) {
piName = investigator.getIndividualLabel();
}
/*
* To make sure that null/empty records for PI names do not cause any mischief.
* */
if (StringUtils.isBlank(piName)) {
piName = "no-principal-investigator";
}
String outputFileName = UtilityFunctions.slugify(piName)
+ "_grants-per-year" + ".csv";
Map<String, Object> fileContents = new HashMap<String, Object>();
fileContents.put("fileContent", getGrantsOverTimeCSVContent(yearToGrantCount));
return new FileResponseValues(new ContentType(), outputFileName, fileContents);
}
/**
* Provides response when an entire page dedicated to grant sparkline is requested.
* @param vreq
* @param valueObjectContainer
* @return
*/
private TemplateResponseValues prepareStandaloneResponse(VitroRequest vreq,
SparklineData valueObjectContainer) {
Portal portal = vreq.getPortal();
String standaloneTemplate = "/visualization/grantCount.ftl";
Map<String, Object> body = new HashMap<String, Object>();
body.put("portalBean", portal);
body.put("title", "Individual Grant Count visualization");
body.put("sparklineVO", valueObjectContainer);
/*
* DO NOT DO THIS HERE. Set stylesheets/scripts in the *.ftl instead using $(scripts.add)
* */
// body.put("scripts", "/templates/visualization/visualization_scripts.jsp");
return new TemplateResponseValues(standaloneTemplate, body);
}
/**
* Provides response when the grant sparkline has to be rendered in already existing
* page, e.g. profile page.
* @param vreq
* @param valueObjectContainer
* @param yearToGrantCount
* @return
*/
private TemplateResponseValues prepareDynamicResponse(
VitroRequest vreq,
SparklineData valueObjectContainer,
Map<String, Integer> yearToGrantCount) {
Portal portal = vreq.getPortal();
String dynamicTemplate = "/visualization/sparklineAjaxVisContent.ftl";
Map<String, Object> body = new HashMap<String, Object>();
body.put("portalBean", portal);
body.put("sparklineVO", valueObjectContainer);
/*
* DO NOT DO THIS HERE. Set stylesheets/scripts in the *.ftl instead using $(scripts.add)
* */
// body.put("scripts", "/templates/visualization/visualization_scripts.jsp");
if (yearToGrantCount.size() > 0) {
body.put("shouldVIVOrenderVis", true);
} else {
body.put("shouldVIVOrenderVis", false);
}
return new TemplateResponseValues(dynamicTemplate, body);
}
private void preparePDFResponse(Individual investigator,
Set<Grant> piGrants,
Map<String, Integer> yearToGrantCount,
HttpServletResponse response) {
String piName = null;
// To protect against cases where there are no PI grants
// associated with the
// / individual.
if (piGrants.size() > 0) {
piName = investigator.getIndividualLabel();
}
// To make sure that null/empty records for PI names do not cause
// any mischief.
if (StringUtils.isBlank(piName)) {
piName = "no-principal-investigator";
}
String outputFileName = UtilityFunctions.slugify(piName)
+ "_report" + ".pdf";
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment;filename="
+ outputFileName);
ServletOutputStream responseOutputStream;
try {
responseOutputStream = response.getOutputStream();
Document document = new Document();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter pdfWriter = PdfWriter.getInstance(document, baos);
document.open();
PDFDocument pdfDocument = new PDFDocument(piName,
yearToGrantCount, document, pdfWriter);
document.close();
// setting some response headers & content type
response.setHeader("Expires", "0");
response.setHeader("Cache-Control",
"must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma", "public");
response.setContentLength(baos.size());
// write ByteArrayOutputStream to the ServletOutputStream
baos.writeTo(responseOutputStream);
responseOutputStream.flush();
responseOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,658 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.visualization.freemarker.persongrantcount;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import edu.cornell.mannlib.vitro.webapp.controller.visualization.freemarker.VisualizationController;
import edu.cornell.mannlib.vitro.webapp.controller.visualization.VisualizationFrameworkConstants;
import edu.cornell.mannlib.vitro.webapp.visualization.constants.VOConstants;
import edu.cornell.mannlib.vitro.webapp.visualization.constants.VisConstants;
import edu.cornell.mannlib.vitro.webapp.visualization.freemarker.valueobjects.Grant;
import edu.cornell.mannlib.vitro.webapp.visualization.freemarker.valueobjects.SparklineData;
import edu.cornell.mannlib.vitro.webapp.visualization.freemarker.valueobjects.YearGrantCountDataElement;
public class PersonGrantCountVisCodeGenerator {
/*
* There are 2 modes of sparkline that are available via this visualization.
* 1. Short Sparkline - This sparkline will render all the data points (or sparks),
* which in this case are the grants over the years, from the last 10 years.
*
* 2. Full Sparkline - This sparkline will render all the data points (or sparks)
* spanning the career of the person & last 10 years at the minimum, in case if
* the person started his career in the last 10 yeras.
* */
private static final Map<String, String> VIS_DIV_NAMES = new HashMap<String, String>() { {
put("SHORT_SPARK", "grant_count_short_sparkline_vis");
put("FULL_SPARK", "grant_count_full_sparkline_vis");
} };
private static final String VISUALIZATION_STYLE_CLASS = "sparkline_style";
private static final String DEFAULT_VIS_CONTAINER_DIV_ID = "grant_count_vis_container";
private Map<String, Integer> yearToGrantCount;
private Log log;
private SparklineData sparklineData;
private String contextPath;
private String individualURI;
public PersonGrantCountVisCodeGenerator(String contextPath,
String individualURIParam, String visMode, String visContainer,
Set<Grant> piGrants,
Map<String, Integer> yearToGrantCount, Log log) {
this.contextPath = contextPath;
this.individualURI = individualURIParam;
this.yearToGrantCount = yearToGrantCount;
this.sparklineData = new SparklineData();
sparklineData.setYearToActivityCount(yearToGrantCount);
this.log = log;
generateVisualizationCode(visMode, visContainer, piGrants);
}
/**
* This method is used to generate the visualization code (HMTL, CSS & JavaScript).
* There 2 parts to it - 1. Actual Content Code & 2. Context Code.
* 1. Actual Content code in this case is the sparkline image, text related to
* data and the wrapping tables. This is generated via call to google vis API through
* JavaScript.
* 2. Context code is generally optional but contains code pertaining to tabulated
* data & links to download files etc.
* @param visMode
* @param visContainer
* @param piGrants
*/
private void generateVisualizationCode(String visMode,
String visContainer,
Set<Grant> piGrants) {
sparklineData.setSparklineContent(getMainVisualizationCode(piGrants,
visMode,
visContainer));
sparklineData.setSparklineContext(getVisualizationContextCode(visMode));
}
private String getMainVisualizationCode(Set<Grant> piGrants,
String visMode, String providedVisContainerID) {
int numOfYearsToBeRendered = 0;
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
int shortSparkMinYear = currentYear
- VisConstants.MINIMUM_YEARS_CONSIDERED_FOR_SPARKLINE + 1;
/*
* This is required because when deciding the range of years over which
* the vis was rendered we dont want to be influenced by the
* "DEFAULT_GRANT_YEAR".
*/
Set<String> grantYears = new HashSet<String>(yearToGrantCount
.keySet());
grantYears.remove(VOConstants.DEFAULT_GRANT_YEAR);
/*
* We are setting the default value of minGrantYear to be 10 years
* before the current year (which is suitably represented by the
* shortSparkMinYear), this in case we run into invalid set of grant
* years.
*/
int minGrantYear = shortSparkMinYear;
String visContainerID = null;
StringBuilder visualizationCode = new StringBuilder();
if (yearToGrantCount.size() > 0) {
try {
minGrantYear = Integer.parseInt(Collections
.min(grantYears));
} catch (NoSuchElementException e1) {
log.debug("vis: " + e1.getMessage() + " error occurred for "
+ yearToGrantCount.toString());
} catch (NumberFormatException e2) {
log.debug("vis: " + e2.getMessage() + " error occurred for "
+ yearToGrantCount.toString());
}
}
int minGrantYearConsidered = 0;
/*
* There might be a case that the author investigated his first grant
* within the last 10 years but we want to make sure that the sparkline
* is representative of at least the last 10 years, so we will set the
* minGrantYearConsidered to "currentYear - 10" which is also given by
* "shortSparkMinYear".
*/
if (minGrantYear > shortSparkMinYear) {
minGrantYearConsidered = shortSparkMinYear;
} else {
minGrantYearConsidered = minGrantYear;
}
numOfYearsToBeRendered = currentYear - minGrantYearConsidered + 1;
sparklineData.setNumOfYearsToBeRendered(numOfYearsToBeRendered);
visualizationCode.append("<style type='text/css'>" + "."
+ VISUALIZATION_STYLE_CLASS + " table{" + " margin: 0;"
+ " padding: 0;" + " width: auto;"
+ " border-collapse: collapse;" + " border-spacing: 0;"
+ " vertical-align: inherit;" + "}"
+ "table.sparkline_wrapper_table td, th {"
+ " vertical-align: bottom;" + "}" + ".vis_link a{"
+ " padding-top: 5px;" + "}"
+ "td.sparkline_number { text-align:right; "
+ "padding-right:5px; }"
+ "td.sparkline_text {text-align:left;}"
+ ".incomplete-data-holder {" + "" + "}" + "</style>\n");
visualizationCode.append("<script type=\"text/javascript\">\n"
+ "function drawGrantCountVisualization(providedSparklineImgTD) "
+ "{\n" + "var data = new google.visualization.DataTable();\n"
+ "data.addColumn('string', 'Year');\n"
+ "data.addColumn('number', 'Publications');\n"
+ "data.addRows(" + numOfYearsToBeRendered + ");\n");
int grantCounter = 0;
/*
* For the purpose of this visualization I have come up with a term
* "Sparks" which essentially means data points. Sparks that will be
* rendered in full mode will always be the one's which have any year
* associated with it. Hence.
*/
int renderedFullSparks = 0;
List<YearGrantCountDataElement> yearToGrantCountDataTable = new ArrayList<YearGrantCountDataElement>();
for (int grantYear = minGrantYearConsidered; grantYear <= currentYear; grantYear++) {
String stringInvestigatedYear = String.valueOf(grantYear);
Integer currentGrants = yearToGrantCount
.get(stringInvestigatedYear);
if (currentGrants == null) {
currentGrants = 0;
}
visualizationCode.append("data.setValue(" + grantCounter
+ ", 0, '" + stringInvestigatedYear + "');\n");
visualizationCode.append("data.setValue(" + grantCounter
+ ", 1, " + currentGrants + ");\n");
yearToGrantCountDataTable
.add(new YearGrantCountDataElement(
grantCounter, stringInvestigatedYear,
currentGrants));
/*
* Sparks that will be rendered will always be the one's which has
* any year associated with it. Hence.
*/
renderedFullSparks += currentGrants;
grantCounter++;
}
sparklineData
.setYearToGrantCountDataTable(yearToGrantCountDataTable);
sparklineData.setRenderedSparks(renderedFullSparks);
/*
* Total grants will also consider publications that have no year
* associated with it. Hence.
*/
Integer unknownYearGrants = 0;
if (yearToGrantCount.get(VOConstants.DEFAULT_GRANT_YEAR) != null) {
unknownYearGrants = yearToGrantCount
.get(VOConstants.DEFAULT_GRANT_YEAR);
}
sparklineData.setUnknownYearGrants(unknownYearGrants);
String sparklineDisplayOptions = "{width: 65, height: 30, showAxisLines: false, "
+ "showValueLabels: false, labelPosition: 'none'}";
if (providedVisContainerID != null) {
visContainerID = providedVisContainerID;
} else {
visContainerID = DEFAULT_VIS_CONTAINER_DIV_ID;
}
sparklineData.setVisContainerDivID(visContainerID);
/*
* By default these represents the range of the rendered sparks. Only in
* case of "short" sparkline mode we will set the Earliest
* RenderedGrant year to "currentYear - 10".
*/
sparklineData.setEarliestYearConsidered(minGrantYearConsidered);
sparklineData.setEarliestRenderedGrantYear(minGrantYear);
sparklineData.setLatestRenderedGrantYear(currentYear);
/*
* The Full Sparkline will be rendered by default. Only if the url has
* specific mention of SHORT_SPARKLINE_MODE_URL_HANDLE then we render
* the short sparkline and not otherwise.
*/
/*
* Since building StringBuilder objects (which is being used to store
* the vis code) is essentially a side-effecting process, we have both
* the activators method as side- effecting. They both side-effect
* "visualizationCode"
*/
if (VisualizationFrameworkConstants.SHORT_SPARKLINE_VIS_MODE
.equalsIgnoreCase(visMode)) {
sparklineData.setEarliestRenderedGrantYear(shortSparkMinYear);
sparklineData.setShortVisMode(true);
generateShortSparklineVisualizationContent(currentYear,
shortSparkMinYear, visContainerID, visualizationCode,
unknownYearGrants, sparklineDisplayOptions);
} else {
sparklineData.setShortVisMode(false);
generateFullSparklineVisualizationContent(currentYear,
minGrantYearConsidered, visContainerID, visualizationCode,
unknownYearGrants, renderedFullSparks,
sparklineDisplayOptions);
}
log.debug(visualizationCode);
return visualizationCode.toString();
}
private void generateShortSparklineVisualizationContent(int currentYear,
int shortSparkMinYear, String visContainerID,
StringBuilder visualizationCode, int unknownYearGrants,
String sparklineDisplayOptions) {
/*
* Create a view of the data containing only the column pertaining to
* grant count.
*/
visualizationCode.append("var shortSparklineView = "
+ "new google.visualization.DataView(data);\n"
+ "shortSparklineView.setColumns([1]);\n");
/*
* For the short view we only want the last 10 year's view of
* grant count, hence we filter the data we actually want to use
* for render.
*/
visualizationCode.append("shortSparklineView.setRows("
+ "data.getFilteredRows([{column: 0, " + "minValue: '"
+ shortSparkMinYear + "', " + "maxValue: '" + currentYear
+ "'}])" + ");\n");
/*
* Create the vis object and draw it in the div pertaining to
* short-sparkline.
*/
visualizationCode
.append("var short_spark = new google.visualization.ImageSparkLine("
+ "providedSparklineImgTD[0]"
+ ");\n"
+ "short_spark.draw(shortSparklineView, "
+ sparklineDisplayOptions + ");\n");
/*
* We want to display how many grant counts were considered, so
* this is used to calculate this.
*/
visualizationCode
.append("var shortSparkRows = shortSparklineView.getViewRows();\n"
+ "var renderedShortSparks = 0;\n"
+ "$.each(shortSparkRows, function(index, value) {"
+ "renderedShortSparks += data.getValue(value, 1);"
+ "});\n");
/*
* Generate the text introducing the vis.
*/
String imcompleteDataText = "This information is based solely on grants which "
+ "have been loaded into the VIVO system. "
+ "This may only be a small sample of the person\\'s "
+ "total work.";
visualizationCode.append("$('#" + VIS_DIV_NAMES.get("SHORT_SPARK")
+ " td.sparkline_number').text("
+ "parseInt(renderedShortSparks) " + "+ parseInt("
+ unknownYearGrants + "));");
visualizationCode.append("var shortSparksText = ''"
+ "+ ' grant(s) within the last 10 years "
+ "<span class=\"incomplete-data-holder\" title=\""
+ imcompleteDataText + "\">incomplete data</span>'" + "+ '';"
+ "$('#" + VIS_DIV_NAMES.get("SHORT_SPARK") + " "
+ "td.sparkline_text').html(shortSparksText);");
visualizationCode.append("}\n ");
/*
* Generate the code that will activate the visualization. It takes care
* of creating div elements to hold the actual sparkline image and then
* calling the drawGrantCountVisualization function.
*/
visualizationCode.append(generateVisualizationActivator(VIS_DIV_NAMES
.get("SHORT_SPARK"), visContainerID));
}
private void generateFullSparklineVisualizationContent(
int currentYear,
int minGrantYearConsidered,
String visContainerID,
StringBuilder visualizationCode,
int unknownYearGrants,
int renderedFullSparks,
String sparklineDisplayOptions) {
String csvDownloadURLHref = "";
try {
if (getCSVDownloadURL() != null) {
csvDownloadURLHref = "<a href=\"" + getCSVDownloadURL()
+ "\" class=\"inline_href\">(.CSV File)</a>";
} else {
csvDownloadURLHref = "";
}
} catch (UnsupportedEncodingException e) {
csvDownloadURLHref = "";
}
visualizationCode.append("var fullSparklineView = "
+ "new google.visualization.DataView(data);\n"
+ "fullSparklineView.setColumns([1]);\n");
visualizationCode.append("var full_spark = new google.visualization.ImageSparkLine("
+ "providedSparklineImgTD[0]"
+ ");\n"
+ "full_spark.draw(fullSparklineView, "
+ sparklineDisplayOptions + ");\n");
visualizationCode.append("$('#" + VIS_DIV_NAMES.get("FULL_SPARK")
+ " td.sparkline_number').text('" + (renderedFullSparks
+ unknownYearGrants) + "');");
visualizationCode.append("var allSparksText = ''"
+ "+ ' grant(s) '"
+ "+ ' from "
+ "<span class=\"sparkline_range\">"
+ "" + minGrantYearConsidered + " to " + currentYear + ""
+ "</span> '"
+ "+ ' " + csvDownloadURLHref + " ';"
+ "$('#" + VIS_DIV_NAMES.get("FULL_SPARK")
+ " td.sparkline_text').html(allSparksText);");
visualizationCode.append("}\n ");
visualizationCode.append(generateVisualizationActivator(VIS_DIV_NAMES.get("FULL_SPARK"),
visContainerID));
}
private String generateVisualizationActivator(String sparklineID, String visContainerID) {
String sparklineTableWrapper = "\n"
+ "var table = $('<table>');"
+ "table.attr('class', 'sparkline_wrapper_table');"
+ "var row = $('<tr>');"
+ "sparklineImgTD = $('<td>');"
+ "sparklineImgTD.attr('id', '" + sparklineID + "_img');"
+ "sparklineImgTD.attr('width', '65');"
+ "sparklineImgTD.attr('align', 'right');"
+ "sparklineImgTD.attr('class', '" + VISUALIZATION_STYLE_CLASS + "');"
+ "row.append(sparklineImgTD);"
+ "var sparklineNumberTD = $('<td>');"
+ "sparklineNumberTD.attr('width', '30');"
+ "sparklineNumberTD.attr('align', 'right');"
+ "sparklineNumberTD.attr('class', 'sparkline_number');"
+ "row.append(sparklineNumberTD);"
+ "var sparklineTextTD = $('<td>');"
+ "sparklineTextTD.attr('width', '450');"
+ "sparklineTextTD.attr('class', 'sparkline_text');"
+ "row.append(sparklineTextTD);"
+ "table.append(row);"
+ "table.prependTo('#" + sparklineID + "');\n";
return "$(document).ready(function() {"
+ "var sparklineImgTD; "
/*
* This is a nuclear option (creating the container in which everything goes)
* the only reason this will be ever used is the API user never submitted a
* container ID in which everything goes. The alternative was to let the
* vis not appear in the calling page at all. So now atleast vis appears but
* appended at the bottom of the body.
* */
+ "if ($('#" + visContainerID + "').length === 0) {"
+ " $('<div/>', {'id': '" + visContainerID + "'"
+ " }).appendTo('body');"
+ "}"
+ "if ($('#" + sparklineID + "').length === 0) {"
+ "$('<div/>', {'id': '" + sparklineID + "',"
+ "'class': '" + VISUALIZATION_STYLE_CLASS + "'"
+ "}).prependTo('#" + visContainerID + "');"
+ sparklineTableWrapper
+ "}"
+ "drawPubCountVisualization(sparklineImgTD);"
+ "});"
+ "</script>\n";
}
private String getVisualizationContextCode(String visMode) {
String visualizationContextCode = "";
if (VisualizationFrameworkConstants.SHORT_SPARKLINE_VIS_MODE.equalsIgnoreCase(visMode)) {
visualizationContextCode = generateShortVisContext();
} else {
visualizationContextCode = generateFullVisContext();
}
log.debug(visualizationContextCode);
return visualizationContextCode;
}
private String generateFullVisContext() {
StringBuilder divContextCode = new StringBuilder();
String csvDownloadURLHref = "";
if (yearToGrantCount.size() > 0) {
try {
if (getCSVDownloadURL() != null) {
csvDownloadURLHref = "Download data as <a href='"
+ getCSVDownloadURL() + "'>.csv</a> file.<br />";
sparklineData.setDownloadDataLink(getCSVDownloadURL());
} else {
csvDownloadURLHref = "";
}
} catch (UnsupportedEncodingException e) {
csvDownloadURLHref = "";
}
} else {
csvDownloadURLHref = "No data available to export.<br />";
}
String tableCode = generateDataTable();
divContextCode.append("<p>" + tableCode + csvDownloadURLHref + "</p>");
sparklineData.setTable(tableCode);
return divContextCode.toString();
}
private String getCSVDownloadURL()
throws UnsupportedEncodingException {
if (yearToGrantCount.size() > 0) {
String secondaryContextPath = "";
if (!contextPath.contains(VisualizationFrameworkConstants.VISUALIZATION_URL_PREFIX)) {
secondaryContextPath = VisualizationFrameworkConstants.VISUALIZATION_URL_PREFIX;
}
String downloadURL = contextPath
+ secondaryContextPath
+ "?" + VisualizationFrameworkConstants.INDIVIDUAL_URI_KEY
+ "=" + URLEncoder.encode(individualURI,
VisualizationController.URL_ENCODING_SCHEME)
.toString()
+ "&" + VisualizationFrameworkConstants.VIS_TYPE_KEY
+ "=" + URLEncoder.encode(
VisualizationFrameworkConstants
.PERSON_GRANT_COUNT_VIS,
VisualizationController.URL_ENCODING_SCHEME)
.toString()
+ "&" + VisualizationFrameworkConstants.RENDER_MODE_KEY
+ "=" + URLEncoder.encode(VisualizationFrameworkConstants
.DATA_RENDER_MODE,
VisualizationController.URL_ENCODING_SCHEME)
.toString();
return downloadURL;
} else {
return null;
}
}
private String generateShortVisContext() {
StringBuilder divContextCode = new StringBuilder();
try {
String fullTimelineLink;
if (yearToGrantCount.size() > 0) {
String secondaryContextPath = "";
if (!contextPath.contains(VisualizationFrameworkConstants.VISUALIZATION_URL_PREFIX)) {
secondaryContextPath = VisualizationFrameworkConstants.VISUALIZATION_URL_PREFIX;
}
String fullTimelineNetworkURL = contextPath
+ secondaryContextPath
+ "?"
+ VisualizationFrameworkConstants.INDIVIDUAL_URI_KEY
+ "=" + URLEncoder.encode(individualURI,
VisualizationController.URL_ENCODING_SCHEME).toString()
+ "&"
+ VisualizationFrameworkConstants.VIS_TYPE_KEY
+ "=" + URLEncoder.encode("person_level",
VisualizationController.URL_ENCODING_SCHEME).toString()
+ "&"
+ VisualizationFrameworkConstants.RENDER_MODE_KEY
+ "=" + URLEncoder.encode(VisualizationFrameworkConstants
.STANDALONE_RENDER_MODE,
VisualizationController.URL_ENCODING_SCHEME).toString();
fullTimelineLink = "<a href='" + fullTimelineNetworkURL + "'>View all VIVO "
+ "grants and corresponding co-pi network.</a>";
sparklineData.setFullTimelineNetworkLink(fullTimelineNetworkURL);
} else {
fullTimelineLink = "No data available to render full timeline.<br />";
}
divContextCode.append("<span class=\"vis_link\">" + fullTimelineLink + "</span>");
} catch (UnsupportedEncodingException e) {
log.error(e);
}
return divContextCode.toString();
}
private String generateDataTable() {
String csvDownloadURLHref = "";
try {
if (getCSVDownloadURL() != null) {
csvDownloadURLHref = "<a href=\"" + getCSVDownloadURL() + "\">(.CSV File)</a>";
} else {
csvDownloadURLHref = "";
}
} catch (UnsupportedEncodingException e) {
csvDownloadURLHref = "";
}
StringBuilder dataTable = new StringBuilder();
dataTable.append("<table id='sparkline_data_table'>"
+ "<caption>Grants per year " + csvDownloadURLHref + "</caption>"
+ "<thead>"
+ "<tr>"
+ "<th>Year</th>"
+ "<th>Grants</th>"
+ "</tr>"
+ "</thead>"
+ "<tbody>");
for (Entry<String, Integer> currentEntry : yearToGrantCount.entrySet()) {
dataTable.append("<tr>"
+ "<td>" + currentEntry.getKey() + "</td>"
+ "<td>" + currentEntry.getValue() + "</td>"
+ "</tr>");
}
dataTable.append("</tbody>\n </table>\n");
return dataTable.toString();
}
public SparklineData getValueObjectContainer() {
return sparklineData;
}
}

View file

@ -18,9 +18,13 @@ public class SparklineData {
private Integer earliestYearConsidered;
private Integer earliestRenderedPublicationYear;
private Integer latestRenderedPublicationYear;
private Integer earliestRenderedGrantYear;
private Integer latestRenderedGrantYear;
private Integer renderedSparks;
private Integer unknownYearPublications;
private Integer unknownYearGrants;
private Map<String, Integer> yearToActivityCount;
@ -36,6 +40,7 @@ public class SparklineData {
private boolean isShortVisMode = true;
private List<YearPublicationCountDataElement> yearToPublicationCountDataTable;
private List<YearGrantCountDataElement> yearToGrantCountDataTable;
private int numOfYearsToBeRendered;
@ -56,6 +61,39 @@ public class SparklineData {
this.sparklinePublicationRangeText = sparklinePublicationRangeText;
}
public Integer getEarliestRenderedGrantYear() {
return earliestRenderedGrantYear;
}
public void setEarliestRenderedGrantYear(Integer earliestRenderedGrantYear) {
this.earliestRenderedGrantYear = earliestRenderedGrantYear;
}
public Integer getLatestRenderedGrantYear() {
return latestRenderedGrantYear;
}
public void setLatestRenderedGrantYear(Integer latestRenderedGrantYear) {
this.latestRenderedGrantYear = latestRenderedGrantYear;
}
public Integer getUnknownYearGrants() {
return unknownYearGrants;
}
public void setUnknownYearGrants(Integer unknownYearGrants) {
this.unknownYearGrants = unknownYearGrants;
}
public List<YearGrantCountDataElement> getYearToGrantCountDataTable() {
return yearToGrantCountDataTable;
}
public void setYearToGrantCountDataTable(
List<YearGrantCountDataElement> yearToGrantCountDataTable) {
this.yearToGrantCountDataTable = yearToGrantCountDataTable;
}
public void setNumOfYearsToBeRendered(int numOfYearsToBeRendered) {
this.numOfYearsToBeRendered = numOfYearsToBeRendered;
}

View file

@ -0,0 +1,35 @@
package edu.cornell.mannlib.vitro.webapp.visualization.freemarker.valueobjects;
/**
* This object is used to store information about the yearToGrantCount Map in the format
* easily expressed to Google Visualization's DataTableAPI.
* @author bkoniden
* Deepak Konidena
*/
public class YearGrantCountDataElement {
private int grantCounter;
private String investigatedYear;
private int currentGrants;
public YearGrantCountDataElement(int grantCounter,
String investigatedYear, int currentGrants) {
this.grantCounter = grantCounter;
this.investigatedYear = investigatedYear;
this.currentGrants = currentGrants;
}
public int getGrantCounter() {
return grantCounter;
}
public String getInvestigatedYear() {
return investigatedYear;
}
public int getCurrentGrants() {
return currentGrants;
}
}