Use R data.frame object in d3.js using r2d3 - r

I'm trying to understand how an R data frame is passed to a d3.js script using the package r2d3. An extension of the r2d3 bar chart example to access variables in data.frame object would be helpful.
R code:
library(r2d3)
data <- data.frame(nums = c(0.3, 0.6, 0.8, 0.95, 0.40, 0.20))
r2d3(data, script = "test.js")
js code:
var barHeight = Math.floor(height / data.length);
svg.selectAll('rect')
.data(data.nums)
.enter().append('rect')
.attr('width', function(d) { return d * width; })
.attr('height', barHeight)
.attr('y', function(d, i) { return i * barHeight; })
.attr('fill', 'steelblue');
Error:
Error: svg.selectAll(...).data(...).enter is not a function in (test.js#5:4)
TypeError: svg.selectAll(...).data(...).enter is not a function

This error occurs because data.nums is undefined.
Your data variable is not an object containing an array as follows:
var data = {
nums: [0.3,0.6,0.8,0.95,0.40,0.20]
}
But, rather an array containing objects:
var data = [
{nums:0.3},
{nums:0.6},
{nums:0.8},
{nums:0.95},
{nums:0.40},
{nums:0.2}
]
To keep you r side code, we just need to pass the data array itself to selection.data() and access the nums property of each datum:
var barHeight = Math.floor(height / data.length);
svg.selectAll('rect')
.data(data)
.enter().append('rect')
.attr('width', function(d) { return d.nums * width; })
.attr('height', barHeight)
.attr('y', function(d, i) { return i * barHeight; })
.attr('fill', 'steelblue');

Related

D3.js how to merge my real data into a pie chart

I am new to D3 and data visualizing and I'm having some troubles loading my real data.
You'll find my code in the below sections.
Right now I have some data stored in an array, and now what I want to do is, store my actual data from my database into pie charts.
Also if I do this :
var mydata=d3.json("mydatafile");
console.log(mydata);
It shows me all the data I have retrieved from database in a promise array.
Is there any way possible I can get these data and put them in my charts?
The code for my pie chart written in D3js is below:
var aColor = [
'rgb(127, 212, 123)', //green
'rgb(240, 149, 164)', // red
'rgb(181, 174, 175)' //gray
]
var data = [{
"platform": "Yes",
"percentage": 87.00
}, //skyblue
{
"platform": "No",
"percentage": 1.00
}, //darkblue
{
"platform": "N/A",
"percentage": 17.00
}]; //orange
var svgWidth = 200,
svgHeight = 200,
radius = Math.min(svgWidth, svgHeight) / 2;
var svg = d3.select('#graph1').append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
//Create group element to hold pie chart
var g = svg.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var pie = d3.layout.pie().value(function (d) {
return d.percentage;
});
var path = d3.svg.arc()
.outerRadius(80)
.innerRadius(40);
var arc = g.selectAll("arc")
.data(pie(data))
.enter()
.append("g")
.sort((a, b) => b.data.percentage - a.data.percentage);
arc.append("path")
.attr("d", path)
.attr("fill", function (d, i) { return aColor[i]; });
var label = d3.svg.arc()
.outerRadius(20)
.innerRadius(100);
arc.append("text")
.attr("transform", function (d) {
return "translate(" + label.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function (d) {
return +d.data.percentage;
});
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="graph1"></div>
On the other hand as per my backend, I have this project written on asp.net .net framework and I have this function which retrieves all the data on JSON format, which is cool.
public JsonResult BarChart()
{
string query = "select e.ProjectName,cyn.Name From Events e left join ConstYesNoes cyn on cyn.ID = e.ApproveId";
IEnumerable<BarChartsViewModel> ListResults = db.Database.SqlQuery<BarChartsViewModel>(query).ToList();
return Json(ListResults.Select(x => new { Name = x.Name, ApprovedId = x.ApprovedId, ID = x.ID, ProjectName = x.ProjectName }).ToList(), JsonRequestBehavior.AllowGet);
}

How do you animate axis transitions with d3 in r2d3?

I'm trying to create a histogram using D3 which has nice animated transitions for both the bars and the axes. It's straightforward to get the bars working, but I'm struggling to see how to do the same thing with the axes. In the example below the transition looks like it is happening, but it's actually adding a new axis each time without removing the old one.
My ultimate goal is to do the development of widgets like this using R2D3 and then hand over the javascript to someone else to implement in an app in Java, so I need to make sure it is transferable and doesn't use R/shiny/R2D3 specific things in the javascript file.
This is the hist.js script
// !preview r2d3 data=data.frame(density = c(10,20,5), from = c(0, 1, 3), to = c(1, 3, 4))
//
// r2d3: https://rstudio.github.io/r2d3
//
var margin = {left:40, right:30, top:10, bottom:30, axis_offset:10};
var min_from = d3.min(data, function(d) {return d.from;});
var max_to = d3.max(data, function(d) {return d.to;});
var max_density = d3.max(data, function(d) { return d.density;});
svg.append('g')
.attr('transform', 'translate('+(margin.left - margin.axis_offset)+', 0)')
.attr("class", "y_axis");
svg.append('g')
.attr('transform', 'translate(0, '+(height - margin.bottom + margin.axis_offset)+')')
.attr("class", "x_axis");
var x = d3.scaleLinear()
.domain([min_from, max_to])
.range([margin.left, width-margin.right])
.nice();
var y = d3.scaleLinear()
.domain([0, max_density])
.range([height-margin.bottom, margin.top])
.nice();
svg.selectAll('.y_axis')
.transition()
.duration(500)
.call(d3.axisLeft(y));
svg.selectAll('.x_axis')
.transition()
.duration(500)
.call(d3.axisBottom(x));
var bars = svg.selectAll('rect').data(data);
bars.enter().append('rect')
.attr('x', function(d) { return x(d.from); })
.attr('width', function(d) { return x(d.to) - x(d.from)-1;})
.attr('y', function(d) { return y(d.density); })
.attr('height', function(d) { return y(0) - y(d.density); })
.attr('fill', 'steelblue');
bars.exit().remove();
bars.transition()
.duration(500)
.attr('x', function(d) { return x(d.from); })
.attr('width', function(d) { return x(d.to) - x(d.from)-1;})
.attr('y', function(d) { return y(d.density); })
.attr('height', function(d) { return y(0) - y(d.density); });
and this is my shiny app which runs it
library(shiny)
library(r2d3)
library(data.table)
library(jsonlite)
get_hist <- function(x) {
buckets <- seq(0, mean(x)+3*sd(x), length.out = 21)
h <- hist(x, breaks = c(buckets, Inf), plot = FALSE)
y <- data.table(count = h$counts, from = head(h$breaks, -1), to = head(shift(h$breaks, -1), -1))[-.N]
y[, density := count/(to-from)]
y[]
}
new_data <- function() {
sh <- 1
rgamma(10, sh, 1/sh)
}
ui <- fluidPage(
actionButton("add_data", "Add more data"),
d3Output('d3_hist')
)
server <- function(input, output, session) {
samp <- reactiveVal(new_data())
observeEvent(input$add_data, {
samp(c(samp(), new_data()))
})
output$d3_hist <- renderD3({
y <- get_hist(samp())
r2d3(data = toJSON(y), script = 'hist.js')
})
}
shinyApp(ui, server)
Every time you update the data, you create a new g element for each axis. This creates multiple g elements with the class x_axis / y_axis, all of which you call the axis generators on:
svg.append('g') // append a new g every update for x axis
.attr('transform', 'translate(0, '+(height - margin.bottom + margin.axis_offset)+')')
.attr("class", "x_axis");
svg.selectAll('.x_axis') // call axis generator for every x axis g.
.transition()
.duration(500)
.call(d3.axisBottom(x));
The setup with r2d3 is a bit different in that usually you'd create a single g for each axis and then use an update function to update the data and the axes. Here the entire javascript script runs every time, so we need to avoid appending a g for each axis once we have one:
For example:
if(svg.select(".y_axis").empty()) {
svg.append('g')
.attr('transform', 'translate('+(margin.left - margin.axis_offset)+', 0)')
.attr("class", "y_axis");
}
if(svg.select(".x_axis").empty()) {
svg.append('g')
.attr('transform', 'translate(0, '+(height - margin.bottom + margin.axis_offset)+')')
.attr("class", "x_axis");
}
There are different approaches that could be taken here in the javascript, but this is probably the most straight forward. Ideally r2d3 would more easily allow you run an update function rather than the entire script every update.

How to make d3.js box plot handle large dataset?

I am using d3.js , when calling box plot function.
It's working fine with small dataset. But its not working with large
dataset (900 000 data).
I tried with many data set but its only working with small set of
data and its getting hang on large set of data.
I tried to debug the d3.js script for box plot but was not able to
find where the dataset becomes null.
Does the performance of d3.js graphs degrade with increase in dataset? How can I optimize it?
Here's the code :
function box(g) {
g.each(function(data, i) {
//d = d.map(value).sort(d3.ascending);
//var boxIndex = data[0];
//var boxIndex = 1;
var d = data[1].sort(d3.ascending);
var t = tableData.sort(function(a, b){return a-b;});
// console.log(boxIndex);
//console.log(d);
var g = d3.select(this),
n = t.length;
min = 0,
max = d[n-1];
// Compute quartiles. Must return exactly 3 elements.
var quartileData = [];
quartileData[0]=d[1];
quartileData[1]=parseFloat(d[2]);
quartileData[2]=d[3];
var data1 = parseFloat(d[1]);
var data2 = parseFloat(d[3]);
var common = data2-data1;
var value = common.toFixed(5);
// Compute whiskers. Must return exactly 2 elements, or null.
var whiskerData= [];
whiskerData[0] = (data1 - ((value)*1.5)).toFixed(5);
whiskerData[1]= (data2 + ((value)*1.5)).toFixed(5);
// Compute whiskers. Must return exactly 2 elements, or null.
var whiskerIndices = [];
var isRangeFound = false;
whiskerIndices[0] = $.map( t , function( val,i )
{
if(whiskerData[0]< parseFloat(val) && !isRangeFound)
{
isRangeFound = true;
return i;
}
});
var isRangeFound1 = false;
whiskerIndices[1] = $.map( t , function( val,i )
{
if(whiskerData[1] < parseFloat(val) && !isRangeFound1)
{
isRangeFound1 = true;
return i-1;
}
});
// Compute outliers. If no whiskers are specified, all data are "outliers".
// We compute the outliers as indices, so that we can join across transitions!
var outlierIndices = whiskerIndices
? d3.range(0, whiskerIndices[0]).concat(d3.range(parseInt(whiskerIndices[1]) + 1, n))
: d3.range(n);
// Compute the new x-scale.
var x1 = d3.scale.linear()
.domain(domain && domain.call(this,t, i) || [minBound, maxBound])
.range([height, 0]);
// Retrieve the old x-scale, if this is an update.
var x0 = this.__chart__ || d3.scale.linear()
.domain([0, Infinity])
// .domain([0, max])
.range(x1.range());
// Stash the new scale.
this.__chart__ = x1;
// Note: the box, median, and box tick elements are fixed in number,
// so we only have to handle enter and update. In contrast, the outliers
// and other elements are variable, so we need to exit them! Variable
// elements also fade in and out.
// Update center line: the vertical line spanning the whiskers.
var center = g.selectAll("line.center")
.data(whiskerData ? [whiskerData] : []);
//vertical line
center.enter().insert("line", "rect")
.attr("class", "center")
.attr("x1", width / 2)
.attr("y1", function(d) { return x0(d[0]); })
.attr("x2", width / 2)
.attr("y2", function(d) { return x0(d[1]); })
.style("opacity", 1e-6)
.transition()
.duration(duration)
.style("opacity", 1)
.attr("y1", function(d) { return x1(d[0]); })
.attr("y2", function(d) { return x1(d[1]); });
center.transition()
.duration(duration)
.style("opacity", 1)
.attr("y1", function(d) { return x1(d[0]); })
.attr("y2", function(d) { return x1(d[1]); });
center.exit().transition()
.duration(duration)
.style("opacity", 1e-6)
.attr("y1", function(d) { return x1(d[0]); })
.attr("y2", function(d) { return x1(d[1]); })
.remove();
// Update innerquartile box.
var box = g.selectAll("rect.box")
.data([quartileData]);
box.enter().append("rect")
.attr("class", "box")
.attr("x", 0)
.attr("y", function(d) { return x0(d[2]); })
.attr("width", width)
.attr("height", function(d) { return x0(d[0]) - x0(d[2]); })
.transition()
.duration(duration)
.attr("y", function(d) { return x1(d[2]); })
.attr("height", function(d) { return x1(d[0]) - x1(d[2]); });
box.transition()
.duration(duration)
.attr("y", function(d) { return x1(d[2]); })
.attr("height", function(d) { return x1(d[0]) - x1(d[2]); });
// Update median line.
var medianLine = g.selectAll("line.median")
.data((quartileData[1]));
medianLine.enter().append("line")
.attr("class", "median")
.attr("x1", 0)
.attr("y1", x0)
.attr("x2", width)
.attr("y2", x0)
.transition()
.duration(duration)
.attr("y1", x1)
.attr("y2", x1);
medianLine.transition()
.duration(duration)
.attr("y1", x1)
.attr("y2", x1);
// Update whiskers.
var whisker = g.selectAll("line.whisker")
.data(whiskerData || []);
whisker.enter().insert("line", "circle, text")
.attr("class", "whisker")
.attr("x1", 0)
.attr("y1", x0)
.attr("x2", 0 + width)
.attr("y2", x0)
.style("opacity", 1e-6)
.transition()
.duration(duration)
.attr("y1", x1)
.attr("y2", x1)
.style("opacity", 1);
whisker.transition()
.duration(duration)
.attr("y1", x1)
.attr("y2", x1)
.style("opacity", 1);
whisker.exit().transition()
.duration(duration)
.attr("y1", x1)
.attr("y2", x1)
.style("opacity", 1e-6)
.remove();
var outlier = g.selectAll("circle.outlier")
.data(outlierIndices, Number);
outlier.enter().insert("circle", "text")
.attr("class", "outlier")
.attr("r", 5)
.attr("cx", width / 2)
.attr("cy", function(i) { return x0(t[i]); })
.style("opacity", 1e-6)
.transition()
.duration(duration)
.attr("cy", function(i) { return x1(t[i]); })
.style("opacity", 1);
outlier.transition()
.duration(duration)
.attr("cy", function(i) { return x1(t[i]); })
.style("opacity", 1);
outlier.exit().transition()
.duration(duration)
.attr("cy", function(i) { return x1(t[i]); })
.style("opacity", 1e-6)
.remove();
// Compute the tick format.
var format = tickFormat || x1.tickFormat(8);
// Update box ticks.
var boxTick = g.selectAll("text.box")
.data(quartileData);
if(showLabels == true) {
boxTick.enter().append("text")
.attr("class", "box")
.attr("dy", ".3em")
.attr("dx", function(d, i) { return i & 1 ? 6 : -6 ;})
.attr("x", function(d, i) { return i & 1 ? + width : 0; })
.attr("y", x0)
.attr("text-anchor", function(d, i) { return i & 1 ? "start" : "end"; })
.text(format)
.transition()
.duration(duration)
.attr("y", x1);
}
boxTick.transition()
.duration(duration)
.text(format)
.attr("y", x1);
// Update whisker ticks. These are handled separately from the box
// ticks because they may or may not exist, and we want don't want
// to join box ticks pre-transition with whisker ticks post-.
var whiskerTick = g.selectAll("text.whisker")
.data(whiskerData || []);
if(showLabels == true) {
whiskerTick.enter().append("text")
.attr("class", "whisker")
.attr("dy", ".3em")
.attr("dx", 6)
.attr("x", width)
.attr("y", x0)
.text(format)
.style("opacity", 1e-6)
.transition()
.duration(duration)
.attr("y", x1)
.style("opacity", 1);
}
whiskerTick.transition()
.duration(duration)
.text(format)
.attr("y", x1)
.style("opacity", 1);
whiskerTick.exit().transition()
.duration(duration)
.attr("y", x1)
.style("opacity", 1e-6)
.remove();
});
d3.timer.flush();
}
function getBoxPlotChart(csvFetch,chartDiv,outilerTotalValue,tableDataValue)
{
var labels = true;
$("#" + chartDiv).empty();
var margin = {top: 30, right: 50, bottom: 70, left: 50};
var width = 410 - margin.left - margin.right;
var height = 380 - margin.top - margin.bottom;
var min = Infinity,
max = -Infinity;
tableData = tableDataValue;
var data = [];
data[0] = [];
// add more rows if your csv file has more columns
// add here the header of the csv file
data[0][0] = "";
// add more rows if your csv file has more columns
data[0][1] = [];
var v1 = parseFloat(csvFetch.firstQuartile),
v2 = parseFloat(csvFetch.medianValue),
v3=parseFloat(csvFetch.thirdQuartile),
v4=parseFloat(csvFetch.minValue),
v5=parseFloat(csvFetch.maxValue);
outlierData = outilerTotalValue;
var rowMax = Math.max(v1, Math.max(v2, Math.max(v3,v5)));
//var rowMin = Math.min(v4, Math.max(v2, Math.max(v3,v1)));
var rowMin=0;
// add more variables if your csv file has more columns
var data1 = parseFloat(v1);
var data2 = parseFloat(v3);
var absDiff = Math.abs(data1-data2);
var iqrValue = absDiff * 1.5;
//var common = data2-data1;
//var value = common.toFixed(5);
// Compute whiskers. Must return exactly 2 elements, or null.
var fence= [];
fence[0] = data1 - iqrValue;
fence[1]= data2 + iqrValue;
var lowerFence=fence[0];
var upperFence=fence[1];
/*End : To correct Y axis labelling : Pranjal */
var rowMax1 = Math.max(v1, Math.max(v2, Math.max(v3,v5)));
var rowMin1 = Math.min(v4, Math.max(v2, Math.max(v3,v1)));
minBound=Math.floor(Math.min(lowerFence,rowMin1));
maxBound=Math.ceil(Math.max(upperFence,rowMax1));
data[0][1].push(v1);
data[0][1].push(v2);
data[0][1].push(v3);
data[0][1].push(v4);
data[0][1].push(v5);
// add more rows if your csv file has more columns
if (rowMax > max) max = rowMax;
if (rowMin < min) min = rowMin;
var chart = d3.box()
//.whiskers(iqr(1.5))
.height(height)
.domain([minBound, maxBound])
.showLabels(labels);
var svg = d3.select("#" + chartDiv).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "box")
.append("g")
.attr("transform", "translate(" + (margin.left+200) + "," + margin.top + ")");
// the x-axis
var x = d3.scale.ordinal()
.domain( data.map(function(d) { console.log(d); return d[0]; } ) )
.rangeRoundBands([0 , width], 0.7, 0.3);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// the y-axis
var y = d3.scale.linear()
.domain([minBound, maxBound])
.range([height + margin.top, 0 + margin.top]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// draw the boxplots
svg.selectAll(".box")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x(d[0]) + "," + margin.top + ")"; } )
.call(chart.width(x.rangeBand()));
/*// add a title
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 + (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "18px");*/
/* Start: Bug: attribute label should present : Salman : 11/03/2016 */
// .text(csvFetch.attributeName+" "+"Vs"+" "+"Distribution");
/* End: Bug: attribute label should present : Salman : 11/03/2016 */
// draw y axis labelling
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text") // and text1
.attr("transform", "rotate(0)")
.attr("y", 6)
.attr("dy", ".71em")
/* Start: Bug: y axis label was getting hidden : Salman : 11/03/2016 */
.style("text-anchor", "start")
/* End: Bug: y axis label was getting hidden : Salman : 11/03/2016 */
.text(csvFetch.attributeName);
// draw x axis labelling
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height + margin.top + 10) + ")")
.call(xAxis)
.append("text") // text label for the x axis
.attr("x", (width / 2)+350 )
.attr("y", 10 )
.attr("dy", ".71em");
// Returns a function to compute the interquartile range.
/*function iqr(k) {
return function(d, i) {
var q1 =d[1];
var q3 = d[3];
var iqr = (q3 - q1)*k;
};
}*/
}

