htmlwidget draws a new plot when I update a shiny input? - r

I'm creating my first package using htmlwidgetsand I'm having some troubles integrating the output with shiny. When I update an input my widget draws a new plot below the original one instead of just updating the original plot. For example:
This my js code, my guess is that the problem is there:
HTMLWidgets.widget({
name: 'IMPosterior',
type: 'output',
factory: function(el, width, height) {
// TODO: define shared variables for this instance
return {
renderValue: function(opts) {
//transition
var transDuration = 2500;
var dataDiscrete = opts.bars.map((b, i) => {
b.y = Number(b.y);
b.desc = opts.text[i];
return b;
});
var distParams = {
min: d3.min(opts.data, d => d.x),
max: d3.max(opts.data, d => d.x)
};
distParams.cuts = [-opts.MME, opts.MME, distParams.max];
opts.data = opts.data.sort((a,b) => a.x - b.x);
var dataContinuousGroups = [];
distParams.cuts.forEach((c, i) => {
let data = opts.data.filter(d => {
if (i === 0) {
return d.x < c;
} else if (i === distParams.cuts.length - 1) {
return d.x > distParams.cuts[i - 1];
} else {
return d.x < c && d.x > distParams.cuts[i - 1];
}
});
data.unshift({x:data[0].x, y:0});
data.push({x:data[data.length - 1].x, y:0});
dataContinuousGroups.push({
color: opts.colors[i],
data: data
});
});
var margin = {
top: 50,
right: 20,
bottom: 80,
left: 70
},
dims = {
width: width - margin.left - margin.right,
height: height - margin.top - margin.bottom
};
var xContinuous = d3.scaleLinear()
.domain([distParams.min - 1, distParams.max + 1])
.range([0, dims.width]);
var xDiscrete = d3.scaleBand()
.domain(dataDiscrete.map(function(d) { return d.x; }))
.rangeRound([0, dims.width]).padding(0.1);
var y = d3.scaleLinear()
.domain([0, 1])
.range([dims.height, 0]);
var svg = d3.select(el).append("svg")
.attr("width", dims.width + margin.left + margin.right)
.attr("height", dims.height + margin.top + margin.bottom);
var g = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xAxis = d3.axisBottom()
.scale(xDiscrete);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(10)
.tickFormat(d3.format(".0%"));
var yLabel = g.append("text")
.attr("class", "y-axis-label")
.attr("transform", "rotate(-90)")
.attr("y", -52)
.attr("x", -160)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style("font-size", 14 + "px")
.text("Probability");
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + dims.height + ")")
.call(xAxis);
g.append("g")
.attr("class", "y axis")
.call(yAxis);
var areas = g.selectAll(".area")
.data(dataDiscrete)
.enter().append("path")
.attr("class", "area")
.style("fill", function(d) { return d.color; })
.attr("d", function(d, i) {
let numPts = dataContinuousGroups[i].data.length - 2;
var path = d3.path();
path.moveTo(xDiscrete(d.x), y(0));
for (j=0; j<numPts; j++) {
path.lineTo(xDiscrete(d.x) + j*xDiscrete.bandwidth()/(numPts-1), y(d.y));
}
path.lineTo(xDiscrete(d.x) + xDiscrete.bandwidth(), y(0));
return path.toString();
});
var tooltip = d3.tip()
.attr('class', 'd3-tip chart-data-tip')
.offset([30, 0])
.direction('s')
.html(function(d, i) {
return "<span>" + dataDiscrete[i].desc + "</span>";
});
g.call(tooltip);
areas
.on('mouseover', tooltip.show)
.on('mouseout', tooltip.hide);
var thresholdLine = g.append("line")
.attr("stroke", "black")
.style("stroke-width", "1.5px")
.style("stroke-dasharray", "5,5")
.style("opacity", 1)
.attr("x1", 0)
.attr("y1", y(opts.threshold))
.attr("x2", dims.width)
.attr("y2", y(opts.threshold));
var updateXAxis = function(type, duration) {
if (type === "continuous") {
xAxis.scale(xContinuous);
} else {
xAxis.scale(xDiscrete);
}
d3.select(".x").transition().duration(duration).call(xAxis);
};
var updateYAxis = function(data, duration) {
var extent = d3.extent(data, function(d) {
return d.y;
});
extent[0] = 0;
extent[1] = extent[1] + 0.2*(extent[1] - extent[0]);
y.domain(extent);
d3.select(".y").transition().duration(duration).call(yAxis);
};
var toggle = function(to, duration) {
if (to === "distribution") {
updateYAxis(dataContinuousGroups[0].data.concat(dataContinuousGroups[1].data).concat(dataContinuousGroups[2].data), 0);
updateXAxis("continuous", duration);
areas
.data(dataContinuousGroups)
.transition()
.duration(duration)
.attr("d", function(d) {
var gen = d3.line()
.x(function(p) {
return xContinuous(p.x);
})
.y(function(p) {
return y(p.y);
});
return gen(d.data);
});
thresholdLine
.style("opacity", 0);
g.select(".y.axis")
.style("opacity", 0);
g.select(".y-axis-label")
.style("opacity", 0);
} else {
y.domain([0, 1]);
d3.select(".y").transition().duration(duration).call(yAxis);
updateXAxis("discrete", duration);
areas
.data(dataDiscrete)
.transition()
.duration(duration)
.attr("d", function(d, i) {
let numPts = dataContinuousGroups[i].data.length - 2;
var path = d3.path();
path.moveTo(xDiscrete(d.x), y(0));
for (j=0; j<numPts; j++) {
path.lineTo(xDiscrete(d.x) + j*xDiscrete.bandwidth()/(numPts-1), y(d.y));
}
path.lineTo(xDiscrete(d.x) + xDiscrete.bandwidth(), y(0));
return path.toString();
});
thresholdLine
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1)
.attr("y1", y(opts.threshold))
.attr("y2", y(opts.threshold));
g.select(".y.axis")
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1);
g.select(".y-axis-label")
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1);
}
};
// Add buttons
//container for all buttons
var allButtons = svg.append("g")
.attr("id", "allButtons");
//fontawesome button labels
var labels = ["B", "D"];
//colors for different button states
var defaultColor = "#E0E0E0";
var hoverColor = "#808080";
var pressedColor = "#000000";
//groups for each button (which will hold a rect and text)
var buttonGroups = allButtons.selectAll("g.button")
.data(labels)
.enter()
.append("g")
.attr("class", "button")
.style("cursor", "pointer")
.on("click", function(d, i) {
updateButtonColors(d3.select(this), d3.select(this.parentNode));
d3.select("#numberToggle").text(i + 1);
if (d === "D") {
toggle("distribution", transDuration);
} else {
toggle("discrete", transDuration);
}
})
.on("mouseover", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill", hoverColor);
}
})
.on("mouseout", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill", defaultColor);
}
});
var bWidth = 40; //button width
var bHeight = 25; //button height
var bSpace = 10; //space between buttons
var x0 = 20; //x offset
var y0 = 10; //y offset
//adding a rect to each toggle button group
//rx and ry give the rect rounded corner
buttonGroups.append("rect")
.attr("class", "buttonRect")
.attr("width", bWidth)
.attr("height", bHeight)
.attr("x", function(d, i) {
return x0 + (bWidth + bSpace) * i;
})
.attr("y", y0)
.attr("rx", 5) //rx and ry give the buttons rounded corners
.attr("ry", 5)
.attr("fill", defaultColor);
//adding text to each toggle button group, centered
//within the toggle button rect
buttonGroups.append("text")
.attr("class", "buttonText")
.attr("x", function(d, i) {
return x0 + (bWidth + bSpace) * i + bWidth / 2;
})
.attr("y", y0 + bHeight / 2)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.attr("fill", "white")
.text(function(d) {
return d;
});
function updateButtonColors(button, parent) {
parent.selectAll("rect")
.attr("fill", defaultColor);
button.select("rect")
.attr("fill", pressedColor);
}
toggle("distribution", 0);
setTimeout(() => {
toggle("discrete", transDuration);
}, 1000);
},
resize: function(width, height) {
// TODO: code to re-render the widget with a new size
}
};
}
});
This is the code for the shiny app:
# Gen data --------------------------------------------------------------
set.seed(9782)
x <- rnorm(1000)
# Usy my widget -----------------------------------------------------------
library(IMPosterior)
IMPosterior(x= x, MME=1)
## app.R ##
library(shiny)
library(shinydashboard)
library(IMPosterior) # https://github.com/ignacio82/IMPosterior
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
sliderInput(
"threshold",
h4("Threshold:"),
min = 50,
max = 99,
value = 75,
step = 1,
post = "%"
)
),
dashboardBody(
box(
title = "Posterior Distribution",
status = "primary",
solidHeader = TRUE,
width = 6,
IMPosteriorOutput("plot", height = "350px")
)
)
)
server <- function(input, output) {
output$plot <- renderIMPosterior({
p <- IMPosterior(x = x, MME = 1, threshold = input$threshold/100)
return(p)
})
}
shinyApp(ui, server)
What am I doing wrong?

