How to Integrate a data-driven d3JS graph with Shi

2020-07-18 02:32发布

问题:

Yesterday I asked how to bring a d3js javacript file with self-contained data into Shiny to draw a force network graph. Now I am looking for the next step: server.R will read a JSON data file for display in the graph. I've been trying to adapt this example that uses messageHandlers to pass the data into the d3JS. This is beyond my expertise so I am looking for some help. I'm pretty sure it is in the messageHandler where things go awry.

I will gladly publish the full working example because this will lead me to a new level of understanding of R, Shiny and d3JS integration. PS: yes, I have looked at networkD3 and other tools. My d3JS charts are much more complex that the simple example here. :) Next steps will also include making the graph reactive to choosing inputs in Shiny, but I need this issue solved first of course. Thank you so much! Tim

ui.R - Push the button, receive graph!

shinyUI(fluidPage(
  titlePanel("Shiny Binding to d3JS"),
  sidebarLayout(
    sidebarPanel(
      tags$head(tags$link(rel = "stylesheet", type = "text/css", href = "twoNodes.css")),
      actionButton("var_run",label="Create Graph!")
      ),
    mainPanel(
      h3("D3JS FN OUTPUT:"),
      # load D3JS library
      tags$script(src="d3.min.js"),
      # load javascript
      tags$script(src="twoNodes.js"),
      # create div
      tags$div(id="div_graph")
    )
  )
)) 

server.R - currently reads in two nodes and their link. IRL it would query a data store.

library(shiny)
library(rjson)
# Read JSON from the file
json_data <- fromJSON(file="twoNodes.JSON")

shinyServer(
  function(input, output, session) {
    # exception handler for when action button is clicked
    # session$sendCustomMessage is where things start to fall apart
    observe({
      if (input$var_run == 0){
        return()
      } 
      session$sendCustomMessage(type="jsondata",json_data)
      })
  }
)

twoNodes.JSON - the data

{
        "nodes":[
          {"id":0,"name":"Observations","group":"1"},
          {"id":1,"name":"DataSet","group":"2"}
          ],
        "edges":[
          {"source":0,"target":1,"value":""}
          ]
    }

twoNodes.css - style sheet

#nodegroup1{
    fill:#000000;
    fon-family: Serif, Georgia;
    font-size: 14px;   
     font-weight: bold;
  }              
  .nodetext{
     font-size:8;
     color: red;
  }
  .edgelabel{
    font-size:12px;
    fill:darkblue;
  }
  .edges{
    stroke: #ccc;
    stroke-width: 2;
   }

twoNodes.js - the d3JS magic I am trying to harness

Shiny.addCustomMessageHandler("jsondata",
  function(message){
    var dataset = [message];

   d3.select("#tempID").remove()

  // lines from original d3JS follow
  //Width and height for SVG area
  var w = 300;
  var h = 200;
  // changed from body to #div_graph. Added tempID
  var svg = d3.select("#div_graph").append("svg")
              .attr("id","tempID")
              .attr("width", w)
              .attr("height", h);

  svg.append("text")
     .text("Two Nodes in a Force Network")
     .attr("x",10)
     .attr("y",15);

// Data source   - Now comes in with message handler
// d3.json("/d3/CubeStructure/twoNodes.JSON", function(dataset) {

  var force = d3.layout.force()
                .nodes(dataset.nodes)
                .links(dataset.edges)
                .gravity(.05)
                .charge(-180)
                .linkDistance(100)
                .size([w, h])
                .start();
  var drag = force.drag()
                  .on("dragstart", dragstart);

  var edges = svg.selectAll("line")
                 .data(dataset.edges)
                 .enter()
                 .append("line")
                 .attr("id",function(d,i){return 'edge'+i})
                 .attr("class", "edges")
                 .attr("marker-end", "url(#end)");  

  var nodes = svg.selectAll("g.node")
                 .data(dataset.nodes)
                 .enter()
                 .append("g")
                 .attr("class", "node")
                 .on("dblclick", dblclick)
                 .call(drag);
  nodes.append("circle")
       .attr("r", 10)
       .style("stroke", "black") 
       // Mousover Node - highlight node by fading the node colour during mouseover
       .on('mouseover', function(d){
            var nodeSelection = d3.select(this).style({opacity:'0.5'});
       })
       //Mouseout Node  - bring node back to full colour   
       .on('mouseout', function(d){
            var nodeSelection= d3.select(this).style({opacity:'1.0',}) 
       })

 // Node label
  nodes.append("text")
       .attr("class", "nodetext")
       .attr("dx", 12)
       .attr("dy", ".35em")
       .attr("id", function(d,i){return 'nodegroup1'}) // all get the same style
       .text(function(d) { return d.name });            // Just the name
  // Paths along which to apply the edge label
  var edgepaths = svg.selectAll(".edgepath")
                     .data(dataset.edges)
                     .enter()
                     .append('path')
                     .attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
                            'class':'edgepath',
                            'fill-opacity':0,
                            'stroke-opacity':0,
                            'fill':'blue',
                            'stroke':'red',
                            'id':function(d,i) {return 'edgepath'+i}})
                     .style("pointer-events", "none");
  // dx : the starting distance of the label from the source node
  var edgelabels = svg.selectAll(".edgelabel")
                      .data(dataset.edges)
                      .enter()
                      .append('text')
                      .style("pointer-events", "none")
                     .attr({'class':'edgelabel',
                             'id':function(d,i){return 'edgelabel'+i},
                             'dx':40,
                             'dy':0
                            }) ;
    force.on("tick", function() {
             edges.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; });
    nodes.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

    edgepaths.attr('d', function(d) { var path='M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y;
                                        //console.log(d)
                                        return path});       
    // positioning of the label along the edge
    edgelabels.attr('transform',function(d,i){
                 if (d.target.x<d.source.x){
                   bbox = this.getBBox();
                   rx = bbox.x+bbox.width/2;
                   ry = bbox.y+bbox.height/2;
                   return 'rotate(180 '+rx+' '+ry+')';
                 }
                 else {
                   return 'rotate(0)';
                 }
    });
  });     
  // });  // not needed due to msg handler End of reading in JSON from file 
  // Double click to 'unfix' the node and have forces start to act on it again.
  function dblclick(d) {
    d3.select(this).classed("fixed", d.fixed = false);
  }
  // Set the "fixed" property of the dragged node to TRUE when a dragstart event is initiated,
  //   - removes "forces" from acting on that node and changing its position.
  function dragstart(d) {
    d3.select(this).classed("fixed", d.fixed = true);
  }
  }); // end of new function

回答1:

You are fairly close. It works with a minor modification; Line 3 in twoNodes.js should be

var dataset = message;


标签: r d3.js shiny