From 68dc8451de2bb00c5b51265c37022e7bd759e770 Mon Sep 17 00:00:00 2001 From: Chris Baker Date: Mon, 23 Apr 2018 11:15:05 -0400 Subject: [PATCH 1/8] change style of elements (topics/terms) upon click and send update to shiny's input object (closes #45) --- inst/htmljs/ldavis.js | 106 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/inst/htmljs/ldavis.js b/inst/htmljs/ldavis.js index 81a6ac0..850cf77 100644 --- a/inst/htmljs/ldavis.js +++ b/inst/htmljs/ldavis.js @@ -16,7 +16,9 @@ LDAvis = function(to_select, json_file) { vis_state = { lambda: 1, topic: 0, - term: "" + term: "", + topic_clicked: 0, + term_clicked: "" }; // Set up a few 'global' variables to hold the data: @@ -89,7 +91,10 @@ LDAvis = function(to_select, json_file) { return 0; }; } - + + // see if vis is being run as part of a shiny app + // (code adapted from https://github.com/ramnathv/htmlwidgets/blob/master/inst/www/htmlwidgets.js#L15) + var inShinyMode = typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; // The actual read-in of the data and main code: d3.json(json_file, function(error, data) { @@ -415,6 +420,7 @@ LDAvis = function(to_select, json_file) { document.getElementById(topicID).value = vis_state.topic = d.topics; state_save(true); topic_on(this); + topic_click(this, d.topics); }) .on("mouseout", function(d) { if (vis_state.topic != d.topics) topic_off(this); @@ -549,6 +555,9 @@ LDAvis = function(to_select, json_file) { // term_on(this); // debugger; // }) + .on("click", function(d) { + term_click(this, d.Term) + }) .on("mouseout", function() { vis_state.term = ""; term_off(this); @@ -992,6 +1001,17 @@ LDAvis = function(to_select, json_file) { function topic_on(circle) { if (circle == null) return null; + // whenever the topic changes we have to remove the underline style + // from any clicked term + var old_term_clicked_id = termID + vis_state.term_clicked; + var topic_clicked_id = topicID + vis_state.topic_clicked; + if (vis_state.term_clicked != "" && circle.id != topic_clicked_id) { + var oldterm = document.getElementById(old_term_clicked_id); + if (oldterm != null) { + oldterm.style.textDecoration = null; + } + } + // grab data bound to this element var d = circle.__data__ var Freq = Math.round(d.Freq * 10) / 10, @@ -1343,8 +1363,24 @@ LDAvis = function(to_select, json_file) { } function state_reset() { + + // set the style of any clicked term back to be non-underline + var old_term_clicked_id = termID + vis_state.term_clicked; + if (vis_state.term_clicked != "") { + var oldterm = document.getElementById(old_term_clicked_id); + if (oldterm != null) { + oldterm.style.textDecoration = null; + } + } + if (vis_state.topic > 0) { topic_off(document.getElementById(topicID + vis_state.topic)); + // set the style of any topic clicked to be back to regular style + // (no thick border around topic circle) + var old_topic_clicked_id = topicID + vis_state.topic_clicked; + if (vis_state.topic_clicked > 0) { + document.getElementById(old_topic_clicked_id).style.strokeWidth = null; + } } if (vis_state.term != "") { term_off(document.getElementById(termID + vis_state.term)); @@ -1352,6 +1388,72 @@ LDAvis = function(to_select, json_file) { vis_state.term = ""; document.getElementById(topicID).value = vis_state.topic = 0; state_save(true); + + // make sure term ids are all correct + d3.selectAll(".terms").attr("id", function(d) { + return (termID + d.Term) + }); + + // update shiny inputs to be null + if (inShinyMode) { + Shiny.onInputChange('ldavis_topic_clicked', null); + Shiny.onInputChange('ldavis_term_clicked', null); + } + + // set state of topic_clicked to 0, so we can click on topic x, reset + // vis, then click on topic x again without any problems + vis_state.topic_clicked = 0; + } + + function topic_click(newtopic, newtopic_num) { + // set style of clicked topic to have thicker border + newtopic.style.strokeWidth = 2; + + // set style of old selected topic back to regular border + var old_topic_clicked_id = topicID + vis_state.topic_clicked; + if (vis_state.topic_clicked > 0 && old_topic_clicked_id != this.id) { + document.getElementById(old_topic_clicked_id).style.strokeWidth = null; + } + + // save state of topic clicked + vis_state.topic_clicked = newtopic_num; + + if (inShinyMode) { + // update shiny input$ldavis_topic_clicked object to be new topic clicked + Shiny.onInputChange('ldavis_topic_clicked', newtopic_num); + + // since topic changed, we want to reset the input$ldavis_term_clicked + // object back to null + Shiny.onInputChange('ldavis_term_clicked', null); + } + } + + function term_click(newterm, newterm_term) { + + // make sure term ids are up to date + d3.selectAll(".terms").attr("id", function(d) { + return (termID + d.Term) + }); + + // underline clicked term + newterm.style.textDecoration = "underline"; + + // set style of old clicked term back to non-underline + var old_term_clicked_id = termID + vis_state.term_clicked; + if (old_term_clicked_id != newterm.id) { + var oldterm = document.getElementById(old_term_clicked_id); + if (oldterm != null) { + oldterm.style.textDecoration = null; + } + } + + // save state of term clicked + vis_state.term_clicked = newterm_term; + + if (inShinyMode) { + // update input$ldavis_term_clicked to know about new term clicked + Shiny.onInputChange('ldavis_term_clicked', newterm_term); + } } }); From 99e85ec8bd6b2548c4f46c5caae10a3a244e0875 Mon Sep 17 00:00:00 2001 From: Chris Baker Date: Mon, 23 Apr 2018 11:21:13 -0400 Subject: [PATCH 2/8] update example shiny app to include demo of input$ldavis_term_clicked and input$ldavis_topic_clicked --- inst/examples/shiny/server.R | 12 +++++++++++- inst/examples/shiny/ui.R | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/inst/examples/shiny/server.R b/inst/examples/shiny/server.R index d3e91fb..20e3191 100644 --- a/inst/examples/shiny/server.R +++ b/inst/examples/shiny/server.R @@ -5,4 +5,14 @@ shinyServer(function(input, output, session) { with(TwentyNewsgroups, createJSON(phi, theta, doc.length, vocab, term.frequency, R = input$nTerms))}) -}) \ No newline at end of file + + output$termClicked <- renderPrint({ + if (is.null(input$ldavis_term_clicked)) return() + paste("You clicked on term:", input$ldavis_term_clicked) + }) + output$topicClicked <- renderPrint({ + if (is.null(input$ldavis_topic_clicked)) return() + paste("You clicked on topic:", input$ldavis_topic_clicked) + }) + +}) diff --git a/inst/examples/shiny/ui.R b/inst/examples/shiny/ui.R index d83d68d..032096d 100644 --- a/inst/examples/shiny/ui.R +++ b/inst/examples/shiny/ui.R @@ -2,6 +2,8 @@ library(LDAvis) shinyUI( fluidPage( sliderInput("nTerms", "Number of terms to display", min = 20, max = 40, value = 30), + textOutput("termClicked"), + textOutput("topicClicked"), visOutput('myChart') ) ) \ No newline at end of file From 456aac70b9e4d7080da4c83eb1bfca89030dbf7f Mon Sep 17 00:00:00 2001 From: Chris Baker Date: Mon, 23 Apr 2018 14:09:32 -0400 Subject: [PATCH 3/8] simulate topic click whenever topic is changed via topic input widget --- inst/htmljs/ldavis.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/inst/htmljs/ldavis.js b/inst/htmljs/ldavis.js index 850cf77..f67e799 100644 --- a/inst/htmljs/ldavis.js +++ b/inst/htmljs/ldavis.js @@ -172,9 +172,11 @@ LDAvis = function(to_select, json_file) { // increment the value in the input box document.getElementById(topicID).value = value_new; topic_off(document.getElementById(topicID + value_old)); - topic_on(document.getElementById(topicID + value_new)); + var oldtopic = document.getElementById(topicID + value_new); + topic_on(oldtopic); vis_state.topic = value_new; state_save(true); + topic_click(oldtopic, value_new); }) d3.select("#" + topicDown) @@ -188,9 +190,11 @@ LDAvis = function(to_select, json_file) { // increment the value in the input box document.getElementById(topicID).value = value_new; topic_off(document.getElementById(topicID + value_old)); - topic_on(document.getElementById(topicID + value_new)); + var oldtopic = document.getElementById(topicID + value_new); + topic_on(oldtopic); vis_state.topic = value_new; state_save(true); + topic_click(oldtopic, value_new); }) d3.select("#" + topicID) @@ -203,10 +207,12 @@ LDAvis = function(to_select, json_file) { var value_new = document.getElementById(topicID).value; if (!isNaN(value_new) && value_new > 0) { value_new = Math.min(K, Math.max(1, value_new)) - topic_on(document.getElementById(topicID + value_new)); + var oldtopic = document.getElementById(topicID + value_new); + topic_on(oldtopic); vis_state.topic = value_new; state_save(true); document.getElementById(topicID).value = vis_state.topic; + topic_click(oldtopic, value_new); } }) From f2973e1c675f444a025b190f3534e38ab9fb45f8 Mon Sep 17 00:00:00 2001 From: christopher crew baker Date: Mon, 23 Apr 2018 20:57:48 -0400 Subject: [PATCH 4/8] only highlight topics/terms on click when in shiny mode --- inst/htmljs/ldavis.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/inst/htmljs/ldavis.js b/inst/htmljs/ldavis.js index f67e799..74666b1 100644 --- a/inst/htmljs/ldavis.js +++ b/inst/htmljs/ldavis.js @@ -1412,6 +1412,9 @@ LDAvis = function(to_select, json_file) { } function topic_click(newtopic, newtopic_num) { + if (!inShinyMode) { + return null; + } // set style of clicked topic to have thicker border newtopic.style.strokeWidth = 2; @@ -1424,18 +1427,18 @@ LDAvis = function(to_select, json_file) { // save state of topic clicked vis_state.topic_clicked = newtopic_num; - if (inShinyMode) { - // update shiny input$ldavis_topic_clicked object to be new topic clicked - Shiny.onInputChange('ldavis_topic_clicked', newtopic_num); - - // since topic changed, we want to reset the input$ldavis_term_clicked - // object back to null - Shiny.onInputChange('ldavis_term_clicked', null); - } + // update shiny input$ldavis_topic_clicked object to be new topic clicked + Shiny.onInputChange('ldavis_topic_clicked', newtopic_num); + + // since topic changed, we want to reset the input$ldavis_term_clicked + // object back to null + Shiny.onInputChange('ldavis_term_clicked', null); } function term_click(newterm, newterm_term) { - + if (!inShinyMode) { + return null; + } // make sure term ids are up to date d3.selectAll(".terms").attr("id", function(d) { return (termID + d.Term) @@ -1456,10 +1459,9 @@ LDAvis = function(to_select, json_file) { // save state of term clicked vis_state.term_clicked = newterm_term; - if (inShinyMode) { - // update input$ldavis_term_clicked to know about new term clicked - Shiny.onInputChange('ldavis_term_clicked', newterm_term); - } + // update input$ldavis_term_clicked to know about new term clicked + Shiny.onInputChange('ldavis_term_clicked', newterm_term); + } }); From a03d4e2169a1e2d5b8d24beb12ee47811f26f20f Mon Sep 17 00:00:00 2001 From: Chris Baker Date: Tue, 24 Apr 2018 11:23:09 -0400 Subject: [PATCH 5/8] add outputid to name of shiny inputs --- inst/examples/shiny/server.R | 10 +++++----- inst/htmljs/ldavis.js | 24 ++++++++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/inst/examples/shiny/server.R b/inst/examples/shiny/server.R index 20e3191..23f298b 100644 --- a/inst/examples/shiny/server.R +++ b/inst/examples/shiny/server.R @@ -7,12 +7,12 @@ shinyServer(function(input, output, session) { R = input$nTerms))}) output$termClicked <- renderPrint({ - if (is.null(input$ldavis_term_clicked)) return() - paste("You clicked on term:", input$ldavis_term_clicked) + if (is.null(input$myChart_term_click)) return() + paste("You clicked on term:", input$myChart_term_click) }) output$topicClicked <- renderPrint({ - if (is.null(input$ldavis_topic_clicked)) return() - paste("You clicked on topic:", input$ldavis_topic_clicked) + if (is.null(input$myChart_topic_click)) return() + paste("You clicked on topic:", input$myChart_topic_click) }) - + }) diff --git a/inst/htmljs/ldavis.js b/inst/htmljs/ldavis.js index 74666b1..1d703a9 100644 --- a/inst/htmljs/ldavis.js +++ b/inst/htmljs/ldavis.js @@ -95,6 +95,12 @@ LDAvis = function(to_select, json_file) { // see if vis is being run as part of a shiny app // (code adapted from https://github.com/ramnathv/htmlwidgets/blob/master/inst/www/htmlwidgets.js#L15) var inShinyMode = typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; + + if (inShinyMode) { + var outputId = to_select.substring(1, to_select.length); + var shinyClickedTopic = outputId + "_topic_click"; + var shinyClickedTerm = outputId + "_term_click"; + } // The actual read-in of the data and main code: d3.json(json_file, function(error, data) { @@ -1402,8 +1408,8 @@ LDAvis = function(to_select, json_file) { // update shiny inputs to be null if (inShinyMode) { - Shiny.onInputChange('ldavis_topic_clicked', null); - Shiny.onInputChange('ldavis_term_clicked', null); + Shiny.onInputChange(shinyClickedTopic, null); + Shiny.onInputChange(shinyClickedTerm, null); } // set state of topic_clicked to 0, so we can click on topic x, reset @@ -1427,12 +1433,11 @@ LDAvis = function(to_select, json_file) { // save state of topic clicked vis_state.topic_clicked = newtopic_num; - // update shiny input$ldavis_topic_clicked object to be new topic clicked - Shiny.onInputChange('ldavis_topic_clicked', newtopic_num); + // update shiny topic input object to be new topic clicked + Shiny.onInputChange(shinyClickedTopic, newtopic_num); - // since topic changed, we want to reset the input$ldavis_term_clicked - // object back to null - Shiny.onInputChange('ldavis_term_clicked', null); + // since topic changed, we want to reset the input term object back to null + Shiny.onInputChange(shinyClickedTerm, null); } function term_click(newterm, newterm_term) { @@ -1459,9 +1464,8 @@ LDAvis = function(to_select, json_file) { // save state of term clicked vis_state.term_clicked = newterm_term; - // update input$ldavis_term_clicked to know about new term clicked - Shiny.onInputChange('ldavis_term_clicked', newterm_term); - + // update shiny term input object to know about new term clicked + Shiny.onInputChange(shinyClickedTerm, newterm_term); } }); From 87f58d573626693d5ee82d4fd755610b156e3a8e Mon Sep 17 00:00:00 2001 From: Chris Baker Date: Tue, 24 Apr 2018 14:37:36 -0400 Subject: [PATCH 6/8] Update news --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 04a1e4e..a3d3152 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ + CHANGES IN LDAvis VERSION 0.3.5 + +Click events (on topics and terms) are now made available to the Shiny input object (closes [[#45]](https://github.com/cpsievert/LDAvis/issues/45)). When a topic is clicked on, the topic number is made available to Shiny server at input$OUTPUTID_topic_click, where OUTPUTID is the outputId of the LDAvis plot. When a term is clicked on, the term is available at input$OUTPUTID_term_click. See https://github.com/cpsievert/LDAvis/tree/master/inst/examples/shiny for an example Shiny app. + CHANGES IN LDAvis VERSION 0.3.4 A new argument, encoding, was added to serVis(). This sets the encoding which will be used to write the JSON file. This may be set to `UTF-8` to fix an issue where, when running on windows, the JSON output would be encoded in ANSI despite the R data beeing encoded in UTF-8. This should also fix #50. From 8dd039e89da79d0ff944fd08e7bc71c3251e93a0 Mon Sep 17 00:00:00 2001 From: Chris Baker Date: Tue, 24 Apr 2018 14:37:54 -0400 Subject: [PATCH 7/8] add chris as contributor --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7e53371..c033acd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -3,7 +3,7 @@ Title: Interactive Visualization of Topic Models Version: 0.3.4 Authors@R: c(person("Carson", "Sievert", role = c("aut", "cre"), email = "cpsievert1@gmail.com", comment = c(ORCID = "0000-0002-4958-2844")), person("Kenny", "Shirley", role = "aut", - email = "kshirley@research.att.com")) + email = "kshirley@research.att.com"), person("Christopher", "Baker", role = "ctb", email = "chriscrewbaker@gmail.com")) Description: Tools to create an interactive web-based visualization of a topic model that has been fit to a corpus of text data using Latent Dirichlet Allocation (LDA). Given the estimated parameters of From 451dd172b5c32959b0b8d4cc1f0a9c5700fef44c Mon Sep 17 00:00:00 2001 From: Chris Baker Date: Tue, 24 Apr 2018 14:38:01 -0400 Subject: [PATCH 8/8] bump version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index c033acd..5d161fa 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: LDAvis Title: Interactive Visualization of Topic Models -Version: 0.3.4 +Version: 0.3.5 Authors@R: c(person("Carson", "Sievert", role = c("aut", "cre"), email = "cpsievert1@gmail.com", comment = c(ORCID = "0000-0002-4958-2844")), person("Kenny", "Shirley", role = "aut", email = "kshirley@research.att.com"), person("Christopher", "Baker", role = "ctb", email = "chriscrewbaker@gmail.com"))