I'm not sure if this is the best way to solve the problem, but it does. I just needed to add .html("") to my svg right after I select el
var svg = d3.select(el).html("").append("svg")
.attr("width", dims.width + margin.left + margin.right)
.attr("height", dims.height + margin.top + margin.bottom);

Related

Changing the color of an axis line in a D3 chart

I am trying to change the axis to be in red, and I have tried several ways to do it
following this link Blocks.org Example
I tried using a 'class' attribute (see main.css below) and also a 'stroke' and 'fill' attribute. But none of them seem to work. It only works for the Text labels of the axis. See below code and chart screenshot.
Can someone enlighten me?
d3.json("https://dummjsondata",
function(data) {
console.log(data);
// Add X axis --> it is a date format
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
return d3.timeParse("%Y-%m-%d")(d.date);
}))
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
// .attr("class", "axisGrey")
// .attr("stroke", "grey")
// .attr("fill", "grey")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%b")))
.selectAll("text")
.attr("y", 0)
.attr("x", 9)
.attr("dy", ".35em")
// .attr("stroke", "grey")
// .attr("fill", "grey")
.attr("transform", "rotate(45)")
.style("text-anchor", "start");
// Add Y axis Name:
svg.append("text")
.attr("text-anchor", "end")
.attr("x", -25)
.attr("y", height * 0.5)
.attr("stroke", "red")
.text("%")
// Add X axis Name:
svg.append("text")
.attr("text-anchor", "end")
.attr("x", window.innerWidth * 0.5)
.attr("y", height * 1.05)
.attr("stroke", "red")
.text("Time")
// Add Y axis
var y = d3.scaleLinear()
.domain([Math.min(20, d3.min(data, function(d) {
return +d.TFX;
})), Math.max(80, d3.max(data, function(d) {
return +d.TFX;
}))])
// .domain([0, 100])
.range([height, 0]);
svg.append("g")
// .attr("class", "axisGrey")
// .attr("stroke", "grey")
// .attr("fill", "grey")
.attr("class", "axisGrey")
.call(d3.axisLeft(y));
main.css:
.axisGrey line {
stroke: rgb(173, 63, 63);
}
.axisGrey path {
stroke: rgb(197, 40, 40);
}
.axisGrey text {
fill: rgb(212, 36, 36);
}
I changed the code to following
const textColor = "rgb(230, 230, 230)"
const opacValue = 1.0
const xScale = d3.scaleTime().domain(d3.extent(data, function(d) {
return d3.timeParse("%Y-%m-%d")(d.date);
})).range([0, width]);
const yScale = d3.scaleLinear().domain([Math.min(20, d3.min(data, function(d) {
return +d.TFX;
})), Math.max(80, d3.max(data, function(d) {
return +d.TFX;
}))]).range([height, 0]);
const xAxis = d3.axisBottom(xScale).tickFormat(d3.timeFormat("%b"))
const yAxis = d3.axisLeft(yScale)
const gXAxis = svg.append("g").attr("transform", "translate(0," + height + ")")
const gYAxis = svg.append("g")
gXAxis.call(xAxis)
gYAxis.call(yAxis)
gYAxis.selectAll('.tick line').attr('opacity', opacValue).attr('stroke', textColor)
gYAxis.selectAll('.domain').attr('opacity', opacValue).attr('stroke', textColor)
gYAxis.selectAll('.tick').attr('opacity', opacValue).attr('stroke', textColor)
gYAxis.append("text").attr("text-anchor", "end").attr("x", -25).attr("y", height * 0.5).attr("stroke", textColor).text("%")
gXAxis.selectAll('.tick line').attr('opacity', opacValue).attr('stroke', textColor)
gXAxis.selectAll('.domain').attr('opacity', opacValue).attr('stroke', textColor)
gXAxis.selectAll('.tick').attr('opacity', opacValue).attr('stroke', textColor)
gXAxis.selectAll('text').attr("y", 0).attr("x", 9).attr("dy", ".35em").style("text-anchor", "start").attr("transform", "rotate(45)")
gXAxis.append("text").attr("text-anchor", "end").attr("x", innerWidth * 0.5).attr("transform", "translate(0," + 30 + ")").attr("stroke", textColor).text("Time")
First, separate the code of axis from its parts:
const axisBottom = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%b"));
const axisBottomG = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(axisBottom);
axisBottomG.classed('axisGrey', true);
axisBottomG.selectAll("text")
.attr("y", 0)
.attr("x", 9)
.attr("dy", ".35em")
.attr("transform", "rotate(45)")
.style("text-anchor", "start");
Then, inspect (F12) axis <g> element to make sure it has axisGrey class and has the class styles as well.

