Skip to content
This repository has been archived by the owner on Apr 11, 2023. It is now read-only.

Commit

Permalink
First pass at graphing for squabble.
Browse files Browse the repository at this point in the history
  • Loading branch information
markmandel committed Nov 3, 2011
1 parent c147ec0 commit 256a1bc
Show file tree
Hide file tree
Showing 347 changed files with 62,379 additions and 10 deletions.
44 changes: 37 additions & 7 deletions squabble/SquabbleGateway.cfc
Expand Up @@ -295,19 +295,31 @@

<cffunction name="getCombinationTotalVisitors" hint="Returns a query of visitor totals per combination" access="public" returntype="query" output="false">
<cfargument name="testName" type="string" required="true" hint="The test name to return data for">
<cfargument name="unitGrouping" type="string" required="false" hint="Optional grouping for data by either 'hour' or 'minute'">

<cfset var getCombinationTotalVisitorsQuery = "">

<cfquery name="getCombinationTotalVisitorsQuery">
SELECT
count(combinations.id) as total_visitors,
combinations.combination,
combinations.most_recent_visit AS most_recent_visit
count(combinations.id) as total_visitors
,combinations.combination
<cfif StructKeyExists(arguments, "unitGrouping")>
,date(combinations.visit_date) as date
,#arguments.unitGrouping#(combinations.visit_date) as unit
<cfelse>
,combinations.most_recent_visit AS most_recent_visit
</cfif>
FROM
(
SELECT
squabble_visitors.id,
GROUP_CONCAT(squabble_combinations.variation_name ORDER BY squabble_combinations.section_name) AS combination,
MAX(squabble_visitors.visit_date) AS most_recent_visit
GROUP_CONCAT(squabble_combinations.variation_name ORDER BY squabble_combinations.section_name) AS combination
<cfif StructKeyExists(arguments, "unitGrouping")>
,squabble_visitors.visit_date
<cfelse>
<!--- Question: How come the MAX()? --->
,MAX(squabble_visitors.visit_date) AS most_recent_visit
</cfif>
FROM
squabble_visitors
INNER JOIN
Expand All @@ -318,14 +330,21 @@
GROUP BY squabble_visitors.id
) combinations
GROUP BY combination
<cfif StructKeyExists(arguments, "unitGrouping")>
,date, unit

ORDER BY
combination, date, unit
</cfif>
</cfquery>

<cfreturn getCombinationTotalVisitorsQuery>
</cffunction>


<cffunction name="getCombinationTotalConversions" hint="Returns a query of total conversions and value per combination" access="public" returntype="query" output="false">
<cfargument name="testName" type="string" required="true" hint="The test name to return data for">
<cfargument name="unitGrouping" type="string" required="false" hint="Optional grouping for data by either 'hour' or 'minute'">

<cfset var getCombinationTotalConversionsQuery = "">