which part of the d3 initialization should be put into meteor autorun to make the charts reactive?

Just started learning meteor and d3 / crossfilter charting libraries.
Picked up some example code off the Web, and have it working in my local app.
I do have an empty this.autorun() function in my meteor client code, but have no idea what part of the lengthy d3 initialization and composition routine should be put into autorun, in order for these charts to react to the data changes.
I have tried to just put the Flights.find().fetch() inside the autorun, but in that case, the page never seem to finish loading.
Here is my entire meteor code:
if (Meteor.isClient) {
Template.dashboard.helpers({
});
Template.dashboard.events({
});
Template.dashboard.rendered = function(){
var flights = Flights.find().fetch();
if (!flights.length) return;
var crossData = crossfilter(flights);
// d3.csv(data, function(error, flights) {
// Various formatters.
var formatNumber = d3.format(",d"),
formatChange = d3.format("+,d"),
formatDate = d3.time.format("%B %d, %Y"),
formatTime = d3.time.format("%I:%M %p");
// A nest operator, for grouping the flight list.
var nestByDate = d3.nest()
.key(function(d) { return d3.time.day(d.date); });
// A little coercion, since the CSV is untyped.
flights.forEach(function(d, i) {
d.index = i;
d.date = parseDate(d.date);
d.delay = +d.delay;
d.distance = +d.distance;
});
// Create the crossfilter for the relevant dimensions and groups.
var flight = crossfilter(flights),
all = flight.groupAll(),
date = flight.dimension(function(d) { return d.date; }),
dates = date.group(d3.time.day),
hour = flight.dimension(function(d) { return d.date.getHours() + d.date.getMinutes() / 60; }),
hours = hour.group(Math.floor),
delay = flight.dimension(function(d) { return Math.max(-60, Math.min(149, d.delay)); }),
delays = delay.group(function(d) { return Math.floor(d / 10) * 10; }),
distance = flight.dimension(function(d) { return Math.min(1999, d.distance); }),
distances = distance.group(function(d) { return Math.floor(d / 50) * 50; });
var charts = [
barChart()
.dimension(hour)
.group(hours)
.x(d3.scale.linear()
.domain([0, 24])
.rangeRound([0, 10 * 24])),
barChart()
.dimension(delay)
.group(delays)
.x(d3.scale.linear()
.domain([-60, 150])
.rangeRound([0, 10 * 21])),
barChart()
.dimension(distance)
.group(distances)
.x(d3.scale.linear()
.domain([0, 2000])
.rangeRound([0, 10 * 40])),
barChart()
.dimension(date)
.group(dates)
.round(d3.time.day.round)
.x(d3.time.scale()
.domain([new Date(2001, 0, 1), new Date(2001, 3, 1)])
.rangeRound([0, 10 * 90]))
.filter([new Date(2001, 1, 1), new Date(2001, 2, 1)])
];
// Given our array of charts, which we assume are in the same order as the
// .chart elements in the DOM, bind the charts to the DOM and render them.
// We also listen to the chart's brush events to update the display.
var chart = d3.selectAll(".chart")
.data(charts)
.each(function(chart) { chart.on("brush", renderAll).on("brushend", renderAll); });
// Render the initial lists.
var list = d3.selectAll(".list")
.data([flightList]);
// Render the total.
d3.selectAll("#total")
.text(formatNumber(flight.size()));
renderAll();
// Renders the specified chart or list.
function render(method) {
d3.select(this).call(method);
}
// Whenever the brush moves, re-rendering everything.
function renderAll() {
chart.each(render);
list.each(render);
d3.select("#active").text(formatNumber(all.value()));
}
// Like d3.time.format, but faster.
function parseDate(d) {
return new Date(2001,
d.substring(0, 2) - 1,
d.substring(2, 4),
d.substring(4, 6),
d.substring(6, 8));
}
window.filter = function(filters) {
filters.forEach(function(d, i) { charts[i].filter(d); });
renderAll();
};
window.reset = function(i) {
charts[i].filter(null);
renderAll();
};
function flightList(div) {
var flightsByDate = nestByDate.entries(date.top(40));
div.each(function() {
var date = d3.select(this).selectAll(".date")
.data(flightsByDate, function(d) { return d.key; });
date.enter().append("div")
.attr("class", "date")
.append("div")
.attr("class", "day")
.text(function(d) { return formatDate(d.values[0].date); });
date.exit().remove();
var flight = date.order().selectAll(".flight")
.data(function(d) { return d.values; }, function(d) { return d.index; });
var flightEnter = flight.enter().append("div")
.attr("class", "flight");
flightEnter.append("div")
.attr("class", "time")
.text(function(d) { return formatTime(d.date); });
flightEnter.append("div")
.attr("class", "origin")
.text(function(d) { return d.origin; });
flightEnter.append("div")
.attr("class", "destination")
.text(function(d) { return d.destination; });
flightEnter.append("div")
.attr("class", "distance")
.text(function(d) { return formatNumber(d.distance) + " mi."; });
flightEnter.append("div")
.attr("class", "delay")
.classed("early", function(d) { return d.delay < 0; })
.text(function(d) { return formatChange(d.delay) + " min."; });
flight.exit().remove();
flight.order();
});
}
function barChart() {
if (!barChart.id) barChart.id = 0;
var margin = {top: 10, right: 10, bottom: 20, left: 10},
x,
y = d3.scale.linear().range([100, 0]),
id = barChart.id++,
axis = d3.svg.axis().orient("bottom"),
brush = d3.svg.brush(),
brushDirty,
dimension,
group,
round;
function chart(div) {
var width = x.range()[1],
height = y.range()[0];
y.domain([0, group.top(1)[0].value]);
div.each(function() {
var div = d3.select(this),
g = div.select("g");
// Create the skeletal chart.
if (g.empty()) {
div.select(".title").append("a")
.attr("href", "javascript:reset(" + id + ")")
.attr("class", "reset")
.text("reset")
.style("display", "none");
g = div.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
g.append("clipPath")
.attr("id", "clip-" + id)
.append("rect")
.attr("width", width)
.attr("height", height);
g.selectAll(".bar")
.data(["background", "foreground"])
.enter().append("path")
.attr("class", function(d) { return d + " bar"; })
.datum(group.all());
g.selectAll(".foreground.bar")
.attr("clip-path", "url(#clip-" + id + ")");
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(axis);
// Initialize the brush component with pretty resize handles.
var gBrush = g.append("g").attr("class", "brush").call(brush);
gBrush.selectAll("rect").attr("height", height);
gBrush.selectAll(".resize").append("path").attr("d", resizePath);
}
// Only redraw the brush if set externally.
if (brushDirty) {
brushDirty = false;
g.selectAll(".brush").call(brush);
div.select(".title a").style("display", brush.empty() ? "none" : null);
if (brush.empty()) {
g.selectAll("#clip-" + id + " rect")
.attr("x", 0)
.attr("width", width);
} else {
var extent = brush.extent();
g.selectAll("#clip-" + id + " rect")
.attr("x", x(extent[0]))
.attr("width", x(extent[1]) - x(extent[0]));
}
}
g.selectAll(".bar").attr("d", barPath);
});
function barPath(groups) {
var path = [],
i = -1,
n = groups.length,
d;
while (++i < n) {
d = groups[i];
path.push("M", x(d.key), ",", height, "V", y(d.value), "h9V", height);
}
return path.join("");
}
function resizePath(d) {
var e = +(d == "e"),
x = e ? 1 : -1,
y = height / 3;
return "M" + (.5 * x) + "," + y
+ "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6)
+ "V" + (2 * y - 6)
+ "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y)
+ "Z"
+ "M" + (2.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8)
+ "M" + (4.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8);
}
}
brush.on("brushstart.chart", function() {
var div = d3.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", null);
});
brush.on("brush.chart", function() {
var g = d3.select(this.parentNode),
extent = brush.extent();
if (round) g.select(".brush")
.call(brush.extent(extent = extent.map(round)))
.selectAll(".resize")
.style("display", null);
g.select("#clip-" + id + " rect")
.attr("x", x(extent[0]))
.attr("width", x(extent[1]) - x(extent[0]));
dimension.filterRange(extent);
});
brush.on("brushend.chart", function() {
if (brush.empty()) {
var div = d3.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", "none");
div.select("#clip-" + id + " rect").attr("x", null).attr("width", "100%");
dimension.filterAll();
}
});
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return x;
x = _;
axis.scale(x);
brush.x(x);
return chart;
};
chart.y = function(_) {
if (!arguments.length) return y;
y = _;
return chart;
};
chart.dimension = function(_) {
if (!arguments.length) return dimension;
dimension = _;
return chart;
};
chart.filter = function(_) {
if (_) {
brush.extent(_);
dimension.filterRange(_);
} else {
brush.clear();
dimension.filterAll();
}
brushDirty = true;
return chart;
};
chart.group = function(_) {
if (!arguments.length) return group;
group = _;
return chart;
};
chart.round = function(_) {
if (!arguments.length) return round;
round = _;
return chart;
};
return d3.rebind(chart, brush, "on");
}
// });
this.autorun(function(){
})
}
}
if (Meteor.isServer) {
Meteor.startup(function () {
});
}
If this helps, here is my attempt at reproducing one of the d3 force layout examples with collision detection / custom gravity functions https://gist.github.com/gmlnchv/80dd206440cca39800b8. I'm using observe() to react to changes.

