|
| 1 | +/** |
| 2 | + A collection of table column definitions |
| 3 | +*/ |
| 4 | + |
| 5 | +var d3 = require("d3"), |
| 6 | + _ = require("underscore"), |
| 7 | + clustersOfInterest = require("./clustersOfInterest.js"), |
| 8 | + HTX = require("./hiv_tx_network.js"); |
| 9 | + |
| 10 | +/** |
| 11 | + Column definitions for rendered tables |
| 12 | + Each column definition is object-based and has the following components |
| 13 | + |
| 14 | + description: [this relates to the heading] |
| 15 | + value (text): the displayed name of the column |
| 16 | + sort (function): a function which takes the value associated with the table cell, and returns a value upon which to sort the column |
| 17 | + help (text): the text to display in a popover when the user hovers over the column name |
| 18 | + generator: |
| 19 | + a function that returns a data-driven definition of a cell |
| 20 | + it takes as an argument the value that is associated with a cell |
| 21 | + and returns an object with the following values |
| 22 | + |
| 23 | + html (bool): whether or not the returned value should be rendered as HTML (default is no, i.e. text) |
| 24 | + value: what is the value associated with the cell |
| 25 | + volatile (bool): if set, this cell will be re-rendered under operations which could modify how its displayed (e.g. is a CoI editor open) |
| 26 | + format (function): how to render the cell value |
| 27 | + actions (function): generate context-specific menus for the cell |
| 28 | + returns null (none) or a vector (definitions of actions) |
| 29 | + |
| 30 | + An action is an object with the following fields |
| 31 | + icon (text): use this font awesome icon |
| 32 | + action (function): a function that takes the clicked button and the cell value and does something |
| 33 | + help (text): the help message to display on hover over the button |
| 34 | + |
| 35 | + |
| 36 | +*/ |
| 37 | + |
| 38 | +/** |
| 39 | + Secure HIV-TRACE subcluster table columns |
| 40 | +*/ |
| 41 | +function secure_hiv_trace_subcluster_columns(self) { |
| 42 | + return [ |
| 43 | + /** definition for the column which shows the #of cases dx'ed within 36 months |
| 44 | + the value is an array, which enumerates the number of connected components of the 0.5% subcluster, which are ALL within 36 month dx, |
| 45 | + so can be more than one. |
| 46 | + |
| 47 | + The only action is to add the nodes in this subcluster to a CoI editor if open |
| 48 | + |
| 49 | + Accepts a _self_ argument for transitive closure |
| 50 | + |
| 51 | + */ |
| 52 | + { |
| 53 | + description: { |
| 54 | + value: "Cases dx within 36 months", |
| 55 | + sort: function (c) { |
| 56 | + return c.value.length ? c.value[0].length : 0; |
| 57 | + }, |
| 58 | + help: "Number of cases diagnosed in the past 36 months connected only through cases diagnosed within the past 36 months", |
| 59 | + }, |
| 60 | + generator: function (cluster) { |
| 61 | + return { |
| 62 | + html: true, |
| 63 | + value: cluster.recent_nodes, |
| 64 | + volatile: true, |
| 65 | + format: function (v) { |
| 66 | + v = v || []; |
| 67 | + if (v.length) { |
| 68 | + return _.map(v, (e) => e.length).join(", "); |
| 69 | + } |
| 70 | + return ""; |
| 71 | + }, |
| 72 | + actions: function (item, value) { |
| 73 | + if ( |
| 74 | + !clustersOfInterest.get_editor() || |
| 75 | + cluster.recent_nodes.length === 0 |
| 76 | + ) { |
| 77 | + return null; |
| 78 | + } |
| 79 | + return _.map(cluster.recent_nodes, (c) => { |
| 80 | + const nodeset = new Set(c); |
| 81 | + return { |
| 82 | + icon: "fa-plus", |
| 83 | + action: function (button, v) { |
| 84 | + if (clustersOfInterest.get_editor()) { |
| 85 | + clustersOfInterest |
| 86 | + .get_editor() |
| 87 | + .append_node_objects( |
| 88 | + _.filter( |
| 89 | + cluster.children, |
| 90 | + (n) => nodeset.has(n.id) && n.priority_flag > 0 |
| 91 | + ) |
| 92 | + ); |
| 93 | + } |
| 94 | + return false; |
| 95 | + }, |
| 96 | + help: "Add to cluster of interest", |
| 97 | + }; |
| 98 | + }); |
| 99 | + }, |
| 100 | + }; |
| 101 | + }, |
| 102 | + }, |
| 103 | + |
| 104 | + /** definition for the column which shows the #of cases dx'ed within 12 months |
| 105 | + the value is an array, which enumerates the number of connected components of the 0.5% subcluster, which connect through nodes dx'ed 36 month dx, so can be more than one. |
| 106 | + |
| 107 | + The actions are to add the nodes in this subcluster to a CoI editor if open, and to determine if the nodes in this set are already a part of the CoI. |
| 108 | + |
| 109 | + */ |
| 110 | + |
| 111 | + { |
| 112 | + description: { |
| 113 | + value: "Cases dx within 12 months", |
| 114 | + //"value", |
| 115 | + sort: function (c) { |
| 116 | + const v = c.value || []; |
| 117 | + return v.length > 0 ? v[0].length : 0; |
| 118 | + }, |
| 119 | + presort: "desc", |
| 120 | + help: "Number of cases diagnosed in the past 12 months connected only through cases diagnosed within the past 36 months", |
| 121 | + }, |
| 122 | + generator: function (cluster) { |
| 123 | + const definition = { |
| 124 | + html: true, |
| 125 | + value: cluster.priority_score, |
| 126 | + volatile: true, |
| 127 | + format: function (v) { |
| 128 | + v = v || []; |
| 129 | + if (v.length) { |
| 130 | + var str = _.map(v, (c) => c.length).join(", "); |
| 131 | + if ( |
| 132 | + v[0].length >= self.CDC_data["autocreate-priority-set-size"] |
| 133 | + ) { |
| 134 | + var color = "red"; |
| 135 | + return "<span style='color:" + color + "'>" + str + "</span>"; |
| 136 | + } |
| 137 | + return str; |
| 138 | + } |
| 139 | + return ""; |
| 140 | + }, |
| 141 | + }; |
| 142 | + |
| 143 | + definition["actions"] = function (item, value) { |
| 144 | + let result = []; |
| 145 | + |
| 146 | + if (cluster.priority_score.length > 0) { |
| 147 | + result = result.concat( |
| 148 | + _.map(cluster.priority_score, (c) => ({ |
| 149 | + icon: "fa-question", |
| 150 | + help: |
| 151 | + "Do some of these " + |
| 152 | + c.length + |
| 153 | + " nodes belong to a cluster of interest?", |
| 154 | + action: function (this_button, cv) { |
| 155 | + const nodeset = new Set(c); |
| 156 | + this_button = $(this_button.node()); |
| 157 | + if (this_button.data("popover_shown") !== "shown") { |
| 158 | + const popover = this_button |
| 159 | + .popover({ |
| 160 | + sanitize: false, |
| 161 | + placement: "right", |
| 162 | + container: "body", |
| 163 | + html: true, |
| 164 | + content: HTX.HIVTxNetwork.lookup_form_generator, |
| 165 | + trigger: "manual", |
| 166 | + }) |
| 167 | + .on("shown.bs.popover", function (e) { |
| 168 | + var clicked_object = d3.select(this); |
| 169 | + var popover_div = d3.select( |
| 170 | + "#" + clicked_object.attr("aria-describedby") |
| 171 | + ); |
| 172 | + var list_element = popover_div.selectAll( |
| 173 | + self.get_ui_element_selector_by_role( |
| 174 | + "priority-membership-list", |
| 175 | + true |
| 176 | + ) |
| 177 | + ); |
| 178 | + |
| 179 | + list_element.selectAll("li").remove(); |
| 180 | + let check_membership = _.filter( |
| 181 | + _.map(self.defined_priority_groups, (g) => |
| 182 | + //console.log(g); |
| 183 | + [ |
| 184 | + g.name, |
| 185 | + _.filter(g.nodes, (n) => nodeset.has(n.name)) |
| 186 | + .length, |
| 187 | + _.filter( |
| 188 | + g.partitioned_nodes[1]["new_direct"], |
| 189 | + (n) => nodeset.has(n.id) |
| 190 | + ).length, |
| 191 | + _.filter( |
| 192 | + g.partitioned_nodes[1]["new_indirect"], |
| 193 | + (n) => nodeset.has(n.id) |
| 194 | + ).length, |
| 195 | + ] |
| 196 | + ), |
| 197 | + (gg) => gg[1] + gg[2] + gg[3] > 0 |
| 198 | + ); |
| 199 | + |
| 200 | + if (check_membership.length === 0) { |
| 201 | + check_membership = [ |
| 202 | + [ |
| 203 | + "No nodes belong to any cluster of interest or are linked to any of the clusters of interest.", |
| 204 | + ], |
| 205 | + ]; |
| 206 | + } else { |
| 207 | + check_membership = _.map(check_membership, (m) => { |
| 208 | + let description = ""; |
| 209 | + if (m[1]) { |
| 210 | + description += " " + m[1] + " nodes belong"; |
| 211 | + } |
| 212 | + if (m[2]) { |
| 213 | + description += |
| 214 | + (description.length ? ", " : " ") + |
| 215 | + m[2] + |
| 216 | + " nodes are directly linked @ " + |
| 217 | + kGlobals.formats.PercentFormatShort( |
| 218 | + self.subcluster_threshold |
| 219 | + ); |
| 220 | + } |
| 221 | + if (m[3]) { |
| 222 | + description += |
| 223 | + (description.length ? ", " : " ") + |
| 224 | + m[3] + |
| 225 | + " nodes are indirectly linked @ " + |
| 226 | + kGlobals.formats.PercentFormatShort( |
| 227 | + self.subcluster_threshold |
| 228 | + ); |
| 229 | + } |
| 230 | + |
| 231 | + description += |
| 232 | + " to cluster of interest <code>" + |
| 233 | + m[0] + |
| 234 | + "</code>"; |
| 235 | + return description; |
| 236 | + }); |
| 237 | + } |
| 238 | + list_element = list_element |
| 239 | + .selectAll("li") |
| 240 | + .data(check_membership); |
| 241 | + list_element.enter().insert("li"); |
| 242 | + list_element.html((d) => d); |
| 243 | + }); |
| 244 | + |
| 245 | + popover.popover("show"); |
| 246 | + this_button.data("popover_shown", "shown"); |
| 247 | + this_button |
| 248 | + .off("hidden.bs.popover") |
| 249 | + .on("hidden.bs.popover", function () { |
| 250 | + $(this).data("popover_shown", "hidden"); |
| 251 | + }); |
| 252 | + } else { |
| 253 | + this_button.data("popover_shown", "hidden"); |
| 254 | + this_button.popover("destroy"); |
| 255 | + } |
| 256 | + }, |
| 257 | + })) |
| 258 | + ); |
| 259 | + } |
| 260 | + |
| 261 | + if ( |
| 262 | + clustersOfInterest.get_editor() && |
| 263 | + cluster.priority_score.length > 0 |
| 264 | + ) { |
| 265 | + result = result.concat( |
| 266 | + _.map(cluster.priority_score, (c) => { |
| 267 | + const nodeset = new Set(c); |
| 268 | + return { |
| 269 | + icon: "fa-plus", |
| 270 | + action: function (button, v) { |
| 271 | + if (clustersOfInterest.get_editor()) { |
| 272 | + clustersOfInterest |
| 273 | + .get_editor() |
| 274 | + .append_node_objects( |
| 275 | + _.filter( |
| 276 | + cluster.children, |
| 277 | + (n) => |
| 278 | + nodeset.has(n.id) && |
| 279 | + (n.priority_flag === 2 || n.priority_flag === 1) |
| 280 | + ) |
| 281 | + ); |
| 282 | + } |
| 283 | + return false; |
| 284 | + }, |
| 285 | + help: "Add to cluster of interest", |
| 286 | + }; |
| 287 | + }) |
| 288 | + ); |
| 289 | + } |
| 290 | + |
| 291 | + return result; |
| 292 | + }; |
| 293 | + |
| 294 | + return definition; |
| 295 | + }, |
| 296 | + }, |
| 297 | + ]; |
| 298 | +} |
| 299 | + |
| 300 | +/** |
| 301 | + Secure HIV-TRACE node table columns |
| 302 | + SLKP: note that this seems to be fully deprecated. |
| 303 | +*/ |
| 304 | + |
| 305 | +function secure_hiv_trace_node_columns(self) { |
| 306 | + return [ |
| 307 | + { |
| 308 | + description: { |
| 309 | + value: "Recent and Rapid", |
| 310 | + sort: "value", |
| 311 | + help: "Is the node a member of a regular or recent & rapid subcluster?", |
| 312 | + }, |
| 313 | + generator: function (node) { |
| 314 | + return { |
| 315 | + callback: function (element, payload) { |
| 316 | + //payload = _.filter (payload, function (d) {return d}); |
| 317 | + var this_cell = d3.select(element); |
| 318 | + |
| 319 | + var data_to_use = [ |
| 320 | + [payload[0][0], payload[0][1], payload[0][2]], |
| 321 | + [payload[1][0] ? "36 months" : "", payload[1][1]], |
| 322 | + [payload[2][0] ? "12 months" : "", payload[2][1]], |
| 323 | + [ |
| 324 | + payload.length > 3 && payload[3][0] |
| 325 | + ? "Recent cluster >= 3" |
| 326 | + : "", |
| 327 | + payload.length > 3 ? payload[3][1] : null, |
| 328 | + ], |
| 329 | + ]; |
| 330 | + |
| 331 | + this_cell.selectAll("span").remove(); |
| 332 | + |
| 333 | + _.each(data_to_use, (button_text) => { |
| 334 | + //self.open_exclusive_tab_view (cluster_id) |
| 335 | + if (button_text[0].length) { |
| 336 | + var button_obj = this_cell |
| 337 | + .append("span") |
| 338 | + .classed("btn btn-xs btn-node-property", true) |
| 339 | + .classed(button_text[1], true) |
| 340 | + .text(button_text[0]); |
| 341 | + |
| 342 | + if (_.isFunction(button_text[2])) { |
| 343 | + button_obj.on("click", button_text[2]); |
| 344 | + } else { |
| 345 | + button_obj.attr("disabled", true); |
| 346 | + } |
| 347 | + } |
| 348 | + }); |
| 349 | + }, |
| 350 | + value: function () { |
| 351 | + return [ |
| 352 | + [ |
| 353 | + node.subcluster_label |
| 354 | + ? "Subcluster " + node.subcluster_label |
| 355 | + : "", |
| 356 | + "btn-primary", |
| 357 | + node.subcluster_label |
| 358 | + ? function () { |
| 359 | + self.view_subcluster( |
| 360 | + node.subcluster_label, |
| 361 | + (n) => n.subcluster_label === node.subcluster_label, |
| 362 | + "Subcluster " + node.subcluster_label |
| 363 | + ); |
| 364 | + } |
| 365 | + : null, |
| 366 | + ], |
| 367 | + |
| 368 | + [node.priority_flag === 3, "btn-warning"], |
| 369 | + [node.priority_flag === 1, "btn-danger"], |
| 370 | + [node.priority_flag === 2, "btn-danger"], |
| 371 | + ]; |
| 372 | + }, |
| 373 | + }; |
| 374 | + }, |
| 375 | + }, |
| 376 | + ]; |
| 377 | +} |
| 378 | + |
| 379 | +module.exports = { |
| 380 | + secure_hiv_trace_subcluster_columns, |
| 381 | + secure_hiv_trace_node_columns, |
| 382 | +}; |
0 commit comments