<cfquery name="getCombinationTotalConversionsQuery">
Expand All @@ -334,6 +353,10 @@
SUM(squabble_conversions.conversion_value) AS total_value,
SUM(squabble_conversions.conversion_units) AS total_units,
combinations.combination
<cfif StructKeyExists(arguments, "unitGrouping")>
,date(squabble_conversions.conversion_date) as date
,#arguments.unitGrouping#(squabble_conversions.conversion_date) as unit
</cfif>
FROM
(
SELECT
Expand All @@ -354,6 +377,13 @@
ON squabble_conversions.visitor_id = combinations.id

GROUP BY combination
<cfif StructKeyExists(arguments, "unitGrouping")>
,date, unit

ORDER BY
combination, date, unit

</cfif>
</cfquery>

<cfreturn getCombinationTotalConversionsQuery>
Expand All @@ -362,6 +392,7 @@

<cffunction name="getGoalTotalConversions" hint="Returns a query of total conversions and value per combination and goal" access="public" returntype="query" output="false">
<cfargument name="testName" type="string" required="true" hint="The test name to return data for">

<cfset var getGoalTotalConversionsQuery = "">

<cfquery name="getGoalTotalConversionsQuery">
Expand Down Expand Up @@ -397,7 +428,6 @@
<cfreturn getGoalTotalConversionsQuery>
</cffunction>


<!------------------------------------------- PACKAGE ------------------------------------------->

<!------------------------------------------- PRIVATE ------------------------------------------->
Expand Down
225 changes: 222 additions & 3 deletions squabble/reports/index.cfm
Expand Up @@ -36,11 +36,24 @@
<head>
<meta charset="utf-8" />
<title>Squabble Simple Report</title>

<!--[if lt IE 9]><script language="javascript" type="text/javascript" src="./js/jqPlot/excanvas.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="./js/jqPlot/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="./js/jqPlot/jquery.jqplot.min.js"></script>
<script type="text/javascript" language="javascript" src="./js/jqPlot/plugins/jqplot.dateAxisRenderer.js"></script>
<script type="text/javascript" language="javascript" src="./js/jqPlot/plugins/jqplot.canvasTextRenderer.js"></script>
<script type="text/javascript" language="javascript" src="./js/jqPlot/plugins/jqplot.canvasAxisLabelRenderer.js"></script>
<script language="javascript" type="text/javascript" src="./js/jqPlot/plugins/jqplot.cursor.min.js"></script>
<script language="javascript" type="text/javascript" src="./js/jqPlot/plugins/jqplot.highlighter.min.js"></script>
<script language="javascript" type="text/javascript" src="./js/jqPlot/plugins/jqplot.enhancedLegendRenderer.min.js"></script>
<link rel="stylesheet" type="text/css" href="./js/jqPlot/jquery.jqplot.css" />

<style type="text/css">
* { margin: 0; padding: 0; font-family: inherit; }
html { height: 100%; width: 100%; }
body { margin: 20px; font-family: Ubuntu, Arial, Helvetica; font-size: 9pt; }
h2 { margin-bottom: 5px; }
h3 { margin-top: 10px; clear: both; }
#testData { margin-top: 20px; }
#testData table { margin-top: 15px; border: solid 1px #ccc; }
Expand All @@ -57,6 +70,11 @@
.combination-name:hover { text-decoration: none; }
.hint { color: grey; font-style: italic; }
.old { color: grey; }
#conversions, #value, #units {
height: 600px;
width: 100%;
}
</style>

<script type="text/javascript">
Expand Down Expand Up @@ -117,7 +135,7 @@
sections = application.squabble.getGateway().getTestSections(form.testName);
sectionCount = listLen(sections);
</cfscript>


<div id="testData">
<cfoutput>
<h2>#form.testName#</h2>
Expand Down Expand Up @@ -236,10 +254,13 @@
<cfoutput><cfset totalGoals++></cfoutput>

<cfoutput>
<cfquery name="comboVisitors" dbtype="query">
SELECT total_visitors, most_recent_visit FROM combinationTotalVisitors WHERE combination = <cfqueryparam cfsqltype="cf_sql_varchar" value="#combination#">;
</cfquery>
<cfscript>
goalCount++;
combinationVisitors = combinationTotalVisitors.total_visitors[combinationCount];
combinationLastVisit = combinationTotalVisitors.most_recent_visit[combinationCount];
combinationVisitors = comboVisitors.total_visitors;
combinationLastVisit = comboVisitors.most_recent_visit;
combinationConversions = combinationTotalConversions.total_conversions[combinationCount];
combinationConversionTotal = combinationTotalConversions.total_value[combinationCount];
combinationUnitsTotal = combinationTotalConversions.total_units[combinationCount];
Expand Down Expand Up @@ -316,6 +337,204 @@
</table>
</cfif>
</div>
<cfscript>
visitors = application.squabble.getGateway().getCombinationTotalVisitors(form.testName, "hour");
conversions = application.squabble.getGateway().getCombinationTotalConversions(form.testName, "hour");
data = createObject("java", "java.util.LinkedHashMap").init();
</cfscript>

<cfoutput query="visitors" group="combination">
<cfscript>
//keep the order
data[combination] = createObject("java", "java.util.LinkedHashMap").init();
</cfscript>

<cfoutput>
<cfscript>
key = dateformat(visitors.date, "yyyymmdd") & " " & visitors.unit & ":00";
item = { conversions = 0, units = 0, value = 0 };
item.hour = visitors.unit;
item.date = visitors.date;
item.visitors = visitors.total_visitors;
data[combination][key] = item;
</cfscript>
</cfoutput>
</cfoutput>

<cfoutput query="conversions" group="combination">
<cfoutput>
<cfscript>
key = dateformat(conversions.date, "yyyymmdd") & " " & conversions.unit & ":00";
item = structKeyExists(data[combination], key) ? data[combination][key] : { visitors = 0 };
item.hour = conversions.unit;
item.date = conversions.date;
item.conversions = conversions.total_conversions;
item.units = conversions.total_units;
item.value = conversions.total_value;
data[combination][key] = item;
</cfscript>
</cfoutput>
</cfoutput>

