Atlasboard and Rickshaw - node.js

Am trying to create a dashboard using the atlasboard and rickshaw. Now what happens is that atlasboard by default has a black background and am unable to see the x-axis and y-axis when creating graphs (e.g WIKI markdown analytics).
If you look at the example screenshot below from the official atlasboard page it appears that atlassian devs have managed to show the x-axis as a white color, so am wondering :
how they did it as am unable to achieve this with rickshaw
apparently.
Is there any css that needs to be overridden or is there any kind of
settings to be setup in nodejs?
or even another graph framework should be used other than rickshaw
(and how to do that)?
Can someone please explain?
(source: bitbucket.org)
Update1 following answer:
I have added the following within the widget:
widget = {
//runs when we receive data from the job
onData: function(el, data) {
function drawDashLine(val, max, min) {
var container = $('.content', el),
viewport = {
height: container.height(),
width: container.width()
},
topPosition = (viewport.height - Math.ceil(val/max * viewport.height));
var dashedLineLine = $('<hr />')
.attr('class', 'dashedLine')
.css({
width: el.width() - 40,
top: topPosition + 'px'
});
var lineLabel = $("<span />")
.attr('class', 'lineLabel')
.css({
top: topPosition + 'px'
})
.text(val);
container.append(dashedLineLine, lineLabel);
}
function paintMinMax(series) {
var mergedData = [];
for (var i = series.length - 1; i >= 0; i--) {
mergedData = mergedData.concat(series[i].data);
};
var min = _.min(_.pluck(mergedData, 'y')),
max = _.max(_.pluck(mergedData, 'y')),
mid = Math.round(max / 2);
drawDashLine(min, max, min);
drawDashLine(mid, max, min);
drawDashLine(max, max, min);
}
function paintTimeMark(chartWidth, chartHeight, startDate, endDate, timeMarkData) {
if (!timeMarkData || !timeMarkData.length) {
return;
}
var chartLengthMs = endDate - startDate;
for (var i = 0, l = timeMarkData.length; i < l; i++) {
var timeMark = timeMarkData[i];
// are we in the boundaries?
if ((timeMark.epoch < startDate) || (timeMark.epoch > endDate)){
continue; // out of boundaries
}
var lengthOfTimemark = timeMark.epoch - startDate;
var percentage = (lengthOfTimemark / chartLengthMs);
var leftPosition = Math.round(chartWidth * percentage);
var top = timeMark.top || 0;
var markHeight = chartHeight - top;
var color = timeMark.color || 'orange';
var mark = $('<div>')
.attr('class', 'mark')
.css({
left: leftPosition + 'px',
'margin-top': top + 'px',
'background-color' : color,
'height': markHeight + 'px'
});
var legend = $('<span>')
.attr('class', 'legend')
.css({
left: leftPosition + 'px',
'margin-top': top + 'px',
'height': markHeight + 'px'
})
.hide()
.text(timeMark.name);
$('.content', el).append(mark, legend);
var center = Math.round((legend.width() - (mark.width())) / 2);
// center align legend
(function(legend, center){
setTimeout(function(){
legend.css({
'margin-left': (-(center)) + 'px'
}).fadeIn();
},600);
}(legend, center));
}
}
function paintChart(width, height, series, color) {
for (var i = series.length - 1; i >= 0; i--) {
series[i].data = formatData(series[i].data);
}
var graph = new Rickshaw.Graph({
element: $('.graph', el)[0],
width: width,
height: height,
renderer: 'line',
offset: 'expand',
series: series
});
var xAxis = new Rickshaw.Graph.Axis.Time({
graph: graph,
timeFixture: new Rickshaw.Fixtures.Time.Local()
});
xAxis.render();
graph.render();
}
function formatData(rawData) {
var sortedData = _.sortBy(rawData, function(num) {
return num.date;
});
return _.map(sortedData, function(e) {
var ret = {
x: e.date,
y: e.sum
};
return ret;
});
}
var init = function() {
if (data.title) {
$('h2', el).text(data.title);
}
if ($('.graph', el).hasClass('rickshaw_graph')) {
$('.graph', el).empty();
}
if (!data.series.length) {
console.error('There is no results to paint the chart');
return;
}
// paint chart
var width = el.width() - 50;
var height = el.closest('li').height() - 80;
paintChart(width, height, data.series, data.color || 'yellow');
// paint min max dash
paintMinMax(data.series);
// paint time marks
paintTimeMark(width, height, data.startDate, data.endDate, data.timeMarks);
}();
}
};
Then within the job I have added :
module.exports = function(config, dependencies, job_callback) {
var text = "Hello World!";
var date1 = (new Date(2014, 4, 2, 1, 30, 0, 0))/1000;
var date2 = (new Date(2014, 5, 3, 2, 30, 0, 0))/1000;
var date3 = (new Date(2014, 6, 4, 3, 30, 0, 0))/1000;
var date4 = (new Date(2014, 7, 6, 4, 30, 0, 0))/1000;
var totalBacklogDefects = [{ x: date1, y : 40},
{ x: date2, y : 30},
{ x: date3, y : 23}, { x: date4, y : 10} ] ;
var blockedDefects = [{ x: date1, y : 32}, { x: date2, y : 22},
{ x: date3, y : 3}, { x: date4, y : 5} ] ;
var fixedDefects = [{ x: date1, y : 2}, { x: date2, y : 12},
{ x: date3 ,y : 20}, { x: date4, y : 25} ] ;
var series = [
{
data: totalBacklogDefects ,
color: 'steelblue',
name: 'Total Backlog'
},
{
data: blockedDefects ,
color: 'red',
name: 'Blocked Defects'
},
{
data: fixedDefects,
color: 'green',
name: 'Fixed'
}
];
var timeMarks = [
{
top: 0,
color: 'red',
name: 'test2',
epoch: date2
},
{
top: 0,
color: 'blue',
name: 'test1',
epoch: date3
}
];
job_callback(null, {title: "Graph Sandbox", series: series, startDate: date1 ,
endDate : date4 , timeMarks: timeMarks});
};
As for my html page I have added the following:
<h2>graphsandbox</h2>
<div class="content">
<div class="graph rickshaw_graph"></div>
</div>
Can you please let me know whether am using the right approach as currently this renders only an empty box?

