DiagrammeR.js (6644B)
1 HTMLWidgets.widget({ 2 3 name: 'DiagrammeR', 4 5 type: 'output', 6 7 initialize: function(el, width, height) { 8 9 /* wait to initialize until renderValue 10 since x not provided until then 11 and mermaid will try to build the diagram 12 as soon as class of the div is set to "mermaid" 13 */ 14 15 /* to prevent auto init() by mermaid 16 not documented but 17 see lines https://github.com/knsv/mermaid/blob/master/src/main.js#L100-L109 18 mermaid_config in global with mermaid_config.startOnLoad = false 19 appears to turn off the auto init behavior 20 allowing us to callback after manually init and then callback 21 after complete 22 */ 23 window.mermaid.startOnLoad = false; 24 25 // set config options for Gantt 26 // undocumented but these can be provided 27 // so from R 28 // m1 <- mermaid(spec) 29 // m1$x$config = list(ganttConfig = list( barHeight = 100 ) ) 30 mermaid.ganttConfig = { 31 titleTopMargin:25, 32 barHeight:20, 33 barGap:4, 34 topPadding:50, 35 sidePadding:100, 36 gridLineStartPadding:35, 37 fontSize:11, 38 numberSectionStyles:4, 39 axisFormatter: [ 40 // Within a day 41 ["%I:%M", function (d) { 42 return d.getHours(); 43 }], 44 // Monday a week 45 ["w. %U", function (d) { 46 return d.getDay() == 1; 47 }], 48 // Day within a week (not monday) 49 ["%a %d", function (d) { 50 return d.getDay() && d.getDate() != 1; 51 }], 52 // within a month 53 ["%b %d", function (d) { 54 return d.getDate() != 1; 55 }], 56 // Month 57 ["%m-%y", function (d) { 58 return d.getMonth(); 59 }] 60 ] 61 }; 62 63 return { 64 // TODO: add instance fields as required 65 } 66 67 }, 68 69 renderValue: function(el, x, instance) { 70 71 // if no diagram provided then assume 72 // that the diagrams are provided through htmltools tags 73 // and DiagrammeR was just used for dependencies 74 if ( x.diagram != "" ) { 75 el.innerHTML = x.diagram; 76 //if dynamic such as shiny remove data-processed 77 // so mermaid will reprocess and redraw 78 el.removeAttribute("data-processed"); 79 el.classList.add('mermaid'); 80 //make sure if shiny that we turn display back on 81 el.style.display = ""; 82 //again if dynamic such as shiny 83 // explicitly run mermaid.init() 84 } else { 85 // set display to none 86 // should we remove instead?? 87 el.style.display = "none"; 88 } 89 90 // check for undocumented ganttConfig 91 // to override the defaults manually entered 92 // in initialize above 93 // note this is really sloppy and will not 94 // work well if multiple gantt charts 95 // with custom configs here 96 if( typeof x.config !== "undefined" && 97 typeof x.config.ganttConfig !== "undefined" ){ 98 Object.keys(x.config.ganttConfig).map(function(k){ 99 window.mermaid.ganttConfig[k] = x.config.ganttConfig[k]; 100 }) 101 } 102 103 104 // use this to sort of make our diagram responsive 105 // or at a minimum fit within the bounds set by htmlwidgets 106 // for the parent container 107 function makeResponsive(el){ 108 var svg = el.getElementsByTagName("svg")[0]; 109 if(svg){ 110 if(svg.width) {svg.removeAttribute("width")}; 111 if(svg.height) {svg.removeAttribute("height")}; 112 svg.style.width = "100%"; 113 svg.style.height = "100%"; 114 } 115 }; 116 117 118 // get all DiagrammeR mermaids widgets 119 dg = document.getElementsByClassName("DiagrammeR"); 120 // run mermaid.init 121 // but use try catch block 122 // to send error to the htmlwidget for display 123 try{ 124 mermaid.init( el ); 125 126 // sort of make our diagram responsive 127 // should we make this an option? 128 // if so, then could easily add to list of post process tasks 129 makeResponsive( el ); 130 131 if (HTMLWidgets.shinyMode) { 132 // Get widget id 133 var id = el.id; 134 135 $("#" + id + " .node").click(function(e) { 136 // Build return object *obj* with node-id and node textContent 137 var obj = { 138 id: e.currentTarget.id, 139 nodeValues: e.currentTarget.textContent 140 }; 141 // Send *obj* to Shiny's inputs (input$[id]+_click e.g.: input$vtree_click)) 142 Shiny.setInputValue(id + "_click", obj, {priority: "event"}); 143 }); 144 } 145 146 /* 147 // change the id of our SVG assigned by mermaid to prevent conflict 148 // mermaid.init has a counter that will reset to 0 149 // and cause duplication of SVG id if multiple 150 d3.select(el).select("svg") 151 .attr("id", "mermaidChart-" + el.id); 152 // now we have to change the styling assigned by mermaid 153 // to point to our new id that we have assigned 154 // will add if since sequence diagrams do not have stylesheet 155 if(d3.select(el).select("svg").select("style")[0][0]){ 156 d3.select(el).select("svg").select("style")[0][0].innerHTML = d3.select(el).select("svg") 157 .select("style")[0][0].innerHTML 158 */ 159 /// sep comment for / in regex .replace(/mermaidChart[0-9]*/gi, "mermaidChart-" + el.id); 160 /*} 161 */ 162 163 // set up a container for tasks to perform after completion 164 // one example would be add callbacks for event handling 165 // styling 166 if (!(typeof x.tasks === "undefined") ){ 167 if ( (typeof x.tasks.length === "undefined") || 168 (typeof x.tasks === "function" ) ) { 169 // handle a function not enclosed in array 170 // should be able to remove once using jsonlite 171 x.tasks = [x.tasks]; 172 } 173 x.tasks.map(function(t){ 174 // for each tasks add it to the mermaid.tasks with el 175 t.call(el); 176 }) 177 } 178 179 } catch(e) { 180 // if error look for last processed DiagrammeR 181 // and send error to the container div 182 // with pre containing the errors 183 var processedDg = d3.selectAll(".DiagrammeR[data-processed=true]"); 184 // select the last 185 processedDg = d3.select(processedDg[0][processedDg[0].length - 1]) 186 // remove the svg 187 processedDg.select("svg").remove(); 188 189 //if dynamic such as shiny remove data-processed 190 // so mermaid will reprocess and redraw 191 if (HTMLWidgets.shinyMode) { 192 el.removeAttribute("data-processed") 193 } 194 195 processedDg.append("pre").html( ["parse error with " + x.diagram, e.message].join("\n") ) 196 } 197 198 }, 199 200 resize: function(el, width, height, instance) { 201 202 } 203 204 205 });