<!--- now we process it, and calculate totals --->
<cfscript>
for(combination in data)
{
combo = data[combination];
visitorTotal = 0;
conversionTotal = 0;
valueTotal = 0;
unitTotal = 0;
for(date in combo)
{
item = combo[date];
visitorTotal += item.visitors;
conversionTotal += item.conversions;
valueTotal += val(item.value);
unitTotal = val(item.units);
//take a snapshot at now.
item.visitorTotal = visitorTotal;
item.conversionTotal = conversionTotal;
item.valueTotal = valueTotal;
item.unitTotal = unitTotal;
if(visitorTotal == 0)
{
item.conversionRate = 0;
}
else
{
item.conversionRate = (conversionTotal/visitorTotal) * 100;
}
}
}
</cfscript>

<cfoutput>
<script type="text/javascript">
$(document).ready(function(){
$.jqplot.config.enablePlugins = true;
var options =
{
legend:
{
renderer: $.jqplot.EnhancedLegendRenderer,
show:true,
rendererOptions:{
numberRows: 1
},
placement: 'outsideGrid',
location: 's'
,labels:
[
<cfset i = 1>
<cfloop collection="#data#" item="combination">
<cfif i++ neq 1>,</cfif>'#JSStringFormat(combination)#'
</cfloop>
]
}
,axesDefaults:
{
autoscale: true
}
,axes:
{
xaxis:
{
label: "Date, Hourly Breakdown"
,renderer:$.jqplot.DateAxisRenderer
,tickOptions:{formatString:'%d %b %H:00'}
}
,yaxis:
{
label: "Conversion Percentage"
,labelRenderer: $.jqplot.CanvasAxisLabelRenderer
,labelOptions:
{
angle: 270
}
,min: 0
}
}
,cursor:{zoom:true}
,highlighter:{show:true}
};
<cfset counter = 1>
<cfset vars = "">
<cfloop collection="#data#" item="combination">
<cfset vars =ListAppend(vars, "series#counter#")>
<cfset combo = data[combination]>
series#counter++# =
[
<cfset i = 1>
<cfloop collection="#combo#" item="date"><cfset item = combo[date]>
<cfif i++ neq 1>,</cfif>["#DateFormat(item.date, 'dd mmm yyyy')# #item.hour#:00", #item.conversionRate#]
</cfloop>
];
</cfloop>
conv = $.jqplot('conversions', [#vars#], options);
<cfset vars = "">
<cfloop collection="#data#" item="combination">
<cfset vars =ListAppend(vars, "series#counter#")>
<cfset combo = data[combination]>
series#counter++# =
[
<cfset i = 1>
<cfloop collection="#combo#" item="date"><cfset item = combo[date]>
<cfif i++ neq 1>,</cfif>["#DateFormat(item.date, 'dd mmm yyyy')# #item.hour#:00", #item.valueTotal#]
</cfloop>
];
</cfloop>
options.axes.yaxis.label = "Total Value";
values = $.jqplot('value', [#vars#], options);
<cfset vars = "">
<cfloop collection="#data#" item="combination">
<cfset vars =ListAppend(vars, "series#counter#")>
<cfset combo = data[combination]>
series#counter++# =
[
<cfset i = 1>
<cfloop collection="#combo#" item="date"><cfset item = combo[date]>
<cfif i++ neq 1>,</cfif>["#DateFormat(item.date, 'dd mmm yyyy')# #item.hour#:00", #item.unitTotal#]
</cfloop>
];
</cfloop>
options.axes.yaxis.label = "Total Units";
units = $.jqplot('units', [#vars#], options);
});
</script>
</cfoutput>

<h3>Conversion Rate Per Hour</h3>
<div id="conversions" ></div>
<div><button value="reset" type="button" onclick="conv.resetZoom();">Zoom Out</button></div>

<h3>Total Value Per Hour</h3>

<div id="value" ></div>
<div><button value="reset" type="button" onclick="values.resetZoom();">Zoom Out</button></div>

<h3>Total Units Per Hour</h3>

<div id="units" ></div>
<div><button value="reset" type="button" onclick="units.resetZoom();">Zoom Out</button></div>

<cfelseif structKeyExists(form, "fieldnames")>
<br /><br />No Test Data Found
</cfif>
Expand Down
21 changes: 21 additions & 0 deletions squabble/reports/js/jqPlot/MIT-LICENSE.txt
@@ -0,0 +1,21 @@
Title: MIT License

Copyright (c) 2009-2011 Chris Leonello

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

0 comments on commit 256a1bc

Please sign in to comment.