Added D3 Histogram
diff --git a/exp_portal/templates/status.html b/exp_portal/templates/status.html
index 4d66ae0..79546a9 100644
--- a/exp_portal/templates/status.html
+++ b/exp_portal/templates/status.html
@@ -322,22 +322,22 @@
</div>
<div id="metrics" class="expTraySection">
<div id="metricsNav">
- <div id="{{ experimentName }}LoadBtn" class="metricsNavBtn active">Load</div>
- <div id="{{ experimentName }}DifficultyBtn" class="metricsNavBtn">Difficulty</div>
- <div id="{{ experimentName }}PerformanceBtn" class="metricsNavBtn">Performance</div>
- <div id="{{ experimentName }}ConfidenceBtn" class="metricsNavBtn">Confidence</div>
+ <div id="{{ experimentName }}LoadBtn" class="metricsNavBtn active" data-category="Load">Load</div>
+ <div id="{{ experimentName }}DifficultyBtn" class="metricsNavBtn" data-category="Difficulty">Difficulty</div>
+ <div id="{{ experimentName }}PerformanceBtn" class="metricsNavBtn" data-category="Performance">Performance</div>
+ <div id="{{ experimentName }}ConfidenceBtn" class="metricsNavBtn" data-category="Confidence">Confidence</div>
<!-- <div id="{{ experimentName }}ActivityBtn" class="metricsNavBtn">Activity</div> -->
- <div id="{{ experimentName }}TimeBtn" class="metricsNavBtn">Time</div>
+ <div id="{{ experimentName }}TimeBtn" class="metricsNavBtn" data-category="Time">Time</div>
</div>
<div class="metricsBody">
<div class="metricsDropdowns">
<div class="selectTitle">Tool</div>
<select name="tools" id="toolsSelect">
- <!-- <option value="all">All</option> -->
+ <option value="all">All</option>
{% for expObjKey, expObjVal in experimentVal.items %}
{% if 'products' in expObjKey %}
{% for product in expObjVal %}
- <!-- <option value="{{ product.name }}">{{ product.name }}</option> -->
+ <option value="{{ product.name }}">{{ product.name }}</option>
{% endfor %}
{% endif %}
{% endfor %}
@@ -354,24 +354,7 @@
{% endfor %}
</select>
</div>
- <div id="{{ experimentName }}Load" class="metricsSection active">
- <svg id="{{ experimentName }}LoadChart" class="chart"></svg>
- </div>
- <div id="{{ experimentName }}Difficulty" class="metricsSection">
- <svg id="{{ experimentName }}DifficultyChart" class="chart"></svg>
- </div>
- <div id="{{ experimentName }}Performance" class="metricsSection">
- <svg id="{{ experimentName }}PerformanceChart" class="chart"></svg>
- </div>
- <div id="{{ experimentName }}Confidence" class="metricsSection">
- <svg id="{{ experimentName }}ConfidenceChart" class="chart"></svg>
- </div>
- <!-- <div id="activity" class="metricsSection">
- <svg id="{{ experimentName }}ActivityChart" class="chart"></svg>
- </div> -->
- <div id="{{ experimentName }}Time" class="metricsSection">
- <svg id="{{ experimentName }}TimeChart" class="chart"></svg>
- </div>
+ <div id="canvasD3" class="metricsSection active" data-experiment="{{ experimentName }}"></div>
</div>
</div>
</div>
diff --git a/exp_portal/urls.py b/exp_portal/urls.py
index accf510..07ce2df 100644
--- a/exp_portal/urls.py
+++ b/exp_portal/urls.py
@@ -6,6 +6,7 @@
urlpatterns= patterns('',
url(r'^$', views.home_page, name='home'),
url(r'^all_status$', views.view_status, name='view_status'),
+ url(r'^metrics_data$', views.metrics_data, name='metrics_data'),
url(r'^experiments/manage$', views.manage_exps, name='manage_exps'),
url(r'^experiments/details/(?P<exppk>.*)$', views.view_exp_details, name='view_exp_details'),
url(r'^experiment/edit/(?P<exppk>.*)$', views.edit_exp, name='edit_exp'),
diff --git a/exp_portal/views.py b/exp_portal/views.py
index 6222f99..cd970f8 100644
--- a/exp_portal/views.py
+++ b/exp_portal/views.py
@@ -1,6 +1,10 @@
+import json
+import os.path
+
from django.shortcuts import render, redirect
from op_tasks.models import UserProfile, Product, Dataset, OpTask, TaskListItem, Experiment
from django.contrib.auth.decorators import login_required
+from django.views.decorators.csrf import csrf_protect
from django.http import JsonResponse
from users import *
from products import *
@@ -18,6 +22,46 @@
if user_authorized(request):
return render(request, 'experimenthome.html')
+@csrf_protect
+@login_required(login_url='/tasking/login')
+def metrics_data(request):
+ rparams = json.loads(request.body)
+ if request.method == 'POST':
+ experiment = rparams['experiment']
+ category = rparams['category']
+ tool = rparams['tool']
+ task = rparams['task']
+ # load experiment data from file
+ histDataFile = "/home/ubuntu/SCOtCH/"+experiment+".json"
+ histDataAll = []
+ if os.path.isfile(histDataFile):
+ with open(histDataFile) as data_file:
+ histDataAll = json.load(data_file)
+ # filter experiment data
+ histData = []
+ for row in histDataAll:
+ if (tool!="all" and tool!=row['SYS.FIL.APP.']) or (task!="all" and task!=row['SYS.FIL.TSK.']):
+ continue
+ if category=="Load":
+ if row['PST.EXP.CLD.'] != "NA" and row['PST.EXP.CLD.'] != "NaN":
+ histData.append(row['PST.EXP.CLD.'])
+ elif category=="Difficulty":
+ if row['PST.EXP.BED.'] != "NA" and row['PST.EXP.BED.'] != "NaN":
+ histData.append(row['PST.EXP.BED.'])
+ elif category=="Performance":
+ if row['TSK.PRB.ANS.'] != "NA" and row['TSK.PRB.ANS.'] != "NaN":
+ histData.append(row['TSK.PRB.ANS.'])
+ elif category=="Confidence":
+ if row['TSK.CON.'] != "NA" and row['TSK.CON.'] != "NaN":
+ histData.append(row['TSK.CON.'])
+ elif category=="Time":
+ if row['TSK.TIME.DIFF.'] != "NA" and row['TSK.TIME.DIFF.'] != "NaN" and row['TSK.TIME.DIFF.'] > 0 and row['TSK.TIME.DIFF.'] < 3000:
+ histData.append(row['TSK.TIME.DIFF.'])
+
+ return JsonResponse({"data":json.dumps(histData)})
+ else:
+ return JsonResponse({"request": "Not Supported"})
+
@login_required(login_url='/tasking/login')
def view_status(request):
if user_authorized(request):
@@ -196,4 +240,4 @@
experimentList["percentageComplete"] = percentageComplete
masterList[name] = experimentList
response = JsonResponse({'experimentInfo': str(masterList)})
- return response
\ No newline at end of file
+ return response
diff --git a/static/css/styles.css b/static/css/styles.css
index b26a4ed..463b2a0 100644
--- a/static/css/styles.css
+++ b/static/css/styles.css
@@ -1000,6 +1000,21 @@
text-anchor: end;
}
+.bar rect {
+ fill: steelblue;
+ shape-rendering: crispEdges;
+}
+
+.bar text {
+ fill: #fff;
+}
+
+.axis path, .axis line {
+ fill: none;
+ stroke: #000;
+ shape-rendering: crispEdges;
+}
+
.emailRow {
float: left;
margin-bottom: 20px;
diff --git a/static/javascript/base.js b/static/javascript/base.js
index cd91d3f..993add3 100644
--- a/static/javascript/base.js
+++ b/static/javascript/base.js
@@ -1,3 +1,18 @@
+var csrftoken = $.cookie('csrftoken');
+
+function csrfSafeMethod(method) {
+ // these HTTP methods do not require CSRF protection
+ return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+}
+
+$.ajaxSetup({
+ beforeSend: function(xhr, settings) {
+ if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
+ xhr.setRequestHeader("X-CSRFToken", csrftoken);
+ }
+ }
+});
+
$("#contacts li").click(function(){
var emailAddress = $(this).html();
$("#email-to").val(emailAddress);
@@ -44,16 +59,30 @@
var divId = id.slice(0, -3);
$this.find(".expTraySection").removeClass("active");
$this.find("#" + divId).addClass("active");
+
+ if(divId=="metrics") {
+ var experiment = $(this).parents(".experimentStatusRow").attr("id");
+ var category = $this.find(".metricsNavBtn.active").attr("data-category");
+ var mparams = {"experiment":experiment,"tool":$this.find("#toolsSelect").val(),"task":$this.find("#tasksSelect").val(),"category":category};
+ getHistData(experiment, category, JSON.stringify(mparams));
+ }
})
$(".metricsNavBtn").click(function(){
$(this).parent().find(".metricsNavBtn").removeClass("active");
$(this).addClass("active");
- var rowId = $(this).parents(".experimentStatusRow").attr("id");
- $("#" + rowId + " .metricsSection").removeClass("active");
- var id = $(this).attr("id");
- id = id.slice(0, -3);
- $("#" + id).addClass("active");
+ //var rowId = $(this).parents(".experimentStatusRow").attr("id");
+ //$("#" + rowId + " .metricsSection").removeClass("active");
+ //var id = $(this).attr("id");
+ //id = id.slice(0, -3);
+ //$("#" + id).addClass("active");
+
+ var $this = $(this).parents(".expTray");
+ var experiment = $(this).parents(".experimentStatusRow").attr("id");
+ var category = $(this).attr("data-category");
+ var mparams = {"experiment":experiment,"tool":$this.find("#toolsSelect").val(),"task":$this.find("#tasksSelect").val(),"category":category};
+ //console.log(".metricsNavBtn experiment = ", mparams);
+ getHistData(experiment, category, JSON.stringify(mparams));
})
$(".expShelf").click(function(){
@@ -75,15 +104,24 @@
loopCharts(start);
$("#toolsSelect, #tasksSelect").change(function(){
- start = false;
- var id = $(this).parents(".experimentStatusRow").attr("id");
- $(".chart").empty();
- loopCharts(start, id);
+// start = false;
+// var id = $(this).parents(".experimentStatusRow").attr("id");
+// $(".chart").empty();
+// loopCharts(start, id);
+
+ var $this = $(this).parents(".expTray");
+ var experiment = $(this).parents(".experimentStatusRow").attr("id");
+ var category = $this.find(".metricsNavBtn.active").attr("data-category");
+ var mparams = {"experiment":experiment,"tool":$this.find("#toolsSelect").val(),"task":$this.find("#tasksSelect").val(),"category":category};
+ //console.log(".metricsNavBtn experiment = ", mparams);
+ getHistData(experiment, category, JSON.stringify(mparams));
+
});
function loopCharts(start, id) {
if (start==true) {
- startChartBuildwithToolLists();
+ //startChartBuildwithToolLists();
+ buildCharts();
} else if (start==false) {
buildCharts();
}
@@ -239,3 +277,96 @@
}
}
+function getHistData(experimentName, categoryName, params) {
+ var xtick = 1;
+ if(categoryName=="Load") {
+ xtick = 2;
+ } else if(categoryName=="Time") {
+ xtick = 0.01;
+ }
+ $.ajax({
+ 'type': 'POST',
+ 'url': 'metrics_data',
+ 'contentType': 'application/json',
+ 'data': params,
+ 'dataType': 'json',
+ 'complete': function(xhrObj, msg){
+ //console.log(xhrObj);
+ var values = JSON.parse(xhrObj.responseJSON.data);
+ //console.log(values);
+ if(values.length>0) {
+ buildD3Histogram('#canvasD3[data-experiment="'+experimentName+'"]', xtick, values);
+ } else {
+ $('#canvasD3[data-experiment="'+experimentName+'"]').text("Data unavailable.")
+ }
+ }
+ });
+
+}
+
+function buildD3Histogram(canvasSelector, tickScale, histValues) {
+ $(canvasSelector).empty();
+
+ // Generate a Bates distribution of 10 random variables.
+ //var values = d3.range(1000).map(d3.random.bates(10));
+ var values = histValues;
+
+ var minVal = Math.floor(Math.min.apply(Math, values));
+ var maxVal = Math.ceil(Math.max.apply(Math, values));
+
+ // A formatter for counts.
+ var formatCount = d3.format(",.0f");
+
+ var margin = {top: 10, right: 30, bottom: 30, left: 30},
+ //width = 960 - margin.left - margin.right,
+ width = $(canvasSelector).width(),
+ //height = 500 - margin.top - margin.bottom;
+ height = 320;
+
+ var x = d3.scale.linear()
+ .domain([0, maxVal+minVal])
+ .range([0, width]);
+
+ // Generate a histogram using twenty uniformly-spaced bins.
+ var data = d3.layout.histogram()
+ .bins(x.ticks((maxVal+minVal)*tickScale))
+ (values);
+
+ var y = d3.scale.linear()
+ .domain([0, d3.max(data, function(d) { return d.y; })])
+ .range([height, 0]);
+
+ var xAxis = d3.svg.axis()
+ .scale(x)
+ .orient("bottom");
+
+ var svg = d3.select(canvasSelector).append("svg")
+ .attr("width", width + margin.left + margin.right)
+ .attr("height", height + margin.top + margin.bottom)
+ .append("g")
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+ var bar = svg.selectAll(".bar")
+ .data(data)
+ .enter().append("g")
+ .attr("class", "bar")
+ .attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
+
+ bar.append("rect")
+ .attr("x", 1)
+ .attr("width", x(data[0].dx) - 1)
+ .attr("height", function(d) { return height - y(d.y); });
+
+ bar.filter(function(d) { return d.y>0; })
+ .append("text")
+ .attr("dy", ".75em")
+ .attr("y", 6)
+ .attr("x", x(data[0].dx) / 2)
+ .attr("text-anchor", "middle")
+ .text(function(d) { return formatCount(d.y); });
+
+ svg.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + height + ")")
+ .call(xAxis);
+}
diff --git a/static/javascript/lib/jquery.cookie.js b/static/javascript/lib/jquery.cookie.js
new file mode 100644
index 0000000..c7f3a59
--- /dev/null
+++ b/static/javascript/lib/jquery.cookie.js
@@ -0,0 +1,117 @@
+/*!
+ * jQuery Cookie Plugin v1.4.1
+ * https://github.com/carhartl/jquery-cookie
+ *
+ * Copyright 2013 Klaus Hartl
+ * Released under the MIT license
+ */
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // CommonJS
+ factory(require('jquery'));
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+}(function ($) {
+
+ var pluses = /\+/g;
+
+ function encode(s) {
+ return config.raw ? s : encodeURIComponent(s);
+ }
+
+ function decode(s) {
+ return config.raw ? s : decodeURIComponent(s);
+ }
+
+ function stringifyCookieValue(value) {
+ return encode(config.json ? JSON.stringify(value) : String(value));
+ }
+
+ function parseCookieValue(s) {
+ if (s.indexOf('"') === 0) {
+ // This is a quoted cookie as according to RFC2068, unescape...
+ s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
+ }
+
+ try {
+ // Replace server-side written pluses with spaces.
+ // If we can't decode the cookie, ignore it, it's unusable.
+ // If we can't parse the cookie, ignore it, it's unusable.
+ s = decodeURIComponent(s.replace(pluses, ' '));
+ return config.json ? JSON.parse(s) : s;
+ } catch(e) {}
+ }
+
+ function read(s, converter) {
+ var value = config.raw ? s : parseCookieValue(s);
+ return $.isFunction(converter) ? converter(value) : value;
+ }
+
+ var config = $.cookie = function (key, value, options) {
+
+ // Write
+
+ if (value !== undefined && !$.isFunction(value)) {
+ options = $.extend({}, config.defaults, options);
+
+ if (typeof options.expires === 'number') {
+ var days = options.expires, t = options.expires = new Date();
+ t.setTime(+t + days * 864e+5);
+ }
+
+ return (document.cookie = [
+ encode(key), '=', stringifyCookieValue(value),
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+ options.path ? '; path=' + options.path : '',
+ options.domain ? '; domain=' + options.domain : '',
+ options.secure ? '; secure' : ''
+ ].join(''));
+ }
+
+ // Read
+
+ var result = key ? undefined : {};
+
+ // To prevent the for loop in the first place assign an empty array
+ // in case there are no cookies at all. Also prevents odd result when
+ // calling $.cookie().
+ var cookies = document.cookie ? document.cookie.split('; ') : [];
+
+ for (var i = 0, l = cookies.length; i < l; i++) {
+ var parts = cookies[i].split('=');
+ var name = decode(parts.shift());
+ var cookie = parts.join('=');
+
+ if (key && key === name) {
+ // If second argument (value) is a function it's a converter...
+ result = read(cookie, value);
+ break;
+ }
+
+ // Prevent storing a cookie that we couldn't decode.
+ if (!key && (cookie = read(cookie)) !== undefined) {
+ result[name] = cookie;
+ }
+ }
+
+ return result;
+ };
+
+ config.defaults = {};
+
+ $.removeCookie = function (key, options) {
+ if ($.cookie(key) === undefined) {
+ return false;
+ }
+
+ // Must not alter options, thus extending a fresh object...
+ $.cookie(key, '', $.extend({}, options, { expires: -1 }));
+ return !$.cookie(key);
+ };
+
+}));
diff --git a/xdata/templates/base.html b/xdata/templates/base.html
index 8e97de2..2bd7b29 100755
--- a/xdata/templates/base.html
+++ b/xdata/templates/base.html
@@ -19,6 +19,7 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.6.0/moment.min.js"></script>
<!-- // <script src="{% static "bootstrap/js/bootstrap.min.js" %}"></script> -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
+ <script type="text/javascript" src="{% static 'javascript/lib/jquery.cookie.js' %}"></script>
<script type="text/javascript" src="{% static 'javascript/lib/d3.min.js' %}"></script>
<script type="text/javascript" src="{% static 'javascript/base.js' %}"></script>
<script type="text/javascript" src="{% static 'javascript/userale.js' %}"></script>