The chart widget that you see in the screenshot belongs to an internal package, but I can share it with you :)
Rickshaw is available globally anyway, so you can create your own chart widgets check Rickshaw's examples.
widget = {
onData: function(el, data) {
function drawDashLine(val, max, min) {
var container = $('.content', el),
viewport = {
height: container.height(),
width: container.width()
},
topPosition = (viewport.height - Math.ceil(val/max * viewport.height));
var dashedLineLine = $('<hr />')
.attr('class', 'dashedLine')
.css({
width: el.width() - 40,
top: topPosition + 'px'
});
var lineLabel = $("<span />")
.attr('class', 'lineLabel')
.css({
top: topPosition + 'px'
})
.text(val);
container.append(dashedLineLine, lineLabel);
}
function paintMinMax(series) {
var mergedData = [];
for (var i = series.length - 1; i >= 0; i--) {
mergedData = mergedData.concat(series[i].data);
};
var min = _.min(_.pluck(mergedData, 'y')),
max = _.max(_.pluck(mergedData, 'y')),
mid = Math.round(max / 2);
drawDashLine(min, max, min);
drawDashLine(mid, max, min);
drawDashLine(max, max, min);
}
function paintTimeMark(chartWidth, chartHeight, startDate, endDate, timeMarkData) {
if (!timeMarkData || !timeMarkData.length) {
return;
}
var chartLengthMs = endDate - startDate;
for (var i = 0, l = timeMarkData.length; i < l; i++) {
var timeMark = timeMarkData[i];
// are we in the boundaries?
if ((timeMark.epoch < startDate) || (timeMark.epoch > endDate)){
continue; // out of boundaries
}
var lengthOfTimemark = timeMark.epoch - startDate;
var percentage = (lengthOfTimemark / chartLengthMs);
var leftPosition = Math.round(chartWidth * percentage);
var top = timeMark.top || 0;
var markHeight = chartHeight - top;
var color = timeMark.color || 'orange';
var mark = $('<div>')
.attr('class', 'mark')
.css({
left: leftPosition + 'px',
'margin-top': top + 'px',
'background-color' : color,
'height': markHeight + 'px'
});
var legend = $('<span>')
.attr('class', 'legend')
.css({
left: leftPosition + 'px',
'margin-top': top + 'px',
'height': markHeight + 'px'
})
.hide()
.text(timeMark.name);
$('.content', el).append(mark, legend);
var center = Math.round((legend.width() - (mark.width())) / 2);
// center align legend
(function(legend, center){
setTimeout(function(){
legend.css({
'margin-left': (-(center)) + 'px'
}).fadeIn();
},600);
}(legend, center));
}
}
function paintChart(width, height, series, color) {
for (var i = series.length - 1; i >= 0; i--) {
series[i].data = formatData(series[i].data);
}
var graph = new Rickshaw.Graph({
element: $('.graph', el)[0],
width: width,
height: height,
renderer: 'line',
offset: 'expand',
series: series
});
var xAxis = new Rickshaw.Graph.Axis.Time({
graph: graph,
timeFixture: new Rickshaw.Fixtures.Time.Local()
});
xAxis.render();
graph.render();
}
function formatData(rawData) {
var sortedData = _.sortBy(rawData, function(num) {
return num.date;
});
return _.map(sortedData, function(e) {
var ret = {
x: e.date,
y: e.sum
};
return ret;
});
}
var init = function() {
if (data.title) {
$('h2', el).text(data.title);
}
if ($('.graph', el).hasClass('rickshaw_graph')) {
$('.graph', el).empty();
}
if (!data.series.length) {
console.error('There is no results to paint the chart');
return;
}
// paint chart
var width = el.width() - 50;
var height = el.closest('li').height() - 80;
paintChart(width, height, data.series, data.color || 'yellow');
// paint min max dash
paintMinMax(data.series);
// paint time marks
paintTimeMark(width, height, data.startDate, data.endDate, data.timeMarks);
}();
}
};