Background image to bubble chart

I would like to add a background image for each circle of my bubble chart. I already read some solutions on different subject but it doesn't solve my problem. All the other style are correctly added but not the background-image style.
scope.chart = function(rootData){
var diameter = 900,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.value(function(d) { return (d.size); })
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("id","svg")
.attr("class", "bubble");
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(rootData))
.filter(function (d) {
return !d.children;
}))
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function (d) {
return d.r;
})
.style("opacity", "0")
.style("fill", function (d) {
return "black"
})
.style("background-image", function (d){
return "myImage2.png"
});
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); })
.style("opacity", "0");
node.on("click", click);
d3.selectAll("circle").style("fill", function (d) {
return "black"
});
d3.select(this).select("circle").style('fill', '#f2f40d');
d3.selectAll("circle").style("filter", null);
d3.select(this).select("circle").style("filter", "url(#f1)");
}
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function (child) {
recurse(node.name, child);
});
else classes.push({
parameter1: name,
className: node.name,
size: node.size,
});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
}
Edit : This is the code I tried but the image not appears.
/**
* Create the bubble chart graph
* #constructor
*/
d3DemoApp.directive('chart', [function($scope) {
return {
restrict: 'EA',
transclude: true,
scope: {
parameter1: '=parameter1',
paramete2: '=paramete2',
paramete3: '=paramete3',
paramete4: '=paramete4',
paramete5: '=paramete5',
chartData : '=',
myFunction : '=myFunction'
},
link: function(scope) {
scope.$watch('chartData', function(newValue) {
if (newValue){
scope.chart(newValue);
}
});
scope.chart = function(rootData){
d3.select('svg').remove(); // This delete the graph not wanted
d3.select('svg').remove(); // This delete the graph not wanted
var diameter = 900,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.value(function(d) { return (d.paramete5+1); })
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("id","svg")
.attr("class", "bubble");
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(rootData))
.filter(function (d) {
return !d.children;
}))
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
svg.append("defs")
.append("pattern")
.attr("id", "image")
.attr("height","100%")
.attr("width","100%")
.attr("x", "37%")
.attr("y","40%")
.append("image")
.attr("x", "0%")
.attr("y","0%")
.attr("viewBox", "0 0 200 200")
.attr("height","100")
.attr("width","100")
.attr("xlink:href", "linkImage");
node.append("circle")
.attr("r", function (d) {
return d.r;
})
.style("opacity", "0")
.style("fill", function (d) {
return "black"
})
.attr("fill", 'url("#image")');
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); })
.style("opacity", "0");
node.on("click", click);
function click(d) {
scope.$apply(function () {
console.log("Call on apply");
scope.myFunction(); // Call is just correct once scope.parameter1 = d.parameter1;
scope.paramete3 = d.paramete3;
scope.paramete2 = d.title;
scope.paramete4 = d.paramete4;
scope.paramete5 = d.paramete5;
});
d3.selectAll("circle").style("fill", function (d) {
return "black"
});
d3.select(this).select("circle").style('fill', 'circle');
d3.selectAll("circle").style("filter", null);
d3.select(this).select("circle").style("filter", "url(#f1)");
var test = document.getElementById("svg");
function whatClicked(evt) {
if (evt.target.id == "svg"){
d3.selectAll("circle").style("fill", function (d) {
return "black"
});
}
scope.myFunction();
}
test.addEventListener("click", whatClicked, true);
}
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function (child) {
recurse(node.name, child);
});
else classes.push({
parameter1: name,
className: node.name,
title: node.title,
value: node.paramete5,
paramete4: node.projectId,
parameter6: node.parameter7,
paramete5: node.paramete5,
paramete3: node.paramete3,
isLocked:node.isLocked
});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
}
if(typeof scope.chartData != "undefined"){
scope.drawChart(scope.chartData);
}
}
};
}]);
Here's the plunker: https://plnkr.co/edit/Kzr2a4HFG2yJxWfu90ri?p=preview
If you want to show an image in the background of a svg element use defs:
svg.append("defs")
.append("pattern")
.attr("id", "image")
.attr("height","100%")
.attr("width","100%")
.attr("x", "37%")
.attr("y","40%")
.append("image")
.attr("x", "0%")
.attr("y","0%")
.attr("viewBox", "0 0 200 200")
.attr("height","100")
.attr("width","100")
.attr("xlink:href", "ImageLink");
And give this reference to your element by url:
.attr("fill", 'url("#image")');

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.

