Giter Club home page Giter Club logo

xapi-dashboard's Introduction

xAPI-Dashboard

This project attempts to simplify the process of extracting meaningful aggregate data from Experience API statements. It is composed of two parts. The first part, the Collection class, allows developers to run SQL-like queries, filters, and aggregations over xAPI data. The second part, the XAPIDashboard class, can generate numerous types of charts and visualizations based on that aggregated xAPI data.

If you'd rather learn by reference, we have a set of live demos at this repo's Github.io page.

Installation

Either clone:

git clone https://github.com/adlnet/xAPI-Dashboard

or

Download directly

Making Your First Chart

Generating your first chart is easy. First, include the libraries:

<link rel="stylesheet" href="lib/nv.d3.css"></link>
<script type="text/javascript" src="dist/xapidashboard.min.js"></script>
<script type="text/javascript" src="dist/xapicollection.min.js"></script>

Next, you should fetch your data from an LRS. You can either retrieve them yourself, or use the convenience function provided by the dashboard object:

var wrapper = ADL.XAPIWrapper;
wrapper.changeConfig({"endpoint" : 'https://lrs.adlnet.gov/xAPI/'});
var dash = new ADL.XAPIDashboard();

window.onload = function(){
	// get all statements made in the last two weeks
	var query = {'since': new Date(Date.now() - 1000*60*60*24*30).toISOString()};
	dash.fetchAllStatements(query, fetchDoneCallback);
};

Now that your data is loaded, the real magic happens:

function fetchDoneCallback(){
	var chart = dash.createBarChart({
		container: '#graphContainer svg',
		groupBy: 'verb.id',
		aggregate: ADL.count(),
		customize: function(chart){
			chart.xAxis.rotateLabels(45);
			chart.xAxis.tickFormat(function(d){ return /[^\/]+$/.exec(d)[0]; });
		}
	});
	chart.draw();
}

This generates a bar graph (dash.createBarChart), places it in a particular place in the DOM ('#graphContainer svg'), and populates it with your previously fetched data. Each bar corresponds with a unique value of a specified section in the statements (in this example, groupBy: 'verb.id'), and each bar's height is the number of statements with that value (e.g. aggregate: ADL.count()).

An additional customize function is specified to format the graph labels. The customization is all done via the NVD3 chart library. In this case, we tilt the labels 45 degress so they don't overlap, and since we don't want the full verb id URIs, we strip off everything before the last slash.

Finally, once the chart is configured, we call the draw() function to actually render the graph to the document.

After all that effort, the final result was worth it:

Example Bar Chart

It's still not perfect though. It would be nice if the bars were sorted by height. This is simple to do using the provided Collection methods.

Processing the Data

This package includes the ADL.Collection object, a powerful statement processor. It comes in two forms, ADL.CollectionSync and ADL.CollectionAsync. They have the same API, and their usage is simple. Just load your statements into it automatically like we did above:

dash.fetchAllStatements(query, callback);

Or manually:

var ret = ADL.XAPIWrapper.getStatements(...);
var statements = new ADL.Collection(ret.statements);

You can then run filters on the statements to produce useful and interesting summaries. For example, to get the list of activities performed in the set of statements, you could run:

var activities = statements
	// remove statements with duplicate object ids
	.groupBy('object.id')
	// and then pick out the ids
	.select('group');

// run the query, and what do you get?
activities.exec(function(data){
	console.log(data);
});
>>> [{group: 'act_id1'}, {group: 'act_id2'}]

And if you wanted a list of the top 10 highest-scoring actors, you could run:

var actors = statements
	// take all the statements for each actor
	.groupBy('actor.name')
	// get their best score
	.max('result.score.raw')
	// then sort the actors by those scores, high to low
	.orderBy('max', 'descending')
	// pick out only the first (highest) 10 scorers
	.slice(0,10)
	// and discard all the parts of the statements you won't be using
	.select('group, max');

