I am trying to program a drag-and-drop exercise in qualtrics, where participants drag and drop tokens along a number line. The code is printed below along with my survey flow.
However, when I test my survey, all I see is the manually written question text
Survey flow:

Javascript (which modifies the single question in the “distribution” block):
Qualtrics.SurveyEngine.addOnload(function()
{
/*Place your JavaScript here to run when the page loads*/
// Qualtrics.SurveyEngine.addOnload(function () //{jQuery.getScript("https://d3js.org/d3.v4.min.js");});
// Hide next button
this.hideNextButton();
// Stash location of question
var that = this;
// Get embedded variables
var Out_Party = Qualtrics.SurveyEngine.getEmbeddedData('Out_Party');
// alert(Out_Party);
// Set variables by party
if (Out_Party === 'Democrat') {
// Colors
var light_color = "#5e70fd"
var dark_color = "#00108a"
} else {
Colors
var light_color = "#f96f67"
var dark_color = "#850801"
}
// Create stash for coin-placement order
var coin_order = [];
// Set scale labels
var left_label1 = 'Always';
var left_label2 = 'liberal';
var left_label3 = '';
var right_label1 = 'Always';
var right_label2 = 'conservative';
var right_label3 = '';
// Define visual parameters
var plot_width = 800,
plot_height = 600,
margin = {top: 30, right: 30, bottom: 30, left: 110}, // Plot skews left
width = plot_width - margin.left - margin.right,
height = plot_height - margin.top - margin.bottom,
v_space = 18,
bins = 11,
binWidth = (width / bins),
binHeight = 250, // I think this is arbitrary
tokens = 20,
coinHeight = (binHeight) / tokens - 1,
usedTokens = 0,
dist_title_y = 15,
dist_axis_text_y = dist_title_y + v_space + binHeight + v_space,
submit_button_width = 100,
button_height = 25,
title_font_size = 20,
axis_text_font_size = 18,
dist_tracker = {};
var svg = d3.select('#dist_body')
.append('svg')
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 " + (plot_width * 1.1) + " " + (plot_height * 1.1))
.style('display', 'inline-block')
.style('position', 'absolute')
.style('top', 0)
.style('left', 0)
.append('g')
.attr('transform',"translate(" + margin.left + "," + margin.top + ")");
var coins = svg.append('g'),
bars = svg.append('g');
// Add plot title
var plot_title = svg.append('text')
.attr('y', dist_title_y)
.attr('x', width / 2)
.attr('text-anchor', 'middle')
.text('Tokens left: ' + tokens)
.style('user-select',' none')
.style('font-size', title_font_size)
.classed('texts', true);
// Add submit button
/// Container
var end_g = svg.append('g')
.attr('opacity', 0);
/// Visible button
end_g.append('rect')
.attr('x', (width / 2) - (submit_button_width / 2))
.attr('width', submit_button_width)
.attr('y', dist_axis_text_y + (v_space * 4) + button_height + v_space)
.attr('height', button_height)
.attr('rx', 10)
.attr('fill', 'green');
/// Text
end_g.append('text')
.attr('x', width / 2)
.attr('y', dist_axis_text_y + (v_space * 4) + button_height + v_space + (button_height / 1.3))
.attr('fill', 'white')
.attr('text-anchor', 'middle')
.style('user-select', 'none')
.attr('font-size', title_font_size)
.text('Submit');
/// Active invisible layer
var end_button = end_g.append('rect')
.attr('x', (width / 2) - (submit_button_width / 2))
.attr('width', submit_button_width)
.attr('y', dist_axis_text_y + (v_space * 4) + button_height + v_space)
.attr('height', button_height)
.attr('rx', 10)
.attr('opacity', 0)
.on('click', return_data);
// Draw background and scale points
for (let i = 0; i < bins; i++){
// Shaded regions denoting columns
coins.append('rect')
.attr('x', i * binWidth)
.attr('width', binWidth - 1)
.attr('y', dist_title_y + v_space)
.attr('height', binHeight + (v_space * 1.2)) // Extend past text
.attr('fill', 'grey')
.attr('fill-opacity', i % 2 === 0 ? 0 : 0.1)
.attr('id', 'bin-' + i)
.attr('class', 'bin');
// I think a container for coin placement?
bars.append('rect')
.attr('x', i * binWidth)
.attr('width', binWidth - 1)
.attr('y', dist_title_y + v_space)
.attr('height', binHeight)
.attr('fill-opacity', 0)
.attr('id', 'bin-' + i)
.attr('class', 'bin');
// Scale points text
bars.append('text')
.attr('x',(i * binWidth) + (binWidth / 2))
.attr('y', dist_axis_text_y)
.attr('text-anchor', 'middle')
.style('user-select', 'none')
.style('font-size', axis_text_font_size)
.text(i);
// Remove buttons
/// Text
bars.append('text')
.attr('x', (i * binWidth) + (binWidth / 2))
.attr('y', dist_axis_text_y + (v_space * 4) + (button_height / 1.5))
.attr('text-anchor', 'middle')
.attr('font-size', axis_text_font_size - 4)
.attr('stroke', 'red')
.style('user-select', 'none')
.text("remove");
/// Button
bars.append('rect')
.attr('x', i * binWidth)
.attr('width', binWidth)
.attr('y', dist_axis_text_y + (v_space * 4))
.attr('height', button_height)
.attr('fill', 'white')
.attr('fill-opacity', 0)
.attr('stroke-width', '1')
.attr('rx', 10)
.attr('stroke', 'black')
.attr('id', 'bminus-' + i)
.attr('class', 'deleter');
// Tracker
dist_tracker['bin-'+i] = {'currentY':binHeight+coinHeight,'coins':[]}
}
// Add scale labels to perceived distribution
/// Left labels
bars.append('text')
.attr('x', binWidth / 2)
.attr('y', dist_axis_text_y + v_space)
.attr('text-anchor', 'middle')
.style('user-select', 'none')
.style('font-size', axis_text_font_size)
.text(left_label1)
bars.append('text')
.attr('x', binWidth / 2)
.attr('y', dist_axis_text_y + (v_space * 2))
.attr('text-anchor', 'middle')
.style('user-select', 'none')
.style('font-size', axis_text_font_size)
.text(left_label2)
bars.append('text')
.attr('x', binWidth / 2)
.attr('y', dist_axis_text_y + (v_space * 3))
.attr('text-anchor', 'middle')
.style('user-select', 'none')
.style('font-size', axis_text_font_size)
.text(left_label3)
/// Right labels
bars.append('text')
.attr('x', (10 * binWidth) + binWidth / 2)
.attr('y', dist_axis_text_y + v_space)
.attr('text-anchor', 'middle')
.style('user-select', 'none')
.style('font-size', axis_text_font_size)
.text(right_label1)
bars.append('text')
.attr('x', (10 * binWidth) + binWidth / 2)
.attr('y', dist_axis_text_y + (v_space * 2))
.attr('text-anchor', 'middle')
.style('user-select', 'none')
.style('font-size', axis_text_font_size)
.text(right_label2)
bars.append('text')
.attr('x', (10 * binWidth) + binWidth / 2)
.attr('y', dist_axis_text_y + (v_space * 3))
.attr('text-anchor', 'middle')
.style('user-select', 'none')
.style('font-size', axis_text_font_size)
.text(right_label3)
// Allow participant to add coins
d3.selectAll('.bin')
.on('click',function(d){
if (usedTokens < tokens){
var Bin = d3.select(this);
var count = dist_tracker[Bin.attr('id')].coins.length
var bin_coin_id = 'cb-'+Bin.attr('id') + "-" + count;
coins.append('rect')
.attr('x',Bin.attr('x'))
.attr('width', binWidth - 1)
.attr('y', dist_tracker[Bin.attr('id')].currentY)
.attr('height', coinHeight)
.attr('fill', light_color)
.attr('stroke-width', 0.5)
.attr('stroke', 'black')
.attr('rx', 6)
.attr('id', bin_coin_id);
dist_tracker[Bin.attr('id')].currentY -= coinHeight;
dist_tracker[Bin.attr('id')].coins.push(bin_coin_id);
// Add token addition to order
coin_order.push("ADD" + Bin.attr('id'));
// Increment tokens used
usedTokens += 1
// Update plot title
plot_title.text('Tokens left: ' + (tokens - usedTokens))
if (usedTokens === tokens){
end_g.attr('opacity', 1);
}
}
});
// Allow participant to delete coins
d3.selectAll('.deleter')
.on('click',function(d){
var Del = d3.select(this);
var vals = dist_tracker["bin-"+Del.attr('id').split('-')[1]].coins;
if (vals.length > 0){
var lastcoin = vals[vals.length - 1];
d3.select('#' + lastcoin).remove();
dist_tracker["bin-"+Del.attr('id').split('-')[1]].coins.splice(-1, 1);
dist_tracker["bin-"+Del.attr('id').split('-')[1]].currentY += coinHeight;
// Add token deletion to order
coin_order.push("DEL" + Del.attr('id'));
// Increment tokens used
usedTokens -= 1;
// Update plot title
plot_title.text('Tokens left: ' + (tokens - usedTokens))
}
if (usedTokens !== tokens){
end_g.attr('opacity', 0);
}
})
// Define function for returning distribution data
function return_data(){
if (usedTokens === tokens){
// Create stash for tokens
let arr = [];
// Count tokens in each bin
for (const [key, value] of Object.entries(dist_tracker)) {
arr.push((key.split('-')[1], value['coins'].length));
}
// Record perceived distribution
Qualtrics.SurveyEngine.setEmbeddedData('Out_dist_Q', arr.toString());
// Record coin-placement order
Qualtrics.SurveyEngine.setEmbeddedData('out_dist_order', coin_order.toString());
// Advance to next page
(function(){that.clickNextButton();}).delay(0.2);
}
}
});
But the only thing that displays when I look at the survey is the manually written question text, none of what I’m trying to create visually with Javascript.
What I’ve done already:
I’ve tested whether the code is accurately grabbing the embedded data variable, Out_Party, using the alert() command, and it is. I’ve also tried manually calling d3.js, in case that’s the issue (display doesn’t change either way, so you’ll see that line commented out.) I’m just struggling to debug why the tokens and line isn’t displaying.
I’m relatively new to both Qualtrics and Javascript, so any and all help is greatly appreciated. Happy to provide a .qsf file, too.