d3.js: align link-label to link itself

http://i.imgur.com/F2Nqz4b.png?1
Hello Community! That in the picture is what I'd like to achieve...
This is my working code so far (associated to the left half of the picture):
var diagramElement = this.getElement();
var links = eval(this.getState().string);
// string = e.g. "[{source: \"Germany\", target: \"Europe\", property: \"is type\", type: \"connection\"}]"
var width = 800, height = 300;
var svg = d3.select(diagramElement).append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.size([width, height])
.linkDistance(100)
.charge(-400)
.on("tick", tick);
var link;
var circle;
var text;
var nodes = {};
var linktext;
svg.append("defs")
.selectAll("marker").data(["connection", "new"]).enter()
.append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", 0)
.attr("markerWidth", 8)
.attr("markerHeight", 8)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.onStateChange = function() {
svg.selectAll("g").remove();
nodes = {};
links = [];
links = eval(this.getState().string);
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name : link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name : link.target});
});
force
.nodes(d3.values(nodes))
.links(links)
.on("tick", tick)
.start();
link = svg.append("g").selectAll("line.link")
.data(force.links())
.enter()
.append("svg:line")
.attr("class", function(d) {return "link " + d.type;})
.attr("marker-end", function(d) {return "url(#" + d.type + ")";});
circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter()
.append("circle")
.attr("r", 8)
.call(force.drag);
text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter()
.append("text")
.style("font-size","15px")
.attr("x", 10)
.attr("y", ".42em")
.text(function(d) {return d.name;});
// this is where the linktext is aligned relatively to the links
// must change something here
linktext = svg.append("g").selectAll("text")
.data(force.links())
.enter()
.append("text")
.style("font-size","15px")
.attr("dx", 1)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {return d.type;});
};
function tick() {
circle.attr("cx", function(d) { return d.x = Math.max(12, Math.min(798 - 12, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(12, Math.min(279 - 12, d.y)); });
text.attr("transform", transform);
// linktext position update
linktext.attr("transform", function(d) {return "translate(" + (d.source.x + d.target.x) / 2 + "," + (d.source.y + d.target.y) / 2 + ")";});
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; });
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
So I've appended the text successfully to the links, but now how would I position in slightly above the link, in link direction, as shown in my attached picture?
I appreciate any help
Inside on('tick') after determining the position of your links you can also apply some transformations like so:
linkLabels
.attr('transform', d => {
// Adding to the transform. Multiple transforms would just get overwritten.
let transformation = ``
// The text position should be at the middle of the line
const x = (d.source.x + d.target.x) / 2
const y = (d.source.y + d.target.y) / 2
transformation += `translate(${x}, ${y}) `
// Make sure the text is always upright
if(d.source.x > d.target.x) {
transformation += `rotate(180) `
}
// Aligning text to the slope.
// The formula for the tangent of a slope is (y2 - y1) / (x2 - x1)
// where (xi, yi) are the coordinates of the line endings
const angle = Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x)
transformation += `rotate(${angle * 180/Math.PI}) `
return transformation
})

Resources