// run the query, and what do you get?
actors.exec(function(data){
	console.log(data);
});
>>> 
[{'group': 'James Bond', 'max': 94},
 {'group': 'Dr. No',     'max': 88},
 ...
]

Putting It All Together

So to finish our chart, we want to sort the bars by height, and for good measure limit the number of bars to 10. We can do all of this by providing a post-format hook to our createBarChart call:

var chart = dash.createBarChart({
	container: '#graphContainer svg',
	groupBy: 'verb.id',
	aggregate: ADL.count(),
	post: function(data){
		data.orderBy('result.count', 'descending')
			.slice(0,10);
	},
	customize: function(chart){
		chart.xAxis.rotateLabels(45);
		chart.xAxis.tickFormat(function(d){ return /\/[^\/]+$/.exec(d)[0]; });
	}
});
chart.draw();

This function performs custom processing on the data before it is presented to the charting software. First we sort the data by the result.count field, then filter the set down to 10 elements.

Throw a header on there, and we get this final result:

Final Bar Chart

Resources

Contributing to the project

We welcome contributions to this project. Fork this repository, make changes, and submit pull requests. If you're not comfortable with editing the code, please submit an issue and we'll be happy to address it.

License

Copyright ©2016 Advanced Distributed Learning

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

xapi-dashboard's People

Contributors

creighton avatar mickmuzac avatar pauliejes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xapi-dashboard's Issues

Derivative reporting in ADL dashboard

Hi,
Can anyone help me with derivative information in ADL dashboard. So I want to display a header which will have information about the what chart is displaying. Like in case of pie chart it will have percentage information also

Need to filter by hour

My chart right now currently filters my results into bars according to dates. I was wondering if it were possible to take this even further and filter the bar graph by hour.

this is what I have as of now:

dash.Mia = dash.createBarChart({
container: '#Mia.graph svg',
pre: function(data){return data.where('actor.name = "Mia Wallace" and verb.id = "http://activitystrea.ms/schema/1.0/access"').math('date', '$[timestamp|0,10]')},
groupBy: 'date',
aggregate: ADL.count(),
customize: function(chart){
chart.xAxis.rotateLabels(45);
chart.xAxis.tickFormat(function(d){ return /[^\/]+$/.exec(d)[0]; });
}
})

Error with creating ordinal axes

Came across an error using lineChart and multiBarChart. If you want non-numeric axis values it is not being handled correctly by dashboard and fails. Will look into this later

How to select / display statements for only one Activity, preferably by name

After successfully retrieving statements based on Verb ID, I can display graphs on a dashboard grouped by 'object.definition.name.en-US' for all (e.g.) "passed" verbs.

My goal is to present a chart of live data for one course:
object.definition.name.en-US = 'My Course'
Then one bar / pie slice for each verb: Launched, Failed, Passed

I am struggling to reduce my dataset to just one Activity (preferably by name as per the groupBy value shown above).

Should filter by acitivity take place in dash.fetchAllStatements or in fetchDoneCallback function as a pre or post function? How would those filters be applied?

I have tried both but with no success.

Any pointers to reduce the dataset to a specific course would be really helpful.

having issue working with get more statements and adl.collection

I am facing issue processing more statements from XAPI. In my code below, I am using XAPI Wrapper and I am getting around 900 statements from lrs in rek, however, when I convert them into a collection and try to use the where clause, it does not give me any result.

Please Note: I can get the same thing done using fetchAllStatementes and it also works fine with getStatements (for only the first 100 records), however, I wish to do it manually using getMoreStatements.

Is there an example of merging statements using get more statements, creating a collection on top of it and then using the where query to filter the data?

My code:
var stmt1 = JSON.stringify({
"mbox": actor.mbox ,
"name": actor.name
});

var stmt2 = JSON.stringify({
"mbox": "mailto:[email protected]"
});

var stmtVerb = JSON.stringify({
"id": "http://adlnet.gov/expapi/verbs/started"
});

