http://git-wip-us.apache.org/repos/asf/incubator-senssoft/blob/6b90be61/images/incubator-logo.png ---------------------------------------------------------------------- diff --git a/images/incubator-logo.png b/images/incubator-logo.png new file mode 100644 index 0000000..c04e70d Binary files /dev/null and b/images/incubator-logo.png differ
http://git-wip-us.apache.org/repos/asf/incubator-senssoft/blob/6b90be61/images/stout.png ---------------------------------------------------------------------- diff --git a/images/stout.png b/images/stout.png new file mode 100644 index 0000000..29ec1de Binary files /dev/null and b/images/stout.png differ http://git-wip-us.apache.org/repos/asf/incubator-senssoft/blob/6b90be61/images/stout_inverted.png ---------------------------------------------------------------------- diff --git a/images/stout_inverted.png b/images/stout_inverted.png new file mode 100644 index 0000000..597c819 Binary files /dev/null and b/images/stout_inverted.png differ http://git-wip-us.apache.org/repos/asf/incubator-senssoft/blob/6b90be61/images/system.png ---------------------------------------------------------------------- diff --git a/images/system.png b/images/system.png new file mode 100644 index 0000000..6849d34 Binary files /dev/null and b/images/system.png differ http://git-wip-us.apache.org/repos/asf/incubator-senssoft/blob/6b90be61/images/tap.png ---------------------------------------------------------------------- diff --git a/images/tap.png b/images/tap.png new file mode 100644 index 0000000..82b4991 Binary files /dev/null and b/images/tap.png differ http://git-wip-us.apache.org/repos/asf/incubator-senssoft/blob/6b90be61/images/tap_inverted.png ---------------------------------------------------------------------- diff --git a/images/tap_inverted.png b/images/tap_inverted.png new file mode 100644 index 0000000..dd1b363 Binary files /dev/null and b/images/tap_inverted.png differ http://git-wip-us.apache.org/repos/asf/incubator-senssoft/blob/6b90be61/images/userale.png ---------------------------------------------------------------------- diff --git a/images/userale.png b/images/userale.png new file mode 100644 index 0000000..d3267e5 Binary files /dev/null and b/images/userale.png differ http://git-wip-us.apache.org/repos/asf/incubator-senssoft/blob/6b90be61/images/userale_inverted.png ---------------------------------------------------------------------- diff --git a/images/userale_inverted.png b/images/userale_inverted.png new file mode 100644 index 0000000..c873912 Binary files /dev/null and b/images/userale_inverted.png differ http://git-wip-us.apache.org/repos/asf/incubator-senssoft/blob/6b90be61/index.html ---------------------------------------------------------------------- diff --git a/index.html b/index.html new file mode 100644 index 0000000..cfb9152 --- /dev/null +++ b/index.html @@ -0,0 +1,103 @@ +--- +layout: default +--- + +<div id="main-masthead" class="ui masthead padded center aligned inverted vertical segment"> + + <h1 class="ui header">Apache SensSoft</h1> + <h2 class="ui header">Instrument Software. Understand Humans.</h2> + +</div> + +<div class="ui very padded vertical segment"> + <div class="ui center aligned text container"> + <p> + Apache Software as a Sensor⢠(SensSoft) incorporates a number of components that work together to provide user-experience researchers, project managers and software developers insights about how web applications are used. Each of the components of Apache SensSoft is made available through its own repository, allowing its distinctive capabilities to grow organically. A simplified system architecture ensures that the components will continue to share data and work together efficiently throughout their evolution. + </p> + + <br> + + <a class="ui big red button" href="{{ '/system' | prepend: site.baseurl }}"> + Learn More + </a> + <a class="ui big blue button" href="{{ '/releases' | prepend: site.baseurl }}"> + Download SensSoft + </a> + <a class="ui big yellow button" href="mailto:users-subscr...@senssoft.incubator.apache.org"> + Sign Up For News + </a> + </div> +</div> + +<div class="ui inverted grey very padded vertical segment"> + <div class="ui text container"> + <div class="ui two column middle aligned grid"> + <div class="column"> + With a single line of code, capture all of your usersâ behavior on your web page with enough resolution and granularity for advanced behavioral modeling. Apache UserALE is a free, open-source application for standard user event tracking, but is specially tailored for capturing behavior in web applications build for productivityâdata analysis, logistics, system monitoring, and service provisioning. + </div> + <div class="center aligned column"> + <a href="{{ '/userale' | prepend: site.baseurl }}"> + <img class="component-image" src="/images/userale_inverted.png"> + </a> + </div> + </div> + </div> +</div> + +{% include useralejsDemo.html inverted=false %} + +<div class="ui inverted grey very padded vertical segment"> + <div class="ui text container"> + <div class="ui two column middle aligned grid"> + <div class="center aligned column"> + <a href="{{ '/distill' | prepend: site.baseurl }}"> + <img class="component-image" src="/images/distill_inverted.png"> + </a> + </div> + <div class="column"> + Process and control your own user event data. Apache Distill is a scalable, customizable analytics stack built in Python that allows you to segment your user data and generate both simple usage statistics on different features of your application and complex graph models of user workflow. Distill provides an easy access point into databases storing your user event data and packages data for discovery through visual analytics. + </div> + </div> + </div> +</div> + +<div class="ui very padded vertical segment"> + <div class="ui text container"> + <div class="ui two column middle aligned grid"> + <div class="column"> + Explore your usersâ behavior and discover trends through visual analytics. Apache TAP is a customizable visual analytics application that allows you to see trends in usage across users of different types, usage trends across different versions of your application, and rich visualizations of usersâ workflow. Tailor visual analytics to your needs customizing TAP with your favorite D3 visualizations. + </div> + <div class="center aligned column"> + <a href="{{ '/tap' | prepend: site.baseurl }}"> + <img class="component-image" src="/images/tap.png"> + </a> + </div> + </div> + </div> +</div> + +{% include tapDemo.html inverted=true %} + +<div class="ui very padded vertical segment"> + <div class="ui text container"> + <div class="ui two column middle aligned grid"> + <div class="center aligned column"> + <a href="{{ '/stout' | prepend: site.baseurl }}"> + <img class="component-image" src="/images/stout.png"> + </a> + </div> + <div class="column"> + Stage systematic user research on the web. Apache STOUT provides everyone the ability to perform user research on the web. Track research participants through different tasks with your web applications, or track them through different versions of the same web application. Apache STOUT allows you to manage user research participants and data collected through Apache UserALE and 3rd party form data services. + </div> + </div> + </div> +</div> + +<div class="ui inverted grey very padded vertical segment"> + <div class="ui text container"> + <h2 class="ui inverted header">About the Project</h2> + <p> + Apache SensSoft is a user activity logging and analytics system that enables developers to instrument and extract design and user insights from their applications. It was originally developed at Draper for DARPA's XDATA program as a means of evaluating and improving tools for big data analysis and exploration. Commercially available as Draper's Software as a Sensorâ¢, Apache SensSoft aims to provide a more complete and customizable user analytics platform. + </p> + </div> +</div> http://git-wip-us.apache.org/repos/asf/incubator-senssoft/blob/6b90be61/js/bowiePlot.js ---------------------------------------------------------------------- diff --git a/js/bowiePlot.js b/js/bowiePlot.js new file mode 100644 index 0000000..b36d740 --- /dev/null +++ b/js/bowiePlot.js @@ -0,0 +1,408 @@ +var Bowie = (function () { + // Set up module variables + var element = '#bowie'; + var data; + var metric; + + + // Grab data for this static case + d3.json('/js/graphData.json', function (error, response) { + if (error) throw error; + data = response; + + if (typeof metric !== 'undefined') { + update(metric); + } + }); + + + // Set up D3 variables + var svg; + var tooltip; + var arcs; + var chords; + var circles; + var margin = { + top : 20, + right : 20, + bottom : 20, + left : 20, + }; + var fullWidth = 600; + var fullHeight = 400; + var width = fullWidth - margin.left - margin.right; + var height = fullHeight - margin.top - margin.bottom; + var mainRadius = 280; + + var color = d3.scaleOrdinal() + .range([ + '#E24614', + '#DBA915', + '#BFD02C', + '#38A6D8', + '#852EB7' + ]); + + var arc = d3.arc() + .innerRadius(mainRadius - 50) + .outerRadius(mainRadius); + + var ribbon = d3.ribbon(); + + var graphLayout = graphFlow() + .radius(mainRadius - 50) + .innerRadius(mainRadius - 150); + + + // Define bowie layout function + function graphFlow() { + var tau = Math.PI * 2; + + var padAngle = 0; + var spaceAngle = tau / 4; + var radius = 0; + var innerRadius = 0; + + function layout(data) { + var result = {}; + + result.in = arrayToObj(data.in); + result.out = arrayToObj(data.out); + result.blt = arrayToObj(circleLayout(data.blt, innerRadius)); + + var arcAngle = (tau - (spaceAngle * 2)) / 2; + var inStart = (tau + spaceAngle) / 2; + var outStart = spaceAngle / 2; + + var inSide = sideLayout(data.inMatrix, result.blt, inStart, arcAngle, padAngle, radius, 'in'); + var outSide = sideLayout(data.outMatrix, result.blt, outStart, arcAngle, padAngle, radius, 'out'); + + result.inArcs = inSide[0]; + result.inChords = inSide[1]; + result.outArcs = outSide[0]; + result.outChords = outSide[1]; + + return result; + } + + layout.padAngle = function (value) { + return value ? (padAngle = value, layout) : padAngle; + }; + + layout.spaceAngle = function (value) { + return value ? (spaceAngle = value, layout) : spaceAngle; + }; + + layout.radius = function (value) { + return value ? (radius = value, layout) : radius; + }; + + layout.innerRadius = function (value) { + return value ? (innerRadius = value, layout) : innerRadius; + }; + + return layout; + } + + function sideLayout(matrix, circles, startAngle, angle, padAngle, radius, type) { + var n = matrix.length; + var m = matrix[0].length; + var groupSums = []; + var total = 0; + var arcs = new Array(n); + var chordTemp = new Array(n * m); + var chords = []; + var k; + var dx; + var x; + var x0; + var i; + var j; + + matrix.forEach(function (group) { + groupSums.push(group.reduce(function (prev, curr) { return prev + curr; })); + }); + + total = groupSums.reduce(function (prev, curr) { return prev + curr; }); + + k = Math.max(0, angle - padAngle * n) / total; + dx = k ? padAngle : angle / n; + + x = startAngle; + i = -1; + + while(++i < n) { + x0 = x; + j = -1; + + while(++j < n) { + var v = matrix[i][j]; + var a0 = x; + var a1 = x += v * k; + + chordTemp[j + (n * i)] = { + index : i, + subindex : j, + startAngle : a0, + endAngle : a1, + value : v, + }; + } + + arcs[i] = { + index : i, + type : type, + startAngle : x0, + endAngle : x, + value : groupSums[i], + }; + + x += dx; + } + + chordTemp.forEach(function (chord) { + if (chord.value > 0) { + var circle = circles[chord.subindex]; + + chords.push({ + index : chord.index, + subindex : chord.subindex, + type : type, + source : { + startAngle : chord.startAngle, + endAngle : chord.endAngle, + radius : radius, + }, + target : { + startAngle : circle.theta - 0.001, + endAngle : circle.theta + 0.001, + radius : circle.radius, + }, + }); + } + }); + + return [arcs, chords]; + } + + function circleLayout(circles, innerRadius) { + circles.forEach(function (d) { + d.r = d.value; + }); + + d3.packSiblings(circles); + var enclose = d3.packEnclose(circles); + var k = innerRadius / enclose.r; + + circles.forEach(function (d) { + d.r = d.r * k; + d.x = d.x * k; + d.y = d.y * k; + + var rSq = Math.pow(d.x, 2) + Math.pow(d.y, 2); + d.radius = Math.sqrt(rSq); + d.theta = Math.atan2(d.y, d.x) + (Math.PI / 2); + }); + + return circles; + } + + function arrayToObj(a) { + var o = {}; + + a.forEach(function (d) { + o[d.index] = d; + }); + + return o; + } + + // Initial full build of bowie + function create() { + svg = d3.select(element).append('svg') + .attr('width', fullWidth) + .attr('height', fullHeight) + .append('g') + .attr('transform', 'translate(' + ((width / 2) + margin.left) + ',' + ((height / 2) + margin.top) + ')'); + + tooltip = d3.select('body').append('div') + .attr('class', 'tooltip') + .style('opacity', 0); + } + + + // Actually render bowie + function update(m) { + metric = m; + if (typeof data === 'undefined') { + return false; + } + + var currentData = data[metric]; + var layout = graphLayout(currentData); + + var t = d3.transition() + .duration(500); + + arcs = svg.selectAll('.arc') + .data(layout.inArcs.concat(layout.outArcs), function (d) { + return d.type + d.index; + }); + + arcs.exit() + .attr('class', 'exit') + .transition(t) + .style('fill-opacity', 0) + .remove(); + + arcs = arcs.enter() + .append('path') + .attr('class', 'arc') + .merge(arcs); + + arcs + .on('mouseover', function (d) { + highlight(d, 'arc'); + showTooltip(currentData.in[d.index], d3.event.pageX, d3.event.pageY); + }) + .on('mouseout', function (d) { + restore(); + hideTooltip(); + }) + .transition(t) + // TODO: add arc tweens + .attr('d', arc) + .style('fill', function (d) { return color(currentData.in[d.index].elementGroup); }); + + chords = svg.selectAll('.chord') + .data(layout.inChords.concat(layout.outChords), function (d) { + return d.index + d.type + d.subindex; + }); + + chords.exit() + .attr('class', 'exit') + .transition(t) + .style('fill-opacity', 0) + .remove(); + + chords = chords.enter() + .append('path') + .attr('class', 'chord') + .style('fill', '#B0B9BE') + .merge(chords); + + chords + .transition(t) + .attr('d', ribbon) + .style('fill-opacity', 0.5); + + circles = svg.selectAll('.node') + .data($.map(layout.blt, function (val) { return val; }), function (d) { return d.index; }); + + circles.exit() + .attr('class', 'exit') + .transition(t) + .attr('r', 0) + .remove(); + + circles = circles.enter() + .append('circle') + .attr('class', 'node') + .merge(circles); + + circles + .on('mouseover', function (d) { + highlight(d, 'circle'); + showTooltip(currentData.in[d.index], d3.event.pageX, d3.event.pageY); + }) + .on('mouseout', function (d) { + restore(); + hideTooltip(); + }) + .transition(t) + .attr('r', function (d) { return d.r; }) + .attr('cx', function (d) { return d.x; }) + .attr('cy', function (d) { return d.y; }) + .style('fill', function (d) { + return color(currentData.in[d.index].elementGroup) + }) + .style('fill-opacity', 0.75); + } + + + // Helper functions for mouse behaviors + function hideTooltip() { + tooltip.transition() + .duration(350) + .style('opacity', 0); + } + + function showTooltip(activity, x, y) { + tooltip.transition() + .duration(350) + .style('opacity', 0.9); + + tooltip + .style('left', (x + 6) + 'px') + .style('top', (y - 28) + 'px') + .html('Action: ' + activity.action + '<br>Id: ' + activity.elementId + '<br>Group: ' + activity.elementGroup); + } + + function highlight(d, type) { + var indices = []; + + if (type === 'arc') { + chords + .style('fill-opacity', function (c) { + if (c.index !== d.index || c.type !== d.type) { + return 0.1; + } else { + indices.push(c.subindex); + return 0.5; + } + }); + + circles + .style('fill-opacity', function (c) { + return indices.includes(c.index) ? 0.75 : 0.1; + }); + + arcs + .style('fill-opacity', function (c) { + return c === d ? 1 : 0.25; + }); + } else if (type === 'circle') { + chords + .style('fill-opacity', function (c) { + if (c.subindex !== d.index) { + return 0.1; + } else { + indices.push(c.index); + return 0.5; + } + }); + + circles + .style('fill-opacity', function (c) { + return c === d ? 0.75 : 0.25; + }); + + arcs + .style('fill-opacity', function (c) { + return indices.includes(c.index) ? 1 : 0.1; + }); + } + } + + function restore() { + chords.style('fill-opacity', 0.5); + circles.style('fill-opacity', 0.75); + arcs.style('fill-opacity', 1); + } + + // Return API + return { + create: create, + update: update + }; +})();