Related

Fabric.js: src attribute missing on grouping subclassed object

I tried to use subclassing as shown in the example at http://fabricjs.com/polaroid.
The PolaroidPhoto subclass just adds a border on the image as shown on the following fiddle: https://jsfiddle.net/gusy54rr/6/
canvas = this.__canvas = new fabric.Canvas('c', {
backgroundColor: '#333',
HOVER_CURSOR: 'pointer'
});
var PolaroidPhoto = fabric.util.createClass(fabric.Image, {
H_PADDING: 20,
V_PADDING: 20,
originX: 'center',
originY: 'center',
initialize: function(src, options) {
this.callSuper('initialize', options);
this.image = new Image();
this.image.src = src;
console.log("In initialize, src is:" + src);
this.image.onload = (function() {
this.width = this.image.width;
this.height = this.image.height;
this.loaded = true;
this.setCoords();
this.fire('image:loaded');
}).bind(this);
},
_render: function(ctx) {
if (this.loaded) {
ctx.fillStyle = '#fff';
ctx.fillRect(
-(this.width / 2) - this.H_PADDING,
-(this.height / 2) - this.H_PADDING,
this.width + this.H_PADDING * 2,
this.height + this.V_PADDING * 2);
ctx.drawImage(this.image, -this.width / 2, -this.height / 2);
}
}
});
var photo = new PolaroidPhoto('https://i.stack.imgur.com/cqmQ9.png', { });
photo.on('image:loaded', canvas.renderAll.bind(canvas));
photo.set('scaleX', 1);
photo.set('scaleY', 1);
photo.set('top', 180);
photo.set('left', 150);
console.log("photo,src is :" + photo.get('src'));
// forcing src value (but ineffective)
photo.set('src', 'https://i.stack.imgur.com/cqmQ9.png');
canvas.add(photo);
canvas.add(
rect= new fabric.Rect({ top: 50, left: 100, width: 50, height: 50, fill: '#f55' }),
circle = new fabric.Circle({ top: 140, left: 230, radius: 75, fill: 'green' }),
triangle = new fabric.Triangle({ top: 300, left: 210, width: 100, height: 100, fill: 'blue' })
);
$("#group").on('click', function() {
var activegroup = canvas.getActiveGroup();
var objectsInGroup = activegroup.getObjects();
activegroup.clone(function(newgroup) {
canvas.discardActiveGroup();
objectsInGroup.forEach(function(object) {
canvas.remove(object);
});
canvas.add(newgroup);
});
});
$("#ungroup").click(function(){
var activeObject = canvas.getActiveObject();
if(activeObject.type=="group"){
var items = activeObject._objects;
alert(items);
activeObject._restoreObjectsState();
canvas.remove(activeObject);
for(var i = 0; i < items.length; i++) {
canvas.add(items[i]);
items[i].dirty = true;
canvas.item(canvas.size()-1).hasControls = true;
}
canvas.renderAll();
}
});
It works fine until I want to stringify or make some grouping with a subclassed object.
In the fiddle, I completed the Fabric demo's example by adding a few basis objects (a rectangle, a circle and a triangle).
If I select the subclassed image and any other object and then click on the group button:
The image disappears.
The scr property of the photo is not set (as shown by the alert on "ungroup" for the former group).
A stringification of the canvas also shows that "src" is missing.
Even if I force (see the fiddle) a src value using "photo.set('src',...)" :
- the grouping still makes the picture to disappear.
- The stringification still lacks the "src" attribute. (I tried to extend toObjects to no avail)
How to get grouping and stringification to work with subclassed objects?
Thanks for your help.
Here is a new jsfiddle showing correct grouping and JSON load with a sub-classed (PolaroidPhoto) image. Fabric version is 1.7.19
https://jsfiddle.net/rpzk7wL6/2/
I put some comments in the code to show my modifications.
The main problem in the former script was the absence of fromObjects() method.
I also added a handler listening to "image:loaded" to the sub-class instance created
by fromObjects, in order to render it after loading .
fabric.Object.prototype.transparentCorners = false;
canvas = this.__canvas = new fabric.Canvas('c', {
backgroundColor: '#333',
HOVER_CURSOR: 'pointer'
});
fabric.Polaroidphoto = fabric.util.createClass(fabric.Image, {
type: 'polaroidphoto',
H_PADDING: 20,
V_PADDING: 20,
originX: 'center',
originY: 'center',
initialize: function(src, options) {
this.image = new Image();
this.image.src = src;
this.callSuper('initialize',src, options);
console.log("initialize, src:" + src);
this.image.onload = (function() {
this.width = this.image.width;
console.log("initialize, scaleX:" + this.image.scaleX);
this.height = this.image.height;
this.src= this.image.src;
console.log("initialize image.onload, src:" + src);
this.loaded = true;
this.setCoords();
this.fire('image:loaded');
}).bind(this);
},
_render: function(ctx) {
if (this.loaded) {
console.log("_render:is_loaded");
ctx.fillStyle = '#fff';
ctx.fillRect(
-(this.width / 2) - this.H_PADDING,
-(this.height / 2) - this.H_PADDING,
this.width + this.H_PADDING * 2,
this.height + this.V_PADDING * 2);
ctx.drawImage(this.image, -this.width / 2, -this.height / 2);
} else {
console.log("_render:is_NOT__loaded");
}
}
});
// Added fromObject function for sub-class
fabric.Polaroidphoto.async = true;
fabric.Polaroidphoto.fromObject = function (object, callback) {
console.log("fabric.Polaroidphoto.fromObject object.src) :" + object.src);
var instance = new fabric.Polaroidphoto(object.src, object);
callback && callback(instance);
// added handler to render instance
instance.on('image:loaded', when_loaded );
};
var photo = new fabric.Polaroidphoto('https://i.stack.imgur.com/cqmQ9.png', { });
photo.on('image:loaded', when_loaded );
photo.set('scaleX', 1);
photo.set('scaleY', 1);
photo.set('top', 180);
photo.set('left', 150);
canvas.add(photo);
canvas.add(
rect= new fabric.Rect({ top: 50, left: 100, width: 50, height: 50, fill: '#f55' }),
circle = new fabric.Circle({ top: 140, left: 230, radius: 75, fill: 'green' }),
triangle = new fabric.Triangle({ top: 300, left: 210, width: 100, height: 100, fill: 'blue' })
);
// required at load to render sub-classed image in group
function when_loaded() {
console.log("when_loaded");
dirty(); // to set dirty : true
canvas.renderAll();
}
// required at load to display sub-classed image in group,
// set dirty:true for groups
function dirty() {
$.each(canvas._objects, function( index, obj ) {
if( typeof obj.type !== 'undefined' && obj.type == 'group') {
obj.dirty= true;
}
});
}
$("#group").on('click', function() {
var activegroup = canvas.getActiveGroup();
var objectsInGroup = activegroup.getObjects();
activegroup.clone(function(newgroup) {
canvas.discardActiveGroup();
objectsInGroup.forEach(function(object) {
canvas.remove(object);
});
canvas.add(newgroup);
newgroup.dirty = true;
});
});
$("#ungroup").click(function(){
var activeObject = canvas.getActiveObject();
if(activeObject.type=="group"){
var items = activeObject._objects;
alert(items);
activeObject._restoreObjectsState();
canvas.remove(activeObject);
for(var i = 0; i < items.length; i++) {
canvas.add(items[i]);
items[i].dirty = true;
canvas.item(canvas.size()-1).hasControls = true;
}
canvas.renderAll();
}
});
Thanks