var stmtCtx = JSON.stringify({
"contextActivities": {
"grouping": [
{
"id": competency.iri
}
]
}
});

var search = ADL.XAPIWrapper.searchParams();
search['agent'] = stmt1;
search['authority'] = stmt2;
search['context'] = stmtCtx;
var rek = []
ADL.XAPIWrapper.getStatements(search, null,
function getmore(r){
var res = JSON.parse(r.response);
$.each(res.statements, function(x,y){
rek.push(y) ;
});

  if (res.more && res.more !== ""){
     ADL.XAPIWrapper.getStatements(search, res.more, getmore);
  }

});

console.log(rek);

//var ret = ADL.XAPIWrapper.getStatements(search); //works fine
//var statements = new ADL.Collection(ret.statements);

var stmt = new ADL.Collection(rek);
var filtered_data = stmt.where('actor.name = "ccazabon"').exec(function(data){
console.log(data);//no output-empty array, however, matching data does exists
});
var p = [{'name': 'Steven', 'age': 25, 'dob':{
'mm': 'jan',
'dd': '18'
}},
{'name': 'John', 'age': 35, 'dob':{
'mm': 'feb',
'dd': '19'
}}];
//console.log(p);
var a = new ADL.Collection(p);
//console.log(a);
var d = a.where('dob.mm = "jan"').exec(function(data){console.log(data)});//works as expected

I think there is some issue with merging all the statements and creating the ADL.Collection, however, I am not getting any online help to resolve the same.
Your help is greatly appreciated.

Sorting Graphs By Dates

I am trying to produce a graph that displays how many statements a particular actor produces a day. Is there a way I can use the timestamp to sort my results by how many times a specific actor produces a certain verb a day?

right now I can only produce how many statements a particular has produced overall, which is far too many bars on the chart.
this is how my code is set up

