Author: jcouteau Date: 2013-01-06 18:20:34 +0100 (Sun, 06 Jan 2013) New Revision: 291 Url: http://chorem.org/projects/chorem/repository/revisions/291 Log: krefs #862 : refactor sales reports. Reports are now year by year. Can put all years on chart. Chart are now bar chart (easier to read with several years). Added: trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/ trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/AcceptedQuotationsReportAction.java trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/QuotationYearData.java trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SalesAction.java trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SalesReportHelper.java trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SentQuotationsReportAction.java trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/ trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/acceptedQuotation.jsp trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/dashboardSalesReportSalesEvolutionPerProject.jsp trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/dashboardSalesReportSalesPerProject.jsp trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/menu.jsp trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/sales.jsp trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/sentQuotation.jsp trunk/chorem-webmotion/src/main/webapp/css/jquery.jqplot.css trunk/chorem-webmotion/src/main/webapp/js/jqplot.barRenderer.js trunk/chorem-webmotion/src/main/webapp/js/jqplot.dateAxisRenderer.min.js Removed: trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/SalesAction.java trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/dashboardSalesReport.jsp trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/sales.jsp Modified: trunk/chorem-webmotion/src/main/resources/mapping trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/decorator.jsp Deleted: trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/SalesAction.java =================================================================== --- trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/SalesAction.java 2012-12-29 13:13:20 UTC (rev 290) +++ trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/SalesAction.java 2013-01-06 17:20:34 UTC (rev 291) @@ -1,326 +0,0 @@ -package org.chorem.webmotion.actions; - -/* - * #%L - * Chorem webmotion - * $Id:$ - * $HeadURL:$ - * %% - * Copyright (C) 2011 - 2012 CodeLutin - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 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. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * #L% - */ - -import org.apache.commons.lang3.time.DateUtils; -import org.chorem.ChoremClient; -import org.chorem.entities.Accepted; -import org.chorem.entities.Draft; -import org.chorem.entities.Quotation; -import org.chorem.entities.QuotationStatus; -import org.chorem.entities.Rejected; -import org.chorem.entities.Sent; -import org.chorem.entities.Started; -import org.debux.webmotion.server.WebMotionController; -import org.debux.webmotion.server.render.Render; -import org.nuiton.util.DateUtil; -import org.nuiton.wikitty.query.FacetTopic; -import org.nuiton.wikitty.query.WikittyQuery; -import org.nuiton.wikitty.query.WikittyQueryMaker; -import org.nuiton.wikitty.query.WikittyQueryResult; - -import java.util.Date; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * - * @author couteau - * @version $Revision$ - * - * Last update: $Date$ - * by : $Author$ - */ -public class SalesAction extends WebMotionController { - - /** - * Rend le tableau des leads - * - * @param client - * @return - */ - public Render lead(ChoremClient client) { - // recuperation des quotations en statut lead - WikittyQuery quotationQuery = new WikittyQueryMaker().and() - .exteq(Quotation.EXT_QUOTATION) - .extne(Draft.EXT_DRAFT) - .end().setLimit(WikittyQuery.MAX); - - WikittyQueryResult<Quotation> result = - client.findAllByQuery(Quotation.class, quotationQuery); - - // calcul des montants totaux - double amount = 0; - double amountHope = 0; - for (Quotation q : result) { - amount += q.getAmount(); - amountHope += q.getAmount() * q.getConversionHope() / 100.0; - } - - return renderView( "dashboardQuotationLead.jsp", - "quotations", result.getAll(), - "amount", amount, - "amountHope", amountHope); - - } - - /** - * Rend le tableau des propositions en statut draft (en cours de réponse) - * - * @param client - * @return - */ - public Render draft(ChoremClient client) { - // recuperation des quotations en statut draft - WikittyQuery quotationQuery = new WikittyQueryMaker().and() - .exteq(Draft.EXT_DRAFT) - .extne(Sent.EXT_SENT) - .end().setLimit(WikittyQuery.MAX); - - WikittyQueryResult<Quotation> result = - client.findAllByQuery(Quotation.class, quotationQuery); - - // calcul des montants totaux - double amount = 0; - double amountHope = 0; - for (Quotation q : result) { - amount += q.getAmount(); - amountHope += q.getAmount() * q.getConversionHope() / 100.0; - } - - return renderView("dashboardQuotationDraft.jsp", - "quotations", result.getAll(), - "amount", amount, - "amountHope", amountHope); - } - - /** - * Rend le tableau des propositions en attente de réponse - * - * @param client - * @return - */ - public Render sent(ChoremClient client) { - // recuperation des quotations en statut sent - WikittyQuery quotationQuery = new WikittyQueryMaker().and() - .exteq(Sent.EXT_SENT) - .extne(Accepted.EXT_ACCEPTED) - .extne(Rejected.EXT_REJECTED) - .end().setLimit(WikittyQuery.MAX); - - WikittyQueryResult<Quotation> result = - client.findAllByQuery(Quotation.class, quotationQuery); - - // calcul des montants totaux - double amount = 0; - double amountHope = 0; - for (Quotation q : result) { - amount += q.getAmount(); - amountHope += q.getAmount() * q.getConversionHope() / 100.0; - } - - return renderView("dashboardQuotationSent.jsp", - "quotations", result.getAll(), - "amount", amount, - "amountHope", amountHope); - } - - /** - * Rend le tableau des propositions acceptées mais non commencées - * - * @param client - * @return - */ - public Render accepted(ChoremClient client) { - // recuperation des quotations en statut accepted - WikittyQuery quotationQuery = new WikittyQueryMaker().and() - .exteq(Accepted.EXT_ACCEPTED) - .extne(Started.EXT_STARTED) - .end().setLimit(WikittyQuery.MAX); - - WikittyQueryResult<Quotation> result = - client.findAllByQuery(Quotation.class, quotationQuery); - - // calcul des montants totaux - double amount = 0; - for (Quotation q : result) { - amount += q.getAmount(); - } - - return renderView("dashboardQuotationAccepted.jsp", - "quotations", result.getAll(), - "amount", amount); - } - - /** - * Rend les indicateurs de performance commerciale - * - * @param client - * @return - */ - public Render report(ChoremClient client) { - - //Début et fin d'année courante (N) - Date currentYearFirst = DateUtil.setFirstDayOfYear(new Date()); - Date currentYearLast = DateUtil.setLastDayOfYear(new Date()); - - //Sers uniquement à calculer le début et fin d'année précédente (N-1) - Date previousYearDay = DateUtil.getYesterday(currentYearFirst); - - //Début et fin d'année précédente (N-1) - Date previousYearFirst = DateUtil.setFirstDayOfYear(previousYearDay); - Date previousYearLast = DateUtil.setLastDayOfYear(previousYearDay); - - //Liste de statut correspondant aux devis envoyés - EnumSet<QuotationStatus> sentStatus = EnumSet.range(QuotationStatus.SENT, - QuotationStatus.CLOSED); - - //Nombre de devis envoyés sur l'année (N) - WikittyQuery sentCurrentYearQuery = new WikittyQueryMaker().and() - .exteq(Sent.EXT_SENT) - .bw(Sent.FQ_FIELD_SENT_POSTEDDATE, currentYearFirst, currentYearLast) - .end().setLimit(WikittyQuery.MAX); - - WikittyQueryResult<Quotation> sentCurrentYearResult = - client.findAllByQuery(Quotation.class, sentCurrentYearQuery); - int currentYearSentQuotation = sentCurrentYearResult.size(); - - //Nombre de devis envoyés sur l'année n-1 - WikittyQuery sentPreviousYearQuery = new WikittyQueryMaker().and() - .exteq(Sent.EXT_SENT) - .bw(Sent.FQ_FIELD_SENT_POSTEDDATE, previousYearFirst, previousYearLast) - .end().setLimit(WikittyQuery.MAX); - - WikittyQueryResult<Quotation> sentPreviousYearResult = - client.findAllByQuery(Quotation.class, sentPreviousYearQuery); - int previousYearSentQuotation = sentPreviousYearResult.size(); - - //Progression devis envoyés - int sentQuotationProgression = 0; - if (previousYearSentQuotation != 0){ - sentQuotationProgression = 100 * (currentYearSentQuotation - previousYearSentQuotation) / previousYearSentQuotation; - } - - //Graphe devis envoyés - Date baseValue = DateUtil.setFirstDayOfMonth(new Date()); - - //Create the basis wikitty query - Date lastDayForQuotationData = DateUtil.getYesterday(baseValue); - Date firstDayForQuotationData = DateUtils.addMonths(baseValue, -12); - WikittyQuery sentQuotationDataQuery = new WikittyQueryMaker().and() - .exteq(Sent.EXT_SENT) - .bw(Sent.FQ_FIELD_SENT_POSTEDDATE, - firstDayForQuotationData, - lastDayForQuotationData) - .end().setLimit(WikittyQuery.MAX); - - //add a facet per month - Map<Integer,String> sentQuotationLabels = new HashMap<Integer, String>(); - for (int i=0;i<12;i++){ - Date lastDayOfMonth = DateUtil.getYesterday(baseValue); - Date firstDayOfMonth = DateUtil.setFirstDayOfMonth(lastDayOfMonth); - baseValue=firstDayOfMonth; - - WikittyQuery monthFacet = new WikittyQueryMaker().and() - .bw(Sent.FQ_FIELD_SENT_POSTEDDATE, firstDayOfMonth, lastDayOfMonth) - .end(); - - sentQuotationDataQuery = sentQuotationDataQuery.addFacetQuery(String.valueOf(i), monthFacet.getCondition()); - - sentQuotationLabels.put(i, DateUtil.getMonthLibelle(DateUtil.getMonth(lastDayOfMonth)+1)); - } - - WikittyQueryResult<Quotation> sentQuotationDataResult = - client.findAllByQuery(Quotation.class, sentQuotationDataQuery.setFacetMinCount(0)); - - Map<String,Integer> sentQuotationData = new LinkedHashMap<String, Integer>(); - - Map<String,List<FacetTopic>> sentQuotationDataFacet = sentQuotationDataResult.getFacets(); - - for (int i=11;i>=0;i--) { - sentQuotationData.put(sentQuotationLabels.get(i), sentQuotationDataFacet.get(String.valueOf(i)).get(0).getCount()); - } - - //Nombre de devis acceptés sur l'année - - //Nombre de devis acceptés sur l'année n-1 - - //Progression devis acceptés - - //Nombre de nouveaux clients sur l'année - - //Nombre de nouveaux clients sur l'année n-1 - - //Progression nombre de nouveaux clients - - //Nombre et nom des clients perdus sur l'année - - //Nombre et nom des clients perdus sur l'année n-1 - - //Diminution du nombre de clients perdus - - return renderView("dashboardSalesReport.jsp", - "currentYearSentQuotation", currentYearSentQuotation, - "previousYearSentQuotation", previousYearSentQuotation, - "sentQuotationProgression", sentQuotationProgression, - "sentQuotationData", sentQuotationData); - } - - public Render send(ChoremClient client, String id) { - Sent sent = client.restore(Sent.class, id); - sent.setPostedDate(new Date()); - client.store(sent); - - return renderView("sales.jsp"); - } - - public Render accept(ChoremClient client, String id) { - Accepted accepted = client.restore(Accepted.class, id); - accepted.setAcceptedDate(new Date()); - client.store(accepted); - - return renderView("sales.jsp"); - } - - public Render start(ChoremClient client, String id) { - - Started started = client.restore(Started.class, id); - started.setStartedDate(new Date()); - client.store(started); - - return renderView("sales.jsp"); - - } - - public Render answer(ChoremClient client, String id) { - Draft draft = client.restore(Draft.class, id); - client.store(draft); - - return renderView("sales.jsp"); - - } -} Added: trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/AcceptedQuotationsReportAction.java =================================================================== --- trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/AcceptedQuotationsReportAction.java (rev 0) +++ trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/AcceptedQuotationsReportAction.java 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,135 @@ +package org.chorem.webmotion.actions.sales; + +import org.apache.commons.lang3.time.DateUtils; +import org.chorem.ChoremClient; +import org.chorem.entities.Accepted; +import org.chorem.entities.Quotation; +import org.debux.webmotion.server.WebMotionController; +import org.debux.webmotion.server.render.Render; +import org.nuiton.util.DateUtil; +import org.nuiton.wikitty.WikittyClient; +import org.nuiton.wikitty.query.FacetTopic; +import org.nuiton.wikitty.query.WikittyQuery; +import org.nuiton.wikitty.query.WikittyQueryMaker; +import org.nuiton.wikitty.query.WikittyQueryResult; + +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * @author jcouteau <couteau@codelutin.com> + */ +public class AcceptedQuotationsReportAction extends WebMotionController { + + /** + * Rend le graphe des devis acceptés par mois + * + * @param client + * @return + */ + public Render acceptedQuotationPerMonth(ChoremClient client, String from, String to) { + + if (null == from) { + from = String.valueOf(SalesReportHelper.getFirstYear(client)); + } + + if (null == to) { + to = String.valueOf(SalesReportHelper.getLastYear()); + } + + Map<Integer, QuotationYearData> data = new LinkedHashMap<Integer, QuotationYearData>(); + + List<Integer> listAllYears = SalesReportHelper.listAllYears(from, to); + + List<Integer> listAllYearsInChorem = SalesReportHelper.listAllYears(client); + + int previousYearValue = 0; + + for (Integer year:listAllYears){ + Date yearFirstDay = SalesReportHelper.getFirstDayOfYear(year); + Date yearLastDay = SalesReportHelper.getLastDayOfYear(year); + + QuotationYearData yearData = new QuotationYearData(); + + //Nombre de devis acceptés sur l'année + WikittyQuery acceptedCurrentYearQuery = new WikittyQueryMaker().and() + .exteq(Accepted.EXT_ACCEPTED) + .bw(Accepted.FQ_FIELD_ACCEPTED_ACCEPTEDDATE, yearFirstDay, yearLastDay) + .end().setLimit(WikittyQuery.MAX); + + WikittyQueryResult<Quotation> acceptedCurrentYearResult = + client.findAllByQuery(Quotation.class, acceptedCurrentYearQuery); + int yearAcceptedQuotation = acceptedCurrentYearResult.size(); + + //Progression devis envoyés + int acceptedQuotationProgression = 0; + if (previousYearValue != 0){ + acceptedQuotationProgression = 100 * (yearAcceptedQuotation - previousYearValue) / previousYearValue; + } + + previousYearValue = yearAcceptedQuotation; + + //Graphe devis envoyés + Map<String, Integer> acceptedQuotationData = getAcceptedQuotationData(year, client); + + yearData.setBaseValue(yearAcceptedQuotation); + yearData.setProgression(acceptedQuotationProgression); + yearData.setPlotValues(acceptedQuotationData); + + data.put(year, yearData); + } + + return renderView("salesReports/acceptedQuotation.jsp", + "data", data, + "allYears", listAllYearsInChorem, + "fromYear", from, + "toYear", to); + } + + protected Map<String,Integer> getAcceptedQuotationData(Integer year, WikittyClient client){ + + Date first = SalesReportHelper.getFirstDayOfYear(year); + Date last = SalesReportHelper.getLastDayOfYear(year); + + //Create the basis wikitty query + WikittyQuery acceptedQuotationDataQuery = new WikittyQueryMaker().and() + .exteq(Accepted.EXT_ACCEPTED) + .bw(Accepted.FQ_FIELD_ACCEPTED_ACCEPTEDDATE, first, last) + .end().setLimit(WikittyQuery.MAX); + + //add a facet per month + Map<Integer,String> acceptedQuotationLabels = new HashMap<Integer, String>(); + Date baseValue = DateUtils.addDays(last, 1); + for (int i=0;i<12;i++){ + Date lastDayOfMonth = DateUtil.getYesterday(baseValue); + Date firstDayOfMonth = DateUtil.setFirstDayOfMonth(lastDayOfMonth); + baseValue=firstDayOfMonth; + + WikittyQuery monthFacet = new WikittyQueryMaker().and() + .bw(Accepted.FQ_FIELD_ACCEPTED_ACCEPTEDDATE, firstDayOfMonth, lastDayOfMonth) + .end(); + + acceptedQuotationDataQuery = acceptedQuotationDataQuery.addFacetQuery(String.valueOf(i), monthFacet.getCondition()); + + acceptedQuotationLabels.put(i, DateUtil.getMonthLibelle(DateUtil.getMonth(lastDayOfMonth) + 1)); + } + + WikittyQueryResult<Quotation> acceptedQuotationDataResult = + client.findAllByQuery(Quotation.class, acceptedQuotationDataQuery.setFacetMinCount(0)); + + Map<String,Integer> acceptedQuotationData = new LinkedHashMap<String, Integer>(); + + Map<String,List<FacetTopic>> acceptedQuotationDataFacet = acceptedQuotationDataResult.getFacets(); + + for (int i=11;i>=0;i--) { + acceptedQuotationData.put(acceptedQuotationLabels.get(i), acceptedQuotationDataFacet.get(String.valueOf(i)).get(0).getCount()); + } + + return acceptedQuotationData; + } + + +} Added: trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/QuotationYearData.java =================================================================== --- trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/QuotationYearData.java (rev 0) +++ trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/QuotationYearData.java 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,39 @@ +package org.chorem.webmotion.actions.sales; + +import java.util.Map; + +/** + * @author jcouteau <couteau@codelutin.com> + */ +public class QuotationYearData { + + protected Integer baseValue; + + protected Integer progression; + + protected Map<String, Integer> plotValues; + + public Integer getBaseValue() { + return baseValue; + } + + public void setBaseValue(Integer baseValue) { + this.baseValue = baseValue; + } + + public Integer getProgression() { + return progression; + } + + public void setProgression(Integer progression) { + this.progression = progression; + } + + public Map<String, Integer> getPlotValues() { + return plotValues; + } + + public void setPlotValues(Map<String, Integer> plotValues) { + this.plotValues = plotValues; + } +} Copied: trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SalesAction.java (from rev 290, trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/SalesAction.java) =================================================================== --- trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SalesAction.java (rev 0) +++ trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SalesAction.java 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,203 @@ +package org.chorem.webmotion.actions.sales; + +/* + * #%L + * Chorem webmotion + * $Id:$ + * $HeadURL:$ + * %% + * Copyright (C) 2011 - 2012 CodeLutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ + +import org.chorem.ChoremClient; +import org.chorem.entities.Accepted; +import org.chorem.entities.Draft; +import org.chorem.entities.Quotation; +import org.chorem.entities.Rejected; +import org.chorem.entities.Sent; +import org.chorem.entities.Started; +import org.debux.webmotion.server.WebMotionController; +import org.debux.webmotion.server.render.Render; +import org.nuiton.wikitty.query.WikittyQuery; +import org.nuiton.wikitty.query.WikittyQueryMaker; +import org.nuiton.wikitty.query.WikittyQueryResult; + +import java.util.Date; + +/** + * + * @author couteau + * @version $Revision$ + * + * Last update: $Date$ + * by : $Author$ + */ +public class SalesAction extends WebMotionController { + + /** + * Rend le tableau des leads + * + * @param client + * @return + */ + public Render lead(ChoremClient client) { + // recuperation des quotations en statut lead + WikittyQuery quotationQuery = new WikittyQueryMaker().and() + .exteq(Quotation.EXT_QUOTATION) + .extne(Draft.EXT_DRAFT) + .end().setLimit(WikittyQuery.MAX); + + WikittyQueryResult<Quotation> result = + client.findAllByQuery(Quotation.class, quotationQuery); + + // calcul des montants totaux + double amount = 0; + double amountHope = 0; + for (Quotation q : result) { + amount += q.getAmount(); + amountHope += q.getAmount() * q.getConversionHope() / 100.0; + } + + return renderView( "dashboardQuotationLead.jsp", + "quotations", result.getAll(), + "amount", amount, + "amountHope", amountHope); + + } + + /** + * Rend le tableau des propositions en statut draft (en cours de réponse) + * + * @param client + * @return + */ + public Render draft(ChoremClient client) { + // recuperation des quotations en statut draft + WikittyQuery quotationQuery = new WikittyQueryMaker().and() + .exteq(Draft.EXT_DRAFT) + .extne(Sent.EXT_SENT) + .end().setLimit(WikittyQuery.MAX); + + WikittyQueryResult<Quotation> result = + client.findAllByQuery(Quotation.class, quotationQuery); + + // calcul des montants totaux + double amount = 0; + double amountHope = 0; + for (Quotation q : result) { + amount += q.getAmount(); + amountHope += q.getAmount() * q.getConversionHope() / 100.0; + } + + return renderView("dashboardQuotationDraft.jsp", + "quotations", result.getAll(), + "amount", amount, + "amountHope", amountHope); + } + + /** + * Rend le tableau des propositions en attente de réponse + * + * @param client + * @return + */ + public Render sent(ChoremClient client) { + // recuperation des quotations en statut sent + WikittyQuery quotationQuery = new WikittyQueryMaker().and() + .exteq(Sent.EXT_SENT) + .extne(Accepted.EXT_ACCEPTED) + .extne(Rejected.EXT_REJECTED) + .end().setLimit(WikittyQuery.MAX); + + WikittyQueryResult<Quotation> result = + client.findAllByQuery(Quotation.class, quotationQuery); + + // calcul des montants totaux + double amount = 0; + double amountHope = 0; + for (Quotation q : result) { + amount += q.getAmount(); + amountHope += q.getAmount() * q.getConversionHope() / 100.0; + } + + return renderView("dashboardQuotationSent.jsp", + "quotations", result.getAll(), + "amount", amount, + "amountHope", amountHope); + } + + /** + * Rend le tableau des propositions acceptées mais non commencées + * + * @param client + * @return + */ + public Render accepted(ChoremClient client) { + // recuperation des quotations en statut accepted + WikittyQuery quotationQuery = new WikittyQueryMaker().and() + .exteq(Accepted.EXT_ACCEPTED) + .extne(Started.EXT_STARTED) + .end().setLimit(WikittyQuery.MAX); + + WikittyQueryResult<Quotation> result = + client.findAllByQuery(Quotation.class, quotationQuery); + + // calcul des montants totaux + double amount = 0; + for (Quotation q : result) { + amount += q.getAmount(); + } + + return renderView("dashboardQuotationAccepted.jsp", + "quotations", result.getAll(), + "amount", amount); + } + + public Render send(ChoremClient client, String id) { + Sent sent = client.restore(Sent.class, id); + sent.setPostedDate(new Date()); + client.store(sent); + + return renderView("sales.jsp"); + } + + public Render accept(ChoremClient client, String id) { + Accepted accepted = client.restore(Accepted.class, id); + accepted.setAcceptedDate(new Date()); + client.store(accepted); + + return renderView("sales.jsp"); + } + + public Render start(ChoremClient client, String id) { + + Started started = client.restore(Started.class, id); + started.setStartedDate(new Date()); + client.store(started); + + return renderView("sales.jsp"); + + } + + public Render answer(ChoremClient client, String id) { + Draft draft = client.restore(Draft.class, id); + client.store(draft); + + return renderView("sales.jsp"); + + } +} Added: trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SalesReportHelper.java =================================================================== --- trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SalesReportHelper.java (rev 0) +++ trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SalesReportHelper.java 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,118 @@ +package org.chorem.webmotion.actions.sales; + +import org.chorem.entities.Draft; +import org.nuiton.util.DateUtil; +import org.nuiton.wikitty.WikittyClient; +import org.nuiton.wikitty.query.WikittyQuery; +import org.nuiton.wikitty.query.WikittyQueryMaker; +import org.nuiton.wikitty.query.WikittyQueryResult; + +import java.util.Calendar; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** + * @author jcouteau <couteau@codelutin.com> + */ +public class SalesReportHelper { + + /** + * This method return the first day of the year string given in parameter. + * + * @param year the year of the expected Date + * @return a Date object representing the first day of the year given in + * parameter and the minimum time on this day. + */ + public static Date getFirstDayOfYear(Integer year){ + Date today = new Date(); + + Calendar c = Calendar.getInstance(); + c.setTime(today); + c.set(Calendar.YEAR, year); + + Date result = c.getTime(); + result = DateUtil.setFirstDayOfYear(result); + result = DateUtil.setMinTimeOfDay(result); + + return result; + + } + + /** + * This method return the last day of the year string given in parameter. + * + * @param year the year of the expected Date + * @return a Date object representing the last day of the year given in + * parameter and the maximum time on this day. + */ + public static Date getLastDayOfYear(Integer year){ + Date today = new Date(); + + Calendar c = Calendar.getInstance(); + c.setTime(today); + c.set(Calendar.YEAR, year); + + Date result = c.getTime(); + result = DateUtil.setLastDayOfYear(result); + result = DateUtil.setMaxTimeOfDay(result); + + return result; + + } + + public static List<Integer> listAllYears(WikittyClient client) { + + List<Integer> years = new LinkedList<Integer>(); + + for (int i=getFirstYear(client);i<=getLastYear();i++) { + years.add(i); + } + + return years; + + } + + public static Integer getLastYear() { + Calendar c = Calendar.getInstance(); + return c.get(Calendar.YEAR); + } + + public static Integer getFirstYear(WikittyClient client) { + + //Tous les devis + WikittyQuery sentCurrentYearQuery = new WikittyQueryMaker().and() + .exteq(Draft.EXT_DRAFT) + .end().setLimit(WikittyQuery.MAX).addSortAscending(Draft.ELEMENT_FIELD_DRAFT_SENDINGDATE); + + WikittyQueryResult<Draft> sentCurrentYearResult = + client.findAllByQuery(Draft.class, sentCurrentYearQuery); + + System.out.println(sentCurrentYearResult.getAll()); + + System.out.println(); + + Date firstDraft = sentCurrentYearResult.get(0).getSendingDate(); + + Calendar c = Calendar.getInstance(); + //c.setTime(firstDraft); + //return c.get(Calendar.YEAR); + + return 2010; + } + + public static List<Integer> listAllYears(String begin, String end) { + + int beginYear = Integer.parseInt(begin); + int lastYear = Integer.parseInt(end); + + List<Integer> years = new LinkedList<Integer>(); + + for (int i = beginYear; i<= lastYear; i++) { + years.add(i); + } + + return years; + + } +} Added: trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SentQuotationsReportAction.java =================================================================== --- trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SentQuotationsReportAction.java (rev 0) +++ trunk/chorem-webmotion/src/main/java/org/chorem/webmotion/actions/sales/SentQuotationsReportAction.java 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,143 @@ +package org.chorem.webmotion.actions.sales; + +import org.apache.commons.lang3.time.DateUtils; +import org.chorem.ChoremClient; +import org.chorem.entities.Quotation; +import org.chorem.entities.Sent; +import org.debux.webmotion.server.WebMotionController; +import org.debux.webmotion.server.render.Render; +import org.nuiton.util.DateUtil; +import org.nuiton.wikitty.WikittyClient; +import org.nuiton.wikitty.query.FacetTopic; +import org.nuiton.wikitty.query.WikittyQuery; +import org.nuiton.wikitty.query.WikittyQueryMaker; +import org.nuiton.wikitty.query.WikittyQueryResult; + +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * @author jcouteau <couteau@codelutin.com> + */ +public class SentQuotationsReportAction extends WebMotionController { + + /** + * Rend le graphe des devis envoyés par mois + * + * @param client + * @return + */ + /**public Render sentQuotationPerMonth(ChoremClient client) { + return sentQuotationPerMonth(client, "2007", "2012"); + }**/ + + /** + * Rend le graphe des devis envoyés par mois + * + * @param client + * @return + */ + public Render sentQuotationPerMonth(ChoremClient client, String from, String to) { + + if (null == from) { + from = String.valueOf(SalesReportHelper.getFirstYear(client)); + } + + if (null == to) { + to = String.valueOf(SalesReportHelper.getLastYear()); + } + + Map<Integer, QuotationYearData> data = new LinkedHashMap<Integer, QuotationYearData>(); + + List<Integer> listAllYears = SalesReportHelper.listAllYears(from, to); + + List<Integer> listAllYearsInChorem = SalesReportHelper.listAllYears(client); + + int previousYearValue = 0; + + for (Integer year:listAllYears){ + Date yearFirstDay = SalesReportHelper.getFirstDayOfYear(year); + Date yearLastDay = SalesReportHelper.getLastDayOfYear(year); + + QuotationYearData yearData = new QuotationYearData(); + + //Nombre de devis envoyés sur l'année + WikittyQuery sentCurrentYearQuery = new WikittyQueryMaker().and() + .exteq(Sent.EXT_SENT) + .bw(Sent.FQ_FIELD_SENT_POSTEDDATE, yearFirstDay, yearLastDay) + .end().setLimit(WikittyQuery.MAX); + + WikittyQueryResult<Quotation> sentCurrentYearResult = + client.findAllByQuery(Quotation.class, sentCurrentYearQuery); + int currentYearSentQuotation = sentCurrentYearResult.size(); + + //Progression devis envoyés + int sentQuotationProgression = 0; + if (previousYearValue != 0){ + sentQuotationProgression = 100 * (currentYearSentQuotation - previousYearValue) / previousYearValue; + } + + previousYearValue = currentYearSentQuotation; + + //Graphe devis envoyés + Map<String, Integer> sentQuotationData = getSentQuotationData(year, client); + + yearData.setBaseValue(currentYearSentQuotation); + yearData.setProgression(sentQuotationProgression); + yearData.setPlotValues(sentQuotationData); + + data.put(year, yearData); + } + + return renderView("salesReports/sentQuotation.jsp", + "data", data, + "allYears", listAllYearsInChorem, + "fromYear", from, + "toYear", to); + } + + protected Map<String,Integer> getSentQuotationData(Integer year, WikittyClient client){ + + Date first = SalesReportHelper.getFirstDayOfYear(year); + Date last = SalesReportHelper.getLastDayOfYear(year); + + WikittyQuery sentQuotationDataQuery = new WikittyQueryMaker().and() + .exteq(Sent.EXT_SENT) + .bw(Sent.FQ_FIELD_SENT_POSTEDDATE, first, last) + .end().setLimit(WikittyQuery.MAX); + + //add a facet per month + Map<Integer,String> sentQuotationLabels = new HashMap<Integer, String>(); + Date baseValue = DateUtils.addDays(last,1); + for (int i=0;i<12;i++){ + Date lastDayOfMonth = DateUtil.getYesterday(baseValue); + Date firstDayOfMonth = DateUtil.setFirstDayOfMonth(lastDayOfMonth); + baseValue=firstDayOfMonth; + + WikittyQuery monthFacet = new WikittyQueryMaker().and() + .bw(Sent.FQ_FIELD_SENT_POSTEDDATE, firstDayOfMonth, lastDayOfMonth) + .end(); + + sentQuotationDataQuery = sentQuotationDataQuery.addFacetQuery(String.valueOf(i), monthFacet.getCondition()); + + sentQuotationLabels.put(i, DateUtil.getMonthLibelle(DateUtil.getMonth(lastDayOfMonth)+1)); + } + + WikittyQueryResult<Quotation> sentQuotationDataResult = + client.findAllByQuery(Quotation.class, sentQuotationDataQuery.setFacetMinCount(0)); + + Map<String,Integer> sentQuotationData = new LinkedHashMap<String, Integer>(); + + Map<String,List<FacetTopic>> sentQuotationDataFacet = sentQuotationDataResult.getFacets(); + + for (int i=11;i>=0;i--) { + sentQuotationData.put(sentQuotationLabels.get(i), sentQuotationDataFacet.get(String.valueOf(i)).get(0).getCount()); + } + + return sentQuotationData; + } + +} Modified: trunk/chorem-webmotion/src/main/resources/mapping =================================================================== --- trunk/chorem-webmotion/src/main/resources/mapping 2012-12-29 13:13:20 UTC (rev 290) +++ trunk/chorem-webmotion/src/main/resources/mapping 2013-01-06 17:20:34 UTC (rev 291) @@ -41,7 +41,7 @@ * /wikitty-json/get/{id} action:GenericAction.getById * /fragment/dashboard/{method} action:DashboardAction.{method} * /fragment/dashboardHR/{method} action:DashboardHRAction.{method} -* /fragment/sales/{method} action:SalesAction.{method} +* /fragment/sales/{method} action:sales.SalesAction.{method} * /admin view:contact.jsp * /admin/{method} action:AdminAction.{method} * /contact view:contact.jsp @@ -51,6 +51,9 @@ * /hr/vacationDiv/{ids} action:HrAction.editVacationDiv * /hr/vacationRequest/save action:HrAction.saveVacationRequest * /hr/vacationRequest/delete/{id} action:HrAction.deleteVacationRequest -* /sales view:sales.jsp -* /sales/{method} action:SalesAction.{method} -* /sales/{method}/{id} action:SalesAction.{method} +* /sales redirect:/report/sentQuotation +* /salesMenu view:sales.jsp +* /sales/report/sentQuotation action:sales.SentQuotationsReportAction.sentQuotationPerMonth +* /sales/report/acceptedQuotation action:sales.AcceptedQuotationsReportAction.acceptedQuotationPerMonth +* /sales/{method} action:sales.SalesAction.{method} +* /sales/{method}/{id} action:sales.SalesAction.{method} Deleted: trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/dashboardSalesReport.jsp =================================================================== --- trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/dashboardSalesReport.jsp 2012-12-29 13:13:20 UTC (rev 290) +++ trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/dashboardSalesReport.jsp 2013-01-06 17:20:34 UTC (rev 291) @@ -1,77 +0,0 @@ -<%-- - #%L - Chorem webmotion - $Id:$ - $HeadURL:$ - %% - Copyright (C) 2011 - 2012 CodeLutin - %% - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 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. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. - #L% - --%> -<%@page contentType="text/html" pageEncoding="UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="f" %> -<%@ taglib uri="/WEB-INF/wikitty.tld" prefix="w"%> - -<script type="text/javascript"> - $(document).ready(function(){ - - //Sent quotations per month graph - var sentQuotations = [ - <c:forEach var="entry" items="${sentQuotationData}" varStatus="counter"> - ['${entry.key}', ${entry.value}] - <c:if test="${!counter.last}">, </c:if> - </c:forEach> - ]; - - var plot1 = $.jqplot ('sentQuotations', [sentQuotations], { - title:'Devis envoyés par mois', - axes:{ - xaxis:{ - pad: 0, - tickRenderer: $.jqplot.CanvasAxisTickRenderer , - tickOptions: { - angle: -30, - fontSize: '10pt' - }, - renderer: $.jqplot.CategoryAxisRenderer - }, - }, - highlighter: { - show: true, - tooltipAxes: 'y' - }, - cursor: { - show: false - }, - series:[{lineWidth:2, markerOptions:{style:'square'}}] - }); - - }); -</script> - -<h1>Indicateurs de performance commerciale</h1> - -<div> -<ul style="float:left;"> - <li>Nombre de devis envoyés cette année : ${currentYearSentQuotation}</li> - <li>Nombre de devis envoyés l'an dernier : ${previousYearSentQuotation}</li> - <li>Progression : ${sentQuotationProgression} %</li> -</ul> - -<div id="sentQuotations" style="height:200px; width:300px; float:left;"></div> -</div> - -<div style="clear:both;"/> \ No newline at end of file Modified: trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/decorator.jsp =================================================================== --- trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/decorator.jsp 2012-12-29 13:13:20 UTC (rev 290) +++ trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/decorator.jsp 2013-01-06 17:20:34 UTC (rev 291) @@ -47,6 +47,8 @@ <script type="text/javascript" src="<c:url value="/js/jqplot.canvasTextRenderer.min.js"/>"></script> <script type="text/javascript" src="<c:url value="/js/jqplot.highlighter.min.js"/>"></script> <script type="text/javascript" src="<c:url value="/js/jqplot.cursor.min.js"/>"></script> + <script type="text/javascript" src="<c:url value="/js/jqplot.barRenderer.js"/>"></script> + <link rel="stylesheet" type="text/css" href="<c:url value="/css/jquery.jqplot.css"/>" /> <%-- Bootstrap --%> <link rel="stylesheet" href="<c:url value="/css/bootstrap.min.css"/>"> <link rel="stylesheet" href="<c:url value="/css/bootstrap-responsive.min.css"/>"> Deleted: trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/sales.jsp =================================================================== --- trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/sales.jsp 2012-12-29 13:13:20 UTC (rev 290) +++ trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/sales.jsp 2013-01-06 17:20:34 UTC (rev 291) @@ -1,30 +0,0 @@ -<%-- - #%L - Chorem webmotion - $Id:$ - $HeadURL:$ - %% - Copyright (C) 2011 - 2012 CodeLutin - %% - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 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. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. - #L% - --%> -<%@page contentType="text/html" pageEncoding="UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - <jsp:include page="/fragment/sales/report"/> - <jsp:include page="/fragment/sales/lead"/> - <jsp:include page="/fragment/sales/draft"/> - <jsp:include page="/fragment/sales/sent"/> - <jsp:include page="/fragment/sales/accepted"/> \ No newline at end of file Added: trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/acceptedQuotation.jsp =================================================================== --- trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/acceptedQuotation.jsp (rev 0) +++ trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/acceptedQuotation.jsp 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,124 @@ +<%-- + #%L + Chorem webmotion + $Id:$ + $HeadURL:$ + %% + Copyright (C) 2011 - 2012 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --%> +<%@page contentType="text/html" pageEncoding="UTF-8"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="f" %> +<%@ taglib uri="/WEB-INF/wikitty.tld" prefix="w"%> + +<div class="row-fluid"> + <div class="span2"> + <jsp:include page="/salesMenu"/> + </div> + + <div class="span10"> + <!-- Javascript to display graph --> + <script type="text/javascript"> + $(document).ready(function(){ + var acceptedQuotations = [ + <c:forEach var="entry" items="${data}" varStatus="counter"> + [ + <c:forEach var="entry2" items="${entry.value.plotValues}" varStatus="counter2"> + ['${entry2.key}', ${entry2.value}] + <c:if test="${!counter2.last}">, </c:if> + </c:forEach> + ] + <c:if test="${!counter.last}">, </c:if> + </c:forEach> + ]; + + var plot1 = $.jqplot ('acceptedQuotations', acceptedQuotations, { + title:'Devis acceptés par mois', + seriesDefaults:{ + renderer:$.jqplot.BarRenderer, + rendererOptions: {fillToZero: true} + }, + axes:{ + xaxis:{ + pad: 0, + tickRenderer: $.jqplot.CanvasAxisTickRenderer , + tickOptions: { + angle: -30, + fontSize: '10pt' + }, + renderer: $.jqplot.CategoryAxisRenderer + }, + }, + highlighter: { + show: true, + tooltipAxes: 'y' + }, + cursor: { + show: false + }, + series:[ + <c:forEach var="entry" items="${data}" varStatus="counter"> + {label:${entry.key}}<c:if test="${!counter.last}">,</c:if> + </c:forEach>], + legend: {show:true, location: 'e', placement: 'outsideGrid' } + }); + + }); + </script> + + <h2>Devis acceptés</h2> + + <form action="acceptedQuotation" method="get"> + <select name="from"> + <c:forEach var="entry" items="${allYears}" varStatus="counter"> + <option value="${entry}" <c:if test="${entry==fromYear}">selected</c:if>>${entry}</option> + </c:forEach> + </select> + <select name="to"> + <c:forEach var="entry" items="${allYears}" varStatus="counter"> + <option value="${entry}" ${entry == toYear ? 'selected' : ''} >${entry}</option> + </c:forEach> + </select> + <input type="submit" value="Rechercher"> + </form> + + <div id="acceptedQuotations" style="height:200px;"></div> + + + <table class="table table-striped table-bordered table-condensed"> + <thead> + <tr> + <th>Année</th> + <th>Devis acceptés</th> + <th>Progression</th> + </tr> + </thead> + <tbody> + <c:forEach var="year" items="${data}"> + <tr> + <td>${year.key}</td> + <td class="currency">${year.value.baseValue}</td> + <td class="percent">${year.value.progression} %</td> + </tr> + </c:forEach> + </tbody> + </table> + + </div> +</div> + +<div style="clear:both;"/> \ No newline at end of file Added: trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/dashboardSalesReportSalesEvolutionPerProject.jsp =================================================================== --- trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/dashboardSalesReportSalesEvolutionPerProject.jsp (rev 0) +++ trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/dashboardSalesReportSalesEvolutionPerProject.jsp 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,38 @@ +<%-- + #%L + Chorem webmotion + $Id:$ + $HeadURL:$ + %% + Copyright (C) 2011 - 2012 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --%> +<%@page contentType="text/html" pageEncoding="UTF-8"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="f" %> +<%@ taglib uri="/WEB-INF/wikitty.tld" prefix="w"%> + +<div class="row-fluid"> + <div class="span2"> + <!--Sidebar content--> + </div> + + <div class="span10"> + <!--Body content--> + </div> +</div> + +<div style="clear:both;"/> \ No newline at end of file Added: trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/dashboardSalesReportSalesPerProject.jsp =================================================================== --- trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/dashboardSalesReportSalesPerProject.jsp (rev 0) +++ trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/dashboardSalesReportSalesPerProject.jsp 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,38 @@ +<%-- + #%L + Chorem webmotion + $Id:$ + $HeadURL:$ + %% + Copyright (C) 2011 - 2012 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --%> +<%@page contentType="text/html" pageEncoding="UTF-8"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="f" %> +<%@ taglib uri="/WEB-INF/wikitty.tld" prefix="w"%> + +<div class="row-fluid"> + <div class="span2"> + <!--Sidebar content--> + </div> + + <div class="span10"> + <!--Body content--> + </div> +</div> + +<div style="clear:both;"/> \ No newline at end of file Added: trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/menu.jsp =================================================================== --- trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/menu.jsp (rev 0) +++ trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/menu.jsp 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,7 @@ +<ul class="nav nav-list"> + <li><a href="#"><i class="icon-chevron-right"></i> Ventes</a></li> + <li><a href="#"><i class="icon-chevron-right"></i> Ventes par client</a></li> + <li><a href="#"><i class="icon-chevron-right"></i> Ventes par projet</a></li> + <li><a href="sentQuotation"><i class="icon-chevron-right"></i> Devis envoyés</a></li> + <li><a href="acceptedQuotation"><i class="icon-chevron-right"></i> Devis acceptés</a></li> +</ul> \ No newline at end of file Copied: trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/sales.jsp (from rev 290, trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/sales.jsp) =================================================================== --- trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/sales.jsp (rev 0) +++ trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/sales.jsp 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,29 @@ +<%-- + #%L + Chorem webmotion + $Id:$ + $HeadURL:$ + %% + Copyright (C) 2011 - 2012 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --%> +<%@page contentType="text/html" pageEncoding="UTF-8"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> + + <jsp:include page="/fragment/sales/lead"/> + <jsp:include page="/fragment/sales/draft"/> + <jsp:include page="/fragment/sales/sent"/> + <jsp:include page="/fragment/sales/accepted"/> \ No newline at end of file Added: trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/sentQuotation.jsp =================================================================== --- trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/sentQuotation.jsp (rev 0) +++ trunk/chorem-webmotion/src/main/webapp/WEB-INF/jsp/salesReports/sentQuotation.jsp 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,124 @@ +<%-- + #%L + Chorem webmotion + $Id:$ + $HeadURL:$ + %% + Copyright (C) 2011 - 2012 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --%> +<%@page contentType="text/html" pageEncoding="UTF-8"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="f" %> +<%@ taglib uri="/WEB-INF/wikitty.tld" prefix="w"%> + +<div class="row-fluid"> + <div class="span2"> + <jsp:include page="/salesMenu"/> + </div> + + <div class="span10"> + <!-- Javascript to display graph --> + <script type="text/javascript"> + $(document).ready(function(){ + var sentQuotations = [ + <c:forEach var="entry" items="${data}" varStatus="counter"> + [ + <c:forEach var="entry2" items="${entry.value.plotValues}" varStatus="counter2"> + ['${entry2.key}', ${entry2.value}] + <c:if test="${!counter2.last}">, </c:if> + </c:forEach> + ] + <c:if test="${!counter.last}">, </c:if> + </c:forEach> + ]; + + var plot1 = $.jqplot ('sentQuotations', sentQuotations, { + title:'Devis envoyés par mois', + seriesDefaults:{ + renderer:$.jqplot.BarRenderer, + rendererOptions: {fillToZero: true} + }, + axes:{ + xaxis:{ + pad: 0, + tickRenderer: $.jqplot.CanvasAxisTickRenderer , + tickOptions: { + angle: -30, + fontSize: '10pt' + }, + renderer: $.jqplot.CategoryAxisRenderer + }, + }, + highlighter: { + show: true, + tooltipAxes: 'y' + }, + cursor: { + show: false + }, + series:[ + <c:forEach var="entry" items="${data}" varStatus="counter"> + {label:${entry.key}}<c:if test="${!counter.last}">,</c:if> + </c:forEach>], + legend: {show:true, location: 'e', placement: 'outsideGrid' } + }); + + }); + </script> + + <h2>Devis envoyés</h2> + + <form action="sentQuotation" method="get"> + <select name="from"> + <c:forEach var="entry" items="${allYears}" varStatus="counter"> + <option value="${entry}" <c:if test="${entry==fromYear}">selected</c:if>>${entry}</option> + </c:forEach> + </select> + <select name="to"> + <c:forEach var="entry" items="${allYears}" varStatus="counter"> + <option value="${entry}" ${entry == toYear ? 'selected' : ''} >${entry}</option> + </c:forEach> + </select> + <input type="submit" value="Rechercher"> + </form> + + <div id="sentQuotations" style="height:200px;"></div> + + + <table class="table table-striped table-bordered table-condensed"> + <thead> + <tr> + <th>Année</th> + <th>Devis envoyés</th> + <th>Progression</th> + </tr> + </thead> + <tbody> + <c:forEach var="year" items="${data}"> + <tr> + <td>${year.key}</td> + <td class="currency">${year.value.baseValue}</td> + <td class="percent">${year.value.progression} %</td> + </tr> + </c:forEach> + </tbody> + </table> + + </div> +</div> + +<div style="clear:both;"/> \ No newline at end of file Added: trunk/chorem-webmotion/src/main/webapp/css/jquery.jqplot.css =================================================================== --- trunk/chorem-webmotion/src/main/webapp/css/jquery.jqplot.css (rev 0) +++ trunk/chorem-webmotion/src/main/webapp/css/jquery.jqplot.css 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,259 @@ +/*rules for the plot target div. These will be cascaded down to all plot elements according to css rules*/ +.jqplot-target { + position: relative; + color: #666666; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-size: 1em; +/* height: 300px; + width: 400px;*/ +} + +/*rules applied to all axes*/ +.jqplot-axis { + font-size: 0.75em; +} + +.jqplot-xaxis { + margin-top: 10px; +} + +.jqplot-x2axis { + margin-bottom: 10px; +} + +.jqplot-yaxis { + margin-right: 10px; +} + +.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis { + margin-left: 10px; + margin-right: 10px; +} + +/*rules applied to all axis tick divs*/ +.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, .jqplot-yMidAxis-tick { + position: absolute; + white-space: pre; +} + + +.jqplot-xaxis-tick { + top: 0px; + /* initial position untill tick is drawn in proper place */ + left: 15px; +/* padding-top: 10px;*/ + vertical-align: top; +} + +.jqplot-x2axis-tick { + bottom: 0px; + /* initial position untill tick is drawn in proper place */ + left: 15px; +/* padding-bottom: 10px;*/ + vertical-align: bottom; +} + +.jqplot-yaxis-tick { + right: 0px; + /* initial position untill tick is drawn in proper place */ + top: 15px; +/* padding-right: 10px;*/ + text-align: right; +} + +.jqplot-yaxis-tick.jqplot-breakTick { + right: -20px; + margin-right: 0px; + padding:1px 5px 1px 5px; +/* background-color: white;*/ + z-index: 2; + font-size: 1.5em; +} + +.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick { + left: 0px; + /* initial position untill tick is drawn in proper place */ + top: 15px; +/* padding-left: 10px;*/ +/* padding-right: 15px;*/ + text-align: left; +} + +.jqplot-yMidAxis-tick { + text-align: center; + white-space: nowrap; +} + +.jqplot-xaxis-label { + margin-top: 10px; + font-size: 11pt; + position: absolute; +} + +.jqplot-x2axis-label { + margin-bottom: 10px; + font-size: 11pt; + position: absolute; +} + +.jqplot-yaxis-label { + margin-right: 10px; +/* text-align: center;*/ + font-size: 11pt; + position: absolute; +} + +.jqplot-yMidAxis-label { + font-size: 11pt; + position: absolute; +} + +.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label { +/* text-align: center;*/ + font-size: 11pt; + margin-left: 10px; + position: absolute; +} + +.jqplot-meterGauge-tick { + font-size: 0.75em; + color: #999999; +} + +.jqplot-meterGauge-label { + font-size: 1em; + color: #999999; +} + +table.jqplot-table-legend { + margin-top: 12px; + margin-bottom: 12px; + margin-left: 12px; + margin-right: 12px; +} + +table.jqplot-table-legend, table.jqplot-cursor-legend { + background-color: rgba(255,255,255,0.6); + border: 1px solid #cccccc; + position: absolute; + font-size: 0.75em; +} + +td.jqplot-table-legend { + vertical-align:middle; +} + +/* +These rules could be used instead of assigning +element styles and relying on js object properties. +*/ + +/* +td.jqplot-table-legend-swatch { + padding-top: 0.5em; + text-align: center; +} + +tr.jqplot-table-legend:first td.jqplot-table-legend-swatch { + padding-top: 0px; +} +*/ + +td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active { + cursor: pointer; +} + +.jqplot-table-legend .jqplot-series-hidden { + text-decoration: line-through; +} + +div.jqplot-table-legend-swatch-outline { + border: 1px solid #cccccc; + padding:1px; +} + +div.jqplot-table-legend-swatch { + width:0px; + height:0px; + border-top-width: 5px; + border-bottom-width: 5px; + border-left-width: 6px; + border-right-width: 6px; + border-top-style: solid; + border-bottom-style: solid; + border-left-style: solid; + border-right-style: solid; +} + +.jqplot-title { + top: 0px; + left: 0px; + padding-bottom: 0.5em; + font-size: 1.2em; +} + +table.jqplot-cursor-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; +} + + +.jqplot-cursor-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; + white-space: nowrap; + background: rgba(208,208,208,0.5); + padding: 1px; +} + +.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; + white-space: nowrap; + background: rgba(208,208,208,0.5); + padding: 1px; +} + +.jqplot-point-label { + font-size: 0.75em; + z-index: 2; +} + +td.jqplot-cursor-legend-swatch { + vertical-align: middle; + text-align: center; +} + +div.jqplot-cursor-legend-swatch { + width: 1.2em; + height: 0.7em; +} + +.jqplot-error { +/* Styles added to the plot target container when there is an error go here.*/ + text-align: center; +} + +.jqplot-error-message { +/* Styling of the custom error message div goes here.*/ + position: relative; + top: 46%; + display: inline-block; +} + +div.jqplot-bubble-label { + font-size: 0.8em; +/* background: rgba(90%, 90%, 90%, 0.15);*/ + padding-left: 2px; + padding-right: 2px; + color: rgb(20%, 20%, 20%); +} + +div.jqplot-bubble-label.jqplot-bubble-label-highlight { + background: rgba(90%, 90%, 90%, 0.7); +} + +div.jqplot-noData-container { + text-align: center; + background-color: rgba(96%, 96%, 96%, 0.3); +} Added: trunk/chorem-webmotion/src/main/webapp/js/jqplot.barRenderer.js =================================================================== --- trunk/chorem-webmotion/src/main/webapp/js/jqplot.barRenderer.js (rev 0) +++ trunk/chorem-webmotion/src/main/webapp/js/jqplot.barRenderer.js 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,797 @@ +/** + * jqPlot + * Pure JavaScript plotting plugin using jQuery + * + * Version: 1.0.4 + * Revision: 1121 + * + * Copyright (c) 2009-2012 Chris Leonello + * jqPlot is currently available for use in all personal or commercial projects + * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL + * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can + * choose the license that best suits your project and use it accordingly. + * + * Although not required, the author would appreciate an email letting him + * know of any substantial use of jqPlot. You can reach the author at: + * chris at jqplot dot com or see http://www.jqplot.com/info.php . + * + * If you are feeling kind and generous, consider supporting the project by + * making a donation at: http://www.jqplot.com/donate.php . + * + * sprintf functions contained in jqplot.sprintf.js by Ash Searle: + * + * version 2007.04.27 + * author Ash Searle + * http://hexmen.com/blog/2007/03/printf-sprintf/ + * http://hexmen.com/js/sprintf.js + * The author (Ash Searle) has placed this code in the public domain: + * "This code is unrestricted: you are free to use it however you like." + * + */ +(function($) { + + // Class: $.jqplot.BarRenderer + // A plugin renderer for jqPlot to draw a bar plot. + // Draws series as a line. + + $.jqplot.BarRenderer = function(){ + $.jqplot.LineRenderer.call(this); + }; + + $.jqplot.BarRenderer.prototype = new $.jqplot.LineRenderer(); + $.jqplot.BarRenderer.prototype.constructor = $.jqplot.BarRenderer; + + // called with scope of series. + $.jqplot.BarRenderer.prototype.init = function(options, plot) { + // Group: Properties + // + // prop: barPadding + // Number of pixels between adjacent bars at the same axis value. + this.barPadding = 8; + // prop: barMargin + // Number of pixels between groups of bars at adjacent axis values. + this.barMargin = 10; + // prop: barDirection + // 'vertical' = up and down bars, 'horizontal' = side to side bars + this.barDirection = 'vertical'; + // prop: barWidth + // Width of the bar in pixels (auto by devaul). null = calculated automatically. + this.barWidth = null; + // prop: shadowOffset + // offset of the shadow from the slice and offset of + // each succesive stroke of the shadow from the last. + this.shadowOffset = 2; + // prop: shadowDepth + // number of strokes to apply to the shadow, + // each stroke offset shadowOffset from the last. + this.shadowDepth = 5; + // prop: shadowAlpha + // transparency of the shadow (0 = transparent, 1 = opaque) + this.shadowAlpha = 0.08; + // prop: waterfall + // true to enable waterfall plot. + this.waterfall = false; + // prop: groups + // group bars into this many groups + this.groups = 1; + // prop: varyBarColor + // true to color each bar of a series separately rather than + // have every bar of a given series the same color. + // If used for non-stacked multiple series bar plots, user should + // specify a separate 'seriesColors' array for each series. + // Otherwise, each series will set their bars to the same color array. + // This option has no Effect for stacked bar charts and is disabled. + this.varyBarColor = false; + // prop: highlightMouseOver + // True to highlight slice when moused over. + // This must be false to enable highlightMouseDown to highlight when clicking on a slice. + this.highlightMouseOver = true; + // prop: highlightMouseDown + // True to highlight when a mouse button is pressed over a slice. + // This will be disabled if highlightMouseOver is true. + this.highlightMouseDown = false; + // prop: highlightColors + // an array of colors to use when highlighting a bar. + this.highlightColors = []; + // prop: transposedData + // NOT IMPLEMENTED YET. True if this is a horizontal bar plot and + // x and y values are "transposed". Tranposed, or "swapped", data is + // required prior to rev. 894 builds of jqPlot with horizontal bars. + // Allows backward compatability of bar renderer horizontal bars with + // old style data sets. + this.transposedData = true; + this.renderer.animation = { + show: false, + direction: 'down', + speed: 3000, + _supported: true + }; + this._type = 'bar'; + + // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver + if (options.highlightMouseDown && options.highlightMouseOver == null) { + options.highlightMouseOver = false; + } + + ////// + // This is probably wrong here. + // After going back and forth on wether renderer should be the thing + // or extend the thing, it seems that it it best if it is a property + // on the thing. This should be something that is commonized + // among series renderers in the future. + ////// + $.extend(true, this, options); + + // really should probably do this + $.extend(true, this.renderer, options); + // fill is still needed to properly draw the legend. + // bars have to be filled. + this.fill = true; + + // if horizontal bar and animating, reset the default direction + if (this.barDirection === 'horizontal' && this.rendererOptions.animation && this.rendererOptions.animation.direction == null) { + this.renderer.animation.direction = 'left'; + } + + if (this.waterfall) { + this.fillToZero = false; + this.disableStack = true; + } + + if (this.barDirection == 'vertical' ) { + this._primaryAxis = '_xaxis'; + this._stackAxis = 'y'; + this.fillAxis = 'y'; + } + else { + this._primaryAxis = '_yaxis'; + this._stackAxis = 'x'; + this.fillAxis = 'x'; + } + // index of the currenty highlighted point, if any + this._highlightedPoint = null; + // total number of values for all bar series, total number of bar series, and position of this series + this._plotSeriesInfo = null; + // Array of actual data colors used for each data point. + this._dataColors = []; + this._barPoints = []; + + // set the shape renderer options + var opts = {lineJoin:'miter', lineCap:'round', fill:true, isarc:false, strokeStyle:this.color, fillStyle:this.color, closePath:this.fill}; + this.renderer.shapeRenderer.init(opts); + // set the shadow renderer options + var sopts = {lineJoin:'miter', lineCap:'round', fill:true, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, closePath:this.fill}; + this.renderer.shadowRenderer.init(sopts); + + plot.postInitHooks.addOnce(postInit); + plot.postDrawHooks.addOnce(postPlotDraw); + plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove); + plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown); + plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp); + plot.eventListenerHooks.addOnce('jqplotClick', handleClick); + plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick); + }; + + // called with scope of series + function barPreInit(target, data, seriesDefaults, options) { + if (this.rendererOptions.barDirection == 'horizontal') { + this._stackAxis = 'x'; + this._primaryAxis = '_yaxis'; + } + if (this.rendererOptions.waterfall == true) { + this._data = $.extend(true, [], this.data); + var sum = 0; + var pos = (!this.rendererOptions.barDirection || this.rendererOptions.barDirection === 'vertical' || this.transposedData === false) ? 1 : 0; + for(var i=0; i<this.data.length; i++) { + sum += this.data[i][pos]; + if (i>0) { + this.data[i][pos] += this.data[i-1][pos]; + } + } + this.data[this.data.length] = (pos == 1) ? [this.data.length+1, sum] : [sum, this.data.length+1]; + this._data[this._data.length] = (pos == 1) ? [this._data.length+1, sum] : [sum, this._data.length+1]; + } + if (this.rendererOptions.groups > 1) { + this.breakOnNull = true; + var l = this.data.length; + var skip = parseInt(l/this.rendererOptions.groups, 10); + var count = 0; + for (var i=skip; i<l; i+=skip) { + this.data.splice(i+count, 0, [null, null]); + this._plotData.splice(i+count, 0, [null, null]); + this._stackData.splice(i+count, 0, [null, null]); + count++; + } + for (i=0; i<this.data.length; i++) { + if (this._primaryAxis == '_xaxis') { + this.data[i][0] = i+1; + this._plotData[i][0] = i+1; + this._stackData[i][0] = i+1; + } + else { + this.data[i][1] = i+1; + this._plotData[i][1] = i+1; + this._stackData[i][1] = i+1; + } + } + } + } + + $.jqplot.preSeriesInitHooks.push(barPreInit); + + // needs to be called with scope of series, not renderer. + $.jqplot.BarRenderer.prototype.calcSeriesNumbers = function() { + var nvals = 0; + var nseries = 0; + var paxis = this[this._primaryAxis]; + var s, series, pos; + // loop through all series on this axis + for (var i=0; i < paxis._series.length; i++) { + series = paxis._series[i]; + if (series === this) { + pos = i; + } + // is the series rendered as a bar? + if (series.renderer.constructor == $.jqplot.BarRenderer) { + // gridData may not be computed yet, use data length insted + nvals += series.data.length; + nseries += 1; + } + } + // return total number of values for all bar series, total number of bar series, and position of this series + return [nvals, nseries, pos]; + }; + + $.jqplot.BarRenderer.prototype.setBarWidth = function() { + // need to know how many data values we have on the approprate axis and figure it out. + var i; + var nvals = 0; + var nseries = 0; + var paxis = this[this._primaryAxis]; + var s, series, pos; + var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this); + nvals = temp[0]; + nseries = temp[1]; + var nticks = paxis.numberTicks; + var nbins = (nticks-1)/2; + // so, now we have total number of axis values. + if (paxis.name == 'xaxis' || paxis.name == 'x2axis') { + if (this._stack) { + this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals * nseries - this.barMargin; + } + else { + this.barWidth = ((paxis._offsets.max - paxis._offsets.min)/nbins - this.barPadding * (nseries-1) - this.barMargin*2)/nseries; + // this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals - this.barPadding - this.barMargin/nseries; + } + } + else { + if (this._stack) { + this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals * nseries - this.barMargin; + } + else { + this.barWidth = ((paxis._offsets.min - paxis._offsets.max)/nbins - this.barPadding * (nseries-1) - this.barMargin*2)/nseries; + // this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals - this.barPadding - this.barMargin/nseries; + } + } + return [nvals, nseries]; + }; + + function computeHighlightColors (colors) { + var ret = []; + for (var i=0; i<colors.length; i++){ + var rgba = $.jqplot.getColorComponents(colors[i]); + var newrgb = [rgba[0], rgba[1], rgba[2]]; + var sum = newrgb[0] + newrgb[1] + newrgb[2]; + for (var j=0; j<3; j++) { + // when darkening, lowest color component can be is 60. + newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]); + newrgb[j] = parseInt(newrgb[j], 10); + } + ret.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')'); + } + return ret; + } + + function getStart(sidx, didx, comp, plot, axis) { + // check if sign change + var seriesIndex = sidx, + prevSeriesIndex = sidx - 1, + start, + prevVal, + aidx = (axis === 'x') ? 0 : 1; + + // is this not the first series? + if (seriesIndex > 0) { + prevVal = plot.series[prevSeriesIndex]._plotData[didx][aidx]; + + // is there a sign change + if ((comp * prevVal) < 0) { + start = getStart(prevSeriesIndex, didx, comp, plot, axis); + } + + // no sign change. + else { + start = plot.series[prevSeriesIndex].gridData[didx][aidx]; + } + + } + + // if first series, return value at 0 + else { + + start = (aidx === 0) ? plot.series[seriesIndex]._xaxis.series_u2p(0) : plot.series[seriesIndex]._yaxis.series_u2p(0); + } + + return start; + } + + + $.jqplot.BarRenderer.prototype.draw = function(ctx, gridData, options, plot) { + var i; + // Ughhh, have to make a copy of options b/c it may be modified later. + var opts = $.extend({}, options); + var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow; + var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine; + var fill = (opts.fill != undefined) ? opts.fill : this.fill; + var xaxis = this.xaxis; + var yaxis = this.yaxis; + var xp = this._xaxis.series_u2p; + var yp = this._yaxis.series_u2p; + var pointx, pointy; + // clear out data colors. + this._dataColors = []; + this._barPoints = []; + + if (this.barWidth == null) { + this.renderer.setBarWidth.call(this); + } + + var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this); + var nvals = temp[0]; + var nseries = temp[1]; + var pos = temp[2]; + var points = []; + + if (this._stack) { + this._barNudge = 0; + } + else { + this._barNudge = (-Math.abs(nseries/2 - 0.5) + pos) * (this.barWidth + this.barPadding); + } + if (showLine) { + var negativeColors = new $.jqplot.ColorGenerator(this.negativeSeriesColors); + var positiveColors = new $.jqplot.ColorGenerator(this.seriesColors); + var negativeColor = negativeColors.get(this.index); + if (! this.useNegativeColors) { + negativeColor = opts.fillStyle; + } + var positiveColor = opts.fillStyle; + var base; + var xstart; + var ystart; + + if (this.barDirection == 'vertical') { + for (var i=0; i<gridData.length; i++) { + if (!this._stack && this.data[i][1] == null) { + continue; + } + points = []; + base = gridData[i][0] + this._barNudge; + + // stacked + if (this._stack && this._prevGridData.length) { + ystart = getStart(this.index, i, this._plotData[i][1], plot, 'y'); + } + + // not stacked + else { + if (this.fillToZero) { + ystart = this._yaxis.series_u2p(0); + } + else if (this.waterfall && i > 0 && i < this.gridData.length-1) { + ystart = this.gridData[i-1][1]; + } + else if (this.waterfall && i == 0 && i < this.gridData.length-1) { + if (this._yaxis.min <= 0 && this._yaxis.max >= 0) { + ystart = this._yaxis.series_u2p(0); + } + else if (this._yaxis.min > 0) { + ystart = ctx.canvas.height; + } + else { + ystart = 0; + } + } + else if (this.waterfall && i == this.gridData.length - 1) { + if (this._yaxis.min <= 0 && this._yaxis.max >= 0) { + ystart = this._yaxis.series_u2p(0); + } + else if (this._yaxis.min > 0) { + ystart = ctx.canvas.height; + } + else { + ystart = 0; + } + } + else { + ystart = ctx.canvas.height; + } + } + if ((this.fillToZero && this._plotData[i][1] < 0) || (this.waterfall && this._data[i][1] < 0)) { + if (this.varyBarColor && !this._stack) { + if (this.useNegativeColors) { + opts.fillStyle = negativeColors.next(); + } + else { + opts.fillStyle = positiveColors.next(); + } + } + else { + opts.fillStyle = negativeColor; + } + } + else { + if (this.varyBarColor && !this._stack) { + opts.fillStyle = positiveColors.next(); + } + else { + opts.fillStyle = positiveColor; + } + } + + if (!this.fillToZero || this._plotData[i][1] >= 0) { + points.push([base-this.barWidth/2, ystart]); + points.push([base-this.barWidth/2, gridData[i][1]]); + points.push([base+this.barWidth/2, gridData[i][1]]); + points.push([base+this.barWidth/2, ystart]); + } + // for negative bars make sure points are always ordered clockwise + else { + points.push([base-this.barWidth/2, gridData[i][1]]); + points.push([base-this.barWidth/2, ystart]); + points.push([base+this.barWidth/2, ystart]); + points.push([base+this.barWidth/2, gridData[i][1]]); + } + this._barPoints.push(points); + // now draw the shadows if not stacked. + // for stacked plots, they are predrawn by drawShadow + if (shadow && !this._stack) { + var sopts = $.extend(true, {}, opts); + // need to get rid of fillStyle on shadow. + delete sopts.fillStyle; + this.renderer.shadowRenderer.draw(ctx, points, sopts); + } + var clr = opts.fillStyle || this.color; + this._dataColors.push(clr); + this.renderer.shapeRenderer.draw(ctx, points, opts); + } + } + + else if (this.barDirection == 'horizontal'){ + for (var i=0; i<gridData.length; i++) { + if (!this._stack && this.data[i][0] == null) { + continue; + } + points = []; + base = gridData[i][1] - this._barNudge; + xstart; + + if (this._stack && this._prevGridData.length) { + xstart = getStart(this.index, i, this._plotData[i][0], plot, 'x'); + } + // not stacked + else { + if (this.fillToZero) { + xstart = this._xaxis.series_u2p(0); + } + else if (this.waterfall && i > 0 && i < this.gridData.length-1) { + xstart = this.gridData[i-1][0]; + } + else if (this.waterfall && i == 0 && i < this.gridData.length-1) { + if (this._xaxis.min <= 0 && this._xaxis.max >= 0) { + xstart = this._xaxis.series_u2p(0); + } + else if (this._xaxis.min > 0) { + xstart = 0; + } + else { + xstart = 0; + } + } + else if (this.waterfall && i == this.gridData.length - 1) { + if (this._xaxis.min <= 0 && this._xaxis.max >= 0) { + xstart = this._xaxis.series_u2p(0); + } + else if (this._xaxis.min > 0) { + xstart = 0; + } + else { + xstart = ctx.canvas.width; + } + } + else { + xstart = 0; + } + } + if ((this.fillToZero && this._plotData[i][1] < 0) || (this.waterfall && this._data[i][1] < 0)) { + if (this.varyBarColor && !this._stack) { + if (this.useNegativeColors) { + opts.fillStyle = negativeColors.next(); + } + else { + opts.fillStyle = positiveColors.next(); + } + } + } + else { + if (this.varyBarColor && !this._stack) { + opts.fillStyle = positiveColors.next(); + } + else { + opts.fillStyle = positiveColor; + } + } + + + if (!this.fillToZero || this._plotData[i][0] >= 0) { + points.push([xstart, base + this.barWidth / 2]); + points.push([xstart, base - this.barWidth / 2]); + points.push([gridData[i][0], base - this.barWidth / 2]); + points.push([gridData[i][0], base + this.barWidth / 2]); + } + else { + points.push([gridData[i][0], base + this.barWidth / 2]); + points.push([gridData[i][0], base - this.barWidth / 2]); + points.push([xstart, base - this.barWidth / 2]); + points.push([xstart, base + this.barWidth / 2]); + } + + this._barPoints.push(points); + // now draw the shadows if not stacked. + // for stacked plots, they are predrawn by drawShadow + if (shadow && !this._stack) { + var sopts = $.extend(true, {}, opts); + delete sopts.fillStyle; + this.renderer.shadowRenderer.draw(ctx, points, sopts); + } + var clr = opts.fillStyle || this.color; + this._dataColors.push(clr); + this.renderer.shapeRenderer.draw(ctx, points, opts); + } + } + } + + if (this.highlightColors.length == 0) { + this.highlightColors = $.jqplot.computeHighlightColors(this._dataColors); + } + + else if (typeof(this.highlightColors) == 'string') { + var temp = this.highlightColors; + this.highlightColors = []; + for (var i=0; i<this._dataColors.length; i++) { + this.highlightColors.push(temp); + } + } + + }; + + + // for stacked plots, shadows will be pre drawn by drawShadow. + $.jqplot.BarRenderer.prototype.drawShadow = function(ctx, gridData, options, plot) { + var i; + var opts = (options != undefined) ? options : {}; + var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow; + var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine; + var fill = (opts.fill != undefined) ? opts.fill : this.fill; + var xaxis = this.xaxis; + var yaxis = this.yaxis; + var xp = this._xaxis.series_u2p; + var yp = this._yaxis.series_u2p; + var pointx, points, pointy, nvals, nseries, pos; + + if (this._stack && this.shadow) { + if (this.barWidth == null) { + this.renderer.setBarWidth.call(this); + } + + var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this); + nvals = temp[0]; + nseries = temp[1]; + pos = temp[2]; + + if (this._stack) { + this._barNudge = 0; + } + else { + this._barNudge = (-Math.abs(nseries/2 - 0.5) + pos) * (this.barWidth + this.barPadding); + } + if (showLine) { + + if (this.barDirection == 'vertical') { + for (var i=0; i<gridData.length; i++) { + if (this.data[i][1] == null) { + continue; + } + points = []; + var base = gridData[i][0] + this._barNudge; + var ystart; + + if (this._stack && this._prevGridData.length) { + ystart = getStart(this.index, i, this._plotData[i][1], plot, 'y'); + } + else { + if (this.fillToZero) { + ystart = this._yaxis.series_u2p(0); + } + else { + ystart = ctx.canvas.height; + } + } + + points.push([base-this.barWidth/2, ystart]); + points.push([base-this.barWidth/2, gridData[i][1]]); + points.push([base+this.barWidth/2, gridData[i][1]]); + points.push([base+this.barWidth/2, ystart]); + this.renderer.shadowRenderer.draw(ctx, points, opts); + } + } + + else if (this.barDirection == 'horizontal'){ + for (var i=0; i<gridData.length; i++) { + if (this.data[i][0] == null) { + continue; + } + points = []; + var base = gridData[i][1] - this._barNudge; + var xstart; + + if (this._stack && this._prevGridData.length) { + xstart = getStart(this.index, i, this._plotData[i][0], plot, 'x'); + } + else { + if (this.fillToZero) { + xstart = this._xaxis.series_u2p(0); + } + else { + xstart = 0; + } + } + + points.push([xstart, base+this.barWidth/2]); + points.push([gridData[i][0], base+this.barWidth/2]); + points.push([gridData[i][0], base-this.barWidth/2]); + points.push([xstart, base-this.barWidth/2]); + this.renderer.shadowRenderer.draw(ctx, points, opts); + } + } + } + + } + }; + + function postInit(target, data, options) { + for (var i=0; i<this.series.length; i++) { + if (this.series[i].renderer.constructor == $.jqplot.BarRenderer) { + // don't allow mouseover and mousedown at same time. + if (this.series[i].highlightMouseOver) { + this.series[i].highlightMouseDown = false; + } + } + } + } + + // called within context of plot + // create a canvas which we can draw on. + // insert it before the eventCanvas, so eventCanvas will still capture events. + function postPlotDraw() { + // Memory Leaks patch + if (this.plugins.barRenderer && this.plugins.barRenderer.highlightCanvas) { + + this.plugins.barRenderer.highlightCanvas.resetCanvas(); + this.plugins.barRenderer.highlightCanvas = null; + } + + this.plugins.barRenderer = {highlightedSeriesIndex:null}; + this.plugins.barRenderer.highlightCanvas = new $.jqplot.GenericCanvas(); + + this.eventCanvas._elem.before(this.plugins.barRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-barRenderer-highlight-canvas', this._plotDimensions, this)); + this.plugins.barRenderer.highlightCanvas.setContext(); + this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); }); + } + + function highlight (plot, sidx, pidx, points) { + var s = plot.series[sidx]; + var canvas = plot.plugins.barRenderer.highlightCanvas; + canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height); + s._highlightedPoint = pidx; + plot.plugins.barRenderer.highlightedSeriesIndex = sidx; + var opts = {fillStyle: s.highlightColors[pidx]}; + s.renderer.shapeRenderer.draw(canvas._ctx, points, opts); + canvas = null; + } + + function unhighlight (plot) { + var canvas = plot.plugins.barRenderer.highlightCanvas; + canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height); + for (var i=0; i<plot.series.length; i++) { + plot.series[i]._highlightedPoint = null; + } + plot.plugins.barRenderer.highlightedSeriesIndex = null; + plot.target.trigger('jqplotDataUnhighlight'); + canvas = null; + } + + + function handleMove(ev, gridpos, datapos, neighbor, plot) { + if (neighbor) { + var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; + var evt1 = jQuery.Event('jqplotDataMouseOver'); + evt1.pageX = ev.pageX; + evt1.pageY = ev.pageY; + plot.target.trigger(evt1, ins); + if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.barRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) { + var evt = jQuery.Event('jqplotDataHighlight'); + evt.which = ev.which; + evt.pageX = ev.pageX; + evt.pageY = ev.pageY; + plot.target.trigger(evt, ins); + highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points); + } + } + else if (neighbor == null) { + unhighlight (plot); + } + } + + function handleMouseDown(ev, gridpos, datapos, neighbor, plot) { + if (neighbor) { + var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; + if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.barRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) { + var evt = jQuery.Event('jqplotDataHighlight'); + evt.which = ev.which; + evt.pageX = ev.pageX; + evt.pageY = ev.pageY; + plot.target.trigger(evt, ins); + highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points); + } + } + else if (neighbor == null) { + unhighlight (plot); + } + } + + function handleMouseUp(ev, gridpos, datapos, neighbor, plot) { + var idx = plot.plugins.barRenderer.highlightedSeriesIndex; + if (idx != null && plot.series[idx].highlightMouseDown) { + unhighlight(plot); + } + } + + function handleClick(ev, gridpos, datapos, neighbor, plot) { + if (neighbor) { + var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; + var evt = jQuery.Event('jqplotDataClick'); + evt.which = ev.which; + evt.pageX = ev.pageX; + evt.pageY = ev.pageY; + plot.target.trigger(evt, ins); + } + } + + function handleRightClick(ev, gridpos, datapos, neighbor, plot) { + if (neighbor) { + var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; + var idx = plot.plugins.barRenderer.highlightedSeriesIndex; + if (idx != null && plot.series[idx].highlightMouseDown) { + unhighlight(plot); + } + var evt = jQuery.Event('jqplotDataRightClick'); + evt.which = ev.which; + evt.pageX = ev.pageX; + evt.pageY = ev.pageY; + plot.target.trigger(evt, ins); + } + } + + +})(jQuery); \ No newline at end of file Added: trunk/chorem-webmotion/src/main/webapp/js/jqplot.dateAxisRenderer.min.js =================================================================== --- trunk/chorem-webmotion/src/main/webapp/js/jqplot.dateAxisRenderer.min.js (rev 0) +++ trunk/chorem-webmotion/src/main/webapp/js/jqplot.dateAxisRenderer.min.js 2013-01-06 17:20:34 UTC (rev 291) @@ -0,0 +1,79 @@ +/* + * #%L + * Chorem webmotion + * $Id:$ + * $HeadURL:$ + * %% + * Copyright (C) 2011 - 2012 CodeLutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +/** + * jqPlot + * Pure JavaScript plotting plugin using jQuery + * + * Version: 1.0.4r1121 + * + * Copyright (c) 2009-2011 Chris Leonello + * jqPlot is currently available for use in all personal or commercial projects + * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL + * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can + * choose the license that best suits your project and use it accordingly. + * + * Although not required, the author would appreciate an email letting him + * know of any substantial use of jqPlot. You can reach the author at: + * chris at jqplot dot com or see http://www.jqplot.com/info.php . + * + * If you are feeling kind and generous, consider supporting the project by + * making a donation at: http://www.jqplot.com/donate.php . + * + * sprintf functions contained in jqplot.sprintf.js by Ash Searle: + * + * version 2007.04.27 + * author Ash Searle + * http://hexmen.com/blog/2007/03/printf-sprintf/ + * http://hexmen.com/js/sprintf.js + * The author (Ash Searle) has placed this code in the public domain: + * "This code is unrestricted: you are free to use it however you like." + * + * included jsDate library by Chris Leonello: + * + * Copyright (c) 2010-2011 Chris Leonello + * + * jsDate is currently available for use in all personal or commercial projects + * under both the MIT and GPL version 2.0 licenses. This means that you can + * choose the license that best suits your project and use it accordingly. + * + * jsDate borrows many concepts and ideas from the Date Instance + * Methods by Ken Snyder along with some parts of Ken's actual code. + * + * Ken's origianl Date Instance Methods and copyright notice: + * + * Ken Snyder (ken d snyder at gmail dot com) + * 2008-09-10 + * version 2.0.2 (http://kendsnyder.com/sandbox/date/) + * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/) + * + * jqplotToImage function based on Larry Siden's export-jqplot-to-png.js. + * Larry has generously given permission to adapt his code for inclusion + * into jqPlot. + * + * Larry's original code can be found here: + * + * https://github.com/lsiden/export-jqplot-to-png + * + * + */ +(function(h){h.jqplot.DateAxisRenderer=function(){h.jqplot.LinearAxisRenderer.call(this);this.date=new h.jsDate()};var c=1000;var e=60*c;var f=60*e;var l=24*f;var b=7*l;var j=30.4368499*l;var k=365.242199*l;var g=[31,28,31,30,31,30,31,30,31,30,31,30];var i=["%M:%S.%#N","%M:%S.%#N","%M:%S.%#N","%M:%S","%M:%S","%M:%S","%M:%S","%H:%M:%S","%H:%M:%S","%H:%M","%H:%M","%H:%M","%H:%M","%H:%M","%H:%M","%a %H:%M","%a %H:%M","%b %e %H:%M","%b %e %H:%M","%b %e %H:%M","%b %e %H:%M","%v","%v","%v","%v","%v","%v","%v"];var m=[0.1*c,0.2*c,0.5*c,c,2*c,5*c,10*c,15*c,30*c,e,2*e,5*e,10*e,15*e,30*e,f,2*f,4*f,6*f,8*f,12*f,l,2*l,3*l,4*l,5*l,b,2*b];var d=[];function a(p,s,t){var o=Number.MAX_VALUE;var u,r,v;for(var q=0,n=m.length;q<n;q++){u=Math.abs(t-m[q]);if(u<o){o=u;r=m[q];v=i[q]}}return[r,v]}h.jqplot.DateAxisRenderer.prototype=new h.jqplot.LinearAxisRenderer();h.jqplot.DateAxisRenderer.prototype.constructor=h.jqplot.DateAxisRenderer;h.jqplot.DateTickFormatter=function(n,o){if(!n){n="%Y/%m/%d"}return h.jsDate.strftime(o,n)};h.jqplot.DateAxisRenderer.prototype.init=function(E){this.tickOptions.formatter=h.jqplot.DateTickFormatter;this.tickInset=0;this.drawBaseline=true;this.baselineWidth=null;this.baselineColor=null;this.daTickInterval=null;this._daTickInterval=null;h.extend(true,this,E);var C=this._dataBounds,u,x,D,y,A,z,o;for(var t=0;t<this._series.length;t++){u={intervals:[],frequencies:{},sortedIntervals:[],min:null,max:null,mean:null};x=0;D=this._series[t];y=D.data;A=D._plotData;z=D._stackData;o=0;for(var r=0;r<y.length;r++){if(this.name=="xaxis"||this.name=="x2axis"){y[r][0]=new h.jsDate(y[r][0]).getTime();A[r][0]=new h.jsDate(y[r][0]).getTime();z[r][0]=new h.jsDate(y[r][0]).getTime();if((y[r][0]!=null&&y[r][0]<C.min)||C.min==null){C.min=y[r][0]}if((y[r][0]!=null&&y[r][0]>C.max)||C.max==null){C.max=y[r][0]}if(r>0){o=Math.abs(y[r][0]-y[r-1][0]);u.intervals.push(o);if(u.frequencies.hasOwnProperty(o)){u.frequencies[o]+=1}else{u.frequencies[o]=1}}x+=o}else{y[r][1]=new h.jsDate(y[r][1]).getTime();A[r][1]=new h.jsDate(y[r][1]).getTime();z[r][1]=new h.jsDate(y[r][1]).getTime();if((y[r][1]!=null&&y[r][1]<C.min)||C.min==null){C.min=y[r][1]}if((y[r][1]!=null&&y[r][1]>C.max)||C.max==null){C.max=y[r][1]}if(r>0){o=Math.abs(y[r][1]-y[r-1][1]);u.intervals.push(o);if(u.frequencies.hasOwnProperty(o)){u.frequencies[o]+=1}else{u.frequencies[o]=1}}}x+=o}if(D.renderer.bands){if(D.renderer.bands.hiData.length){var w=D.renderer.bands.hiData;for(var r=0,q=w.length;r<q;r++){if(this.name==="xaxis"||this.name==="x2axis"){w[r][0]=new h.jsDate(w[r][0]).getTime();if((w[r][0]!=null&&w[r][0]>C.max)||C.max==null){C.max=w[r][0]}}else{w[r][1]=new h.jsDate(w[r][1]).getTime();if((w[r][1]!=null&&w[r][1]>C.max)||C.max==null){C.max=w[r][1]}}}}if(D.renderer.bands.lowData.length){var w=D.renderer.bands.lowData;for(var r=0,q=w.length;r<q;r++){if(this.name==="xaxis"||this.name==="x2axis"){w[r][0]=new h.jsDate(w[r][0]).getTime();if((w[r][0]!=null&&w[r][0]<C.min)||C.min==null){C.min=w[r][0]}}else{w[r][1]=new h.jsDate(w[r][1]).getTime();if((w[r][1]!=null&&w[r][1]<C.min)||C.min==null){C.min=w[r][1]}}}}}var B=0,v=0;for(var p in u.frequencies){u.sortedIntervals.push({interval:p,frequency:u.frequencies[p]})}u.sortedIntervals.sort(function(s,n){return n.frequency-s.frequency});u.min=h.jqplot.arrayMin(u.intervals);u.max=h.jqplot.arrayMax(u.intervals);u.mean=x/y.length;this._intervalStats.push(u);u=x=D=y=A=z=null}C=null};h.jqplot.DateAxisRenderer.prototype.reset=function(){this.min=this._options.min;this.max=this._options.max;this.tickInterval=this._options.tickInterval;this.numberTicks=this._options.numberTicks;this._autoFormatString="";if(this._overrideFormatString&&this.tickOptions&&this.tickOptions.formatString){this.tickOptions.formatString=""}this.daTickInterval=this._daTickInterval};h.jqplot.DateAxisRenderer.prototype.createTicks=function(p){var W=this._ticks;var L=this.ticks;var F=this.name;var H=this._dataBounds;var M=this._intervalStats;var n=(this.name.charAt(0)==="x")?this._plotDimensions.width:this._plotDimensions.height;var w;var ad,J;var y,x;var ac,Z;var s=30;var O=1;var v=this.tickInterval;ad=((this.min!=null)?new h.jsDate(this.min).getTime():H.min);J=((this.max!=null)?new h.jsDate(this.max).getTime():H.max);var A=p.plugins.cursor;if(A&&A._zoom&&A._zoom.zooming){this.min=null;this.max=null}var B=J-ad;if(this.tickOptions==null||!this.tickOptions.formatString){this._overrideFormatString=true}if(L.length){for(Z=0;Z<L.length;Z++){var P=L[Z];var X=new this.tickRenderer(this.tickOptions);if(P.constructor==Array){X.value=new h.jsDate(P[0]).getTime();X.label=P[1];if(!this.showTicks){X.showLabel=false;X.showMark=false}else{if(!this.showTickMarks){X.showMark=false}}X.setTick(X.value,this.name);this._ticks.push(X)}else{X.value=new h.jsDate(P).getTime();if(!this.showTicks){X.showLabel=false;X.showMark=false}else{if(!this.showTickMarks){X.showMark=false}}X.setTick(X.value,this.name);this._ticks.push(X)}}this.numberTicks=L.length;this.min=this._ticks[0].value;this.max=this._ticks[this.numberTicks-1].value;this.daTickInterval=[(this.max-this.min)/(this.numberTicks-1)/1000,"seconds"]}else{if(this.min==null&&this.max==null&&H.min==H.max){var E=h.extend(true,{},this.tickOptions,{name:this.name,value:null});var S=300000;this.min=H.min-S;this.max=H.max+S;this.numberTicks=3;for(var Z=this.min;Z<=this.max;Z+=S){E.value=Z;var X=new this.tickRenderer(E);if(this._overrideFormatString&&this._autoFormatString!=""){X.formatString=this._autoFormatString}X.showLabel=false;X.showMark=false;this._ticks.push(X)}if(this.showTicks){this._ticks[1].showLabel=true}if(this.showTickMarks){this._ticks[1].showTickMarks=true}}else{if(this.min==null&&this.max==null){var N=h.extend(true,{},this.tickOptions,{name:this.name,value:null});var ab,I;if(!this.tickInterval&&!this.numberTicks){var R=Math.max(n,s+1);var Y=115;if(this.tickRenderer===h.jqplot.CanvasAxisTickRenderer&&this.tickOptions.angle){Y=115-40*Math.abs(Math.sin(this.tickOptions.angle/180*Math.PI))}ab=Math.ceil((R-s)/Y+1);I=(J-ad)/(ab-1)}else{if(this.tickInterval){I=this.tickInterval}else{if(this.numberTicks){ab=this.numberTicks;I=(J-ad)/(ab-1)}}}if(I<=19*l){var Q=a(ad,J,I);var r=Q[0];this._autoFormatString=Q[1];ad=Math.floor(ad/r)*r;ad=new h.jsDate(ad);ad=ad.getTime()+ad.getUtcOffset();ab=Math.ceil((J-ad)/r)+1;this.min=ad;this.max=ad+(ab-1)*r;if(this.max<J){this.max+=r;ab+=1}this.tickInterval=r;this.numberTicks=ab;for(var Z=0;Z<ab;Z++){N.value=this.min+Z*r;X=new this.tickRenderer(N);if(this._overrideFormatString&&this._autoFormatString!=""){X.formatString=this._autoFormatString}if(!this.showTicks){X.showLabel=false;X.showMark=false}else{if(!this.showTickMarks){X.showMark=false}}this._ticks.push(X)}O=this.tickInterval}else{if(I<=9*j){this._autoFormatString="%v";var D=Math.round(I/j);if(D<1){D=1}else{if(D>6){D=6}}var U=new h.jsDate(ad).setDate(1).setHours(0,0,0,0);var q=new h.jsDate(J);var z=new h.jsDate(J).setDate(1).setHours(0,0,0,0);if(q.getTime()!==z.getTime()){z=z.add(1,"month")}var T=z.diff(U,"month");ab=Math.ceil(T/D)+1;this.min=U.getTime();this.max=U.clone().add((ab-1)*D,"month").getTime();this.numberTicks=ab;for(var Z=0;Z<ab;Z++){if(Z===0){N.value=U.getTime()}else{N.value=U.add(D,"month").getTime()}X=new this.tickRenderer(N);if(this._overrideFormatString&&this._autoFormatString!=""){X.formatString=this._autoFormatString}if(!this.showTicks){X.showLabel=false;X.showMark=false}else{if(!this.showTickMarks){X.showMark=false}}this._ticks.push(X)}O=D*j}else{this._autoFormatString="%v";var D=Math.round(I/k);if(D<1){D=1}var U=new h.jsDate(ad).setMonth(0,1).setHours(0,0,0,0);var z=new h.jsDate(J).add(1,"year").setMonth(0,1).setHours(0,0,0,0);var K=z.diff(U,"year");ab=Math.ceil(K/D)+1;this.min=U.getTime();this.max=U.clone().add((ab-1)*D,"year").getTime();this.numberTicks=ab;for(var Z=0;Z<ab;Z++){if(Z===0){N.value=U.getTime()}else{N.value=U.add(D,"year").getTime()}X=new this.tickRenderer(N);if(this._overrideFormatString&&this._autoFormatString!=""){X.formatString=this._autoFormatString}if(!this.showTicks){X.showLabel=false;X.showMark=false}else{if(!this.showTickMarks){X.showMark=false}}this._ticks.push(X)}O=D*k}}}else{if(F=="xaxis"||F=="x2axis"){n=this._plotDimensions.width}else{n=this._plotDimensions.height}if(this.min!=null&&this.max!=null&&this.numberTicks!=null){this.tickInterval=null}if(this.tickInterval!=null){if(Number(this.tickInterval)){this.daTickInterval=[Number(this.tickInterval),"seconds"]}else{if(typeof this.tickInterval=="string"){var aa=this.tickInterval.split(" ");if(aa.length==1){this.daTickInterval=[1,aa[0]]}else{if(aa.length==2){this.daTickInterval=[aa[0],aa[1]]}}}}}if(ad==J){var o=24*60*60*500;ad-=o;J+=o}B=J-ad;var G=2+parseInt(Math.max(0,n-100)/100,10);var V,C;V=(this.min!=null)?new h.jsDate(this.min).getTime():ad-B/2*(this.padMin-1);C=(this.max!=null)?new h.jsDate(this.max).getTime():J+B/2*(this.padMax-1);this.min=V;this.max=C;B=this.max-this.min;if(this.numberTicks==null){if(this.daTickInterval!=null){var u=new h.jsDate(this.max).diff(this.min,this.daTickInterval[1],true);this.numberTicks=Math.ceil(u/this.daTickInterval[0])+1;this.max=new h.jsDate(this.min).add((this.numberTicks-1)*this.daTickInterval[0],this.daTickInterval[1]).getTime()}else{if(n>200){this.numberTicks=parseInt(3+(n-200)/100,10)}else{this.numberTicks=2}}}O=B/(this.numberTicks-1)/1000;if(this.daTickInterval==null){this.daTickInterval=[O,"seconds"]}for(var Z=0;Z<this.numberTicks;Z++){var ad=new h.jsDate(this.min);ac=ad.add(Z*this.daTickInterval[0],this.daTickInterval[1]).getTime();var X=new this.tickRenderer(this.tickOptions);if(!this.showTicks){X.showLabel=false;X.showMark=false}else{if(!this.showTickMarks){X.showMark=false}}X.setTick(ac,this.name);this._ticks.push(X)}}}}if(this.tickInset){this.min=this.min-this.tickInset*O;this.max=this.max+this.tickInset*O}if(this._daTickInterval==null){this._daTickInterval=this.daTickInterval}W=null}})(jQuery); \ No newline at end of file