Building a force-directed concentric circle graph with node ordering from inside out in D3.V4

I am currently building a graph of the relationship between research papers in D3.js. Currently my code allows me to generate a force-directed graph. I can zoom and drag the graph and for the time being "ugly" tooltips display node information on "mouseover" (but that is irrelevant for this question).
I am looking for the best way to visualize the article network based on publishing year. I believe that the best way to do this is to display the nodes by year in a concentric circle pattern, like this:
Simple representation of the expected result of a concentric circle force-directed graph
In the image like in my code nodes are colored based on year.
Here is my plunk link: http://plnkr.co/edit/RCzGe0OFaQNnI32kBuSn?p=preview
And here is my code:
HTML:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<script src="script.js"></script>
</body>
</html>
style.CSS:
/* Styles go here */
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
div.tooltip {
position: absolute;
text-align: center;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
test-data.JSON:
{
"papers":[
{
"id":"1",
"title":"Title 1",
"year":"2016",
"authors":["A1","A2"],
"problematic":"",
"solution":"",
"references":["2","3"]
},
{
"id":"2",
"title":"Title 2",
"year":"2015",
"authors":["A2","A3"],
"problematic":"",
"solution":"",
"references":["4","5"]
},
{
"id":"3",
"title":"Title 3",
"year":"2015",
"authors":["A4","A5"],
"problematic":"",
"solution":"",
"references":["4"]
},
{
"id":"4",
"title":"Title 4",
"year":"2014",
"authors":["A1","A3"],
"problematic":"",
"solution":"",
"references":[]
},
{
"id":"5",
"title":"Title 5",
"year":"2013",
"authors":["A6","A7"],
"problematic":"",
"solution":"",
"references":[]
}
]
}
script.js:
/* ------ DESCRIPTION ------
Properties of the graph:
BASIC:
✓ Graph represents all papers and relationships in RTB research
✓ Graph is force dynamic
✓ Nodes are coloured by publishing year
✓ Graph is draggable
✓ Graph is zoomable
X Graph is "tree like" where the nodes are "ordered" by publishing year, the oldest being at the bottom
~ Hovering over a Node will display it's info
- Clicking a node will allow to visualize it's direct or most important connections
ADVANCED:
- Display papers graph
- Display authors graph
- Search for paper based on info: id, title, author, year, ...
- Add new paper to graph and modify and save JSON file
- Open PDF File in new Tab
*/
// ----- GLOBAL VARIABLES ------
var w = window.innerWidth;
var h = window.innerHeight;
var svg = d3.select("body").append("svg")
.attr("width",w)
.attr("height",h)
.style("cursor","move");
var g = svg.append("g");
// NODE COLORS
var color = d3.scaleOrdinal(d3.schemeCategory20);
// FORCE SIMULATION
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(w / 2, h / 2))
.force("collide", d3.forceCollide(10));
// ZOOM PARAMETERS
var min_zoom = 0.1;
var max_zoom = 7;
var zoom = d3.zoom()
.scaleExtent([min_zoom,max_zoom])
.on("zoom", zoomed);
svg.call(zoom);
var transform = d3.zoomIdentity
.translate(w / 6, h / 6)
.scale(0.5);
svg.call(zoom.transform, transform);
// BASIC NODE SIZE
var nominal_stroke = 1.5;
var nominal_node_size = 8;
// ----- GLOBAL FUNCTIONS -----
function dragStart(d){
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragging(d){
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnd(d){
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function zoomed() {
g.attr("transform", d3.event.transform);
// Manually offsets the zoom to compensate for the initial position. Should get fixed asap or the position variables made global.
//svg.attr("transform", "translate(" + (d3.event.transform.x + 400) + "," + (d3.event.transform.y + 325) + ")scale(" + d3.event.transform.k + ")");
}
function isInList(el, list){
for (var i = 0; i < list.length; i++){
if (el == list[i]) return true;
}
return false;
}
// builds a graph dictionary based on paper references
function referencesGraph(file_data){
var nodes = [];
var links = [];
// we use these to add nodes to references that are missing as nodes
var node_ids = [];
var ref_ids = [];
// for each paper in graph create a node and append result to node list
for (var i = 0; i < file_data.length; i++ ){
var node = {
"id":file_data[i].id,
"title":file_data[i].title,
"year":file_data[i].year,
"authors":file_data[i].authors
};
node_ids.push(file_data[i].id);
nodes.push(node);
// for each referenced paper in graph create a link and append result to link list
for (var j = 0; j < file_data[i].references.length; j++){
var link = {
"source":file_data[i].id,
"target":file_data[i].references[j]
};
ref_ids.push(file_data[i].references[j]);
links.push(link);
}
}
//check if all referenced elements have a node associated
for (var i = 0; i < ref_ids.length; i++){
if (!isInList(ref_ids[i],node_ids)){
var node = {
"id":ref_ids[i],
"title":ref_ids[i],
"year":""
}
nodes.push(node);
}
}
var graph = {
"nodes":nodes,
"links":links
};
return graph;
}
// builds a graph dictionary based on author collaboration
function authorsGraph(data){
}
// DEAL WITH MISSING DATA TO BE WORKED
// ----- MANAGE JSON DATA -----
d3.json("test-data.json",function(error,graph){
if (error) throw error;
// Read the JSON data and create a dictionary of nodes and links based on references
var paper_graph_data = referencesGraph(graph.papers);
//var authors_graph_data; //function not implemented yet
// INITIALIZE THE LINKS
var link = g.append("g")
.attr("class","links")
.selectAll("line")
.data(paper_graph_data.links)
.enter()
.append("line")
.attr("stroke-width",function(d){return nominal_stroke})
/* FUNCTION THAT CREATES DIV ELEMENT TO HOLD NODE INFORMATION
[ PAPER TITLE ]
[ PUBLISHING YEAR ][ PERSONAL RATING ]
[ AUTHORS & LINKS ]
[ PROBLEMATIC ]
[ SOLUTION ]
[OPEN PDF FILE]
*/
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
function createTooltip(d){
//get node data, manage missing values
div.transition()
.duration(200)
.style("opacity", .9);
div.html("<table><tr><td>" + d.title + "</td></tr><tr><td>" + d.year + "</td></tr><tr><td>" + d.authors + "</td></tr><tr><td>" + d.problematic + "</td></tr><tr><td>" + d. solution + "</td></tr></table>")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}
// INITIALIZE THE NODES
var node = g.append("g")
.attr("class","nodes")
.selectAll("circles")
.data(paper_graph_data.nodes)
.enter()
.append("circle")
.attr("r",nominal_node_size)
.attr("fill",function(d){return color(d.year);})
.style("cursor","pointer")
.on("mouseover",createTooltip)
.on("mouseout",function(d){
div.transition()
.duration(500)
.style("opacity", 0);
})
.call(d3.drag()
.on("start", dragStart)
.on("drag", dragging)
.on("end", dragEnd));
simulation.nodes(paper_graph_data.nodes)
.on("tick",ticked);
simulation.force("link")
.links(paper_graph_data.links);
// function to return link and node position when simulation is generated
function ticked(){
// Each year is placed on a different level to get chronological order of paper network
/*
switch(d.source.year){
case "2016":
return 40;
case "2015":
return 80;
case "2014":
return 120;
case "2013":
return 160;
case "2012":
return 200;
case "2011":
return 240;
case "2010":
return 280;
case "2009":
return 320;
case "2008":
return 360;
case "2007":
return 400;
default:
return 600;
}
*/
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function ticked_advanced(){
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) {
switch(d.source.year){
case "2016":
return 40;
case "2015":
return 80;
case "2014":
return 120;
case "2013":
return 160;
case "2012":
return 200;
case "2011":
return 240;
case "2010":
return 280;
case "2009":
return 320;
case "2008":
return 360;
case "2007":
return 400;
default:
return 600;
}
})
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) {
switch(d.target.year){
case "2016":
return 40;
case "2015":
return 80;
case "2014":
return 120;
case "2013":
return 160;
case "2012":
return 200;
case "2011":
return 240;
case "2010":
return 280;
case "2009":
return 320;
case "2008":
return 360;
case "2007":
return 400;
default:
return 600;
}
});
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) {
switch(d.year){
case "2016":
return 40;
case "2015":
return 80;
case "2014":
return 120;
case "2013":
return 160;
case "2012":
return 200;
case "2011":
return 240;
case "2010":
return 280;
case "2009":
return 320;
case "2008":
return 360;
case "2007":
return 400;
default:
return 600;
}
});
}
});
I imagine that I have to modify the tick function so as to return x and y random coordinates within each "year zone" but don't know how to calculate this.
Any ideas on how to do this? Thanks a lot.
Note:
I found this answer to generating a random number in an annulus that also refers to generating a random number in a circle uniformly:
Generate a uniformly random point within an annulus (ring)
I think there are a few ways to do this,
One way as shown below is to constrain the possible locations that the nodes can move to. I created a constrain(d) function that takes in a node and updates its x/y to fit within a circular area defined by the number of years in the dataset. Any time the node positions are updated, just call the constrain function and they will stay within their defined areas. One drawback of this is that the edge forces will tend to pull them to the boundaries.
var graph = {
"papers": [{
"id": "1",
"title": "Title 1",
"year": "2016",
"authors": ["A1", "A2"],
"problematic": "",
"solution": "",
"references": ["2", "3"]
}, {
"id": "2",
"title": "Title 2",
"year": "2015",
"authors": ["A2", "A3"],
"problematic": "",
"solution": "",
"references": ["4", "5"]
}, {
"id": "3",
"title": "Title 3",
"year": "2015",
"authors": ["A4", "A5"],
"problematic": "",
"solution": "",
"references": ["4"]
}, {
"id": "4",
"title": "Title 4",
"year": "2014",
"authors": ["A1", "A3"],
"problematic": "",
"solution": "",
"references": []
}, {
"id": "5",
"title": "Title 5",
"year": "2013",
"authors": ["A6", "A7"],
"problematic": "",
"solution": "",
"references": []
}]
};
var w = window.innerWidth;
var h = window.innerHeight;
var maxRadStep = 100;
var cX = w / 2,
cY = h / 2;
var years = d3.set(graph.papers.map(function(obj) {
return +obj.year;
})).values();
years.sort();
function constrain(d) {
var yearIndex = years.indexOf(d.year);
var max = (maxRadStep * (yearIndex + 1)) - 10;
var min = (max - maxRadStep) + 20;
var vX = d.x - cX;
var vY = d.y - cY;
var magV = Math.sqrt(vX * vX + vY * vY);
if (magV > max) {
d.vx = 0;
d.vy = 0;
d.x = cX + vX / magV * max;
d.y = cY + vY / magV * max;
} else if (magV < min) {
d.vx = 0;
d.vy = 0;
d.x = cX + vX / magV * min;
d.y = cY + vY / magV * min;
}
}
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.style("cursor", "move");
var g = svg.append("g");
// NODE COLORS
var color = d3.scaleOrdinal(d3.schemeCategory20);
// FORCE SIMULATION
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody().strength(-100))
//.force("center", d3.forceCenter(w / 2, h / 2))
.force("collide", d3.forceCollide(10));
// ZOOM PARAMETERS
var min_zoom = 0.1;
var max_zoom = 7;
var zoom = d3.zoom()
.scaleExtent([min_zoom, max_zoom])
.on("zoom", zoomed);
svg.call(zoom);
var transform = d3.zoomIdentity
.translate(w / 6, h / 6)
.scale(0.5);
svg.call(zoom.transform, transform);
// BASIC NODE SIZE
var nominal_stroke = 1.5;
var nominal_node_size = 8;
// ----- GLOBAL FUNCTIONS -----
function dragStart(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragging(d) {
console.log(d3.event.x + ' ' + d3.event.y);
d.fx = d3.event.x;
d.fy = d3.event.y;
constrain(d);
}
function dragEnd(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function zoomed() {
g.attr("transform", d3.event.transform);
// Manually offsets the zoom to compensate for the initial position. Should get fixed asap or the position variables made global.
//svg.attr("transform", "translate(" + (d3.event.transform.x + 400) + "," + (d3.event.transform.y + 325) + ")scale(" + d3.event.transform.k + ")");
}
function isInList(el, list) {
for (var i = 0; i < list.length; i++) {
if (el == list[i]) return true;
}
return false;
}
// builds a graph dictionary based on paper references
function referencesGraph(file_data) {
var nodes = [];
var links = [];
// we use these to add nodes to references that are missing as nodes
var node_ids = [];
var ref_ids = [];
// for each paper in graph create a node and append result to node list
for (var i = 0; i < file_data.length; i++) {
var node = {
"id": file_data[i].id,
"title": file_data[i].title,
"year": file_data[i].year,
"authors": file_data[i].authors
};
node_ids.push(file_data[i].id);
nodes.push(node);
// for each referenced paper in graph create a link and append result to link list
for (var j = 0; j < file_data[i].references.length; j++) {
var link = {
"source": file_data[i].id,
"target": file_data[i].references[j]
};
ref_ids.push(file_data[i].references[j]);
links.push(link);
}
}
//check if all referenced elements have a node associated
for (var i = 0; i < ref_ids.length; i++) {
if (!isInList(ref_ids[i], node_ids)) {
var node = {
"id": ref_ids[i],
"title": ref_ids[i],
"year": ""
}
nodes.push(node);
}
}
var graph = {
"nodes": nodes,
"links": links
};
return graph;
}
// builds a graph dictionary based on author collaboration
function authorsGraph(data) {
}
// DEAL WITH MISSING DATA TO BE WORKED
// ----- MANAGE JSON DATA -----
// Read the JSON data and create a dictionary of nodes and links based on references
var paper_graph_data = referencesGraph(graph.papers);
//var authors_graph_data; //function not implemented yet
// INITIALIZE THE LINKS
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(paper_graph_data.links)
.enter()
.append("line")
.attr("stroke-width", function(d) {
return nominal_stroke
})
// INITIALIZE THE NODES
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circles")
.data(paper_graph_data.nodes)
.enter()
.append("circle")
.attr("r", nominal_node_size)
.attr("fill", function(d) {
return color(d.year);
})
.style("cursor", "pointer")
.call(d3.drag()
.on("start", dragStart)
.on("drag", dragging)
.on("end", dragEnd));
g.append('g')
.attr('class', 'boundry')
.selectAll('.boundry')
.data(years)
.enter()
.append('circle')
.attr('r', function(d, index) {
return (index + 1) * maxRadStep;
}).attr('cx', cX).attr('cy', cY);
simulation.nodes(paper_graph_data.nodes)
.on("tick", ticked);
simulation.force("link")
.links(paper_graph_data.links);
function ticked() {
node.each(constrain);
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
}
/* Styles go here */
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
.boundry circle {
stroke: #000;
fill: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

Flot Selection to Control multiple charts

I've been trying to modify the code from the flot examples (http://www.flotcharts.org/flot/examples/zooming/) so that I can replot a number of charts depending on the selection (i.e. changing xaxis.min and xaxis.max) and keep them all correlated.
I have no idea where to begin with regards to modifying the example, any suggestions or hints would be most welcome.
Thanks
Update modified code from zoom example help still required!
enter code here
$(function () {
var plots = [];
var placeholders = $(".flot");
var d1 = [];
for (var i = 0; i < Math.PI * 2; i += 0.25)
d1.push([i, Math.sin(i)]);
var data = [ d1 ];
var d2 = [];
for (var i = 0; i < Math.PI * 2; i += 0.25)
d2.push([i, Math.cos(i)]);
var data2 = [ d2 ];
var options = {
series: { lines: { show: true }, shadowSize: 0 },
/*xaxis: { zoomRange: [0.1, 10], panRange: [-10, 10] },
yaxis: { zoomRange: [0.1, 10], panRange: [-10, 10] },
zoom: {
interactive: true
},
pan: {
interactive: true
}*/
selection: { mode: "x"}
};
plots.push($.plot(placeholder, data, options));
plots.push($.plot(placeholder1, data2, options));
placeholders.bind("plotselected", function (event, plot) {
var axes = plot.getAxes();
for(var i=0; i< plots.length; i++) {
plots[i].getOptions().xaxes.min = axes.xaxis.from;
plots[i].getOptions().xaxes.max = axes.xaxis.to;
//plots[i].getOptions().yaxes[0].min = axes.yaxis.min;
//plots[i].getOptions().yaxes[0].max = axes.yaxis.max;
plots[i].setupGrid();
plots[i].draw();
plots[i].clearSelection();
}
});
});

nodejs - how to keep an status at top of stdout

I'm trying to output something like these:
counter is: 10 <= fixed line and auto updating
console.logs, etc... <= other console.logs, errors, defaul outputs
console.logs, etc...
console.logs, etc...
console.logs, etc...
Is this possible?
I have tried with process.stdout.write() but it is not working.
var counter = 0;
setInterval(function(){
counter++;
process.stdout.write("counter is " + counter + " \r");
}, 500);
setInterval(function(){
console.log('some output');
}, 1500);
Here's an example using blessed:
var blessed = require('blessed');
var screen = blessed.screen(),
body = blessed.box({
top: 1,
left: 0,
width: '100%',
height: '99%'
}),
statusbar = blessed.box({
top: 0,
left: 0,
width: '100%',
height: 1,
style: {
fg: 'white',
bg: 'blue'
}
});
screen.append(statusbar);
screen.append(body);
screen.key(['escape', 'q', 'C-c'], function(ch, key) {
return process.exit(0);
});
function status(text) { statusbar.setContent(text); screen.render(); }
function log(text) { body.insertLine(0, text); screen.render(); }
var c = 1;
setInterval(function() {
status((new Date()).toISOString());
log('This is line #' + (c++));
}, 100);
Here's a simpler example that has almost the same effect (the status bar doesn't fill in extra space with background color):
var screen = blessed.screen(),
body = blessed.box({
top: 0,
left: 0,
width: '100%',
height: '100%',
tags: true
});
screen.append(body);
screen.key(['escape', 'q', 'C-c'], function(ch, key) {
return process.exit(0);
});
function status(text) {
body.setLine(0, '{blue-bg}' + text + '{/blue-bg}');
screen.render();
}
function log(text) {
body.insertLine(1, text);
screen.render();
}
var c = 1;
setInterval(function() {
status((new Date()).toISOString());
log('This is line #' + (c++));
}, 100);
Aside there are a lot of node modules that can help you do this,(blessed, ncurses, ansi, termhelper), for educational purposes you can also do it with vanilla node easily using process.stdout.moveCursor:
var logs = [];
function log(text) {
logs.push(text);
console.log(text);
}
function changeCounter(n) {
process.stdout.moveCursor(0, -logs.length - 1);
printCounter(n);
logs.forEach(function (log) { console.log(log) });
}
function printCounter(n) {
console.log('Counter is:', n);
}
// Now lets test
printCounter(0);
var i = 1;
setInterval(function () {
log('meoww');
changeCounter(i++);
});
Though you have to write to extra code to prevent overflowing terminal.
A traditional library for doing that sort of thing (drawing text at other than the bottom of the screen) is "curses"...there are bindings for Node.js but there is also "blessed" (ha ha) which looks easier to use: https://github.com/chjj/blessed

Flot tooltip hovering

I have an issue with the tooltip in flot. I'm passing a timestamp data and it will reflect as a number, e.g 1113340002003. What I want to do is that when I hover over a data point, it will reflect as the date: 01/04/2012 and not that number. Any help would be great! Stuck here for a few hours....
This is what I'm passing to plot:
var time = (new Date(dates[i]));
graph.push([time, demand[i]]);
This is the section that I used to plot my graph:
var options = {
series: {
lines: { show: true },
points: { show: true }
},
grid: { hoverable: true, clickable: true },
yaxis: { min: 0, max: 20000 },
xaxis: {
mode: "time", timeformat: "%d/%m/%y"}
var plot = $.plot($("#placeholder"),
[ { data: graph, label: "price" } ],
options);
function showTooltip(x, y, contents) {
$('<div id="tooltip">' + contents + '</div>').css( {
position: 'absolute',
display: 'none',
top: y + 5,
left: x + 5,
border: '1px solid #fdd',
padding: '2px',
'background-color': '#fee',
opacity: 0.80
}).appendTo("body").fadeIn(200);
}
var previousPoint = null;
$("#placeholder").bind("plothover", function (event, pos, item) {
$("#x").text(pos.x.toFixed(2));
$("#y").text(pos.y.toFixed(2));
if($("#enableTooltip:checked").length > 0) {
if (item) {
if (previousPoint != item.dataIndex) {
previousPoint = item.dataIndex;
$("#tooltip").remove();
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
var td = x.split("/");
Just convert it to a javascript data object and then build the string yourself.
var d = new Date(item.datapoint[0]);
var someDay = d.getDate();
var someMonth = d.getMonth() + 1; //months are zero based
var someYear = d.getFullYear();
var stringDate = someMonth + "/" + someDay + "/" + someYear;
var x = stringDate;
var y = item.datapoint[1].toFixed(2);

Resources