I'm trying to get the right workflow to move something i created using {r2d3} to {htmlwidgets}. For example, the following code works in r2d3 and creates a very simple x-axis:
// set up constants used throughout script
const margin = { top: 80, right: 100, bottom: 80, left: 100 };
const plotWidth = 800 - margin.left - margin.right;
const plotHeight = 400 - margin.top - margin.bottom;
// set width and height of svg element (plot + margin)
svg.attr("width", plotWidth + margin.left + margin.right)
.attr("height", plotHeight + margin.top + margin.bottom); // Set plot background opacity
// create plot group and move it
let plotGroup = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// x-axis
let xAxis = d3.scaleLinear()
.domain([0, 1])
.range([0, plotWidth]);
// add x-axis to plot
plotGroup.append("g")
.attr("transform", "translate(0," + plotHeight + ")")
.call(d3.axisBottom(xAxis).tickFormat(d3.format(".0%")));
However, when I try to create an R package using {htmlwidgets} nothings gets plotted. This is my htmlwidget factory:
function factory(el, width, height) {
return {
setupSvg: () => {
d3.select(el).selectAll('*').remove();
svg = d3.select(el).append('svg'); // Assign to the higher-scoped svg variable
return svg;
},
draw: function() {
// set up constants used throughout script
const margin = { top: 80, right: 100, bottom: 80, left: 100 };
const plotWidth = 800 - margin.left - margin.right;
const plotHeight = 400 - margin.top - margin.bottom;
// set width and height of svg element (plot + margin)
svg.attr("width", plotWidth + margin.left + margin.right)
.attr("height", plotHeight + margin.top + margin.bottom); // Set plot background opacity
// create plot group and move it
let plotGroup = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// x-axis
let xAxis = d3.scaleLinear()
.domain([0, 1])
.range([0, plotWidth]);
// add x-axis to plot
plotGroup.append("g")
.attr("transform", "translate(0," + plotHeight + ")")
.call(d3.axisBottom(xAxis).tickFormat(d3.format(".0%")));
},
renderValue: function() {
svg = this.setupSvg();
},
resize: function(newWidth, newHeight) {
console.log('resize w, h', newWidth, newHeight);
width = newWidth;
height = newHeight;
// Update the dimensions of the SVG element
svg.attr('width', newWidth);
svg.attr('height', newHeight);
// Re-render the plot
this.renderValue();
}
};
}
But nothing gets plotted. When I inspect the html code i can confirm that:
Finally, this is my R code for calling the widget:
#' Visualize Probabilities
#'
#' A function to visualize probabilities using a lollipop chart
#'
#' @title lollipops
#'
#' @return A HTML widget object.
#' @export
#'
lollipops <-
function(data,
width = NULL,
height = NULL,
elementId = NULL) {
# forward options using x
opts = list(data = dataframeToD3(data.frame(data)))
# Define sizing policy
sizingPolicy = htmlwidgets::sizingPolicy(
defaultWidth = 400,
defaultHeight = 400,
browser.fill = TRUE
)
# create widget
htmlwidgets::createWidget(
name = 'lollipops',
opts,
width = width,
height = height,
package = 'vizdraws',
elementId = elementId,
sizingPolicy = sizingPolicy
)
}
What am I missing?
If I am reading correctly, you were really close. Here is a minimal example taking advantage of https://github.com/ramnathv/htmlwidgets/issues/305.
I think the primary reason your code is not working is the lack of this.draw()
in renderValue()
. I made a couple of very minor adjustments that I think will improve the finished product. Let me know if you run into any more trouble, and I'll be happy to help.
library(htmlwidgets)
library(htmltools)
widget_js <- tags$script(HTML("
HTMLWidgets.widget({
name: 'lollipops',
type: 'output',
factory: function(el, width, height) {
return {
svg: null,
setupSvg: function() {
d3.select(el).selectAll('*').remove();
// not necessary but more convenient to access later with this.svg
this.svg = d3.select(el).append('svg'); // Assign to the higher-scoped svg variable
},
draw: function() {
// set up constants used throughout script
const margin = { top: 80, right: 100, bottom: 80, left: 100 };
const plotWidth = 800 - margin.left - margin.right;
const plotHeight = 400 - margin.top - margin.bottom;
const svg = this.svg;
// set width and height of svg element (plot + margin)
svg.attr('width', plotWidth + margin.left + margin.right)
.attr('height', plotHeight + margin.top + margin.bottom); // Set plot background opacity
// create plot group and move it
let plotGroup = svg.append('g')
.attr('transform',
'translate(' + margin.left + ',' + margin.top + ')');
// x-axis
let xAxis = d3.scaleLinear()
.domain([0, 1])
.range([0, plotWidth]);
// add x-axis to plot
plotGroup.append('g')
.attr('transform', 'translate(0,' + plotHeight + ')')
.call(d3.axisBottom(xAxis).tickFormat(d3.format('.0%')));
},
renderValue: function() {
this.setupSvg();
this.draw();
},
resize: function(newWidth, newHeight) {
console.log('resize w, h', newWidth, newHeight);
const width = newWidth;
const height = newHeight;
// Update the dimensions of the SVG element
this.svg.attr('width', newWidth);
this.svg.attr('height', newHeight);
// Re-render the plot
this.renderValue();
}
};
}
})
"))
browsable(tagList(
d3r::d3_dep_v7(),
widget_js,
htmlwidgets::createWidget('lollipops', x = list(), width = "100%")
))