├── .gitignore ├── LICENSE ├── QualtricsExample.qsf ├── README.md ├── dependencies ├── mousehold.d.ts └── mousehold.js ├── distributionbuilder.css ├── distributionbuilder.min.js ├── distributionbuilder.zip ├── docs ├── demo │ └── js │ │ └── main.js ├── fonts │ ├── copse-regular-webfont.eot │ ├── copse-regular-webfont.svg │ ├── copse-regular-webfont.ttf │ ├── copse-regular-webfont.woff │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── glyphicons-halflings-regular.woff2 │ ├── quattrocentosans-bold-webfont.eot │ ├── quattrocentosans-bold-webfont.svg │ ├── quattrocentosans-bold-webfont.ttf │ ├── quattrocentosans-bold-webfont.woff │ ├── quattrocentosans-bolditalic-webfont.eot │ ├── quattrocentosans-bolditalic-webfont.svg │ ├── quattrocentosans-bolditalic-webfont.ttf │ ├── quattrocentosans-bolditalic-webfont.woff │ ├── quattrocentosans-italic-webfont.eot │ ├── quattrocentosans-italic-webfont.svg │ ├── quattrocentosans-italic-webfont.ttf │ ├── quattrocentosans-italic-webfont.woff │ ├── quattrocentosans-regular-webfont.eot │ ├── quattrocentosans-regular-webfont.svg │ ├── quattrocentosans-regular-webfont.ttf │ └── quattrocentosans-regular-webfont.woff ├── images │ ├── background.png │ ├── body-background.png │ ├── bullet.png │ ├── hr.gif │ └── octocat-logo.png ├── index.html ├── javascripts │ └── main.js └── stylesheets │ ├── distributionbuilder.css │ ├── github-dark.css │ ├── normalize.css │ └── styles.css ├── lib ├── distributionbuilder.css ├── distributionbuilder.d.ts ├── distributionbuilder.js └── distributionbuilder.min.js ├── package.json ├── public └── fonts │ └── glyphicons-halflings-regular.svg ├── reference.bib ├── src ├── bootstrap.config.js ├── bootstrap.config.less ├── demo.js ├── distributionbuilder.css ├── distributionbuilder.js ├── distributionbuilder.ts ├── entry.js └── postcss.config.js ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | yarn.lock 4 | test.html 5 | yarn-error.log 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Quentin André. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /QualtricsExample.qsf: -------------------------------------------------------------------------------- 1 | {"SurveyEntry":{"SurveyID":"SV_06w5Ku7JpLrmPAx","SurveyName":"DistributionBuilder Preview","SurveyDescription":null,"SurveyOwnerID":"UR_8HNsUBv4Xq1C36B","SurveyBrandID":"insead","DivisionID":null,"SurveyLanguage":"EN","SurveyActiveResponseSet":"RS_57JoMFau2H81LSt","SurveyStatus":"Active","SurveyStartDate":"0000-00-00 00:00:00","SurveyExpirationDate":"0000-00-00 00:00:00","SurveyCreationDate":"2016-11-12 02:26:34","CreatorID":"UR_8HNsUBv4Xq1C36B","LastModified":"2016-11-12 04:11:40","LastAccessed":"0000-00-00 00:00:00","LastActivated":"2016-11-12 02:29:04","Deleted":null},"SurveyElements":[{"SurveyID":"SV_06w5Ku7JpLrmPAx","Element":"BL","PrimaryAttribute":"Survey Blocks","SecondaryAttribute":null,"TertiaryAttribute":null,"Payload":[{"Type":"Default","Description":"DistributionBuilder","ID":"BL_8A1qQupvnjNcAGV","BlockElements":[{"Type":"Question","QuestionID":"QID1"}]},{"Type":"Trash","Description":"Trash \/ Unused Questions","ID":"BL_b89VG1W4bPgdUfb"}]},{"SurveyID":"SV_06w5Ku7JpLrmPAx","Element":"FL","PrimaryAttribute":"Survey Flow","SecondaryAttribute":null,"TertiaryAttribute":null,"Payload":{"Type":"Root","FlowID":"FL_1","Flow":[{"Type":"EmbeddedData","FlowID":"FL_3","EmbeddedData":[{"Description":"DistributionResult","Type":"Recipient","Field":"DistributionResult","VariableType":"Nominal"}]},{"Type":"Block","ID":"BL_8A1qQupvnjNcAGV","FlowID":"FL_2"}],"Properties":{"Count":3}}},{"SurveyID":"SV_06w5Ku7JpLrmPAx","Element":"SO","PrimaryAttribute":"Survey Options","SecondaryAttribute":null,"TertiaryAttribute":null,"Payload":{"BackButton":"false","SaveAndContinue":"true","SurveyProtection":"PublicSurvey","BallotBoxStuffingPrevention":"false","NoIndex":"Yes","SecureResponseFiles":"true","SurveyExpiration":"None","SurveyTermination":"DefaultMessage","Header":" 13 | 14 | 15 | 17 | 18 |
19 |A Javascript library adding distribution builders to your experiments.
22 |In their paper 'Lay 52 | understanding of probability distributions' (2014), published in 2014, Daniel Goldstein and 53 | David Rothschild have highlighted the benefits of using graphical interfaces called distribution 54 | builders to study subjective probabilities, perceptions of frequency, and confidence 55 | judgements. This tool was first developed by Sharpe, Goldstein and Blythe (2000), and was 57 | featured later in Goldstein, Johnson and Sharpe (2008) and Delavande and Rhowedder (2008). 61 | However, the implementation of such distribution builders in online studies is not straightforward, as most 62 | survey platforms do not implement this type of question.
63 |The distBuilder library was created to address this issue, and make the implementation of 64 | distribution builders easy and accessible to researchers. The library is user-friendly, and requires very 65 | little programming knowledge to be used.
66 |You can cite distBuilder using its Digital Object Identifier 68 | (DOI).
69 |This library is developed by Quentin André, an assistant professor of Marketing at 71 | the Leeds School of Business, University of Colorado Boulder. If you have any comment, feedback 72 | or suggestion regarding this library or its documentation, 73 | please let me know at quentin.andre@colorado.edu or use GitHub issues.
74 | 75 |To add the library to your projects and start using distBuilder, just follow the instructions 77 | below.
78 |If you are hosting your experiment on a separate website, here is how to add the script:
80 |Add the Javascript file distributionbuilder.min.js
and the CSS file distributionbuilder.css
85 | to your html code:
<link rel="stylesheet" href="distributionbuilder.css">
87 | <script src="distributionbuilder.min.js"></script>
88 | $j
alias.
92 | Instead of typing $('#Hello')
, type $j('#Hello')
.
93 | If you prefer starting from a minimal working example and tweaking it, you can find 98 | a fully functional integration to Qualtrics here in the form of a .qsf file, that you can import in 99 | Qualtrics.
100 |If you prefer detailed instructions, just follow these simple steps:
101 |<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/QuentinAndre/DistributionBuilder@master/lib/distributionbuilder.css">
107 | <script src="https://cdn.jsdelivr.net/gh/QuentinAndre/DistributionBuilder@master/lib/distributionbuilder.min.js"></script>
108 | Qualtrics.SurveyEngine.addOnReady
section.
112 | $j
alias. Instead of typing $('#Hello')
, type $j('#Hello')
.
114 | The library is extremely easy to use: it allows you to add fully functional distribution builders in a few 120 | lines of code.
121 |<div id="targetdiv">
to your HTML, and copy the code below between
123 | your two <script>
tags.
124 | <div id="targetdiv">
to the HTML of your question, and copy the
126 | code below in the "Custom Javascript" section. See this link if you have never added Javascript to a Qualtrics question before.
129 | var distbuilder = new DistributionBuilder();
137 | distbuilder.render("targetdiv");
138 | distbuilder.labelize();
139 | In just three lines of code, you have created an interactive (try it!) Distribution Builder in the <div>
141 | section called “targetdiv”. It consists of three elements:
Each of these elements can be customized to your liking, both using the Javascript methods wrapped by the 149 | library and the CSS. Browse the sections below to discover how you can customize the appearance and 150 | functionalities of the distribution builder, access the results of your participants, and implement more 151 | complex logics in your studies.
152 | 153 |The complete creation of a Distribution Builder object is done in three phases:
155 |The first step in creating a Distribution Builder is to initialize the object:
162 |myDistBuilder = new DistributionBuilder({})
This function call initializes the internal state of the DistributionBuilder object, with a certain number of 164 | parameters that you can specify:
165 |nRows
, (default: 10): The number of rows of the distribution builder (i.e. the
168 | maximum number of balls that can be allocated to a certain value).
169 | nBuckets
, (default: 10): The number of buckets (columns) to which balls can be
172 | allocated.
173 | minVal
, (default: 0): The value corresponding to the first bucket (the smallest
176 | value).
177 | maxVal
, (default: 10): The value corresponding to the last bucket (the largest
180 | value)
181 | nBalls
, (default: 10): The total number of balls to allocate.
184 | onTouch
, (default: function () {}
): A JavaScript function that will
187 | be called every time the user clicks a button of the distribution builder.
188 | onChange
, (default: function () {}
): A JavaScript function that will
190 | be called every time the user successfully changes the allocation of balls (i.e. when the add/remove
191 | action is performed on non-filled/non-empty bucket, and when there are still balls available to
192 | allocate).
193 | toggleGridClick
, (default: false
): Allow/disallow participants to click
195 | on the distBuilder to change the allocation of the balls.
196 | addTotals
, (default: false
): Add a row to the bottom of the distBuilder
198 | summarizing how many balls are in each bucket.
199 | var distbuilder = new DistributionBuilder({
206 | nRows: 20,
207 | nBuckets: 20,
208 | minVal: 0,
209 | maxVal: 100,
210 | nBalls: 20,
211 | onTouch: function () {
212 | console.log("Distbuilder was touched!")
213 | },
214 | onChange: function () {
215 | console.log("Distbuilder was updated!")
216 | },
217 | toggleGridClick: true,
218 | addTotals: true
219 | });
220 | distbuilder.render("targetdiv");
221 | distbuilder.labelize();
222 | After the DistributionBuilder object is initialized, you must call another method to indicate where you want 225 | to see it displayed on the page:
226 |DistributionBuilder.render()
This method requires the argument target
, and you can optionally supply the order
228 | argument to further tweak the appearance of the Distribution Builder.
target
, required argument: The html id
attribute of the element in
232 | which the distribution builder should be displayed. For best results, this element should be a div with
233 | a fixed width.
234 | order
, (default: ‘grid-labels-buttons’): The order in which the elements of the
237 | Distribution Builder should be rendered. For instance, if you want to labels to appear above the grid,
238 | you should specify ‘labels-grid-buttons’.
239 | var distbuilder = new DistributionBuilder({
247 | nRows: 10,
248 | nBuckets: 20,
249 | minVal: 0,
250 | maxVal: 100,
251 | nBalls: 10,
252 | onTouch: function () {
253 | console.log("Distbuilder was touched!")
254 | },
255 | onChange: function () {
256 | console.log("Distbuilder was updated!")
257 | }
258 | });
259 | distbuilder.render("targetdiv", "labels-grid-buttons");
260 | distbuilder.labelize();
261 |
262 | You generally want to add some labels to the buckets of your distribution builder:
265 |DistributionBuilder.labelize()
By default, the Distribution Builder automatically creates evenly spaced labels, using the distance between 267 | the minimum and maximum value and the number of buckets using the following code:
268 |269 |
step = (maxValue - minValue)/nBuckets;
270 | labels = [minValue + step/2 + step*0, minValue + step/2 + step*1, ..., minValue + step/2 +step*(nBuckets-1)]
271 | Calling the method DistributionBuilder.labelize()
without arguments will display those
272 | labels. However, you are free to customize the labels using the following arguments:
labels
, an array of length nBuckets
code>. Supplying this argument will
275 | override the default labels.
276 | prefix
, a string (e.g. ‘$’) which you would like to see prepended to all the labels.
278 | suffix
, a string (e.g. ‘€’) which you would like to see appended to all the labels.
280 | The prefix
and suffix
arguments are always applied, whether or not you specify
283 | custom labels using the label
argument.
var distbuilder = new DistributionBuilder({
289 | nRows: 10,
290 | nBuckets: 20,
291 | minVal: 0,
292 | maxVal: 100,
293 | nBalls: 10,
294 | onTouch: function () {
295 | console.log("Distbuilder was touched!")
296 | },
297 | onChange: function () {
298 | console.log("Distbuilder was updated!")
299 | }
300 | });
301 | distbuilder.render();
302 | distbuilder.labelize({
303 | prefix: '~',
304 | suffix: '€'
305 | });
306 |
307 | By default, respondents will start from an empty distribution (i.e. zero balls in all buckets).
310 |However, you might want to specify a different starting distribution, or change the current distribution 311 | to a specific value. To do so, you can use the method:
312 |DistributionBuilder.setDistribution(dist)
Here is an example:
314 |var distbuilder = new DistributionBuilder({
316 | nRows: 10,
317 | nBuckets: 10,
318 | minVal: 0,
319 | maxVal: 100,
320 | nBalls: 60
321 | });
322 | distbuilder.render();
323 | distbuilder.labelize(});
324 | var dist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
325 | distbuilder.setDistribution(dist);
326 |
327 | After setting up the Distribution Builder, its internal state can be conveniently accessed through three 335 | methods:
336 |DistributionBuilder.getDistribution()
returns the current allocation of balls in the
339 | form of an array of length nBuckets. If there are four buckets, and the user has allocated two balls
340 | to the first bucket, one to the third one, and zero to the second and fourth, the method will return
341 | [2, 0, 1, 0]
.
DistributionBuilder.getRemainingBalls()
returns the number of balls that have not been
345 | allocated yet.
DistributionBuilder.isComplete()
returns true
is the user has allocated all
349 | the balls, and false
otherwise.
The two examples below demonstrate how those three methods can be used in your experiments and studies.
353 |This can be achieved by creating two <div>
s called “BallsLeft” and “BallsAllocated”, and
355 | assigning a function updating those <div>
s to the onChange
argument of the
356 | Distribution Builder initialization function.
var n_balls = 10;
366 | $j('#BallsLeft').text("You have " + n_balls + " balls left.");
367 | $j('#BallsAllocated').text("You have allocated " + 0 + " balls.");
368 | var distbuilder = new DistributionBuilder({
369 | minVal: 0,
370 | maxVal: 100,
371 | nRows: 10,
372 | nBuckets: 20,
373 | nBalls: n_balls,
374 | onChange: function () {
375 | var remainingballs = this.getRemainingBalls();
376 | var ballsallocated = n_balls - this.getRemainingBalls();
377 | $j('#BallsLeft').text("You have " + remainingballs + " balls left.");
378 | $j('#BallsAllocated').text("You have allocated " + ballsallocated + " balls.");
379 | }
380 | });
381 | distbuilder.render("targetdiv");
382 | distbuilder.labelize();
383 | You probably want the participants to your studies to allocate all the balls before being able to validate 386 | their distribution. Fortunately, it is easy to add a button, and only enable it when all the balls have been 387 | allocated:
388 |button
called “SubmitDistribution” with disabled=true
. When clicked,
391 | this button will for now display "Distribution Validated!".
392 | onChange
argument of the
395 | DistributionBuilder object. This is done by using the value returned by the DistributionBuilder.isComplete()
396 | method.
397 | Together, those two steps are ensuring that the distribution is fully specified before the user can click the 400 | button.
401 |var distbuilder = new DistributionBuilder({
410 | minVal: 0,
411 | maxVal: 100,
412 | nRows: 10,
413 | nBuckets: 20,
414 | nBalls: 10,
415 | onChange: function () {
416 | if (this.isComplete()) {
417 | $j("#SubmitDistribution").attr("disabled", false)
418 | } else {
419 | $j("#SubmitDistribution").attr("disabled", true)
420 | }
421 | }
422 | });
423 | $j("#SubmitDistribution").click(function() {alert("Distribution Validated!")});
424 | distbuilder.render("targetdiv");
425 | distbuilder.labelize({});
426 | We would now like to see in which buckets the balls have been allocated when the user validates the 429 | distribution by clicking on the button.
430 |Building upon the previous example, we can change the message "Distribution Validated!" to display the actual
431 | distribution that the user has specified. by using the method
432 | DistributionBuilder.getDistribution()
.
var distbuilder = new DistributionBuilder({
442 | minVal: 0,
443 | maxVal: 100,
444 | nRows: 10,
445 | nBuckets: 20,
446 | nBalls: 10,
447 | onChange: function () {
448 | if (this.isComplete()) {
449 | $j("#SubmitDistribution").attr("disabled", false)
450 | } else {
451 | $j("#SubmitDistribution").attr("disabled", true)
452 | }
453 | }
454 | });
455 | $j("#SubmitDistribution").click(function() {
456 | var message = "The distribution specified by the user is: " + distbuilder.getDistribution();
457 | alert(message)
458 | });
459 | distbuilder.render("targetdiv");
460 | distbuilder.labelize({});
461 | You will often want the Distribution Builder to be part of larger survey on Qualtrics, and you will want to 464 | store the resulting distribution in a variable. This result can be achieved in the following way:
465 |"MyDistributionResult"
, or any other name that you want to use. Leave
468 | the value empty: this is where we are going to store the results.
469 | Qualtrics.SurveyEngine.setEmbeddedData()
function provided by Qualtrics
472 | to store a value in the "MyDistributionResult"
variable. The syntax is: Qualtrics.SurveyEngine.setEmbeddedData("MyDistributionResult",
473 | "TheStringYouWantToStoreHere")
474 | DistributionBuilder.getDistribution()
does not return a string: it returns an array. You
477 | must therefore convert this array into a string first. To do so, simply use DistributionBuilder.getDistribution().join()
.
478 | This will join the elements of the array by commas, and you will be able to store it in Qualtrics.
479 | Adding the code to the previous example:
482 |var distbuilder = new DistributionBuilder({
491 | minVal: 0,
492 | maxVal: 100,
493 | nRows: 10,
494 | nBuckets: 20,
495 | nBalls: 10,
496 | onChange: function () {
497 | if (this.isComplete()) {
498 | $j("#SubmitDistribution").attr("disabled", false)
499 | } else {
500 | $j("#SubmitDistribution").attr("disabled", true)
501 | }
502 | }
503 | });
504 | distbuilder.render("targetdiv");
505 | distbuilder.labelize({});
506 | $j("#SubmitDistribution").click(function () {
507 | var results = distbuilder.getDistribution().join()
508 | Qualtrics.SurveyEngine.setEmbeddedData("MyDistributionResult", results);
509 | var message = 'The function "Qualtrics.SurveyEngine.setEmbeddedData("MyDistributionResult", ';
510 | message += results + ') was called. Your data would have been stored in Qualtrics!';
511 | alert(message)
512 | });
513 | If you want to customize the appearance of the distribution builder, the full list of classes defined by the
517 | stylesheet can be found in the distributionbuilder.css
file.
The following CSS tree will help you understand the mapping between the CSS classes and the way the 519 | Distribution Builder object is rendered on screen.
520 |<div id="MyContainer" class="distbuilder">
521 | <div class="grid">
522 | <div class="distrow row2">
523 | <div class="cell col0">
524 | <div class="ball col0"></div> // Present in all "cells"
525 | </div>
526 | <div class="cell col1"></div>
527 | <div class="cell col2"></div>
528 | </div>
529 | <div class="distrow row1">
530 | <div class="cell col0 filled"></div>
531 | <div class="cell col1"></div>
532 | <div class="cell col2"></div>
533 | </div>
534 | <div class="distrow row0">
535 | <div class="cell col0 filled"></div>
536 | <div class="cell col1"></div>
537 | <div class="cell col2"></div>
538 | </div>
539 | </div>
540 | <div class="buttons">
541 | <div class="distrow">
542 | <div class="buttongroup">
543 | <a class="btn btn-default distbutton glyphicon glyphicon-plus"></a>
544 | <a class="btn btn-default distbutton glyphicon glyphicon-minus"></a>
545 | </div>
546 | <div class="buttongroup">
547 | <a class="btn btn-default distbutton glyphicon glyphicon-plus"></a>
548 | <a class="btn btn-default distbutton glyphicon glyphicon-minus"></a>
549 | </div>
550 | <div class="buttongroup">
551 | <a class="btn btn-default distbutton glyphicon glyphicon-plus"></a>
552 | <a class="btn btn-default distbutton glyphicon glyphicon-minus"></a>
553 | </div>
554 | </div>
555 | </div>
556 | <div class="labels">
557 | <div class="distrow">
558 | <div class="label col0">~1€</div>
559 | <div class="label col1">~2€</div>
560 | <div class="label col2">~3€</div>
561 | </div>
562 | </div>
563 | </div class="totals"> // Only present if argument `addTotals` is `true`.
564 | <div class="distrow">
565 | <div class="total col0">2</div>
566 | <div class="total col1">0</div>
567 | <div class="total col2">0</div>
568 | </div>
569 | </div>
570 | </div>
571 | distBuilder is published under the MIT 574 | license. The code is written in Typescript, and is transpiled in legacy code 575 | for cross-browsers compatibility. It is developed upon the jQuery 576 | library, and uses some elements of Bootstrap 577 | for styling.
578 |I am grateful for the financial support of ADL Partner and INSEAD, the comments of Nicholas Reinholtz and Bart de Langhe on previous 581 | versions of the Distribution Builder, and Dan Goldstein's 582 | references on the history of the distribution builder. I would also like to thank Ignazio Ziano for uncovering a bug 584 | when trying to use the library.
585 |This page is hosted on GitHub Pages. Theme by mattgraham
586 |