I'm trying to make bar graphs similar to how Github does it for showing how many commits or how many people are watching repositories, e.g, https://github.com/popular/watched.
Does anyone know what library they used to make it?
UPDATE I wanted to re-open this question if possible. Re-investigating this, the solutions below while awesome in their own right, seem a little too involved for what I'm looking for.
I've switched to using this nice Nettuts tutorial which draws a single bar graph but I'm having trouble adapting it to draw multiple bar graphs. http://net.tutsplus.com/tutorials/javascript-ajax/fun-with-canvas-create-a-jquery-graph-plugin/
I've made a fiddle where I've manually added code to deal with a 2nd graph but I believe I need some for loops to make this work for a variable number of graphs. Here's that fiddle: http://jsfiddle.net/trpeters1/zHH76/
might someone be able to edit this fiddle to tackle this question?
Look into d3.js. There are several examples of how you can take an array in JavaScript and turn it into a graph like that.
Here is one (more advance) example: http://mbostock.github.com/d3/ex/population.html
And here is another example that is closer to what you want: http://mbostock.github.com/d3/tutorial/bar-2.html
EDIT
The actual code that Git Hub uses to create the graphs looks something like this:
GitHub.ParticipationGraph = (function(){
function b(target){
this.el = target;
this.onSuccess = $.proxy(this.onSuccess, this);
this.canvas = this.el.getContext("2d");
this.refresh();
}
b.prototype.barWidth = 7;
b.prototype.barMaxHeight = 20;
b.prototype.getUrl = function(){
return $(this.el).data("source");
};
b.prototype.setData = function(data){
this.data = data;
if (data == null || data.all == null || data.owner == null) {
this.data = null;
}
this.scale = this.getScale(this.data);
};
b.prototype.getScale = function(data){
var mx, i;
if (data == null) return;
mx = data.all[0];
for(i = 0; i < data.all.length; i++) {
if (data.all[i] > mx) {
mx = data.all[i];
}
}
return mx >= this.barMaxHeight ? (this.barMaxHeight-.1)/mx : 1;
};
b.prototype.refresh = function(){
$.ajax({
url: this.getUrl(),
dataType: "json",
success: this.onSuccess
});
};
b.prototype.onSuccess = function(data){
this.setData(data);
this.draw();
};
b.prototype.draw = function(){
if (this.data == null) return;
this.drawCommits(this.data.all, "#cacaca");
this.drawCommits(this.data.owner, "#336699");
};
b.prototype.drawCommits = function(data, color){
var i, width, height, x, y;
for (i = 0; i < data.length; i++) {
width = this.barWidth;
height = data[i] * this.scale;
x = i * (this.barWidth + 1);
y = this.barMaxHeight - height;
this.canvas.fillStyle = color;
this.canvas.fillRect(x, y, width, height);
}
};
return b;
})();
Basically, they are calling the data-source tag located on the canvas, which returns some JSON that represents the amount of work/participation/watches (or whatever metric they are calculating) and then they go through each returned value and call this.canvas.fillRect with the predefined width ((Screensize.width/52) - (paddingLeft + paddingRight)) and the height from the JSON returned
Try morris for displaying time series data.
Also Timeplot.
Related
Firstly a little elaboration of the project I'm working on. I have started building a 'map maker' for a 2d game I am working on. The project is just for fun and has proven so far to be a great way to learn new things.
I recently showed my working map maker code to a friend who suggested it would be much more re-usable if I restructured the project to be more OOR, which I am now attempting.
The problem I have is when I add a 'Guild' instance to my map, the first one works fine, but the second causes a type error that is giving me a headache!
I will post all of the relevant code from the different files below, but the overall structure is as follows:
Map.js = Map class file, container for setting the map overall size and iterating over (and placing) map objects.
MapObject.js = Class file for simple map objects such as walls, contains the position and icon properties.
Guild.js = Class file, extends MapObject.js, this is where my problem seems to be, adds an additional 'MapIcon' and will have other features such as levels and names etc.
map-maker.js = Main file for generating the map-maker page, utilises the above class files to create the map.
Below is the code used to create an instance of 'Guild' on my map:
map-maker.js (creating the map / map object / guild instances)
// Initialise a new instance of map class from Map.js using the user
provided values for #mapX and #mapY.
var currentMap = new Map(XY,40);
// Get the user box volume * map boxsize from Map.js
currentMap.calcDimensions();
// Create a Map Object and push it to the currentMap with its position.
function createMapObject(x,y,floor){
currentMap.objects.push(new MapObject(x,y,floor));
}
// Create a Guild Object (extension of Map Object) and push it to the currentMap with its position.
function createGuildObject(x,y,floor){
currentMap.objects.push(new Guild(x,y,floor));
}
....
case 13: // Enter Key (Submit)
unhighlightTools();
currentMap.drawMap();
if(currentFloor != null){
currentFloor.hasFloor = true;
if(currentFloor.tileName == "Guild"){
createGuildObject(currentFloor.position.x,currentFloor.position.y,currentFloor);
}else {
createMapObject(currentFloor.position.x,currentFloor.position.y,currentFloor);
}
console.log("Map object created at - X:"+currentFloor.position.x+" Y:"+currentFloor.position.y);
}
currentFloor = [];
highlightTools();
break;
}
Guild.js (constructor and assigning map icon)
class Guild extends MapObject {
constructor(x,y,floor) {
super(x,y,floor);
this.levels = [];
}
mapIcon() {
this.mapIcon = new Image();
this.mapIcon.src = "../images/mapSprites/obj-1.png"
return this.mapIcon;
}
}
MapObject.js (position setup and constructor)
class MapObject {
constructor(x,y,floor) {
this.position = {x, y};
this.icon = this.wallFloorIcons(floor);
}
wallFloorIcons(floor) {
this.img = new Image();
this.name = "";
this.name += (floor.wallNorth) ? 'n' : '';
this.name += (floor.wallEast) ? 'e' : '';
this.name += (floor.wallSouth) ? 's' : '';
this.name += (floor.wallWest) ? 'w' : '';
this.name = 'wall-'+this.name+'.png';
if(this.name == 'wall-.png'){
this.img.src = "../images/mapSprites/floor.png";
}else {
this.img.src = "../images/mapSprites/"+this.name;
}
return this.img;
}
getIcon() {
return this.img;
}
}
Map.js (processing the objects at a given location and drawing the canvas)
class Map {
// Map Width / Height and number of boxes. Used to generate map and zoom level.
constructor(wh, boxSize) {
this.size = wh;
this.width = wh[0];
this.height = wh[1];
this.boxSize = boxSize;
this.objects = [];
this.boxes = wh[0];
}
// Calculates the width and height * boxSize for rendering the canvas.
calcDimensions(){
this.realX = Math.floor(this.width * this.boxSize);
this.realY = Math.floor(this.height * this.boxSize);
this.realX = parseInt(this.realX,10);
this.realY = parseInt(this.realY,10);
this.realXY = [
this.realX,
this.realY
];
return this.realXY;
}
// Draws the canvas, grid and adds the objects that belong to the map.
drawMap(){
var self = this;
self.canvas = document.getElementById("canvas");
self.c = self.canvas.getContext("2d");
self.background = new Image();
self.background.src = "../images/mapSprites/oldPaperTexture.jpg";
// Make sure the image is loaded first otherwise nothing will draw.
self.background.onload = function(){
self.c.drawImage(self.background,0,0);
self.fillMap();
}
}
fillMap(){
var self = this;
self.c.lineWidth = 1;
self.c.strokeStyle = 'black';
self.c.fillStyle = "rgba(255, 255, 255, 0.2)";
for (var row = 0; row < self.boxes; row++) {
for (var column = 0; column < self.boxes; column++) {
var x = column * self.boxSize;
var y = row * self.boxSize;
self.c.beginPath();
self.c.rect(x, y, self.boxSize, self.boxSize);
self.c.stroke();
self.c.closePath();
for (var i=0; i<self.objects.length; i++) {
var floor = self.objects[i];
if (floor.position.x == column && floor.position.y == row) {
if (self.objectsAtPosition({x:floor.position.x, y:floor.position.y}) != null) {
var mapObjects = self.objectsAtPosition({x:floor.position.x, y:floor.position.y})
for (var mapObject of mapObjects) {
this.c.drawImage(mapObject.getIcon(), x, y, self.boxSize, self.boxSize);
console.log(mapObject);
if(mapObject instanceof Guild){
console.log(mapObject);
this.c.drawImage(mapObject.mapIcon(), x, y, self.boxSize, self.boxSize);
}
}
}
}
}
}
}
}
deleteObject(pos){
this.objectsAtPosition(pos);
for( var i = 0; i < this.objects.length; i++){
if(this.objects[i] == this.objs){
delete this.objects[i];
this.objects.splice(i,1);
}
}
}
objectsAtPosition(position) {
var objs = [];
for (var o of this.objects) {
if (o.position.x == position.x && o.position.y == position.y) {
objs.push(o);
}
}
return objs;
}
}
When I run the code, this is my error:
Uncaught TypeError: mapObject.mapIcon is not a function
at Map.fillMap (Map.js:70)
at Image.self.background.onload (Map.js:39)
The error comes after I add 1 guild then try to add any other map object. Guild or otherwise.
Sorry if this question is a little vague, I'm still learning (as you can see :p).
Thanks for your time!
Earl Lemongrab
Got a solution from a friend in the end.
The issue was that I was reassigning this.mapIcon = new Image() so it exploded when it was called a second time.
Feel pretty silly for not spotting it.
Thanks for the help everyone.
Well i have this SVG canvas element, i've got to the point so far that once a user clicks and drags the canvas is moved about and off-screen elements become on screen etc....
However i have this is issue in which when ever the user then goes and click and drags again then the translate co-ords reset to 0, which makes the canvas jump back to 0,0.
Here is the code that i've Got for those of you whio don't wanna use JS fiddle
Here is the JSfiddle demo - https://jsfiddle.net/2cu2jvbp/2/
edit: Got the solution - here is a JSfiddle DEMO https://jsfiddle.net/hsqnzh5w/
Any and all sugesstion will really help.
var states = '', stateOrigin;
var root = document.getElementById("svgCanvas");
var viewport = root.getElementById("viewport");
var storeCo =[];
function setAttributes(element, attribute)
{
for(var n in attribute) //rool through all attributes that have been created.
{
element.setAttributeNS(null, n, attribute[n]);
}
}
function setupEventHandlers() //self explanatory;
{
setAttributes(root, {
"onmousedown": "mouseDown(evt)", //do function
"onmouseup": "mouseUp(evt)",
"onmousemove": "mouseMove(evt)",
});
}
setupEventHandlers();
function setTranslate(element, x,y,scale) {
var m = "translate(" + x + "," + y+")"+ "scale"+"("+scale+")";
element.setAttribute("transform", m);
}
function getMousePoint(evt) { //this creates an SVG point object with the co-ords of where the mouse has been clicked.
var points = root.createSVGPoint();
points.x = evt.clientX;
points.Y = evt.clientY;
return points;
}
function mouseDown(evt)
{
var value;
if(evt.target == root || viewport)
{
states = "pan";
stateOrigin = getMousePoint(evt);
console.log(value);
}
}
function mouseMove(evt)
{
var pointsLive = getMousePoint(evt);
if(states == "pan")
{
setTranslate(viewport,pointsLive.x - stateOrigin.x, pointsLive.Y - stateOrigin.Y, 1.0); //is this re-intializing every turn?
storeCo[0] = pointsLive.x - stateOrigin.x
storeCo[1] = pointsLive.Y - stateOrigin.Y;
}
else if(states == "store")
{
setTranslate(viewport,storeCo[0],storeCo[1],1); // store the co-ords!!!
stateOrigin = pointsLive; //replaces the old stateOrigin with the new state
states = "stop";
}
}
function mouseUp(evt)
{
if(states == "pan")
{
states = "store";
if(states == "stop")
{
states ='';
}
}
}
In your mousedown function, you are not accounting for the fact that the element might already have a transform and you are just overwriting it.
You are going to need to either look for, and parse, any existing transform. Or an easier approach would be to keep a record of the old x and y offsets and when a new mousedown happens add them to the new offset.
I'm trying to create an online web tool for eeg signal analysis. The tool suppose to display a graph of an eeg signal synchronize with a movie that was display to a subject.
I've already implemented it successfully on csharp but I can't find a way to do it easily with any of the know javascript chart that I saw.
A link of a good tool that do something similar can be found here:
http://www.mesta-automation.com/real-time-line-charts-with-wpf-and-dynamic-data-display/
I've tried using dygraph, and google chart. I know that it should be relatively easy to create an background thread on the server that examine the movie state every ~50ms. What I was not able to do is to create a marker of the movie position on the chart itself dynamically. I was able to draw on the dygraph but was not able to change the marker location.
just for clarification, I need to draw a vertical line as a marker.
I'm in great suffering. Please help :)
Thanks to Danvk I figure out how to do it.
Below is a jsfiddler links that demonstrate such a solution.
http://jsfiddle.net/ng9vy8mb/10/embedded/result/
below is the javascript code that do the task. It changes the location of the marker in synchronizing with the video.
There are still several improvement that can be done.
Currently, if the user had zoomed in the graph and then click on it, the zoom will be reset.
there is no support for you tube movies
I hope that soon I can post a more complete solution that will also enable user to upload the graph data and video from their computer
;
var dc;
var g;
var v;
var my_graph;
var my_area;
var current_time = 0;
//when the document is done loading, intialie the video events listeners
$(document).ready(function () {
v = document.getElementsByTagName('video')[0];
v.onseeking = function () {
current_time = v.currentTime * 1000;
draw_marker();
};
v.oncanplay = function () {
CreateGraph();
};
v.addEventListener('timeupdate', function (event) {
var t = document.getElementById('time');
t.innerHTML = v.currentTime;
g.updateOptions({
isZoomedIgnoreProgrammaticZoom: true
});
current_time = v.currentTime * 1000;
}, false);
});
function change_movie_position(e, x, points) {
v.currentTime = x / 1000;
}
function draw_marker() {
dc.fillStyle = "rgba(255, 0, 0, 0.5)";
var left = my_graph.toDomCoords(current_time, 0)[0] - 2;
var right = my_graph.toDomCoords(current_time + 2, 0)[0] + 2;
dc.fillRect(left, my_area.y, right - left, my_area.h);
};
//data creation
function CreateGraph() {
number_of_samples = v.duration * 1000;
// A basic sinusoidal data series.
var data = [];
for (var i = 0; i < number_of_samples; i++) {
var base = 10 * Math.sin(i / 90.0);
data.push([i, base, base + Math.sin(i / 2.0)]);
}
// Shift one portion out of line.
var highlight_start = 450;
var highlight_end = 500;
for (var i = highlight_start; i <= highlight_end; i++) {
data[i][2] += 5.0;
}
g = new Dygraph(
document.getElementById("div_g"),
data, {
labels: ['X', 'Est.', 'Actual'],
animatedZooms: true,
underlayCallback: function (canvas, area, g) {
dc = canvas;
my_area = area;
my_graph = g;
bottom_left = g.toDomCoords(0, 0);
top_right = g.toDomCoords(highlight_end, +20);
draw_marker();
}
});
g.updateOptions({
clickCallback: change_movie_position
}, true);
}
I have created a loader for tiled in the JSON format for my JavaScript game. The game is based on a 2048 x 1280 map and my canvas size is 704 x 608.
So my predicament is how would i create a viewport/camera when the player approaches the edge of the canvas ,so that the map will move?
In my situation, it is a little more difficult, because im not using a standard 2D array like most of the tutorials, so it is confusing me very much.
The code for loading my map is like so:
function Level (){
var data;
this.tileset = null;
var returned;
var t = this;
this.load = function(name){
$.ajax({
type:"POST",
url: "maps/" + name + ".json",
dataType: 'JSON',
async: false,
success: function(success){
returned = loadTileset(success);
}
});
return returned;
};
function loadTileset(json){
data = json;
Level.tileset = $("<img />", { src: json.tilesets[0].image })[0]
Level.tileset.onload = done = true;
if(done){
return data;
}
else
return 0;
}
this.draw_layer = function(layer){
if (layer.type !== "tilelayer" || !layer.opacity) { return; }
// if (layer.properties && layer.properties.collision) {
// return;
// }
var size = data.tilewidth;
layer.data.forEach(function(tile_idx, i) {
if (!tile_idx) { return; }
var img_x, img_y, s_x, s_y,
tile = data.tilesets[0];
tile_idx--;
img_x = (tile_idx % (tile.imagewidth / size)) * size;
img_y = ~~(tile_idx / (tile.imagewidth / size)) * size;
s_x = (i % layer.width) * size;
s_y = ~~(i / layer.width) * size;
ctx.drawImage(Level.tileset, img_x, img_y, size, size,
s_x, s_y, size, size);
});
};
this.collisionLayer = function(layer){
var row = [];
t.solids = [];
layer.data.forEach(function(idx, i) {
var s_x,s_y,
size = 32;
s_x = (i % layer.width) * size;
s_y = ~~(i / layer.width) * size;
if (i % layer.width === 0 && i) {
t.solids.push(row);
row = [];
}
row.push([idx,s_x,s_y]);
});
t.solids.push(row);
};
return t.solids;
}
And this Level class is called from my main javascript file like this:
layer = scene.load("level_"+level);
layer = $.isArray(layer) ? layer : layer.layers;
layer.forEach(function(layer){
if(layer.name == "v.bottom" && layer.type == "tilelayer"){
b_layer = layer;
}
else if(layer.type == "tilelayer" && !layer.properties){
t_layer.push(layer);
}
else if (layer.type == "objectgroup"){
$.each(layer.objects, function(i, value){
coll_layer.push([value.x,value.y,value.width,value.height,"dialogue"]);
});
}
});
And then scene.draw_layer() is called from my update function lower in my main javascript function.
My map holds different layers and you can see in the code above there is and if statement determining where the layer is the bottom layer, top layer or and object layer and storing it in the specific array. This is just so the player is shown between layers.
So to clarify, for my game, how would i create a view port/camera according to my map loader?
Also do i move the map or the player?
And should i keep the player centered? if the player is centered then what happens when the edge of the map is visible?
Here is a live version of the game so you can see how it works etc:
LIVE DEMO
Use guest as the username and password
Thankyou very much
UPDATE
I have made a small progression i believe but with big consequences. I have tried implementing a viewport from a JSFIDDLE on one of the questions on here, its designed to work with a 2D array, but thought id give it a go.
and the result can be found here:
Updated link
If anyone could have a look to see where im going wrong it would be much appreciated. Thanks
I am trying to adapt the really cool looking WebGL Story Sphere, source code and css here. There's one big problem: once you click on a news article, the text of the article in the popup is always the same. I want to modify the code so that when you click on an article, the right text appears in the popup.
I'm working from a set of article texts that I specify in the code, e.g. var captions = ["good","better","best"]. Though the article titles and images populate correctly in the popup, I can't get the text to do so. Can you help me?? Here's what I've got:
// function code
var passvar = null; // failed attempt to store texts for later
function initialize() {
math = tdl.math;
fast = tdl.fast;
canvas = document.getElementById("canvas");
g_fpsTimer = new tdl.fps.FPSTimer();
hack();
canvas.addEventListener("mousedown", handleMouseDown, false);
canvas.addEventListener("mousemove", handleMouseMove, false);
canvas.addEventListener("mouseup", handleMouseUp, false);
// Create a canvas 2d for making textures with text.
g_canvas2d = document.createElement('canvas');
window.two2w = window.two2h = g_tilesize;
g_canvas2d.width = two2w;
g_canvas2d.height = two2h;
g_ctx2d = g_canvas2d.getContext("2d");
window.gl = wu.create3DContext(canvas);
if (g_debug) {
gl = wd.makeDebugContext(gl, undefined, LogGLCall);
}
//gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, gl.TRUE);
// Here is where I specify article titles, images, captions
// Titles and images populate the popup correctly, captions don't...
var titles = ["a","b","c"];
var captions = ["good","better","best"];
var images = ['imagesphere/assets/1.jpg',
'imagesphere/assets/bp/2.png',
'imagesphere/assets/bp/3.png'
];
var headlines = titles.concat( titles);
var blurbs = captions.concat( captions);
var tmpImages = [];
var tmpHeadlines = [];
var tmpCaptions = [];
// make a bunch of textures.
for (var ii = 0; ii < g_imagesDownGrid; ++ii) {
var textures = [];
for (var jj = 0; jj < g_imagesAcrossGrid; ++jj) {
var imgTexture = new ImgTexture();
textures.push(imgTexture);
if (tmpImages.length == 0) {
tmpImages = images.slice();
}
if (tmpHeadlines.length == 0) {
tmpHeadlines = headlines.slice();
}
if (tmpCaptions.length == 0) {
tmpCaptions = blurbs.slice();
}
var rando = math.randomInt(tmpImages.length);
var img = tmpImages.splice(rando, 1)[0];
var headline = tmpHeadlines.splice(rando, 1)[0];
var caption = tmpCaptions.splice(rando, 1)[0];
passvar = caption;
if (img.indexOf('videoplay.jpg') > -1){
window.vidtexture = imgTexture;
images = images.slice(1); // dont use that thumb again.
headlines = 'WebGL Brings Video To the Party as Well'
}
imgTexture.load(img, /* "[" + jj + "/" + ii + "]" + */ headline);
}
g_textures.push(textures);
}
// And here's where I try to put this in a popup, finally
// But passvar, the stored article text, never refreshes!!!
<div id="story" class="prettybox" style="display:none">
<img class="close" src="imagesphere/assets/close.png">
<div id="storyinner">
<input id = "mytext">
<script>document.getElementById("mytext").value = passvar;</script>
</div>
</div>
And here is my click handler code:
function sphereClick(e){
window.console && console.log('click!', e, e.timeStamp);
var selected = g_textures[sel.y][sel.x];
window.selected = selected;
animateGL('eyeRadius', glProp('eyeRadius'), 4, 500);
var wwidth = $(window).width(),
wheight = $(window).height(),
story = $('#story').width( ~~(wwidth / 7 * 4) ).height( ~~(wheight / 6 * 5) ),
width = story.width(),
height = story.height(),
miniwidth = 30;
story.detach()
.find('#storyinner').find('h3,img,caption').remove().end().end()
.show();
story.css({
left : e.pageX,
top : e.pageY,
marginLeft : - width / 2,
marginTop : - height / 2
}).appendTo($body); // we remove and put back on the DOM to reset it to the correct position.
$('style.anim.story').remove();
$('<style class="anim story">')
.text( '.storyopen #story { left : ' + (wwidth / 3 * 2) + 'px !important; top : ' + wheight / 2 + 'px !important; }' )
.appendTo($body);
$(selected.img).prependTo('#storyinner').parent();
$('<h3>').text(selected.msg.replace(/\(.*/,'')).prependTo('#storyinner');
$body.addClass('storyopen');
} // eo sphereClick()
There's a lot wrong here, but here's a start. It won't solve your problem, but it will help you avoid issues like this.
var passvar = null; is a global variable.
Your loop for (var ii = 0; ... sets that global variable to a new value on every iteration.
Later, you click something and the global variable passvar is never changed.
If you want to use this pattern, you need to set passvar from your click handler so it has the value that was clicked. Since you didn't actually post your click handlers, it's hard to advise more.
But this is also a bad pattern, functions take arguments for a good reason. Since you have to find your clicked item in the click handler anyway, why not pass it directly which does involve a shared global variable at all?
var handleMouseUp = function(event) {
var story = findClickedThing(event);
if (obj) {
showPopup(story.texture, story.caption);
}
}
Which brings me to this:
var titles = ["a","b","c"];
var captions = ["good","better","best"];
var images = ['imagesphere/assets/1.jpg',
'imagesphere/assets/bp/2.png',
'imagesphere/assets/bp/3.png'
];
When you have 3 arrays, all of the same length, each array describing a different property of an object, you are doing it wrong. What you want, is one array of objects instead.
var stories = [
{
title: "a",
caption: "good",
image: "imagesphere/assets/1.jpg"
}, {
title: "b",
caption: "better",
image: "imagesphere/assets/bp/2.jpg"
}, {
title: "c",
caption: "best",
image: "imagesphere/assets/bp/3.jpg"
},
];
console.log(stories[1].caption); // "better"
Now once you find the clicked object, you can just ask it what it's caption is. And you can pass the whole object to the popup maker. And no field is handled differently or passed around in a different manner, because you are not passing around the fields. You are passsing the entire object.