Add vertical line to line chart in rChart NVD3 (nPlot)

I would like to add several vertical lines indicating particular events in a line chart created with nPlot. Any suggestions?
library(reshape2)
library(ggplot2)
library(rCharts)
ecm <- reshape2::melt(economics[,c('date', 'uempmed', 'psavert')], id = 'date')
p7 <- nPlot(value ~ date, group = 'variable', data = ecm, type = 'lineWithFocusChart')
Final goal: add vertical lines with labels at 'date' = '1967-11-30', '2001-11-30', '1968-01-31', etc.
Ok, so this should answer the question. I think you will notice that this is generally considered well beyond the skill level of an average R user. Eventually, it would be nice to write a custom chart to handle this behavior well and make easy and available to the general R user. I would like to generalize both the dashed and the vertical lines to handle in R. Here is the rCharts viewer demo and the live code example.
library(reshape2)
library(ggplot2)
library(rCharts)
ecm <- reshape2::melt(economics[,c('date', 'uempmed', 'psavert')], id = 'date')
p7 <- nPlot(value ~ date, group = 'variable', data = ecm, type = 'lineWithFocusChart')
#let's add this to make date handling easier
p7$xAxis( tickFormat="#!function(d) {return d3.time.format('%b %Y')(new Date( d * 86400000 ));}!#" )
#grab template from
#https://github.com/ramnathv/rCharts/blob/master/inst/libraries/nvd3/layouts/chart.html
#modify to add callback on graph render
p7$setTemplate(script = sprintf("
<script type='text/javascript'>
$(document).ready(function(){
draw{{chartId}}( );
});
function draw{{chartId}}( ){
var opts = {{{ opts }}};
var data = {{{ data }}};
if(!(opts.type==='pieChart' || opts.type==='sparklinePlus' || opts.type==='bulletChart')) {
var data = d3.nest()
.key(function(d){
//return opts.group === undefined ? 'main' : d[opts.group]
//instead of main would think a better default is opts.x
return opts.group === undefined ? opts.y : d[opts.group];
})
.entries(data);
}
if (opts.disabled != undefined){
data.map(function(d, i){
d.disabled = opts.disabled[i]
})
}
nv.addGraph(function() {
chart = nv.models[opts.type]()
.width(opts.width)
.height(opts.height)
if (opts.type != 'bulletChart'){
chart
.x(function(d) { return d[opts.x] })
.y(function(d) { return d[opts.y] })
}
{{{ chart }}}
{{{ xAxis }}}
{{{ x2Axis }}}
{{{ yAxis }}}
d3.select('#' + opts.id)
.append('svg')
.datum(data)
.transition().duration(500)
.call(chart);
chart.dispatch.brush.on('brushstart',function(){ drawVerticalLines( opts ) });
chart.dispatch.brush.on(
'brushend',
function(){ window.setTimeout(
function() {drawVerticalLines( opts )},
250
)}
);
nv.utils.windowResize(chart.update);
return chart;
},%s);
};
%s
</script>
"
,
#here is where you can type your vertical line/label function
"function() { drawVerticalLines( opts ) }"
,
#add the afterScript here if using with shiny
"
function drawVerticalLines( opts ){
if (!(d3.select('#' + opts.id + ' .nvd3 .nv-focus .nv-linesWrap').select('.vertical-lines')[0][0])) {
d3.select('#' + opts.id + ' .nvd3 .nv-focus .nv-linesWrap').append('g')
.attr('class', 'vertical-lines')
}
vertLines = d3.select('#' + opts.id + ' .nvd3 .nv-focus .nv-linesWrap').select('.vertical-lines').selectAll('.vertical-line')
.data(
[
{ 'date' : new Date('1967-11-30'),
'label' : 'something to highlight 1967'
} ,
{ 'date' : new Date('2001-11-30'),
'label' : 'something to highlight 2001'
}
] )
var vertG = vertLines.enter()
.append('g')
.attr('class', 'vertical-line')
vertG.append('svg:line')
vertG.append('text')
vertLines.exit().remove()
vertLines.selectAll('line')
.attr('x1', function(d){
return chart.xAxis.scale()(d.date/60/60/24/1000)
})
.attr('x2', function(d){ return chart.xAxis.scale()(d.date/60/60/24/1000) })
.attr('y1', chart.yAxis.scale().range()[0] )
.attr('y2', chart.yAxis.scale().range()[1] )
.style('stroke', 'red')
vertLines.selectAll('text')
.text( function(d) { return d.label })
.attr('dy', '1em')
//x placement ; change dy above for minor adjustments but mainly
// change the d.date/60/60/24/1000
//y placement ; change 2 to where you want vertical placement
//rotate -90 but feel free to change to what you would like
.attr('transform', function(d){
return 'translate(' +
chart.xAxis.scale()(d.date/60/60/24/1000) +
',' +
chart.yAxis.scale()(2) +
') rotate(-90)'
})
//also you can style however you would like
//here is an example changing the font size
.style('font-size','80%')
}
"
))
p7
Let me know your thoughts.

Resources