/*
* File : Img.java
* Created : 03-feb-2003 16:46
* By : fbusquets
*
* JClic - Authoring and playing system for educational activities
*
* Copyright (C) 2000 - 2005 Francesc Busquets & Departament
* d'Educacio de la Generalitat de Catalunya
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* 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 the LICENSE file).
*/
package edu.xtec.jclic.report.rp;
import edu.xtec.jclic.report.ActivityData;
import edu.xtec.jclic.report.SessionData;
import static edu.xtec.servlet.RequestProcessor.CONTENT_TYPE;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.image.BufferedImage;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import javax.imageio.ImageIO;
/**
*
* @author Francesc Busquets (fbusquets@xtec.cat)
* @version 13.09.17
*/
public class Img extends BasicReport {
public static final String URL="img";
public static final String TYPE="type", HEADER="header", GRAPH="graph",
TEXT="text", DIST="dist", WIDTH="w", HEIGHT="h";
public static final String USER_GRAPH="userGraph", GROUP_GRAPH="groupGraph",
PROJECT_GRAPH="projectGraph";
public static int DEFAULT_WIDTH=0, DIST_WIDTH, DEFAULT_HEIGHT,
DEFAULT_HEADER_HEIGHT, MRG;
public static Color BG_COLOR, TEXT_COLOR, BORDER_COLOR,
HEADER_BG_COLOR, HEADER_TEXT_COLOR,
V1_COLOR, V2_COLOR, DIST_COLOR, ALERT_COLOR;
public static Stroke THIN_STROKE, BOLD_STROKE;
public static Font STD_FONT, BOLD_FONT, ALERT_FONT;
public static int MARGE_X, MARGE_Y, NUM_DIVISIONS_Y, MAX_COLS;
public static int M_X_DIST, M_Y_DIST;
boolean withHeader, isDist;
int width, height;
String titleKey;
static{
if(DEFAULT_WIDTH==0){
try{
loadSettings(null);
} catch(Exception ex){
}
}
}
public Img() throws Exception{
super();
}
public String getTitle(ResourceBundle bundle) {
return "";
}
public String getUrl() {
return URL;
}
@Override
public boolean noCache(){
return false;
}
public static void loadSettings(String file) throws Exception{
loadProperties(file);
DEFAULT_WIDTH=Integer.parseInt(prop.getProperty(GRAPH_WIDTH, "440"));
DIST_WIDTH=Integer.parseInt(prop.getProperty(GRAPH_DIST_WIDTH, "192"));
DEFAULT_HEIGHT=Integer.parseInt(prop.getProperty(GRAPH_HEIGHT, "155"));
DEFAULT_HEADER_HEIGHT=Integer.parseInt(prop.getProperty(GRAPH_HEADER_HEIGHT, "25"));
MRG=Integer.parseInt(prop.getProperty(GRAPH_MARGIN, "8"));
BG_COLOR=new Color(Integer.parseInt(prop.getProperty(GRAPH_COLOR_BG, "008080"), 16));
TEXT_COLOR=new Color(Integer.parseInt(prop.getProperty(GRAPH_COLOR_TEXT, "FFFFFF"), 16));
HEADER_BG_COLOR=new Color(Integer.parseInt(prop.getProperty(GRAPH_COLOR_HEADER_BG, "008080"), 16));
HEADER_TEXT_COLOR=new Color(Integer.parseInt(prop.getProperty(GRAPH_COLOR_HEADER_TEXT, "FFFFFF"), 16));
BORDER_COLOR=new Color(Integer.parseInt(prop.getProperty(GRAPH_COLOR_BORDER, "000000"), 16));
V1_COLOR=new Color(Integer.parseInt(prop.getProperty(GRAPH_COLOR_V1, "00FF00"), 16));
V2_COLOR=new Color(Integer.parseInt(prop.getProperty(GRAPH_COLOR_V2, "0000FF"), 16));
DIST_COLOR=new Color(Integer.parseInt(prop.getProperty(GRAPH_COLOR_DIST, "0000FF"), 16));
ALERT_COLOR=new Color(Integer.parseInt(prop.getProperty(GRAPH_COLOR_ALERT, "FF0000"), 16));
BOLD_STROKE=new BasicStroke(Float.parseFloat(prop.getProperty(GRAPH_STROKE_WIDTH, "3.0")));
THIN_STROKE=new BasicStroke(1.0f);
try{
String fontFamily=prop.getProperty(GRAPH_FONT_FAMILY, "Dialog");
int fontSize=Integer.parseInt(prop.getProperty(GRAPH_FONT_SIZE, "11"));
STD_FONT=new Font(fontFamily, Font.PLAIN, fontSize);
BOLD_FONT=new Font(fontFamily, Font.BOLD, fontSize);
ALERT_FONT=new Font(fontFamily, Font.BOLD, 24);
} catch(Exception ex){
// Sense fonts!!!
}
MARGE_X=Integer.parseInt(prop.getProperty(GRAPH_MARGIN_X, "50"));
MARGE_Y=Integer.parseInt(prop.getProperty(GRAPH_MARGIN_Y, "20"));
NUM_DIVISIONS_Y=Integer.parseInt(prop.getProperty(GRAPH_DIV_Y, "4"));
MAX_COLS=Integer.parseInt(prop.getProperty(GRAPH_MAX_COLS, "10"));
M_X_DIST=Integer.parseInt(prop.getProperty(GRAPH_MARGIN_DIST_X, "15"));
M_Y_DIST=Integer.parseInt(prop.getProperty(GRAPH_MARGIN_DIST_Y, "20"));
}
@Override
public boolean init() throws Exception{
if(!super.init())
return false;
withHeader=getBoolParam(HEADER, TRUE);
isDist=getBoolParam(DIST, TRUE);
type=UNKNOWN;
String s=getParam(TYPE);
if(USER_GRAPH.equals(s)){
type=USR;
titleKey="report_user_evolution";
}
else if(GROUP_GRAPH.equals(s)){
type=GRP;
titleKey="report_group_evolution";
}
else if(PROJECT_GRAPH.equals(s)){
type=PRJ;
titleKey="report_project_evolution";
}
else{
type=UNKNOWN;
errCode=HTTP_BAD_REQUEST;
throw new Exception();
}
if(isDist)
titleKey="report_result_distribution";
width = getIntParam(WIDTH, isDist ? DIST_WIDTH : DEFAULT_WIDTH);
height = getIntParam(HEIGHT, DEFAULT_HEIGHT);
if(width<=0 || height<=0){
System.err.println("EP!!");
}
return true;
};
@Override
public void header(List<String[]> v){
//super.header(v);
//v.add(new String[]{CONTENT_TYPE, "image/gif"});
v.add(new String[]{CONTENT_TYPE, "image/png"});
}
@Override
public boolean usesWriter(){
return false;
}
@Override
public void process(java.io.OutputStream out) throws Exception {
BufferedImage bi=new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2=(Graphics2D)bi.getGraphics();
int h = withHeader ? DEFAULT_HEADER_HEIGHT : 0;
Rectangle allRect=new Rectangle(0, 0, width, height);
Rectangle hr=new Rectangle(0, 0, width, h);
Rectangle gr=new Rectangle(0, h, width, height-h);
g2.setColor(BG_COLOR);
g2.fill(allRect);
if(withHeader){
g2.setColor(HEADER_BG_COLOR);
g2.fill(hr);
}
g2.setColor(BORDER_COLOR);
g2.draw(new Rectangle(0, 0, width-1, height-1));
if(withHeader){
drawHeader(g2, hr);
g2.setColor(BORDER_COLOR);
g2.drawLine(0, h, width, h);
}
if(isDist)
dibuixaDistribucio(g2, getSessionList(), gr);
else
dibuixaImatge(g2, getSessionList(), gr);
g2.dispose();
ImageIO.write(bi, "png", out);
}
protected void drawHeader(Graphics2D g2, Rectangle box) throws Exception{
FontMetrics fm=g2.getFontMetrics(STD_FONT);
int fh=fm.getHeight();
int rs=fh/2;
int dy=(box.height-fh)/2;
int dyr=(box.height-rs)/2;
if(!isDist){
String var1=bundle.getString("report_global_precision");
String var2=bundle.getString("report_solved_activities");
int lvar1=fm.stringWidth(var1);
int lvar2=fm.stringWidth(var2);
Rectangle r1=new Rectangle(box.x+box.width-MRG-lvar2-6-rs-2*MRG-lvar1-6-fh, box.y+dyr, rs, rs);
Rectangle r2=new Rectangle(box.x+box.width-MRG-lvar2-6-rs, box.y+dyr, rs, rs);
g2.setColor(V1_COLOR);
g2.fill(r1);
g2.setColor(V2_COLOR);
g2.fill(r2);
g2.setColor(BORDER_COLOR);
g2.draw(r1);
g2.draw(r2);
g2.setFont(STD_FONT);
g2.setColor(HEADER_TEXT_COLOR);
g2.drawString(var1, box.x+r1.x+rs+6, box.y+dy+fm.getAscent());
g2.drawString(var2, box.x+r2.x+rs+6, box.y+dy+fm.getAscent());
}
g2.setColor(HEADER_TEXT_COLOR);
g2.setFont(BOLD_FONT);
g2.drawString(bundle.getString(titleKey), box.x+MRG, box.y+dy+fm.getAscent());
}
public void dibuixaImatge(Graphics2D g2, List<SessionData> v, Rectangle box) throws Exception{
g2.setColor(TEXT_COLOR);
g2.setStroke(THIN_STROKE);
int gWidth=box.width-(2 * MARGE_X);
int gHeight=box.height-(2 * MARGE_Y);
g2.drawRect(box.x+MARGE_X, box.y+MARGE_Y, gWidth, gHeight);
if(v.size()>0){
float lDivY=gHeight/NUM_DIVISIONS_Y;
for(int i=1; i<NUM_DIVISIONS_Y; i++){ //Posem les linies horitzontals
int y=(int)(MARGE_Y+(i*lDivY));
g2.drawLine(box.x+MARGE_X, box.y+y, box.x+MARGE_X+gWidth, box.y+y);
}
g2.setFont(STD_FONT);
FontMetrics fm=g2.getFontMetrics();
for (int i=0; i<=NUM_DIVISIONS_Y; i++){ //Posem els valors horitzontals en %
String num=Integer.toString((100-(i*(100/NUM_DIVISIONS_Y))));
int l=fm.stringWidth(num);
g2.drawString(num, box.x+MARGE_X-5-l, box.y+MARGE_Y+(int)(lDivY*i)+3);
}
int numArgs=v.size();
if (numArgs==1){ //Hem de replicar el punt
v.add(v.get(0));
numArgs++;
}
int columnesArgs=numArgs-1;
int numColumnesX=Math.min(MAX_COLS, columnesArgs); //nombre de columnes que s'acabara mostrant.
float longColumnaX=gWidth/numColumnesX; //longitud horitzontal de cadascuna de les columnes
float increment=(float)columnesArgs/(float)numColumnesX;
int[] puntsDePas=new int[numColumnesX+2];
int i=0;
for(float f=0.0f; f<=columnesArgs+1; f+=increment)
puntsDePas[i++]=Math.round(f);
int[] abscises=new int[numArgs];
int j=0; //j indicara quantes columnes completes hem fet
for(int k=0; k<=columnesArgs; k++){
if(puntsDePas[j]==k){ //mostrem punt i columna
abscises[k]=(int)(MARGE_X+(j++*longColumnaX));
}
else{
int properPunt=puntsDePas[j];
int anteriorPunt=puntsDePas[j-1];
float x=(k-anteriorPunt)*(longColumnaX/(properPunt-anteriorPunt));
abscises[k]=(int)(MARGE_X+((j-1)*longColumnaX)+x);
}
}
j=0;
Date dataFinal=null;
boolean validat=true; //De moment mostrem les dades, quan trobem una a la que no te acces pararem.
for(i=0; i<columnesArgs && validat; i++){
SessionData sd1=v.get(i);
SessionData sd2=v.get(i+1);
if (puntsDePas[j]==i){ //mostrem punt i columna
g2.setColor(TEXT_COLOR);
g2.setStroke(THIN_STROKE);
g2.drawLine(box.x+abscises[i], box.y+MARGE_Y, box.x+abscises[i], box.y+MARGE_Y+gHeight);
}
g2.setStroke(BOLD_STROKE);
g2.setColor(V2_COLOR);
g2.drawLine(
box.x+abscises[i], (int)(box.y+MARGE_Y+gHeight-((sd1.percentSolved()*gHeight)/100)),
box.x+abscises[i+1], (int)(box.y+MARGE_Y+gHeight-((sd2.percentSolved()*gHeight)/100))
);
g2.setColor(V1_COLOR);
g2.drawLine(
box.x+abscises[i], (int)(box.y+MARGE_Y+gHeight-((sd1.percentPrec()*gHeight)/100)),
box.x+abscises[i+1], (int)(box.y+MARGE_Y+gHeight-((sd2.percentPrec()*gHeight)/100))
);
if (puntsDePas[j]==i){ //mostrem punt i columna
g2.setColor(TEXT_COLOR);
g2.drawString(veryShortDateFormat.format(sd1.date), box.x+abscises[i], box.y+MARGE_Y+gHeight+15);
j++;
}
dataFinal=sd2.date;
}
g2.setColor(TEXT_COLOR);
g2.drawString(veryShortDateFormat.format(dataFinal), box.x+abscises[i], box.y+MARGE_Y+gHeight+15);
}
else{
String noDades=bundle.getString("report_no_data");
g2.setColor(ALERT_COLOR);
g2.setFont(ALERT_FONT);
Rectangle r=ALERT_FONT.getStringBounds(noDades, g2.getFontRenderContext()).getBounds();
g2.drawString(noDades, box.x+MARGE_X+(gWidth-r.width)/2, box.y+MARGE_Y+gHeight/2+r.height/4);
}
}
public static final int N_DIST_ELEMENTS=5;
public void dibuixaDistribucio(Graphics2D g2, List v, Rectangle box){
int[] dist=new int[N_DIST_ELEMENTS];
Iterator it=v.iterator();
int vc=100/N_DIST_ELEMENTS;
while(it.hasNext()){
SessionData sd=(SessionData)it.next();
if(sd.actData!=null && sd.actData.size()>0){
Iterator it2=sd.actData.iterator();
while(it.hasNext()){
ActivityData ad=(ActivityData)it.next();
dist[Math.min(99, ad.qualification)/vc]++;
}
}
else{
dist[Math.min(99, sd.percentPrec())/vc]+=sd.numActs;
}
}
int leftMargin=M_X_DIST;
int topMargin=M_Y_DIST;
g2.setColor(TEXT_COLOR);
g2.setStroke(THIN_STROKE);
g2.setFont(STD_FONT);
float max=0.0f, maxOrdenades=0.0f, maxDivisio=0.0f;
boolean data=false;
for(int i=0; i<dist.length && data==false; i++)
data=(dist[i]>0);
if(data){
max=dist[0];
for (int i=1; i<dist.length;i++)
max=Math.max(max, dist[i]);
maxOrdenades=getMaximOrdenades(max, false);
maxDivisio=getMaximOrdenades(max, true);
String sNum=formatNumber(maxDivisio);
leftMargin+=g2.getFontMetrics().stringWidth(sNum);
}
int gWidth = box.width-leftMargin-10; //10 pixels de marge per la dreta
int gHeight = box.height-(2*topMargin);
g2.drawRect(box.x+leftMargin, box.y+topMargin, gWidth, gHeight);
if (data){
int numDivisionsY=0;
for(float f=max; f>0.01; f-=maxDivisio)
numDivisionsY++;
if (numDivisionsY>0){
float longdivisioY=gHeight/numDivisionsY;
for (int i=1; i<numDivisionsY; i++){ //Posem les linies horitzontals
g2.drawLine(
box.x+leftMargin, box.y+topMargin+(int)(i*longdivisioY),
box.x+leftMargin+gWidth, box.y+topMargin+(int)(i*longdivisioY));
}
for (int i=0;i<=numDivisionsY;i++){ //Posem els valors horitzontals en %
float num=(maxDivisio*numDivisionsY)-(i*maxDivisio);
String sNum=formatNumber(num);
g2.drawString(sNum,
box.x+leftMargin-5-(g2.getFontMetrics().stringWidth(sNum)),
box.y+topMargin+(int)(longdivisioY*i)+3);
}
}
float longColumnaX=gWidth/N_DIST_ELEMENTS; //longitud horitzontal de cadascuna de les columnes
for(int i=0; i<N_DIST_ELEMENTS; i++){
g2.setColor(TEXT_COLOR);
g2.setStroke(THIN_STROKE);
g2.drawLine(
box.x+leftMargin+(int)(i*longColumnaX), box.y+topMargin,
box.x+leftMargin+(int)(i*longColumnaX), box.y+topMargin+gHeight);
StringBuilder sb=new StringBuilder(Integer.toString(i*20));
sb.append("%");
g2.drawString(sb.substring(0),
box.x+leftMargin+(int)(i*longColumnaX),
box.y+topMargin+gHeight+15);
g2.setColor(DIST_COLOR);
int alt=((dist[i]*100/(int)(maxDivisio*numDivisionsY)*gHeight)-1)/100;
int ample=(i==(N_DIST_ELEMENTS-1))?(int)(gWidth-(longColumnaX*i)-1):(int)(longColumnaX-1);
g2.fillRect(
box.x+leftMargin+(int)(i*longColumnaX)+1,
box.y+topMargin+gHeight-alt,
ample, alt);
}
}
else{
String noDades=bundle.getString("report_no_data");
g2.setColor(ALERT_COLOR);
g2.setFont(STD_FONT);
Rectangle r=STD_FONT.getStringBounds(noDades, g2.getFontRenderContext()).getBounds();
g2.drawString(noDades, box.x+leftMargin+(gWidth-r.width)/2, box.y+topMargin+gHeight/2+r.height/4);
}
}
public static float getMaximOrdenades(float maxim, boolean divisio){
float f=maxim*100;
if(divisio)
f/=6;
int i=(int)f; //enter entre 0 i 9 de mes a l'esquerra de f.
int xifresTretes=0;
while (i>9){
i=(int)f;
f=f/10;
xifresTretes++;
}
if (i==1)
i=2;
else if (i>1 && i<5)
i=5;
else
i=10; //i>=5 || i==0
for(int k=1; k<xifresTretes; k++)
i*=10;
return ((float)i)/100;
}
private static DecimalFormat DF=null;
protected static String formatNumber(float n){
if(DF==null){
DF=new DecimalFormat();
DF.setMaximumFractionDigits(1);
DF.setGroupingUsed(false);
}
return DF.format(n);
}
}