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

后端 未结 1 1894
被撕碎了的回忆
被撕碎了的回忆 2020-12-22 05:55

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

相关标签:
1条回答
  • 2020-12-22 06:25
    • 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>

    0 讨论(0)
提交回复
热议问题