Created
July 27, 2012 21:56
-
-
Save timelyportfolio/3190664 to your computer and use it in GitHub Desktop.
r websocket for interaction with d3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title>d3.js Axys Chart</title> | |
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame | |
Remove this if you use the .htaccess --> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> | |
<script src="http://d3js.org/d3.v2.min.js?2.9.1"></script> <style> | |
.axis path, .axis line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.line { | |
fill: none; | |
stroke-width: 1.5px; | |
} | |
</style> | |
</head><body><h3>d3.js Axys Chart <a href="http://ramblings.mcpher.com">(thanks so much to ramblings.mcpher.com)</a></h3> | |
<table><tr> | |
<td id="statustd"> | |
<div id="wsdi_status"> Connection not initialized </div> | |
</td> | |
</table> | |
<input type=button id="drawline" value="Draw Cumulative Growth" onclick="cumulLine();"/> | |
<input type=button id="send" value="Send to R" onclick="sendtoR();"/> | |
<br> <script> | |
var perfdata = new Array(); | |
var margin = {top: 20, right: 60, bottom: 60, left: 60}, | |
width = 960 - margin.right - margin.left, | |
height = 400 - margin.top - margin.bottom; | |
var x = d3.time.scale() | |
.range([0, width - 60]); | |
var y = d3.scale.linear() | |
.range([height - 20, 0]); | |
var duration = 1500, | |
delay = 500; | |
var color = d3.scale.category10(); | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.right + margin.left) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
var parse = d3.time.format("%m/%d/%Y").parse, format = d3.time.format("%Y"), | |
perfdata = new Array(); | |
</script><script> var data, origdata = { | |
"cDataSet":[ { | |
"date":"12/31/1991", | |
"portfolio":"16.27", | |
"sp500":"30.47" | |
}, | |
{ | |
"date":"12/31/1992", | |
"portfolio":"7.99", | |
"sp500":"7.62" | |
}, | |
{ | |
"date":"12/31/1993", | |
"portfolio":"6.55", | |
"sp500":"10.08" | |
}, | |
{ | |
"date":"12/31/1994", | |
"portfolio":"0.44", | |
"sp500":"1.32" | |
}, | |
{ | |
"date":"12/31/1995", | |
"portfolio":"16.08", | |
"sp500":"37.58" | |
}, | |
{ | |
"date":"12/31/1996", | |
"portfolio":"11.56", | |
"sp500":"22.96" | |
}, | |
{ | |
"date":"12/31/1997", | |
"portfolio":"18.2", | |
"sp500":"33.36" | |
}, | |
{ | |
"date":"12/31/1998", | |
"portfolio":"13.98", | |
"sp500":"28.58" | |
}, | |
{ | |
"date":"12/31/1999", | |
"portfolio":"9.09", | |
"sp500":"21.04" | |
}, | |
{ | |
"date":"12/31/2000", | |
"portfolio":"0.13", | |
"sp500":"-9.1" | |
}, | |
{ | |
"date":"12/31/2001", | |
"portfolio":"-4.83", | |
"sp500":"-11.89" | |
}, | |
{ | |
"date":"12/31/2002", | |
"portfolio":"-7.81", | |
"sp500":"-22.1" | |
}, | |
{ | |
"date":"12/31/2003", | |
"portfolio":"16.2", | |
"sp500":"28.68" | |
}, | |
{ | |
"date":"12/31/2004", | |
"portfolio":"7.97", | |
"sp500":"10.88" | |
}, | |
{ | |
"date":"12/31/2005", | |
"portfolio":"5.57", | |
"sp500":"4.91" | |
}, | |
{ | |
"date":"12/31/2006", | |
"portfolio":"11.69", | |
"sp500":"15.79" | |
}, | |
{ | |
"date":"12/31/2007", | |
"portfolio":"6.92", | |
"sp500":"5.49" | |
}, | |
{ | |
"date":"12/31/2008", | |
"portfolio":"-25", | |
"sp500":"-37" | |
}, | |
{ | |
"date":"12/31/2009", | |
"portfolio":"19.92", | |
"sp500":"26.46" | |
}, | |
{ | |
"date":"12/31/2010", | |
"portfolio":"13.59", | |
"sp500":"15.06" | |
}, | |
{ | |
"date":"12/31/2011", | |
"portfolio":"-1.58", | |
"sp500":"2.11" | |
} | |
] | |
};</script> <script> | |
var inc = 0; | |
data = origdata.cDataSet; | |
var cumul = new Array(); | |
for (key in data[0]) { | |
if ( key != "date" ) { | |
cumul[key] = 1; | |
firstdate = parse(data[0].date); | |
firstdate.setFullYear(firstdate.getFullYear()-1); | |
perfdata[inc] = { date: firstdate, perf : 0, cumul : cumul[key], symbol : key }; | |
inc = inc + 1; | |
} | |
} | |
data.forEach( function(d) { | |
for (key in d) { | |
if ( key != "date" ) { | |
cumul[key] = cumul[key] * (1 + parseFloat(d[key]) / 100); | |
perfdata[inc] = { date: parse(d.date) , perf : parseFloat(d[key]), cumul : cumul[key], symbol : key }; | |
inc = inc + 1; | |
} | |
}; | |
}); | |
//if we want to filter only some set of what is there | |
//stocks = perfdata.filter(function(d) { return d.symbol in filter; }); | |
// Nest stock values by symbol. | |
symbols = d3.nest() | |
.key(function(d) { return d.symbol; }) | |
.entries(perfdata); | |
// Parse dates and numbers. We assume values are sorted by date. | |
// Also compute the maximum price per symbol, needed for the y-domain. | |
//symbols.forEach(function(s) { | |
// s.values.forEach(function(d) { d.date = parse(d.date); d.price = +d.price; }); | |
//}); | |
// Sort by maximum price, descending. | |
//symbols.sort(function(a, b) { return b.maxPrice - a.maxPrice; }); | |
// Compute the minimum and maximum date across symbols. | |
x.domain([ | |
d3.min(symbols, function(d) { return d.values[0].date; }), | |
d3.max(symbols, function(d) { return d.values[d.values.length - 1].date; }) | |
]); | |
var g = svg.selectAll("g") | |
.data(symbols) | |
.enter().append("g") | |
.attr("class", "symbol"); | |
groupedBar(); | |
// setTimeout(clearBar, duration + delay); | |
function clearBar() { | |
svg.selectAll("text").transition() | |
.duration(duration/2) | |
.remove(); | |
svg.selectAll(".y.axis").transition() | |
.duration(duration/2) | |
.remove(); | |
svg.selectAll("rect").transition() | |
.duration(duration) | |
.style("fill-opacity", 1e-6) | |
.remove(); | |
} | |
function clearLine() { | |
svg.selectAll(".line").remove(); | |
svg.selectAll(".dot").remove(); | |
svg.selectAll(".y.axis").remove(); | |
svg.selectAll(".x.axis").remove(); | |
} | |
function clearR() { | |
svg.selectAll(".rline").remove(); | |
svg.selectAll(".rdot").remove(); | |
svg.selectAll(".y.axis").remove(); | |
svg.selectAll(".x.axis").remove(); | |
} | |
// setTimeout(cumulLine, duration * 2 + delay); | |
function groupedBar() { | |
x = d3.scale.ordinal() | |
.domain(symbols[0].values.map(function(d) { return d.date; })) | |
.rangeBands([0, width - 60], .1); | |
var x1 = d3.scale.ordinal() | |
.domain(symbols.map(function(d) { return d.key; })) | |
.rangeBands([0, x.rangeBand()]); | |
var y0 = Math.max(Math.abs(d3.min(symbols.map(function(d) { return d3.min(d.values.map(function(d) { return d.perf; })); }))), d3.max(symbols.map(function(d) { return d3.max(d.values.map(function(d) { return d.perf; })); }))); | |
y | |
.domain([-y0, y0]) | |
//.domain([d3.min(symbols.map(function(d) { return d3.min(d.values.map(function(d) { return d.perf; })); })), d3.max(symbols.map(function(d) { return d3.max(d.values.map(function(d) { return d.perf; })); }))]) | |
.range([height, 0]) | |
.nice(); | |
var yAxis = d3.svg.axis().scale(y).orient("left"); | |
svg.selectAll(".labels") | |
.data(symbols[0].values.map(function(d) { return d.date; })) | |
.enter().append("text") | |
.attr("class", "labels") | |
.attr("text-anchor", "middle") | |
.attr("x", function(d,i) { return x(i) + x.rangeBand() / 2 ; }) | |
.attr("y", height / 2 + 15) | |
.text(function(d) {return format(d) }) | |
.style("fill-opacity", 1); | |
var g = svg.selectAll(".symbol"); | |
var t = g.transition() | |
.duration(duration); | |
//got working with lots of help but this section particularly dedicated to http://stackoverflow.com/questions/10127402/bar-chart-with-negative-values | |
g.each(function(p, j) { | |
d3.select(this).selectAll("rect") | |
.data(function(d) { return d.values; }) | |
.enter().append("rect") | |
.attr("x", function(d) { return x(d.date) + x1(p.key); }) | |
.attr("y", function(d, i) { return y(Math.max(0, d.perf)); }) | |
//.attr("y", function(d) { return y(d.perf); }) | |
.attr("width", x1.rangeBand()) | |
.attr("height", function(d, i) { return Math.abs(y(d.perf) - y(0)); }) | |
//.attr("height", function(d) { return height - y(d.perf); }) | |
.style("fill", color(p.key)) | |
.style("fill-opacity", 1e-6) | |
//very grateful to http://blog.nextgenetics.net/demo/entry0032/ for help here on mouseover highlighting | |
//another nice example http://bl.ocks.org/2164562 | |
.on('mouseover', function(d, i) { | |
d3.select(this).style('fill','gray'); | |
statusText | |
.text(p.key + " " + d.perf) | |
.attr('fill',color(p.key)) | |
.attr("text-anchor", d.perf < 0 ? "begin" : "begin") | |
.attr("x", x(d.date) + x1(p.key) + x.rangeBand() / 2 ) | |
.attr("y", y(d.perf)) | |
.attr("transform", d.perf < 0 ? "rotate(90 " + (x(d.date) + x1(p.key) + x.rangeBand() / 4 ) + "," + y(d.perf) + ")" : "rotate(-90 " + (x(d.date) + x1(p.key) + x.rangeBand() / 4 ) + "," + y(d.perf) + ")"); | |
}) | |
.on('mouseout', function(d,i) { | |
statusText | |
.text(''); | |
d3.select(this).style('fill',color(p.key)); | |
}) | |
.transition() | |
.duration(duration) | |
.style("fill-opacity", 1); | |
var statusText = svg.append('svg:text'); | |
//d3.select(this).selectAll("text") | |
// .data(function(d) { return d.values; }) | |
// .enter().append("text") | |
// .attr("x", function(d) { return x(d.date) + x1(p.key) + x1.rangeBand() / 2 ; }) | |
// .attr("y", function(d, i) { return y(d.perf) ; }) | |
// .attr("text-anchor", "middle") | |
// .text(function(d) { return d.perf; }) | |
// .style("fill-opacity", 1e-6) | |
// .transition() | |
// .duration(duration) | |
// .style("fill-opacity", 1); | |
}); | |
//svg.append("g") | |
// .attr("class", "x axis") | |
// .call(d3.svg.axis().scale(x).orient("bottom")); | |
svg.append("g") | |
.attr("class", "y axis") | |
.call(yAxis); | |
// .attr("y1", 0) | |
// .attr("y2", height); | |
}; | |
function cumulLine() { | |
clearBar(); | |
clearLine(); | |
clearR(); | |
x = d3.time.scale() | |
.domain([perfdata[0].date, perfdata[perfdata.length - 1].date]) | |
.range([0,width]) | |
var y0 = d3.max(symbols.map(function(d) { return d3.max(d.values.map(function(d) { return d.cumul; })); })); | |
y | |
.domain([d3.min(symbols.map(function(d) { return d3.min(d.values.map(function(d) { return d.cumul; })); })), | |
d3.max(symbols.map(function(d) { return d3.max(d.values.map(function(d) { return d.cumul; })); }))]) | |
.range([height, 0]) | |
.nice(); | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.ticks(d3.time.years, 1) | |
.tickSubdivide(data.length-1) | |
.tickValues([perfdata[0].date, perfdata[perfdata.length - 1].date]) | |
.tickFormat(d3.time.format("%b %Y")); | |
var yAxis = d3.svg.axis().scale(y).orient("left"); | |
// Add the x-axis. | |
svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + y(1) + ")") | |
.call(xAxis); | |
// Add the y-axis. | |
svg.append("g") | |
.attr("class", "y axis") | |
//.attr("transform", "translate(" + width + ",0)") | |
.call(yAxis); | |
var line = d3.svg.line() | |
.x(function (d) {return x(d.date);}) | |
.y(function (d) {return y(d.cumul);}); | |
var g = svg.selectAll(".symbol"); | |
g.each(function(p) { | |
d3.select(this).append("path") | |
.attr("class", "line") | |
.attr("d", line(p.values)) | |
.style("stroke", color(p.key)); | |
d3.select(this).selectAll(".dot") | |
.data(function(d) { return d.values; }) | |
.enter().append("circle") | |
.attr("class", "dot") | |
.attr("cx", line.x()) | |
.attr("cy", line.y()) | |
.attr("r", 3.5) | |
.style("fill", color(p.key)); | |
}); | |
}; function chartR(data) { | |
clearBar(); | |
clearLine(); | |
clearR(); | |
d3.select("body").selectAll("p").remove(); | |
d3.select("body").selectAll("footer").remove(); | |
d3.select("body") | |
.append("p") | |
.text("now thanks to R packages websockets and PerformanceAnalytics"); | |
inc = 0; | |
data.forEach( function(d) { | |
for (key in d) { | |
if ( key != "date" ) { | |
perfdata[inc] = { date: parse(d.date) , stat : parseFloat(d[key]), symbol : key }; | |
inc = inc + 1; | |
} | |
}; | |
}); | |
//if we want to filter only some set of what is there | |
//stocks = perfdata.filter(function(d) { return d.symbol in filter; }); | |
// Nest stock values by symbol. | |
symbols = d3.nest() | |
.key(function(d) { return d.symbol; }) | |
.entries(perfdata); | |
var g = svg.selectAll("g") | |
.data(symbols) | |
.enter().append("g") | |
.attr("class", "symbol"); | |
drawline(); | |
function drawline() { | |
x = d3.time.scale() | |
.domain([perfdata[0].date, perfdata[perfdata.length - 1].date]) | |
.range([0,width]) | |
var y0 = d3.max(symbols.map(function(d) { return d3.max(d.values.map(function(d) { return d.stat; })); })); | |
y | |
.domain([d3.min(symbols.map(function(d) { return d3.min(d.values.map(function(d) { return d.stat; })); })), | |
d3.max(symbols.map(function(d) { return d3.max(d.values.map(function(d) { return d.stat; })); }))]) | |
.range([height, 0]) | |
.nice(); | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.ticks(d3.time.years, 1) | |
.tickSubdivide(data.length-1) | |
.tickValues([perfdata[0].date, perfdata[perfdata.length - 1].date]) | |
.tickFormat(d3.time.format("%b %Y")); | |
var yAxis = d3.svg.axis().scale(y).orient("left"); | |
// Add the x-axis. | |
svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + y(0) + ")") | |
.call(xAxis); | |
// Add the y-axis. | |
svg.append("g") | |
.attr("class", "y axis") | |
//.attr("transform", "translate(" + width + ",0)") | |
.call(yAxis); | |
var line = d3.svg.line() | |
.x(function (d) {return x(d.date);}) | |
.y(function (d) {return y(d.stat);}); | |
var g = svg.selectAll(".symbol"); | |
g.each(function(p) { | |
var thispath = d3.select(this) | |
.append("path") | |
.attr("class", "rline") | |
.style("stroke", color(p.key)) | |
.style("fill","none"); | |
var k = 0, n = symbols[0].values.length; | |
d3.timer( function() { | |
thispath | |
.transition().delay(k * 1000) | |
.attr("d", function() { return line(p.values.slice(0, k + 1)); }) | |
k = k + 1; | |
if (k == n) return true; | |
}); | |
d3.select(this).selectAll(".rdot") | |
.data(function(d) { return d.values; }) | |
.enter().append("circle") | |
.attr("class", "rdot") | |
.attr("cx", line.x()) | |
.attr("cy", line.y()) | |
.attr("r", 3.5) | |
.style("fill", color(p.key)); | |
}); | |
}; | |
}; | |
function sendtoR() { | |
//alert("tryingtosend"); | |
String.prototype.startsWith = function(str){return (this.indexOf(str) === 0);} | |
var socket = new WebSocket("ws://192.168.102.114:7681", "R"); | |
try { | |
socket.onopen = function() { | |
document.getElementById("wsdi_status").textContent = " websocket connection opened "; | |
document.getElementById("statustd").style.backgroundColor = "#40ff40"; | |
} | |
socket.onmessage = function got_packet(msg) { | |
if(msg.data=="hello") { | |
socket.send(JSON.stringify(origdata)); | |
} else { | |
chartR(JSON.parse(msg.data)); | |
} | |
} | |
socket.onclose = function(){ | |
document.getElementById("wsdi_status").textContent = " websocket connection CLOSED "; | |
document.getElementById("statustd").style.backgroundColor = "#ff4040"; | |
} | |
} | |
catch(ex) { | |
document.getElementById("output").textContent = "Error: " + ex; | |
} | |
} </script> | |
<br> | |
<footer> | |
<aside> | |
<small> works best on Chrome <br />acknowledgements</small> | |
<a href="http://d3js.org/"><small>d3.js</small></a> | |
</aside> | |
</footer> | |
</body> | |
</html> | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#I deserve no credit for this code; most comes from the example provided at http://bigcomputing.com/PerformanceAnalytics.R | |
# R/websockets example | |
if(any(is.na(packageDescription('caTools')))) | |
stop("This demo requires the caTools package.\nRun install.packages('caTools') to install it.\n\n") | |
if(any(is.na(packageDescription('PerformanceAnalytics')))) | |
stop("This demo requires the PerformanceAnalytics package.\nRun install.packages('PerformanceAnalytics') to install it.\n\n") | |
library('websockets') | |
library('caTools') | |
library('quantmod') | |
library('PerformanceAnalytics') | |
require('RJSONIO') | |
#w = createContext(webpage=static_text_service(htmldata)) | |
w = createContext() | |
# Set up an established (initialization) callback | |
g = function(DATA, WS, ...) | |
{ | |
websocket_write("hello",WS) | |
print("established connection") | |
} | |
setCallback("established",g, w) | |
# Set up a receive callback | |
f = function(DATA, WS, ...) | |
{ | |
#get data sent from websocket | |
d = tryCatch(rawToChar(DATA),error=function(e) "") | |
#convert JSON message to data.frame and eventually xts | |
perf <- fromJSON(d) | |
perf.df<- as.data.frame(matrix(unlist(fromJSON(d)),ncol=3,byrow=TRUE),stringsAsFactors=TRUE) | |
perf.df[,2:3] <- apply(perf.df[,2:3],MARGIN=2,as.numeric)/100 | |
#name columns | |
colnames(perf.df) <- c("date","portfolio","sp500") | |
#get as xts so convert dates from %m/%d/%Y to %Y-%m-%d | |
perf.xts <- as.xts(perf.df[,2:NCOL(perf.df)],order.by=as.Date(perf.df[,1],format="%m/%d/%Y")) | |
drawdown <- Drawdowns(perf.xts) | |
drawdown.matrix <- as.matrix(cbind(index(drawdown),coredata(drawdown))) | |
colnames(drawdown.matrix) <- c("date",colnames(drawdown)) | |
drawdown.matrix[,"date"] <- format(as.Date(drawdown.matrix[,"date"]),"%m/%d/%Y") | |
#this will send the dataframe so an object of arrays | |
#websocket_write(toJSON(df),WS); | |
#try with matrix which sends an array of objects (d3 prefers and handles better) | |
websocket_write(toJSON(drawdown.matrix),WS) | |
#websocket_write("got your data; thanks",WS) | |
} | |
setCallback("receive",f, w) | |
#daemonize(w) | |
cat("\nThe web service will run until <CTRL>+C is pressed.\n") | |
cat("Open your local web browser to http://localhost:7681\n") | |
while(TRUE) { | |
service(w, timeout=1000L) | |
#old service(w) | |
#old Sys.sleep(0.05) | |
} | |
rm(w) | |
gc() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, thanks for article!
I got a question! When i click a button 'Send to R' It makes a lot of websocket objects. Can suggest good way to delete/remove unused ws objects?