简体   繁体   中英

Set marker size based on coordinate values, not pixels, in plotly R

This post - Set marker size in plotly - unfortunately did not help with what I was looking for, and is the only post on the topic I could find. Per the plotly documentation on the size parameter for scatter plots:

"size (number or array of numbers greater than or equal to 0), default: 6, Sets the marker size (in px)"

the (in px) is a problem for me. I'd like to create a plot where the marker sizes are based on the coordinate values, not pixels, that way I can increase the size of a graph (by full screening it, for example) and the points increase in size as well. An example of my code currently:

library(plotly)
mydf <- data.frame(x = rep(1:20, times = 20), y = rep(1:20, each = 20),
                   thesize = 10)
plot_ly(mydf) %>%
  add_trace(x = ~x, y = ~y, type = 'scatter', mode = 'markers', 
            marker = list(symbol = 'hexagon', size = ~thesize, opacity = 0.6))

If you create this plot in R, and then make the plot either larger or smaller by dragging the Rstudio viewer window, or in some other way, you'll notice that the markers stay exactly the same size (10 pixels), which is frustrating. I would love it if I could have these markers have diameter == 1 (on the x axis), rather than be set to a number of pixels. Is this possible?

Any help is super appreciated!!!

  • Let's start with a plot with a defined axis range.

     p <- plot_ly() %>% layout(xaxis = list(range = c(1, 5))) 
  • Now add a JavaScript eventlistener which captures any changes in the plot's layout

     javascript <- " var myPlot = document.getElementsByClassName('plotly')[0]; function resize(eventdata) { // magic happens here } myPlot.on('plotly_relayout', function(eventdata) { resize(eventdata); }); " p <- htmlwidgets::prependContent(p, onStaticRenderComplete(javascript)) p 
  • The event passes eventdata from where we can get the new axis range.

     eventdata['xaxis.range[1]'] 
  • Since we don't know the size of the plot in advance, we determine it via the size of the axis line

     var plot_width = Plotly.d3.select('.xlines-above').node().getBBox()['width']; 
  • We need to call the event once manually to make sure that plot is correctly initialized

Initial plot 变焦之前

Zoom in

在此处输入图片说明 Complete R code

library("plotly")
library("htmlwidgets")

p <- plot_ly() %>%
  add_trace(x = c(1.5, 3, 4.2),
            y = c(-2, 1, 2), 
            type = 'scatter',
            mode = 'lines+markers',
            showlegend = F) %>% 
  add_trace(x = c(2, 3, 4.2),
            y = c(2, 0, -1),
            type = 'scatter',
            mode = 'lines+markers',
            showlegend = F) %>%
  add_trace(x = c(1.5, 3, 4.2),
            y = c(1, 0.5, 1),
            type = 'scatter',
            mode = 'markers',
            showlegend = F) %>% 
  layout(xaxis = list(range = c(1, 5)))
javascript <- "
marker_size = 0.5; // x-axis units
var myPlot = document.getElementsByClassName('plotly')[0];

function resize(eventdata) {
  var xaxis_stop = 5;
  var xaxis_start = 1;
  var plot_width = Plotly.d3.select('.xlines-above').node().getBBox()['width'];
  if (eventdata['xaxis.range[1]'] !== undefined) {
    var update = {'marker.size': marker_size * plot_width / (eventdata['xaxis.range[1]'] - eventdata['xaxis.range[0]'])};
  } else {
    var update = {'marker.size': marker_size * plot_width / (xaxis_stop - xaxis_start)};
  }
  Plotly.restyle(myPlot, update, 2);
}
resize({eventdata: {}});

myPlot.on('plotly_relayout', function(eventdata) {
  resize(eventdata);
});
"
p <- htmlwidgets::prependContent(p, 
                                 onStaticRenderComplete(javascript))
p

Interactive JavaScript example

  • You could start with a defined height/width for your graph and a defined axis range, this way you would know how many pixels is one axis unit.
  • The size of your markers would be initially in the size you want.
  • Whenever a user changes the axis range, a plotly_relayout event will be triggered and you can retrieve the new ranges from the eventdata in xaxis.range[1] (start) and xaxis.range[1] (end)
  • Based on the new ranges you can relayout your marker sizes.

 var plot_width = 500; var plot_height = 500; var margin_l = 100; var margin_r = 100; marker_size = 0.5; // x-axis units xaxis_start = 1; xaxis_stop = 5; var trace1 = { x: [1.5, 2, 3, 4], y: [10, 15, 13, 17], mode: "markers", marker: { size: marker_size * (plot_width - margin_l - margin_r) / (xaxis_stop - xaxis_start) }, showlegend: false }; var trace2 = { x: [2, 3, 4, 4.5], y: [16, 5, 11, 10], mode: "lines" }; var trace3 = { x: [1.5, 2, 3, 4], y: [12, 9, 15, 12], mode: "lines+markers", showlegend: false }; var data = [trace1, trace2, trace3]; var layout = { width: plot_width, height: plot_height, margin: { l: margin_l, r: margin_r }, xaxis: { range: [1, 5] }, showlegend: false }; Plotly.newPlot("myDiv", data, layout); var refplot = document.getElementById("myDiv"); refplot.on("plotly_relayout", function(eventdata) { if (eventdata["xaxis.range[1]"] !== undefined) { var update = { "marker.size": marker_size * (plot_width - margin_l - margin_r) / (eventdata["xaxis.range[1]"] - eventdata["xaxis.range[0]"]) }; } else { var update = { "marker.size": marker_size * (plot_width - margin_l - margin_r) / (xaxis_stop - xaxis_start) }; } Plotly.restyle("myDiv", update, 0); }); 
 <head> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> </head> <body> <div id="myDiv"></div> </body> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM