From 593c854c0cc7ddf7157331e6c37ba0bde20ab6ef Mon Sep 17 00:00:00 2001 From: Paul English Date: Mon, 1 Jun 2015 10:36:58 -0600 Subject: [PATCH] fixes interaction issues when multiple LDAvis objects are on the same page --- inst/htmljs/ldavis.js | 799 +++++++++++++++++++++--------------------- 1 file changed, 405 insertions(+), 394 deletions(-) diff --git a/inst/htmljs/ldavis.js b/inst/htmljs/ldavis.js index 2e0c4ee..807e0f8 100644 --- a/inst/htmljs/ldavis.js +++ b/inst/htmljs/ldavis.js @@ -2,39 +2,41 @@ /* Copyright 2013, AT&T Intellectual Property */ /* MIT Licence */ -LDAvis = function(to_select, data_or_file_name) { +'use strict'; + +var LDAvis = function(to_select, data_or_file_name) { // This section sets up the logic for event handling var current_clicked = { what: "nothing", element: undefined }, - current_hover = { - what: "nothing", - element: undefined - }, - old_winning_state = { - what: "nothing", - element: undefined - }, - vis_state = { - lambda: 1, - topic: 0, - term: "" - }; + current_hover = { + what: "nothing", + element: undefined + }, + old_winning_state = { + what: "nothing", + element: undefined + }, + vis_state = { + lambda: 1, + topic: 0, + term: "" + }; // Set up a few 'global' variables to hold the data: var K, // number of topics - R, // number of terms to display in bar chart - mdsData, // (x,y) locations and topic proportions - mdsData3, // topic proportions for all terms in the viz - lamData, // all terms that are among the top-R most relevant for all topics, lambda values - lambda = { - old: 1, - current: 1 - }, - color1 = "#1f77b4", // baseline color for default topic circles and overall term frequencies - color2 = "#d62728"; // 'highlight' color for selected topics and term-topic frequencies + R, // number of terms to display in bar chart + mdsData, // (x,y) locations and topic proportions + mdsData3, // topic proportions for all terms in the viz + lamData, // all terms that are among the top-R most relevant for all topics, lambda values + lambda = { + old: 1, + current: 1 + }, + color1 = "#1f77b4", // baseline color for default topic circles and overall term frequencies + color2 = "#d62728"; // 'highlight' color for selected topics and term-topic frequencies // Set the duration of each half of the transition: var duration = 750; @@ -46,12 +48,12 @@ LDAvis = function(to_select, data_or_file_name) { bottom: 70, left: 30 }, - mdswidth = 530, - mdsheight = 530, - barwidth = 530, - barheight = 530, - termwidth = 90, // width to add between two panels to display terms - mdsarea = mdsheight * mdswidth; + mdswidth = 530, + mdsheight = 530, + barwidth = 530, + barheight = 530, + termwidth = 90, // width to add between two panels to display terms + mdsarea = mdsheight * mdswidth; // controls how big the maximum circle can be // doesn't depend on data, only on mds width and height: var rMax = 60; @@ -62,15 +64,14 @@ LDAvis = function(to_select, data_or_file_name) { // opacity of topic circles: var base_opacity = 0.2, - highlight_opacity = 0.6; + highlight_opacity = 0.6; // topic/lambda selection names are specific to *this* vis var topic_select = to_select + "-topic"; var lambda_select = to_select + "-lambda"; // get rid of the # in the to_select (useful) for setting ID values - var parts = to_select.split("#"); - var visID = parts[parts.length - 1]; + var visID = to_select.replace("#", ""); var topicID = visID + "-topic"; var lambdaID = visID + "-lambda"; var termID = visID + "-term"; @@ -78,6 +79,14 @@ LDAvis = function(to_select, data_or_file_name) { var topicUp = topicID + "-up"; var topicClear = topicID + "-clear"; + var leftPanelID = visID + "-leftpanel"; + var barFreqsID = visID + "-bar-freqs"; + var topID = visID + "-top"; + var lambdaInputID = visID + "-lambdaInput"; + var lambdaZeroID = visID + "-lambdaZero"; + var sliderDivID = visID + "-sliderdiv"; + var lambdaLabelID = visID + "-lamlabel"; + ////////////////////////////////////////////////////////////////////////////// // sort array according to a specified object key name @@ -126,7 +135,7 @@ LDAvis = function(to_select, data_or_file_name) { // large data for the widths of bars in bar-charts. 6 columns: Term, logprob, loglift, Freq, Total, Category // Contains all possible terms for topics in (1, 2, ..., k) and lambda in the user-supplied grid of lambda values - // which defaults to (0, 0.01, 0.02, ..., 0.99, 1). + // which defaults to (0, 0.01, 0.02, ..., 0.99, 1). lamData = []; for (var i = 0; i < data['tinfo'].Term.length; i++) { var obj = {}; @@ -142,8 +151,10 @@ LDAvis = function(to_select, data_or_file_name) { init_forms(topicID, lambdaID, visID); // When the value of lambda changes, update the visualization + console.log('lambda_select', lambda_select); d3.select(lambda_select) .on("mouseup", function() { + console.log('lambda_select mouseup'); // store the previous lambda value lambda.old = lambda.current; lambda.current = document.getElementById(lambdaID).value; @@ -161,10 +172,10 @@ LDAvis = function(to_select, data_or_file_name) { d3.select("#" + topicUp) .on("click", function() { - // remove term selection if it exists (from a saved URL) - var termElem = document.getElementById(termID + vis_state.term); - if (termElem !== undefined) term_off(termElem); - vis_state.term = ""; + // remove term selection if it exists (from a saved URL) + var termElem = document.getElementById(termID + vis_state.term); + if (termElem !== undefined) term_off(termElem); + vis_state.term = ""; var value_old = document.getElementById(topicID).value; var value_new = Math.min(K, +value_old + 1).toFixed(0); // increment the value in the input box @@ -173,14 +184,14 @@ LDAvis = function(to_select, data_or_file_name) { topic_on(document.getElementById(topicID + value_new)); vis_state.topic = value_new; state_save(true); - }) + }); d3.select("#" + topicDown) .on("click", function() { - // remove term selection if it exists (from a saved URL) - var termElem = document.getElementById(termID + vis_state.term); - if (termElem !== undefined) term_off(termElem); - vis_state.term = ""; + // remove term selection if it exists (from a saved URL) + var termElem = document.getElementById(termID + vis_state.term); + if (termElem !== undefined) term_off(termElem); + vis_state.term = ""; var value_old = document.getElementById(topicID).value; var value_new = Math.max(0, +value_old - 1).toFixed(0); // increment the value in the input box @@ -189,71 +200,71 @@ LDAvis = function(to_select, data_or_file_name) { topic_on(document.getElementById(topicID + value_new)); vis_state.topic = value_new; state_save(true); - }) + }); d3.select("#" + topicID) .on("keyup", function() { - // remove term selection if it exists (from a saved URL) - var termElem = document.getElementById(termID + vis_state.term); - if (termElem !== undefined) term_off(termElem); - vis_state.term = ""; - topic_off(document.getElementById(topicID + vis_state.topic)) + // remove term selection if it exists (from a saved URL) + var termElem = document.getElementById(termID + vis_state.term); + if (termElem !== undefined) term_off(termElem); + vis_state.term = ""; + topic_off(document.getElementById(topicID + vis_state.topic)); var value_new = document.getElementById(topicID).value; if (!isNaN(value_new) && value_new > 0) { - value_new = Math.min(K, Math.max(1, value_new)) + value_new = Math.min(K, Math.max(1, value_new)); topic_on(document.getElementById(topicID + value_new)); vis_state.topic = value_new; state_save(true); document.getElementById(topicID).value = vis_state.topic; } - }) + }); d3.select("#" + topicClear) .on("click", function() { state_reset(); state_save(true); - }) + }); // create linear scaling to pixels (and add some padding on outer region of scatterplot) var xrange = d3.extent(mdsData, function(d) { return d.x; }); //d3.extent returns min and max of an array var xdiff = xrange[1] - xrange[0], - xpad = 0.05; + xpad = 0.05; var yrange = d3.extent(mdsData, function(d) { return d.y; }); var ydiff = yrange[1] - yrange[0], - ypad = 0.05; + ypad = 0.05; - if (xdiff > ydiff) { + if (xdiff > ydiff) { var xScale = d3.scale.linear() - .range([0, mdswidth]) - .domain([xrange[0] - xpad * xdiff, xrange[1] + xpad * xdiff]); + .range([0, mdswidth]) + .domain([xrange[0] - xpad * xdiff, xrange[1] + xpad * xdiff]); var yScale = d3.scale.linear() - .range([mdsheight, 0]) - .domain([yrange[0] - 0.5*(xdiff - ydiff) - ypad*xdiff, yrange[1] + 0.5*(xdiff - ydiff) + ypad*xdiff]); - } else { + .range([mdsheight, 0]) + .domain([yrange[0] - 0.5*(xdiff - ydiff) - ypad*xdiff, yrange[1] + 0.5*(xdiff - ydiff) + ypad*xdiff]); + } else { var xScale = d3.scale.linear() - .range([0, mdswidth]) - .domain([xrange[0] - 0.5*(ydiff - xdiff) - xpad*ydiff, xrange[1] + 0.5*(ydiff - xdiff) + xpad*ydiff]); + .range([0, mdswidth]) + .domain([xrange[0] - 0.5*(ydiff - xdiff) - xpad*ydiff, xrange[1] + 0.5*(ydiff - xdiff) + xpad*ydiff]); var yScale = d3.scale.linear() - .range([mdsheight, 0]) - .domain([yrange[0] - ypad * ydiff, yrange[1] + ypad * ydiff]); - } + .range([mdsheight, 0]) + .domain([yrange[0] - ypad * ydiff, yrange[1] + ypad * ydiff]); + } // Create new svg element (that will contain everything): var svg = d3.select(to_select).append("svg") - .attr("width", mdswidth + barwidth + margin.left + termwidth + margin.right) - .attr("height", mdsheight + 2 * margin.top + margin.bottom + 2 * rMax); + .attr("width", mdswidth + barwidth + margin.left + termwidth + margin.right) + .attr("height", mdsheight + 2 * margin.top + margin.bottom + 2 * rMax); // Create a group for the mds plot var mdsplot = svg.append("g") - .attr("id", "leftpanel") - .attr("class", "points") - .attr("transform", "translate(" + margin.left + "," + 2 * margin.top + ")"); + .attr("id", leftPanelID) + .attr("class", "points") + .attr("transform", "translate(" + margin.left + "," + 2 * margin.top + ")"); // Clicking on the mdsplot should clear the selection mdsplot @@ -276,11 +287,11 @@ LDAvis = function(to_select, data_or_file_name) { .attr("y2", mdsheight / 2) .attr("stroke", "gray") .attr("opacity", 0.3); - mdsplot.append("text") // label x-axis - .attr("x", 0) - .attr("y", mdsheight/2 - 5) - .text(data['plot.opts'].xlab) - .attr("fill", "gray"); + mdsplot.append("text") // label x-axis + .attr("x", 0) + .attr("y", mdsheight/2 - 5) + .text(data['plot.opts'].xlab) + .attr("fill", "gray"); mdsplot.append("line") // draw y-axis .attr("x1", mdswidth / 2) @@ -289,23 +300,23 @@ LDAvis = function(to_select, data_or_file_name) { .attr("y2", mdsheight) .attr("stroke", "gray") .attr("opacity", 0.3); - mdsplot.append("text") // label y-axis - .attr("x", mdswidth/2 + 5) - .attr("y", 7) - .text(data['plot.opts'].ylab) - .attr("fill", "gray"); - - // new definitions based on fixing the sum of the areas of the default topic circles: - var newSmall = Math.sqrt(0.02*mdsarea*circle_prop/Math.PI); - var newMedium = Math.sqrt(0.05*mdsarea*circle_prop/Math.PI); - var newLarge = Math.sqrt(0.10*mdsarea*circle_prop/Math.PI); - var cx = 10 + newLarge, - cx2 = cx + 1.5 * newLarge; + mdsplot.append("text") // label y-axis + .attr("x", mdswidth/2 + 5) + .attr("y", 7) + .text(data['plot.opts'].ylab) + .attr("fill", "gray"); + + // new definitions based on fixing the sum of the areas of the default topic circles: + var newSmall = Math.sqrt(0.02*mdsarea*circle_prop/Math.PI); + var newMedium = Math.sqrt(0.05*mdsarea*circle_prop/Math.PI); + var newLarge = Math.sqrt(0.10*mdsarea*circle_prop/Math.PI); + var cx = 10 + newLarge, + cx2 = cx + 1.5 * newLarge; // circle guide inspired from // http://www.nytimes.com/interactive/2012/02/13/us/politics/2013-budget-proposal-graphic.html?_r=0 - circleGuide = function(rSize, size) { - d3.select("#leftpanel").append("circle") + var circleGuide = function(rSize, size) { + d3.select("#" + leftPanelID).append("circle") .attr('class', "circleGuide" + size) .attr('r', rSize) .attr('cx', cx) @@ -313,7 +324,7 @@ LDAvis = function(to_select, data_or_file_name) { .style('fill', 'none') .style('stroke-dasharray', '2 2') .style('stroke', '#999'); - d3.select("#leftpanel").append("line") + d3.select("#" + leftPanelID).append("line") .attr('class', "lineGuide" + size) .attr("x1", cx) .attr("x2", cx2) @@ -321,7 +332,7 @@ LDAvis = function(to_select, data_or_file_name) { .attr("y2", mdsheight + 2 * rSize) .style("stroke", "gray") .style("opacity", 0.3); - } + }; circleGuide(newSmall, "Small"); circleGuide(newMedium, "Medium"); @@ -331,26 +342,26 @@ LDAvis = function(to_select, data_or_file_name) { var defaultLabelMedium = "5%"; var defaultLabelLarge = "10%"; - d3.select("#leftpanel").append("text") + d3.select("#" + leftPanelID).append("text") .attr("x", 10) .attr("y", mdsheight - 10) .attr('class', "circleGuideTitle") .style("text-anchor", "left") .style("fontWeight", "bold") .text("Marginal topic distribtion"); - d3.select("#leftpanel").append("text") + d3.select("#" + leftPanelID).append("text") .attr("x", cx2 + 10) .attr("y", mdsheight + 2 * newSmall) .attr('class', "circleGuideLabelSmall") .style("text-anchor", "start") .text(defaultLabelSmall); - d3.select("#leftpanel").append("text") + d3.select("#" + leftPanelID).append("text") .attr("x", cx2 + 10) .attr("y", mdsheight + 2 * newMedium) .attr('class', "circleGuideLabelMedium") .style("text-anchor", "start") .text(defaultLabelMedium); - d3.select("#leftpanel").append("text") + d3.select("#" + leftPanelID).append("text") .attr("x", cx2 + 10) .attr("y", mdsheight + 2 * newLarge) .attr('class', "circleGuideLabelLarge") @@ -359,8 +370,8 @@ LDAvis = function(to_select, data_or_file_name) { // bind mdsData to the points in the left panel: var points = mdsplot.selectAll("points") - .data(mdsData) - .enter(); + .data(mdsData) + .enter(); // text to indicate topic points.append("text") @@ -397,7 +408,7 @@ LDAvis = function(to_select, data_or_file_name) { }) .attr("stroke", "black") .attr("id", function(d) { - return (topicID + d.topics) + return (topicID + d.topics); }) .on("mouseover", function(d) { var old_topic = topicID + vis_state.topic; @@ -428,63 +439,63 @@ LDAvis = function(to_select, data_or_file_name) { .text("Intertopic Distance Map (via multidimensional scaling)") .attr("x", mdswidth/2 + margin.left) .attr("y", 30) - .style("font-size", "16px") - .style("text-anchor", "middle"); + .style("font-size", "16px") + .style("text-anchor", "middle"); // establish layout and vars for bar chart var barDefault2 = lamData.filter(function(d) { - return d.Category == "Default" + return d.Category == "Default"; }); var y = d3.scale.ordinal() - .domain(barDefault2.map(function(d) { - return d.Term; - })) - .rangeRoundBands([0, barheight], 0.15); + .domain(barDefault2.map(function(d) { + return d.Term; + })) + .rangeRoundBands([0, barheight], 0.15); var x = d3.scale.linear() - .domain([0, d3.max(barDefault2, function(d) { - return d.Total; - })]) - .range([0, barwidth]) - .nice(); + .domain([1, d3.max(barDefault2, function(d) { + return d.Total; + })]) + .range([0, barwidth]) + .nice(); var yAxis = d3.svg.axis() - .scale(y); + .scale(y); // Add a group for the bar chart var chart = svg.append("g") - .attr("transform", "translate(" + +(mdswidth + margin.left + termwidth) + "," + 2 * margin.top + ")") - .attr("id", "bar-freqs"); + .attr("transform", "translate(" + +(mdswidth + margin.left + termwidth) + "," + 2 * margin.top + ")") + .attr("id", barFreqsID); // bar chart legend/guide: var barguide = {"width": 100, "height": 15}; - d3.select("#bar-freqs").append("rect") + d3.select("#" + barFreqsID).append("rect") .attr("x", 0) .attr("y", mdsheight + 10) .attr("height", barguide.height) .attr("width", barguide.width) .style("fill", color1) .attr("opacity", 0.4); - d3.select("#bar-freqs").append("text") + d3.select("#" + barFreqsID).append("text") .attr("x", barguide.width + 5) .attr("y", mdsheight + 10 + barguide.height/2) .style("dominant-baseline", "middle") .text("Overall term frequency"); - d3.select("#bar-freqs").append("rect") + d3.select("#" + barFreqsID).append("rect") .attr("x", 0) .attr("y", mdsheight + 10 + barguide.height + 5) .attr("height", barguide.height) .attr("width", barguide.width/2) .style("fill", color2) .attr("opacity", 0.8); - d3.select("#bar-freqs").append("text") + d3.select("#" + barFreqsID).append("text") .attr("x", barguide.width/2 + 5) .attr("y", mdsheight + 10 + (3/2)*barguide.height + 5) .style("dominant-baseline", "middle") .text("Estimated term frequency within the selected topic"); - // footnotes: - d3.select("#bar-freqs") + // footnotes: + d3.select("#" + barFreqsID) .append("a") .attr("xlink:href", "http://vis.stanford.edu/files/2012-Termite-AVI.pdf") .attr("target", "_blank") @@ -493,7 +504,7 @@ LDAvis = function(to_select, data_or_file_name) { .attr("y", mdsheight + 10 + (6/2)*barguide.height + 5) .style("dominant-baseline", "middle") .text("1. saliency(term w) = frequency(w) * [sum_t p(t | w) * log(p(t | w)/p(t))] for topics t; see Chuang et. al (2012)"); - d3.select("#bar-freqs") + d3.select("#" + barFreqsID) .append("a") .attr("xlink:href", "http://nlp.stanford.edu/events/illvi2014/papers/sievert-illvi2014.pdf") .attr("target", "_blank") @@ -504,9 +515,9 @@ LDAvis = function(to_select, data_or_file_name) { .text("2. relevance(term w | topic t) = \u03BB * p(w | t) + (1 - \u03BB) * p(w | t)/p(w); see Sievert & Shirley (2014)"); // Bind 'default' data to 'default' bar chart - var basebars = chart.selectAll(".bar-totals") - .data(barDefault2) - .enter(); + var basebars = chart.selectAll(to_select + " .bar-totals") + .data(barDefault2) + .enter(); // Draw the gray background bars defining the overall frequency of each word basebars @@ -533,7 +544,7 @@ LDAvis = function(to_select, data_or_file_name) { }) .attr("cursor", "pointer") .attr("id", function(d) { - return (termID + d.Term) + return (termID + d.Term); }) .style("text-anchor", "end") // right align text - use 'middle' for center alignment .text(function(d) { @@ -543,14 +554,14 @@ LDAvis = function(to_select, data_or_file_name) { term_hover(this); }) // .on("click", function(d) { - // var old_term = termID + vis_state.term; - // if (vis_state.term != "" && old_term != this.id) { - // term_off(document.getElementById(old_term)); - // } - // vis_state.term = d.Term; - // state_save(true); - // term_on(this); - // debugger; + // var old_term = termID + vis_state.term; + // if (vis_state.term != "" && old_term != this.id) { + // term_off(document.getElementById(old_term)); + // } + // vis_state.term = d.Term; + // state_save(true); + // term_on(this); + // debugger; // }) .on("mouseout", function() { vis_state.term = ""; @@ -559,41 +570,41 @@ LDAvis = function(to_select, data_or_file_name) { }); var title = chart.append("text") - .attr("x", barwidth/2) - .attr("y", -30) - .attr("class", "bubble-tool") // set class so we can remove it when highlight_off is called - .style("text-anchor", "middle") - .style("font-size", "16px") - .text("Top-" + R + " Most Salient Terms"); + .attr("x", barwidth/2) + .attr("y", -30) + .attr("class", "bubble-tool") // set class so we can remove it when highlight_off is called + .style("text-anchor", "middle") + .style("font-size", "16px") + .text("Top-" + R + " Most Salient Terms"); title.append("tspan") - .attr("baseline-shift", "super") - .attr("font-size", "12px") - .text("(1)"); + .attr("baseline-shift", "super") + .attr("font-size", "12px") + .text("(1)"); // barchart axis adapted from http://bl.ocks.org/mbostock/1166403 var xAxis = d3.svg.axis().scale(x) - .orient("top") - .tickSize(-barheight) - .tickSubdivide(true) - .ticks(6); + .orient("top") + .tickSize(-barheight) + .tickSubdivide(true) + .ticks(6); chart.attr("class", "xaxis") .call(xAxis); - // dynamically create the topic and lambda input forms at the top of the page: + // dynamically create the topic and lambda input forms at the top of the page: function init_forms(topicID, lambdaID, visID) { // create container div for topic and lambda input: - var inputDiv = document.createElement("div"); - inputDiv.setAttribute("id", "top"); - inputDiv.setAttribute("style", "width: 1210px"); // to match the width of the main svg element - document.getElementById(visID).appendChild(inputDiv); + var inputDiv = document.createElement("div"); + inputDiv.setAttribute("id", topID); + inputDiv.setAttribute("style", "width: 1210px"); // to match the width of the main svg element + document.getElementById(visID).appendChild(inputDiv); - // topic input container: - var topicDiv = document.createElement("div"); - topicDiv.setAttribute("style", "padding: 5px; background-color: #e8e8e8; display: inline-block; width: " + mdswidth + "px; height: 50px; float: left"); - inputDiv.appendChild(topicDiv); + // topic input container: + var topicDiv = document.createElement("div"); + topicDiv.setAttribute("style", "padding: 5px; background-color: #e8e8e8; display: inline-block; width: " + mdswidth + "px; height: 50px; float: left"); + inputDiv.appendChild(topicDiv); var topicLabel = document.createElement("label"); topicLabel.setAttribute("for", topicID); @@ -611,54 +622,54 @@ LDAvis = function(to_select, data_or_file_name) { topicInput.id = topicID; topicDiv.appendChild(topicInput); - var previous = document.createElement("button"); - previous.setAttribute("id", topicDown); - previous.setAttribute("style", "margin-left: 5px"); - previous.innerHTML = "Previous Topic"; + var previous = document.createElement("button"); + previous.setAttribute("id", topicDown); + previous.setAttribute("style", "margin-left: 5px"); + previous.innerHTML = "Previous Topic"; topicDiv.appendChild(previous); - var next = document.createElement("button"); - next.setAttribute("id", topicUp); - next.setAttribute("style", "margin-left: 5px"); - next.innerHTML = "Next Topic"; + var next = document.createElement("button"); + next.setAttribute("id", topicUp); + next.setAttribute("style", "margin-left: 5px"); + next.innerHTML = "Next Topic"; topicDiv.appendChild(next); - var clear = document.createElement("button"); - clear.setAttribute("id", topicClear); - clear.setAttribute("style", "margin-left: 5px"); - clear.innerHTML = "Clear Topic"; + var clear = document.createElement("button"); + clear.setAttribute("id", topicClear); + clear.setAttribute("style", "margin-left: 5px"); + clear.innerHTML = "Clear Topic"; topicDiv.appendChild(clear); // lambda inputs - //var lambdaDivLeft = 8 + mdswidth + margin.left + termwidth; - var lambdaDivWidth = barwidth; - var lambdaDiv = document.createElement("div"); - lambdaDiv.setAttribute("id", "lambdaInput"); - lambdaDiv.setAttribute("style", "padding: 5px; background-color: #e8e8e8; display: inline-block; height: 50px; width: " + lambdaDivWidth + "px; float: right; margin-right: 30px"); - inputDiv.appendChild(lambdaDiv); - - var lambdaZero = document.createElement("div"); - lambdaZero.setAttribute("style", "padding: 5px; height: 20px; width: 220px; font-family: sans-serif; float: left"); - lambdaZero.setAttribute("id", "lambdaZero"); - lambdaDiv.appendChild(lambdaZero); - var xx = d3.select("#lambdaZero") - .append("text") - .attr("x", 0) - .attr("y", 0) - .style("font-size", "14px") - .text("Slide to adjust relevance metric:"); - var yy = d3.select("#lambdaZero") - .append("text") - .attr("x", 125) - .attr("y", -5) - .style("font-size", "10px") - .style("position", "absolute") - .text("(2)"); - - var sliderDiv = document.createElement("div"); - sliderDiv.setAttribute("id", "sliderdiv"); - sliderDiv.setAttribute("style", "padding: 5px; height: 40px; width: 250px; float: right; margin-top: -5px; margin-right: 10px"); - lambdaDiv.appendChild(sliderDiv); + //var lambdaDivLeft = 8 + mdswidth + margin.left + termwidth; + var lambdaDivWidth = barwidth; + var lambdaDiv = document.createElement("div"); + lambdaDiv.setAttribute("id", lambdaInputID); + lambdaDiv.setAttribute("style", "padding: 5px; background-color: #e8e8e8; display: inline-block; height: 50px; width: " + lambdaDivWidth + "px; float: right; margin-right: 30px"); + inputDiv.appendChild(lambdaDiv); + + var lambdaZero = document.createElement("div"); + lambdaZero.setAttribute("style", "padding: 5px; height: 20px; width: 220px; font-family: sans-serif; float: left"); + lambdaZero.setAttribute("id", lambdaZeroID); + lambdaDiv.appendChild(lambdaZero); + var xx = d3.select("#" + lambdaZeroID) + .append("text") + .attr("x", 0) + .attr("y", 0) + .style("font-size", "14px") + .text("Slide to adjust relevance metric:"); + var yy = d3.select("#" + lambdaZeroID) + .append("text") + .attr("x", 125) + .attr("y", -5) + .style("font-size", "10px") + .style("position", "absolute") + .text("(2)"); + + var sliderDiv = document.createElement("div"); + sliderDiv.setAttribute("id", sliderDivID); + sliderDiv.setAttribute("style", "padding: 5px; height: 40px; width: 250px; float: right; margin-top: -5px; margin-right: 10px"); + lambdaDiv.appendChild(sliderDiv); var lambdaInput = document.createElement("input"); lambdaInput.setAttribute("style", "width: 250px; margin-left: 0px; margin-right: 0px"); @@ -668,51 +679,51 @@ LDAvis = function(to_select, data_or_file_name) { lambdaInput.step = data['lambda.step']; lambdaInput.value = vis_state.lambda; lambdaInput.id = lambdaID; - lambdaInput.setAttribute("list", "ticks"); // to enable automatic ticks (with no labels, see below) + lambdaInput.setAttribute("list", "ticks"); // to enable automatic ticks (with no labels, see below) sliderDiv.appendChild(lambdaInput); var lambdaLabel = document.createElement("label"); - lambdaLabel.setAttribute("id", "lamlabel"); + lambdaLabel.setAttribute("id", lambdaLabelID); lambdaLabel.setAttribute("for", lambdaID); - lambdaLabel.setAttribute("style", "height: 20px; width: 60px; font-family: sans-serif; font-size: 14px; margin-left: 80px"); - lambdaLabel.innerHTML = "λ = " + vis_state.lambda + ""; + lambdaLabel.setAttribute("style", "height: 20px; width: 60px; font-family: sans-serif; font-size: 14px; margin-left: 80px"); + lambdaLabel.innerHTML = "λ = " + vis_state.lambda + ""; lambdaDiv.appendChild(lambdaLabel); - // Create the svg to contain the slider scale: - var scaleContainer = d3.select("#sliderdiv").append("svg") - .attr("width", 250) - .attr("height", 25); + // Create the svg to contain the slider scale: + var scaleContainer = d3.select("#" + sliderDivID).append("svg") + .attr("width", 250) + .attr("height", 25); var sliderScale = d3.scale.linear() - .domain([0, 1]) - .range([7.5, 242.5]) // trimmed by 7.5px on each side to match the input type=range slider: - .nice(); + .domain([0, 1]) + .range([7.5, 242.5]) // trimmed by 7.5px on each side to match the input type=range slider: + .nice(); // adapted from http://bl.ocks.org/mbostock/1166403 var sliderAxis = d3.svg.axis() - .scale(sliderScale) - .orient("bottom") - .tickSize(10) - .tickSubdivide(true) - .ticks(6); - - // group to contain the elements of the slider axis: - var sliderAxisGroup = scaleContainer.append("g") - .attr("class", "slideraxis") - .attr("margin-top", "-10px") - .call(sliderAxis); - - // Another strategy for tick marks on the slider; simpler, but not labels - // var sliderTicks = document.createElement("datalist"); - // sliderTicks.setAttribute("id", "ticks"); - // for (var tick = 0; tick <= 10; tick++) { - // var tickOption = document.createElement("option"); - // //tickOption.value = tick/10; - // tickOption.innerHTML = tick/10; - // sliderTicks.appendChild(tickOption); - // } + .scale(sliderScale) + .orient("bottom") + .tickSize(10) + .tickSubdivide(true) + .ticks(6); + + // group to contain the elements of the slider axis: + var sliderAxisGroup = scaleContainer.append("g") + .attr("class", "slideraxis") + .attr("margin-top", "-10px") + .call(sliderAxis); + + // Another strategy for tick marks on the slider; simpler, but not labels + // var sliderTicks = document.createElement("datalist"); + // sliderTicks.setAttribute("id", "ticks"); + // for (var tick = 0; tick <= 10; tick++) { + // var tickOption = document.createElement("option"); + // //tickOption.value = tick/10; + // tickOption.innerHTML = tick/10; + // sliderTicks.appendChild(tickOption); + // } // append the forms to the containers - //lambdaDiv.appendChild(sliderTicks); + //lambdaDiv.appendChild(sliderTicks); } @@ -736,101 +747,101 @@ LDAvis = function(to_select, data_or_file_name) { var dat3 = dat2.slice(0, R); var y = d3.scale.ordinal() - .domain(dat3.map(function(d) { - return d.Term; - })) - .rangeRoundBands([0, barheight], 0.15); + .domain(dat3.map(function(d) { + return d.Term; + })) + .rangeRoundBands([0, barheight], 0.15); var x = d3.scale.linear() - .domain([0, d3.max(dat3, function(d) { - return d.Total; - })]) - .range([0, barwidth]) - .nice(); + .domain([1, d3.max(dat3, function(d) { + return d.Total; + })]) + .range([0, barwidth]) + .nice(); // Change Total Frequency bars - var graybars = d3.select("#bar-freqs") - .selectAll(".bar-totals") - .data(dat3, function(d) { - return d.Term; - }); + var graybars = d3.select("#" + barFreqsID) + .selectAll(to_select + " .bar-totals") + .data(dat3, function(d) { + return d.Term; + }); // Change word labels - var labels = d3.select("#bar-freqs") - .selectAll(".terms") - .data(dat3, function(d) { - return d.Term; - }); + var labels = d3.select("#" + barFreqsID) + .selectAll(to_select + " .terms") + .data(dat3, function(d) { + return d.Term; + }); // Create red bars (drawn over the gray ones) to signify the frequency under the selected topic - var redbars = d3.select("#bar-freqs") - .selectAll(".overlay") - .data(dat3, function(d) { - return d.Term; - }); + var redbars = d3.select("#" + barFreqsID) + .selectAll(to_select + " .overlay") + .data(dat3, function(d) { + return d.Term; + }); // adapted from http://bl.ocks.org/mbostock/1166403 var xAxis = d3.svg.axis().scale(x) - .orient("top") - .tickSize(-barheight) - .tickSubdivide(true) - .ticks(6); + .orient("top") + .tickSize(-barheight) + .tickSubdivide(true) + .ticks(6); // New axis definition: - var newaxis = d3.selectAll(".xaxis"); + var newaxis = d3.selectAll(to_select + " .xaxis"); // define the new elements to enter: var graybarsEnter = graybars.enter().append("rect") - .attr("class", "bar-totals") - .attr("x", 0) - .attr("y", function(d) { - return y(d.Term) + barheight + margin.bottom + 2 * rMax; - }) - .attr("height", y.rangeBand()) - .style("fill", color1) - .attr("opacity", 0.4); + .attr("class", "bar-totals") + .attr("x", 0) + .attr("y", function(d) { + return y(d.Term) + barheight + margin.bottom + 2 * rMax; + }) + .attr("height", y.rangeBand()) + .style("fill", color1) + .attr("opacity", 0.4); var labelsEnter = labels.enter() - .append("text") - .attr("x", -5) - .attr("class", "terms") - .attr("y", function(d) { - return y(d.Term) + 12 + barheight + margin.bottom + 2 * rMax; - }) - .attr("cursor", "pointer") - .style("text-anchor", "end") - .attr("id", function(d) { - return (termID + d.Term) - }) - .text(function(d) { - return d.Term; - }) - .on("mouseover", function() { - term_hover(this); - }) + .append("text") + .attr("x", -5) + .attr("class", "terms") + .attr("y", function(d) { + return y(d.Term) + 12 + barheight + margin.bottom + 2 * rMax; + }) + .attr("cursor", "pointer") + .style("text-anchor", "end") + .attr("id", function(d) { + return (termID + d.Term); + }) + .text(function(d) { + return d.Term; + }) + .on("mouseover", function() { + term_hover(this); + }) // .on("click", function(d) { // var old_term = termID + vis_state.term; // if (vis_state.term != "" && old_term != this.id) { - // term_off(document.getElementById(old_term)); + // term_off(document.getElementById(old_term)); // } // vis_state.term = d.Term; // state_save(true); // term_on(this); // }) - .on("mouseout", function() { - vis_state.term = ""; - term_off(this); - state_save(true); - }); + .on("mouseout", function() { + vis_state.term = ""; + term_off(this); + state_save(true); + }); var redbarsEnter = redbars.enter().append("rect") - .attr("class", "overlay") - .attr("x", 0) - .attr("y", function(d) { - return y(d.Term) + barheight + margin.bottom + 2 * rMax; - }) - .attr("height", y.rangeBand()) - .style("fill", color2) - .attr("opacity", 0.8); + .attr("class", "overlay") + .attr("x", 0) + .attr("y", function(d) { + return y(d.Term) + barheight + margin.bottom + 2 * rMax; + }) + .attr("height", y.rangeBand()) + .style("fill", color2) + .attr("opacity", 0.8); if (increase) { @@ -995,32 +1006,32 @@ LDAvis = function(to_select, data_or_file_name) { function topic_on(circle) { if (circle == null) return null; - // grab data bound to this element - var d = circle.__data__ + // grab data bound to this element + var d = circle.__data__; var Freq = Math.round(d.Freq * 10) / 10, - topics = d.topics; + topics = d.topics; - // change opacity and fill of the selected circle + // change opacity and fill of the selected circle circle.style.opacity = highlight_opacity; circle.style.fill = color2; - // Remove 'old' bar chart title - var text = d3.select(".bubble-tool"); + // Remove 'old' bar chart title + var text = d3.select(to_select + " .bubble-tool"); text.remove(); - // append text with info relevant to topic of interest - d3.select("#bar-freqs") - .append("text") - .attr("x", barwidth/2) - .attr("y", -30) - .attr("class", "bubble-tool") // set class so we can remove it when highlight_off is called - .style("text-anchor", "middle") - .style("font-size", "16px") - .text("Top-" + R + " Most Relevant Terms for Topic " + topics + " (" + Freq + "% of tokens)"); + // append text with info relevant to topic of interest + d3.select("#" + barFreqsID) + .append("text") + .attr("x", barwidth/2) + .attr("y", -30) + .attr("class", "bubble-tool") // set class so we can remove it when highlight_off is called + .style("text-anchor", "middle") + .style("font-size", "16px") + .text("Top-" + R + " Most Relevant Terms for Topic " + topics + " (" + Freq + "% of tokens)"); // grab the bar-chart data for this topic only: var dat2 = lamData.filter(function(d) { - return d.Category == "Topic" + topics + return d.Category == "Topic" + topics; }); // define relevance: @@ -1037,22 +1048,22 @@ LDAvis = function(to_select, data_or_file_name) { // scale the bars to the top R terms: var y = d3.scale.ordinal() - .domain(dat3.map(function(d) { - return d.Term; - })) - .rangeRoundBands([0, barheight], 0.15); + .domain(dat3.map(function(d) { + return d.Term; + })) + .rangeRoundBands([0, barheight], 0.15); var x = d3.scale.linear() - .domain([0, d3.max(dat3, function(d) { - return d.Total; - })]) - .range([0, barwidth]) - .nice(); + .domain([1, d3.max(dat3, function(d) { + return d.Total; + })]) + .range([0, barwidth]) + .nice(); // remove the red bars if there are any: - d3.selectAll(".overlay").remove(); + d3.selectAll(to_select + " .overlay").remove(); // Change Total Frequency bars - d3.selectAll(".bar-totals") + d3.selectAll(to_select + " .bar-totals") .data(dat3) .attr("x", 0) .attr("y", function(d) { @@ -1066,14 +1077,14 @@ LDAvis = function(to_select, data_or_file_name) { .attr("opacity", 0.4); // Change word labels - d3.selectAll(".terms") + d3.selectAll(to_select + " .terms") .data(dat3) .attr("x", -5) .attr("y", function(d) { return y(d.Term) + 12; }) .attr("id", function(d) { - return (termID + d.Term) + return (termID + d.Term); }) .style("text-anchor", "end") // right align text - use 'middle' for center alignment .text(function(d) { @@ -1081,7 +1092,7 @@ LDAvis = function(to_select, data_or_file_name) { }); // Create red bars (drawn over the gray ones) to signify the frequency under the selected topic - d3.select("#bar-freqs").selectAll(".overlay") + d3.select("#" + barFreqsID).selectAll(to_select + " .overlay") .data(dat3) .enter() .append("rect") @@ -1099,13 +1110,13 @@ LDAvis = function(to_select, data_or_file_name) { // adapted from http://bl.ocks.org/mbostock/1166403 var xAxis = d3.svg.axis().scale(x) - .orient("top") - .tickSize(-barheight) - .tickSubdivide(true) - .ticks(6); + .orient("top") + .tickSize(-barheight) + .tickSubdivide(true) + .ticks(6); // redraw x-axis - d3.selectAll(".xaxis") + d3.selectAll(to_select + " .xaxis") //.attr("class", "xaxis") .call(xAxis); } @@ -1117,35 +1128,35 @@ LDAvis = function(to_select, data_or_file_name) { circle.style.opacity = base_opacity; circle.style.fill = color1; - var title = d3.selectAll(".bubble-tool") - .text("Top-" + R + " Most Salient Terms"); - title.append("tspan") - .attr("baseline-shift", "super") - .attr("font-size", 12) - .text(1); + var title = d3.selectAll(to_select + " .bubble-tool") + .text("Top-" + R + " Most Salient Terms"); + title.append("tspan") + .attr("baseline-shift", "super") + .attr("font-size", 12) + .text(1); // remove the red bars - d3.selectAll(".overlay").remove(); + d3.selectAll(to_select + " .overlay").remove(); // go back to 'default' bar chart var dat2 = lamData.filter(function(d) { - return d.Category == "Default" + return d.Category == "Default"; }); var y = d3.scale.ordinal() - .domain(dat2.map(function(d) { - return d.Term; - })) - .rangeRoundBands([0, barheight], 0.15); + .domain(dat2.map(function(d) { + return d.Term; + })) + .rangeRoundBands([0, barheight], 0.15); var x = d3.scale.linear() - .domain([0, d3.max(dat2, function(d) { - return d.Total; - })]) - .range([0, barwidth]) - .nice(); + .domain([1, d3.max(dat2, function(d) { + return d.Total; + })]) + .range([0, barwidth]) + .nice(); // Change Total Frequency bars - d3.selectAll(".bar-totals") + d3.selectAll(to_select + " .bar-totals") .data(dat2) .attr("x", 0) .attr("y", function(d) { @@ -1159,7 +1170,7 @@ LDAvis = function(to_select, data_or_file_name) { .attr("opacity", 0.4); //Change word labels - d3.selectAll(".terms") + d3.selectAll(to_select + " .terms") .data(dat2) .attr("x", -5) .attr("y", function(d) { @@ -1172,13 +1183,13 @@ LDAvis = function(to_select, data_or_file_name) { // adapted from http://bl.ocks.org/mbostock/1166403 var xAxis = d3.svg.axis().scale(x) - .orient("top") - .tickSize(-barheight) - .tickSubdivide(true) - .ticks(6); + .orient("top") + .tickSize(-barheight) + .tickSubdivide(true) + .ticks(6); // redraw x-axis - d3.selectAll(".xaxis") + d3.selectAll(to_select + " .xaxis") .attr("class", "xaxis") .call(xAxis); } @@ -1197,10 +1208,10 @@ LDAvis = function(to_select, data_or_file_name) { function term_on(term) { if (term == null) return null; term.style["fontWeight"] = "bold"; - var d = term.__data__ + var d = term.__data__; var Term = d.Term; var dat2 = mdsData3.filter(function(d2) { - return d2.Term == Term + return d2.Term == Term; }); var k = dat2.length; // number of topics for this token with non-zero frequency @@ -1224,23 +1235,23 @@ LDAvis = function(to_select, data_or_file_name) { } var rScaleCond = d3.scale.sqrt() - .domain([0, 1]).range([0, rMax]); + .domain([0, 1]).range([0, rMax]); // Change size of bubbles according to the word's distribution over topics - d3.selectAll(".dot") + d3.selectAll(to_select + " .dot") .data(radius) .transition() .attr("r", function(d) { //return (rScaleCond(d)); - return (Math.sqrt(d*mdswidth*mdsheight*word_prop/Math.PI)); + return (Math.sqrt(d*mdswidth*mdsheight*word_prop/Math.PI)); }); // re-bind mdsData so we can handle multiple selection - d3.selectAll(".dot") - .data(mdsData) + d3.selectAll(to_select + " .dot") + .data(mdsData); // Change sizes of topic numbers: - d3.selectAll(".txt") + d3.selectAll(to_select + " .txt") .data(size) .transition() .style("font-size", function(d) { @@ -1248,7 +1259,7 @@ LDAvis = function(to_select, data_or_file_name) { }); // Alter the guide - d3.select(".circleGuideTitle") + d3.select(to_select + " .circleGuideTitle") .text("Conditional topic distribution given term = '" + term.innerHTML + "'"); } @@ -1256,7 +1267,7 @@ LDAvis = function(to_select, data_or_file_name) { if (term == null) return null; term.style["fontWeight"] = "normal"; - d3.selectAll(".dot") + d3.selectAll(to_select + " .dot") .data(mdsData) .transition() .attr("r", function(d) { @@ -1265,22 +1276,22 @@ LDAvis = function(to_select, data_or_file_name) { }); // Change sizes of topic numbers: - d3.selectAll(".txt") + d3.selectAll(to_select + " .txt") .transition() .style("font-size", "11px"); // Go back to the default guide - d3.select(".circleGuideTitle") + d3.select(to_select + " .circleGuideTitle") .text("Marginal topic distribution"); - d3.select(".circleGuideLabelLarge") + d3.select(to_select + " .circleGuideLabelLarge") .text(defaultLabelLarge); - d3.select(".circleGuideLabelSmall") + d3.select(to_select + " .circleGuideLabelSmall") .attr("y", mdsheight + 2 * newSmall) .text(defaultLabelSmall); - d3.select(".circleGuideSmall") + d3.select(to_select + " .circleGuideSmall") .attr("r", newSmall) .attr("cy", mdsheight + newSmall); - d3.select(".lineGuideSmall") + d3.select(to_select + " .lineGuideSmall") .attr("y1", mdsheight + 2 * newSmall) .attr("y2", mdsheight + 2 * newSmall); } @@ -1295,40 +1306,40 @@ LDAvis = function(to_select, data_or_file_name) { vis_state.lambda = params[1].split("=")[1]; vis_state.term = params[2].split("=")[1]; - // Idea: write a function to parse the URL string - // only accept values in [0,1] for lambda, {0, 1, ..., K} for topics (any string is OK for term) - // Allow for subsets of the three to be entered: - // (1) topic only (lambda = 1 term = "") - // (2) lambda only (topic = 0 term = "") visually the same but upon hovering a topic, the effect of lambda will be seen - // (3) term only (topic = 0 lambda = 1) only fires when the term is among the R most salient - // (4) topic + lambda (term = "") - // (5) topic + term (lambda = 1) - // (6) lambda + term (topic = 0) visually lambda doesn't make a difference unless a topic is hovered - // (7) topic + lambda + term + // Idea: write a function to parse the URL string + // only accept values in [0,1] for lambda, {0, 1, ..., K} for topics (any string is OK for term) + // Allow for subsets of the three to be entered: + // (1) topic only (lambda = 1 term = "") + // (2) lambda only (topic = 0 term = "") visually the same but upon hovering a topic, the effect of lambda will be seen + // (3) term only (topic = 0 lambda = 1) only fires when the term is among the R most salient + // (4) topic + lambda (term = "") + // (5) topic + term (lambda = 1) + // (6) lambda + term (topic = 0) visually lambda doesn't make a difference unless a topic is hovered + // (7) topic + lambda + term - // Short-term: assume format of "#topic=k&lambda=l&term=s" where k, l, and s are strings (b/c they're from a URL) + // Short-term: assume format of "#topic=k&lambda=l&term=s" where k, l, and s are strings (b/c they're from a URL) - // Force k (topic identifier) to be an integer between 0 and K: - vis_state.topic = Math.round(Math.min(K, Math.max(0, vis_state.topic))); + // Force k (topic identifier) to be an integer between 0 and K: + vis_state.topic = Math.round(Math.min(K, Math.max(0, vis_state.topic))); - // Force l (lambda identifier) to be in [0, 1]: - vis_state.lambda = Math.min(1, Math.max(0, vis_state.lambda)); + // Force l (lambda identifier) to be in [0, 1]: + vis_state.lambda = Math.min(1, Math.max(0, vis_state.lambda)); // impose the value of lambda: document.getElementById(lambdaID).value = vis_state.lambda; - document.getElementById(lambdaID + "-value").innerHTML = vis_state.lambda; + document.getElementById(lambdaID + "-value").innerHTML = vis_state.lambda; // select the topic and transition the order of the bars (if approporiate) if (!isNaN(vis_state.topic)) { - document.getElementById(topicID).value = vis_state.topic; - if (vis_state.topic > 0) { + document.getElementById(topicID).value = vis_state.topic; + if (vis_state.topic > 0) { topic_on(document.getElementById(topicID + vis_state.topic)); - } + } if (vis_state.lambda < 1 && vis_state.topic > 0) { - reorder_bars(false); - } + reorder_bars(false); + } } - lambda.current = vis_state.lambda; + lambda.current = vis_state.lambda; var termElem = document.getElementById(termID + vis_state.term); if (termElem !== undefined) term_on(termElem); } @@ -1371,4 +1382,4 @@ LDAvis = function(to_select, data_or_file_name) { //debugger; -} +};