dash.Mia = dash.createBarChart({
container: '#Mia.graph svg',
pre: function(data){return data.where('actor.name = "Mia Wallace" and verb.id = "http://activitystrea.ms/schema/1.0/access"')},
groupBy: 'timestamp',
aggregate: ADL.count(),
customize: function(chart){
chart.xAxis.rotateLabels(45);
chart.xAxis.tickFormat(function(d){ return /[^\/]+$/.exec(d)[0]; });
}

window.statements

I am trying to learn how to use dashboard by looking at the samples. Currently, I am playing with the childcharts.html.

Dumb question - how does window.statements get initiallized with the array of 7000 plus statements?

Thanks for any help!

Null and 0 comparison should probably not evaluate to true

The snippet below works as expected.

data.where('result.score.raw > 0').orderBy('result.score.raw');
data.where('result.score.raw >= 0 and result.score.raw != null').orderBy('result.score.raw');

However this:
data.where('result.score.raw >= 0').orderBy('result.score.raw');

Results in:
xapicollection.js:698 Uncaught TypeError: Cannot read property 'toLowerCase' of null.

Which leads me to believe that the Collection class evaluates 0 == null to true and includes statements with null (or undefined?) score values in the resultant collection. This should probably not happen.

Question: how to keep the best try for each actor

I guys,

I'm having troubles using the ADLCollection and achieving what I want. Here is the thing: I instantiate the collection with some statements returned by an AJAX query to a LRS (I have only statements about the wanted activity, and the 'experienced' verb).

var statements = new ADL.Collection(data);

In these statements, I have multiple statements by actor (each actor tried multiple times the activity... or not), and I would like to keep their best shot at it.
I start by grouping statements by actor. Then i get their max result.score.raw, and then I'm stuck:

statements.groupBy('actor.account.name').max('result.score.raw');
statements.exec(function (data) {
	console.log(data);
});

How can I keep only the best try (i.e. the greatest result.score.raw) for each Actor ?

Thanks for your help

1.2.2 Release Bug

Whenever I try using the newest 1.2.2 release with the Intro to Data Visualizations project, it throws an error:
Uncaught TypeError: Cannot read property 'in' of undefined

whenever you try to process the data in actorChart. The dashboard development folder in the projects works fine though.

Retreive Activity

Is it possible to group by activity and display the activity name as the label

Thanks

Latency Issue

I have generated my dashboard and tried to reduce the latency using date range of xAPI statemnts, still the report generation is slow. Does anyone has solution to it?

How do I filter out duplicates?

I absolutely love this API. I have a question, however.

Let's say the contents of my collection after some manipulation is an array of objects with this signature :

{
       name: String,
       eLearning: String
} 

How would I filter out the dublicate objects? - e.g. objects with the exact name value and exact same eLearning value as an object already in the array?

Group by ZERO?

Hello,
i am using Dashboard to produce some statistics based on scores. I want a pie which shows the
distribution of the score . It is a child graph from another user.
My code is

var scoreRangesPie = dash.createPieChart({
			container: "#svg2",
			groupBy: "result.score.scaled",
			range: {start: 0, end: 1, increment: 0.1},
			pre: function(data, event){
				return data.where('actor.mbox = "' + event.in + '"');
			},
			aggregate: ADL.count(),
			});

when my graph shows up, it does not have a slice for score.scaled=0, although my datas include statements with score.scaled='0. The graph shows correct all other scores but totally ignores statements with score=0.
I would be grateful if you had any clue.
Yours,
K.

cannot fetch live data from LRS: 401 error Not Authorized

I have been attempting to use the xAPI dashboard but I cannot use any of my own data. I have an LRS with learning locker and I have tried both using the exported JSON file with my statements in it and trying to connect to the LRS and get ive data through the endpoint but neither work. I am unsure the correct way to manually fetch my own data and whenever I attempt to use live data I get a 401 error telling me that there was a problem with authorization. My endpoint is correct and I have tried both with and without adding the password and username.

this is the message:
There was a problem communicating with the Learning Record Store. ( 401 | {"error":true,"success":false,"message":"Unauthorized request.","code":401,"trace":"#0 /var/www/learninglocker/app/locker/helpers/Helpers.php(247): Locker\Helpers\Helpers::getLrsFromUserPass('tom', '1234')\n#1 /var/www/learninglocker/app/filters.php(69): Locker\Helpers\Helpers::getLrsFromAuth()\n#2 [internal function]: {closure}(Object(Illuminate\Routing\Route), Object(Illuminate\Http\Request))\n#3 /var/www/learninglocker/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(199): call_user_func_array(Object(Closure), Array)\n#4 /var/www/learninglocker/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(154): Illuminate\Events\Dispatcher->fire('router.filter: ...', Array, true)\n#5 /var/www/learninglocker/vendor/laravel/framework/src/Illuminate/Routing/Router.php(1473): Illuminate\Events\Dispatcher->until('router.filter: ...', Array)\n#6 /v...ninglocker/vendor/laravel/framework/src/Illuminate/Session/Middleware.php(72): Asm89\Stack\Cors->handle(Object(Illuminate\Http\Request), 1, true)\n#15 /var/www/learninglocker/vendor/laravel/framework/src/Illuminate/Cookie/Queue.php(47): Illuminate\Session\Middleware->handle(Object(Illuminate\Http\Request), 1, true)\n#16 /var/www/learninglocker/vendor/laravel/framework/src/Illuminate/Cookie/Guard.php(51): Illuminate\Cookie\Queue->handle(Object(Illuminate\Http\Request), 1, true)\n#17 /var/www/learninglocker/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Illuminate\Cookie\Guard->handle(Object(Illuminate\Http\Request), 1, true)\n#18 /var/www/learninglocker/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(641): Stack\StackedHttpKernel->handle(Object(Illuminate\Http\Request))\n#19 /var/www/learninglocker/public/index.php(49): Illuminate\Foundation\Application->run()\n#20 {main}"} )undefined

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.