├── src ├── postcss.config.js ├── entry.js ├── bootstrap.config.less ├── bootstrap.config.js ├── distributionbuilder.css ├── demo.js ├── distributionbuilder.js └── distributionbuilder.ts ├── dependencies ├── mousehold.d.ts └── mousehold.js ├── .gitignore ├── docs ├── images │ ├── hr.gif │ ├── bullet.png │ ├── background.png │ ├── octocat-logo.png │ └── body-background.png ├── fonts │ ├── copse-regular-webfont.eot │ ├── copse-regular-webfont.ttf │ ├── copse-regular-webfont.woff │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── glyphicons-halflings-regular.woff2 │ ├── quattrocentosans-bold-webfont.eot │ ├── quattrocentosans-bold-webfont.ttf │ ├── quattrocentosans-bold-webfont.woff │ ├── quattrocentosans-italic-webfont.eot │ ├── quattrocentosans-italic-webfont.ttf │ ├── quattrocentosans-italic-webfont.woff │ ├── quattrocentosans-regular-webfont.eot │ ├── quattrocentosans-regular-webfont.ttf │ ├── quattrocentosans-bolditalic-webfont.eot │ ├── quattrocentosans-bolditalic-webfont.ttf │ ├── quattrocentosans-regular-webfont.woff │ ├── quattrocentosans-bolditalic-webfont.woff │ └── quattrocentosans-bold-webfont.svg ├── javascripts │ └── main.js ├── stylesheets │ ├── github-dark.css │ ├── normalize.css │ └── styles.css └── index.html ├── distributionbuilder.zip ├── reference.bib ├── tsconfig.json ├── LICENSE ├── package.json ├── distributionbuilder.css ├── README.md ├── lib ├── distributionbuilder.d.ts └── distributionbuilder.css ├── webpack.config.js └── QualtricsExample.qsf /src/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /dependencies/mousehold.d.ts: -------------------------------------------------------------------------------- 1 | export default function MouseHold($:any): any; -------------------------------------------------------------------------------- /src/entry.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./distributionbuilder.ts').default; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | yarn.lock 4 | test.html 5 | yarn-error.log 6 | -------------------------------------------------------------------------------- /docs/images/hr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/images/hr.gif -------------------------------------------------------------------------------- /src/bootstrap.config.less: -------------------------------------------------------------------------------- 1 | @btn-default-color: #444; 2 | @btn-default-bg: #eee; -------------------------------------------------------------------------------- /docs/images/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/images/bullet.png -------------------------------------------------------------------------------- /distributionbuilder.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/distributionbuilder.zip -------------------------------------------------------------------------------- /docs/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/images/background.png -------------------------------------------------------------------------------- /docs/images/octocat-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/images/octocat-logo.png -------------------------------------------------------------------------------- /docs/images/body-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/images/body-background.png -------------------------------------------------------------------------------- /docs/fonts/copse-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/copse-regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/copse-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/copse-regular-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/copse-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/copse-regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-bold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-bold-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-bold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-italic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-italic-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-italic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-regular-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-bolditalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-bolditalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-bolditalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-bolditalic-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-bolditalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinAndre/DistributionBuilder/HEAD/docs/fonts/quattrocentosans-bolditalic-webfont.woff -------------------------------------------------------------------------------- /reference.bib: -------------------------------------------------------------------------------- 1 | @misc{, 2 | title = {{distBuilder} ({Version} 1.4.1) [{Computer} {Software}]}, 3 | url = {https://quentinandre.github.io/DistributionBuilder/}, 4 | abstract = {A Javascript library adding distribution builders to your experiments.}, 5 | author = {André, Quentin}, 6 | year = {2016} 7 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "lib": ["dom", "es6"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noEmitOnError": true, 8 | "noUnusedLocals": true, 9 | "outDir": "./lib", 10 | "target": "es6", 11 | "strict": true, 12 | "strictNullChecks": false, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "types": [] 15 | }, 16 | "include": ["src/*"] 17 | } 18 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /src/bootstrap.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | scripts: { 3 | 'transition': false, 4 | 'alert': false, 5 | 'button': true, 6 | 'carousel': false, 7 | 'collapse': false, 8 | 'dropdown': false, 9 | 'modal': false, 10 | 'tooltip': false, 11 | 'popover': false, 12 | 'scrollspy': false, 13 | 'tab': false, 14 | 'affix': false 15 | }, 16 | styles: { 17 | "mixins": true, 18 | 19 | "normalize": true, 20 | "print": true, 21 | 22 | "scaffolding": false, 23 | "type": false, 24 | "code": false, 25 | "grid": false, 26 | "tables": false, 27 | "forms": false, 28 | "buttons": true, 29 | 30 | "component-animations": false, 31 | "glyphicons": true, 32 | "dropdowns": false, 33 | "button-groups": false, 34 | "input-groups": false, 35 | "navs": false, 36 | "navbar": false, 37 | "breadcrumbs": false, 38 | "pagination": false, 39 | "pager": false, 40 | "labels": false, 41 | "badges": false, 42 | "jumbotron": false, 43 | "thumbnails": false, 44 | "alerts": false, 45 | "progress-bars": false, 46 | "media": false, 47 | "list-group": false, 48 | "panels": false, 49 | "wells": false, 50 | "close": false, 51 | 52 | "modals": false, 53 | "tooltip": false, 54 | "popovers": false, 55 | "carousel": false, 56 | 57 | "utilities": false, 58 | "responsive-utilities": false 59 | } 60 | }; 61 | 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "distbuilder", 3 | "version": "1.4.1", 4 | "description": "A distribution builder for economic and psychology experiments. Based on Goldstein and Rothschild's 2014 paper 'Lay understanding of probability distributions'.", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --progress --color", 8 | "start": "webpack-dev-server" 9 | }, 10 | "author": "Quentin ANDRE", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bootstrap": "3", 14 | "expose-loader": "^0.7.5", 15 | "jquery": "^3.3.1" 16 | }, 17 | "devDependencies": { 18 | "@types/jquery": "^3.3.6", 19 | "autoprefixer": "^8.6.3", 20 | "babel-core": "^6.26.3", 21 | "babel-loader": "^7.1.4", 22 | "babel-preset-env": "^1.7.0", 23 | "bootstrap-webpack": "^0.0.6", 24 | "clean-webpack-plugin": "^0.1.19", 25 | "css-loader": "^0.28.11", 26 | "exports-loader": "^0.7.0", 27 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 28 | "file-loader": "^1.1.11", 29 | "image-webpack-loader": "^4.3.1", 30 | "imports-loader": "^0.8.0", 31 | "less": "^3.8.1", 32 | "less-loader": "^4.1.0", 33 | "mini-css-extract-plugin": "^0.4.0", 34 | "node-sass": "^4.9.0", 35 | "postcss-loader": "^2.1.5", 36 | "sass-loader": "^7.0.3", 37 | "style-loader": "^0.21.0", 38 | "ts-loader": "^5.1.0", 39 | "typescript": "^3.0.3", 40 | "url-loader": "^1.1.1", 41 | "webpack": "^4.12.0", 42 | "webpack-cli": "^4.5.0", 43 | "webpack-dev-server": "^3.1.4", 44 | "webpack-md5-hash": "^0.0.6" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /distributionbuilder.css: -------------------------------------------------------------------------------- 1 | /* Distbuilder */ 2 | 3 | .distbuilder { 4 | border-width: 2px; 5 | border-color: grey; 6 | border-style: solid; 7 | padding: 2px; 8 | } 9 | 10 | /* Grid */ 11 | 12 | .distbuilder > .grid { 13 | background-color: lightsteelblue; 14 | width: 100%; 15 | padding-bottom: 2px; 16 | padding-top: 5px; 17 | } 18 | 19 | .distbuilder > .grid > .distrow { 20 | display: flex; 21 | } 22 | 23 | .distbuilder > .grid > .distrow > .cell { 24 | flex: 1; 25 | display: flex; 26 | justify-content: center; 27 | } 28 | 29 | .distbuilder > .grid > .distrow > .cell > .ball { 30 | margin-bottom: 1px; 31 | height: 10px; 32 | width: 10px; 33 | border: 1px solid white; 34 | border-radius: 50%; 35 | align-self: center; 36 | } 37 | 38 | .distbuilder > .grid > .distrow > .filled > .ball { 39 | background-color: #FF4500; 40 | } 41 | 42 | /* Labels */ 43 | 44 | .distbuilder > .labels { 45 | background-color: steelblue; 46 | width: 100%; 47 | } 48 | 49 | .distbuilder > .labels > .distrow { 50 | display: flex; 51 | width: 100%; 52 | } 53 | 54 | .distbuilder > .labels > .distrow > .label { 55 | color: white; 56 | float: left; 57 | text-align: center; 58 | font-size: 0.8em; 59 | flex: 1; 60 | } 61 | 62 | /* Buttons */ 63 | 64 | .distbuilder > .buttons { 65 | background-color: steelblue; 66 | width: 100%; 67 | } 68 | 69 | .distbuilder > .buttons > .distrow { 70 | display: flex; 71 | width: 100%; 72 | } 73 | 74 | .distbuilder > .buttons > .distrow > .buttongroup { 75 | text-align: center; 76 | flex: 1; 77 | padding-bottom: 5px; 78 | } 79 | 80 | .distbuilder > .buttons > .distrow > .buttongroup > .distbutton { 81 | border-radius: 10%; 82 | width: 60%; 83 | margin: 0 auto !important; 84 | padding: 0 !important; 85 | line-height: 1 !important; 86 | background-color: lightgrey; 87 | } 88 | 89 | -------------------------------------------------------------------------------- /docs/javascripts/main.js: -------------------------------------------------------------------------------- 1 | var sectionHeight = function() { 2 | var total = $j(window).height(), 3 | $jsection = $j('section').css('height','auto'); 4 | 5 | if ($jsection.outerHeight(true) < total) { 6 | var margin = $jsection.outerHeight(true) - $jsection.height(); 7 | $jsection.height(total - margin - 20); 8 | } else { 9 | $jsection.css('height','auto'); 10 | } 11 | }; 12 | 13 | $j(window).resize(sectionHeight); 14 | 15 | $j(document).ready(function(){ 16 | $j("section h1, section h2").each(function(){ 17 | $j("nav ul").append("
  • " + $j(this).text() + "
  • "); 18 | $j(this).attr("id",$j(this).text().toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g,'')); 19 | $j("nav ul li:first-child a").parent().addClass("active"); 20 | }); 21 | 22 | $j("nav ul li").on("click", "a", function(event) { 23 | var position = $j($j(this).attr("href")).offset().top - 190; 24 | $j("html, body").animate({scrollTop: position}, 400); 25 | $j("nav ul li a").parent().removeClass("active"); 26 | $j(this).parent().addClass("active"); 27 | event.preventDefault(); 28 | }); 29 | 30 | sectionHeight(); 31 | 32 | $j('img').on('load', sectionHeight); 33 | }); 34 | 35 | fixScale = function(doc) { 36 | 37 | var addEvent = 'addEventListener', 38 | type = 'gesturestart', 39 | qsa = 'querySelectorAll', 40 | scales = [1, 1], 41 | meta = qsa in doc ? doc[qsa]('meta[name=viewport]') : []; 42 | 43 | function fix() { 44 | meta.content = 'width=device-width,minimum-scale=' + scales[0] + ',maximum-scale=' + scales[1]; 45 | doc.removeEventListener(type, fix, true); 46 | } 47 | 48 | if ((meta = meta[meta.length - 1]) && addEvent in doc) { 49 | fix(); 50 | scales = [.25, 1.6]; 51 | doc[addEvent](type, fix, true); 52 | } 53 | }; -------------------------------------------------------------------------------- /dependencies/mousehold.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery mousehold plugin - fires an event while the mouse is clicked down. 3 | * Additionally, the function, when executed, is passed a single 4 | * argument representing the count of times the event has been fired during 5 | * this session of the mouse hold. 6 | * 7 | * @author Remy Sharp (leftlogic.com) 8 | * @date 2006-12-15 9 | * @example $("img").mousehold(200, function(i){ }) 10 | * @desc Repeats firing the passed function while the mouse is clicked down 11 | * 12 | * @name mousehold 13 | * @type jQuery 14 | * @param Number timeout The frequency to repeat the event in milliseconds 15 | * @param Function fn A function to execute 16 | * @cat Plugin 17 | */ 18 | 19 | function MouseHold($) { 20 | $.fn.mousehold = function (timestart, timeout, f) { 21 | if (timeout && typeof timeout == 'function') { 22 | f = timeout; 23 | timeout = 100; 24 | } 25 | if (f && typeof f == 'function') { 26 | var timer = 0; 27 | var fireStep = 0; 28 | var timerStart = 0; 29 | return this.each(function () { 30 | $(this).mousedown(function () { 31 | timerStart = setTimeout(function () { 32 | fireStep = 1; 33 | var ctr = 0; 34 | var t = this; 35 | timer = setInterval(function () { 36 | ctr++; 37 | f.call(t, ctr); 38 | fireStep = 2; 39 | }, timeout); 40 | }, timestart); 41 | }); 42 | 43 | function clearMousehold() { 44 | clearInterval(timer); 45 | clearTimeout(timerStart); 46 | if (fireStep == 1) f.call(this, 1); 47 | fireStep = 0; 48 | } 49 | 50 | $(this).mouseout(clearMousehold); 51 | $(this).mouseup(clearMousehold); 52 | }) 53 | } 54 | } 55 | } 56 | 57 | export default MouseHold -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # distBuilder 2 | A Javascript library to conveniently add distribution builders to your online and offline experiments. 3 | 4 | ## Changelog: 5 | 6 | ### v1.4 (master) 7 | * Added arguments to the initialization of the distBuilder: 8 | * `toggleGridClick`. If true, this argument allows user to add balls to bucket by clicking on the grid. 9 | * `addTotals`. If true, add a "totals" row at the bottom of the distBuilder, summarizing how many balls are in each bucket. 10 | Thanks to Marine Hainguerlot for the suggestion! 11 | 12 | * Fixed rare bugs causing the visual aspect of the distBuilder to diverge from its internal state 13 | * Cleaned up the code and documentation. It should now be faster for larger distBuilders. 14 | 15 | 16 | ### v1.3 (master) 17 | * Added method: `distBuilder.setDistribution()`. This function is useful if you want the user to start from 18 | a pre-specified distribution. Thanks to Roy Hsieh for the suggestion! 19 | 20 | 21 | ### v1.2 22 | * Minor changes to CSS to enhance compatibility with Qualtrics. 23 | * distBuilder has been rewritten in Typescript. This does not affect the behavior of the library in any 24 | way, but makes it easier for developers to build more complex apps on top of distBuilder. 25 | 26 | 27 | ### v1.1 28 | * The width of the distribution builder is now automatically adjusted 29 | using CSS `flexbox`. 30 | * The argument `resize` of `DistributionBuilder.render()` will be 31 | deprecated in future versions. For compatibility reasons, using the 32 | `resize` argument does not raise an error, but it no longer affects the 33 | behavior of the distribution builder. 34 | * Changed the HTML structure: the inner `
    ` now 35 | includes a `
    `. The appearance of the "balls" in 36 | the distribution builder can now be changed more easily. 37 | * The method `getDistribution()` now returns a copy of the current allocation. This is to avoid 38 | accidental side-effects. 39 | 40 | 41 | ### v1.0 42 | * First release of the library. 43 | 44 | 45 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.166736.svg)](https://doi.org/10.5281/zenodo.166736) 46 | -------------------------------------------------------------------------------- /lib/distributionbuilder.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Quentin André on 07/10/2016. 3 | */ 4 | import "jquery"; 5 | import './distributionbuilder.css'; 6 | import "bootstrap-webpack!./bootstrap.config.js"; 7 | interface InitConfigObject { 8 | minVal?: number; 9 | maxVal?: number; 10 | nBalls?: number; 11 | nRows?: number; 12 | nBuckets?: number; 13 | onTouch?: Function; 14 | onChange?: Function; 15 | addTotals?: boolean; 16 | toggleGridClick?: boolean; 17 | } 18 | interface LabelizeConfigObject { 19 | labels?: Array; 20 | prefix?: string; 21 | suffix?: string; 22 | } 23 | declare global { 24 | interface JQuery { 25 | mousehold(timestart: number, timeout: number, callback: Function): any; 26 | } 27 | } 28 | declare type ValidRenderOrder = "buttons-grid-labels" | "grid-labels-buttons" | "labels-grid-buttons" | "labels-buttons-grid" | "grid-buttons-labels" | "buttons-labels-grid"; 29 | declare type ValidButtonAction = "increment" | "decrement"; 30 | declare class DistributionBuilder { 31 | min: number; 32 | max: number; 33 | nBalls: number; 34 | nRows: number; 35 | nBuckets: number; 36 | remainingBalls: number; 37 | distribution: Array; 38 | _$target: JQuery; 39 | onTouch: Function; 40 | onChange: Function; 41 | toggleGridClick: Boolean; 42 | addTotals: Boolean; 43 | constructor(o: InitConfigObject); 44 | render(target: string, order: ValidRenderOrder, resize: boolean): void; 45 | labelize(o: LabelizeConfigObject): void; 46 | isComplete(): boolean; 47 | getRemainingBalls(): number; 48 | getDistribution(): Array; 49 | setDistribution(dist: Array): void; 50 | _setLabels(labels: Array): void; 51 | _buttonActionCreator(action: ValidButtonAction): Function; 52 | _gridActionCreator(row: number): Function; 53 | _createGrid(): JQuery; 54 | _createButtons(): JQuery; 55 | _createLabels(): JQuery; 56 | _createTotals(): JQuery; 57 | _updateTotals(): void; 58 | _updateGridCol(col: number, silent?: boolean): void; 59 | } 60 | export default DistributionBuilder; 61 | -------------------------------------------------------------------------------- /src/distributionbuilder.css: -------------------------------------------------------------------------------- 1 | /* Distbuilder */ 2 | 3 | .distbuilder { 4 | border-width: 2px; 5 | border-color: grey; 6 | border-style: solid; 7 | padding: 2px; 8 | } 9 | 10 | /* Grid */ 11 | 12 | .distbuilder > .grid { 13 | background-color: lightsteelblue; 14 | width: 100%; 15 | padding-bottom: 2px; 16 | padding-top: 5px; 17 | } 18 | 19 | .distbuilder > .grid > .distrow { 20 | display: flex; 21 | } 22 | 23 | .distbuilder > .grid > .distrow > .cell { 24 | flex: 1; 25 | display: flex; 26 | justify-content: center; 27 | } 28 | 29 | .distbuilder > .grid > .distrow > .cell > .ball { 30 | margin-bottom: 1px; 31 | height: 10px; 32 | width: 10px; 33 | border: 1px solid white; 34 | border-radius: 50%; 35 | align-self: center; 36 | } 37 | 38 | .distbuilder > .grid > .distrow > .filled > .ball { 39 | background-color: #FF4500; 40 | } 41 | 42 | /* Labels */ 43 | 44 | .distbuilder > .labels { 45 | background-color: steelblue; 46 | width: 100%; 47 | } 48 | 49 | .distbuilder > .labels > .distrow { 50 | display: flex; 51 | width: 100%; 52 | } 53 | 54 | .distbuilder > .labels > .distrow > .label { 55 | color: white; 56 | float: left; 57 | text-align: center; 58 | font-size: 0.8em; 59 | flex: 1; 60 | min-width: 0; 61 | } 62 | 63 | /* Buttons */ 64 | 65 | .distbuilder > .buttons { 66 | background-color: steelblue; 67 | width: 100%; 68 | } 69 | 70 | .distbuilder > .buttons > .distrow { 71 | display: flex; 72 | width: 100%; 73 | } 74 | 75 | .distbuilder > .buttons > .distrow > .buttongroup { 76 | text-align: center; 77 | flex: 1; 78 | padding-bottom: 5px; 79 | } 80 | 81 | .distbuilder > .buttons > .distrow > .buttongroup > .distbutton { 82 | border-radius: 10%; 83 | width: 60%; 84 | margin: 0 auto !important; 85 | padding: 0 !important; 86 | line-height: 1 !important; 87 | background-color: lightgrey; 88 | } 89 | 90 | /* Totals */ 91 | 92 | .totals { 93 | background-color: #FF4500; 94 | } 95 | 96 | .totals > .distrow { 97 | display: flex; 98 | } 99 | 100 | .distbuilder > .totals > .distrow > .total { 101 | color: white; 102 | float: left; 103 | text-align: center; 104 | font-size: 0.8em; 105 | flex: 1; 106 | } 107 | 108 | /* Totals */ 109 | 110 | .totals { 111 | background-color: #FF4500; 112 | } 113 | 114 | .totals > .distrow { 115 | display: flex; 116 | } 117 | 118 | .distbuilder > .totals > .distrow > .total { 119 | color: white; 120 | float: left; 121 | text-align: center; 122 | font-size: 0.8em; 123 | flex: 1; 124 | } -------------------------------------------------------------------------------- /lib/distributionbuilder.css: -------------------------------------------------------------------------------- 1 | /* Distbuilder */ 2 | 3 | .distbuilder { 4 | border-width: 2px; 5 | border-color: grey; 6 | border-style: solid; 7 | padding: 2px; 8 | } 9 | 10 | /* Grid */ 11 | 12 | .distbuilder > .grid { 13 | background-color: lightsteelblue; 14 | width: 100%; 15 | padding-bottom: 2px; 16 | padding-top: 5px; 17 | } 18 | 19 | .distbuilder > .grid > .distrow { 20 | display: flex; 21 | } 22 | 23 | .distbuilder > .grid > .distrow > .cell { 24 | flex: 1; 25 | display: flex; 26 | justify-content: center; 27 | } 28 | 29 | .distbuilder > .grid > .distrow > .cell > .ball { 30 | margin-bottom: 1px; 31 | height: 10px; 32 | width: 10px; 33 | border: 1px solid white; 34 | border-radius: 50%; 35 | align-self: center; 36 | } 37 | 38 | .distbuilder > .grid > .distrow > .filled > .ball { 39 | background-color: #FF4500; 40 | } 41 | 42 | /* Labels */ 43 | 44 | .distbuilder > .labels { 45 | background-color: steelblue; 46 | width: 100%; 47 | } 48 | 49 | .distbuilder > .labels > .distrow { 50 | display: flex; 51 | width: 100%; 52 | } 53 | 54 | .distbuilder > .labels > .distrow > .label { 55 | color: white; 56 | float: left; 57 | text-align: center; 58 | font-size: 0.8em; 59 | flex: 1; 60 | min-width: 0; 61 | } 62 | 63 | /* Buttons */ 64 | 65 | .distbuilder > .buttons { 66 | background-color: steelblue; 67 | width: 100%; 68 | } 69 | 70 | .distbuilder > .buttons > .distrow { 71 | display: flex; 72 | width: 100%; 73 | } 74 | 75 | .distbuilder > .buttons > .distrow > .buttongroup { 76 | text-align: center; 77 | flex: 1; 78 | padding-bottom: 5px; 79 | } 80 | 81 | .distbuilder > .buttons > .distrow > .buttongroup > .distbutton { 82 | border-radius: 10%; 83 | width: 60%; 84 | margin: 0 auto !important; 85 | padding: 0 !important; 86 | line-height: 1 !important; 87 | background-color: lightgrey; 88 | } 89 | 90 | /* Totals */ 91 | 92 | .totals { 93 | background-color: #FF4500; 94 | } 95 | 96 | .totals > .distrow { 97 | display: flex; 98 | } 99 | 100 | .distbuilder > .totals > .distrow > .total { 101 | color: white; 102 | float: left; 103 | text-align: center; 104 | font-size: 0.8em; 105 | flex: 1; 106 | } 107 | 108 | /* Totals */ 109 | 110 | .totals { 111 | background-color: #FF4500; 112 | } 113 | 114 | .totals > .distrow { 115 | display: flex; 116 | } 117 | 118 | .distbuilder > .totals > .distrow > .total { 119 | color: white; 120 | float: left; 121 | text-align: center; 122 | font-size: 0.8em; 123 | flex: 1; 124 | } 125 | -------------------------------------------------------------------------------- /docs/stylesheets/github-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 GitHub, Inc. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | .pl-c /* comment */ { 27 | color: #969896; 28 | } 29 | 30 | .pl-c1 /* constant, variable.other.constant, support, meta.property-name, support.constant, support.variable, meta.module-reference, markup.raw, meta.diff.header */, 31 | .pl-s .pl-v /* string variable */ { 32 | color: #0099cd; 33 | } 34 | 35 | .pl-e /* entity */, 36 | .pl-en /* entity.name */ { 37 | color: #9774cb; 38 | } 39 | 40 | .pl-smi /* variable.parameter.function, storage.modifier.package, storage.modifier.import, storage.type.java, variable.other */, 41 | .pl-s .pl-s1 /* string source */ { 42 | color: #ddd; 43 | } 44 | 45 | .pl-ent /* entity.name.tag */ { 46 | color: #7bcc72; 47 | } 48 | 49 | .pl-k /* keyword, storage, storage.type */ { 50 | color: #cc2372; 51 | } 52 | 53 | .pl-s /* string */, 54 | .pl-pds /* punctuation.definition.string, string.regexp.character-class */, 55 | .pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, 56 | .pl-sr /* string.regexp */, 57 | .pl-sr .pl-cce /* string.regexp constant.character.escape */, 58 | .pl-sr .pl-sre /* string.regexp source.ruby.embedded */, 59 | .pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */ { 60 | color: #3c66e2; 61 | } 62 | 63 | .pl-v /* variable */ { 64 | color: #fb8764; 65 | } 66 | 67 | .pl-id /* invalid.deprecated */ { 68 | color: #e63525; 69 | } 70 | 71 | .pl-ii /* invalid.illegal */ { 72 | color: #f8f8f8; 73 | background-color: #e63525; 74 | } 75 | 76 | .pl-sr .pl-cce /* string.regexp constant.character.escape */ { 77 | font-weight: bold; 78 | color: #7bcc72; 79 | } 80 | 81 | .pl-ml /* markup.list */ { 82 | color: #c26b2b; 83 | } 84 | 85 | .pl-mh /* markup.heading */, 86 | .pl-mh .pl-en /* markup.heading entity.name */, 87 | .pl-ms /* meta.separator */ { 88 | font-weight: bold; 89 | color: #264ec5; 90 | } 91 | 92 | .pl-mq /* markup.quote */ { 93 | color: #00acac; 94 | } 95 | 96 | .pl-mi /* markup.italic */ { 97 | font-style: italic; 98 | color: #ddd; 99 | } 100 | 101 | .pl-mb /* markup.bold */ { 102 | font-weight: bold; 103 | color: #ddd; 104 | } 105 | 106 | .pl-md /* markup.deleted, meta.diff.header.from-file */ { 107 | color: #bd2c00; 108 | background-color: #ffecec; 109 | } 110 | 111 | .pl-mi1 /* markup.inserted, meta.diff.header.to-file */ { 112 | color: #55a532; 113 | background-color: #eaffea; 114 | } 115 | 116 | .pl-mdr /* meta.diff.range */ { 117 | font-weight: bold; 118 | color: #9774cb; 119 | } 120 | 121 | .pl-mo /* meta.output */ { 122 | color: #264ec5; 123 | } 124 | 125 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const WebpackMd5Hash = require("webpack-md5-hash"); 3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 4 | const CleanWebpackPlugin = require("clean-webpack-plugin"); 5 | const webpack = require("webpack"); 6 | 7 | module.exports = { 8 | entry: { 9 | 'docs/demo/js/main': ["./src/demo.js"], 10 | 'lib/distributionbuilder': ["./src/entry.js"], 11 | 'lib/distributionbuilder.min': ["./src/entry.js"] 12 | }, 13 | output: { 14 | path: path.resolve(__dirname), 15 | filename: "[name].js" 16 | }, 17 | devServer: { 18 | contentBase: "./dist", 19 | port: 7700 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.js$/, 25 | exclude: /node_modules/, 26 | use: { 27 | loader: 'babel-loader', 28 | } 29 | }, 30 | { 31 | test: require.resolve('jquery'), 32 | use: [{ 33 | loader: 'expose-loader', 34 | options: 'jQuery' 35 | }, { 36 | loader: 'expose-loader', 37 | options: '$j' 38 | }] 39 | }, 40 | { 41 | test: require.resolve('./src/entry.js'), 42 | use: [{ 43 | loader: 'expose-loader', 44 | options: 'DistributionBuilder' 45 | }] 46 | }, 47 | { 48 | test: /\.css$/, 49 | use: [ 50 | "style-loader", 51 | MiniCssExtractPlugin.loader, 52 | "css-loader", 53 | {loader: "postcss-loader", options: {}} 54 | ], 55 | }, 56 | 57 | { 58 | test: /\.tsx?$/, 59 | loader: "ts-loader" 60 | }, 61 | { 62 | test: /\.svg$/, 63 | loader: 'url-loader?limit=65000&mimetype=image/svg+xml&name=public/fonts/[name].[ext]' 64 | }, 65 | { 66 | test: /\.woff$/, 67 | loader: 'url-loader?limit=65000&mimetype=application/font-woff&name=public/fonts/[name].[ext]' 68 | }, 69 | { 70 | test: /\.woff2$/, 71 | loader: 'url-loader?limit=65000&mimetype=application/font-woff2&name=public/fonts/[name].[ext]' 72 | }, 73 | { 74 | test: /\.[ot]tf$/, 75 | loader: 'url-loader?limit=65000&mimetype=application/octet-stream&name=public/fonts/[name].[ext]' 76 | }, 77 | { 78 | test: /\.eot$/, 79 | loader: 'url-loader?limit=65000&mimetype=application/vnd.ms-fontobject&name=public/fonts/[name].[ext]' 80 | }, 81 | /* 82 | { 83 | test: /\.(woff(2)?|ttf|eot|otf)(\?v=\d+\.\d+\.\d+)?$/, 84 | use: [ 85 | { 86 | loader: "file-loader", 87 | options: { 88 | limit: 5000000, 89 | name: "[name].[ext]", 90 | outputPath: "fonts/" 91 | } 92 | } 93 | ] 94 | }, 95 | { 96 | test: /\.(gif|png|jpe?g|svg)$/i, 97 | use: [ 98 | "file-loader", 99 | { 100 | loader: "image-webpack-loader", 101 | options: { 102 | bypassOnDebug: true 103 | } 104 | } 105 | ] 106 | }*/ 107 | ] 108 | }, 109 | plugins: [ 110 | new CleanWebpackPlugin("dist", {}), 111 | new MiniCssExtractPlugin({ 112 | filename: "/lib/distributionbuilder.css" 113 | }), 114 | new WebpackMd5Hash() 115 | ] 116 | }; 117 | -------------------------------------------------------------------------------- /src/demo.js: -------------------------------------------------------------------------------- 1 | import DistributionBuilder from './distributionbuilder.ts' 2 | import jQuery from 'jquery'; 3 | 4 | var $j = jQuery.noConflict(); 5 | 6 | $j(document).ready(function () { 7 | var distbuilder0 = new DistributionBuilder({}); 8 | distbuilder0.render("targetdiv0"); 9 | distbuilder0.labelize({}); 10 | 11 | var distbuilder1 = new DistributionBuilder({ 12 | minVal: 0, 13 | maxVal: 100, 14 | nRows: 20, 15 | nBuckets: 20, 16 | nBalls: 50, 17 | onTouch: function () { 18 | console.log("Distbuilder was touched!") 19 | }, 20 | onChange: function () { 21 | console.log("Distbuilder was updated!"); 22 | }, 23 | addTotals: true, 24 | toggleGridClick: true 25 | }); 26 | 27 | var distbuilder2 = new DistributionBuilder({ 28 | minVal: 0, 29 | maxVal: 100, 30 | nRows: 10, 31 | nBuckets: 20, 32 | nBalls: 10, 33 | onTouch: function () { 34 | console.log("Distbuilder was touched!") 35 | }, 36 | onChange: function () { 37 | console.log("Distbuilder was updated!"); 38 | } 39 | }); 40 | 41 | var distbuilder3 = new DistributionBuilder({ 42 | minVal: 0, 43 | maxVal: 100, 44 | nRows: 10, 45 | nBuckets: 20, 46 | nBalls: 10, 47 | onTouch: function () { 48 | console.log("Distbuilder was touched!") 49 | }, 50 | onChange: function () { 51 | console.log("Distbuilder was updated!"); 52 | } 53 | }); 54 | 55 | 56 | distbuilder1.render("targetdiv1"); 57 | distbuilder1.labelize({}); 58 | distbuilder2.render("targetdiv2", "labels-grid-buttons"); 59 | distbuilder2.labelize({}); 60 | 61 | distbuilder3.render("targetdiv3"); 62 | distbuilder3.labelize({ 63 | prefix: '~', 64 | suffix: '€' 65 | }); 66 | 67 | var distbuilder4 = new DistributionBuilder({ 68 | minVal: 0, 69 | maxVal: 100, 70 | nRows: 10, 71 | nBuckets: 10, 72 | nBalls: 60 73 | }); 74 | var dist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 75 | distbuilder4.render("targetdiv4"); 76 | distbuilder4.labelize(); 77 | distbuilder4.setDistribution(dist); 78 | 79 | 80 | var n_balls = 10; 81 | $j('#BallsLeft').text("You have " + n_balls + " balls left."); 82 | $j('#BallsAllocated').text("You have allocated " + 0 + " balls."); 83 | var distbuilder5 = new DistributionBuilder({ 84 | minVal: 0, 85 | maxVal: 100, 86 | nRows: 10, 87 | nBuckets: 20, 88 | nBalls: n_balls, 89 | onChange: function () { 90 | var remainingballs = this.getRemainingBalls(); 91 | var ballsallocated = n_balls - this.getRemainingBalls(); 92 | $j('#BallsLeft').text("You have " + remainingballs 93 | + " balls left."); 94 | $j('#BallsAllocated').text("You have allocated " + 95 | ballsallocated + " balls."); 96 | } 97 | }); 98 | 99 | distbuilder5.render("targetdiv5"); 100 | distbuilder5.labelize({}); 101 | 102 | var distbuilder6 = new DistributionBuilder({ 103 | minVal: 0, 104 | maxVal: 100, 105 | nRows: 10, 106 | nBuckets: 20, 107 | nBalls: 10, 108 | onChange: function () { 109 | if (this.isComplete()) { 110 | $j("#SubmitDistribution1").attr("disabled", false) 111 | } else { 112 | $j("#SubmitDistribution1").attr("disabled", true) 113 | } 114 | } 115 | }); 116 | distbuilder6.render("targetdiv6"); 117 | distbuilder6.labelize({}); 118 | $j("#SubmitDistribution1").click(function () { 119 | alert("Distribution Validated!") 120 | }); 121 | 122 | var distbuilder7 = new DistributionBuilder({ 123 | minVal: 0, 124 | maxVal: 100, 125 | nRows: 10, 126 | nBuckets: 20, 127 | nBalls: 10, 128 | onChange: function () { 129 | if (this.isComplete()) { 130 | $j("#SubmitDistribution2").attr("disabled", false) 131 | } else { 132 | $j("#SubmitDistribution2").attr("disabled", true) 133 | } 134 | } 135 | }); 136 | distbuilder7.render("targetdiv7"); 137 | distbuilder7.labelize({}); 138 | $j("#SubmitDistribution2").click(function () { 139 | var message = "The distribution specified by the user is: " + distbuilder7.getDistribution(); 140 | alert(message) 141 | }); 142 | 143 | var distbuilder8 = new DistributionBuilder({ 144 | minVal: 0, 145 | maxVal: 100, 146 | nRows: 10, 147 | nBuckets: 20, 148 | nBalls: 10, 149 | onChange: function () { 150 | if (this.isComplete()) { 151 | $j("#SubmitDistribution3").attr("disabled", false) 152 | } else { 153 | $j("#SubmitDistribution3").attr("disabled", true) 154 | } 155 | } 156 | }); 157 | distbuilder8.render("targetdiv8"); 158 | distbuilder8.labelize({}); 159 | $j("#SubmitDistribution3").click(function () { 160 | var message = 'The function "Qualtrics.SurveyEngine.setEmbeddedData("MyDistributionResult", '; 161 | message += distbuilder8.getDistribution().join() + ')" was called. Your data would have been stored in Qualtrics!'; 162 | alert(message) 163 | }); 164 | 165 | } 166 | ); 167 | -------------------------------------------------------------------------------- /src/distributionbuilder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Quentin André on 07/10/2016. 3 | */ 4 | import * as jQuery from "jquery"; 5 | import './distributionbuilder.css'; 6 | import "bootstrap-webpack!./bootstrap.config.js"; 7 | var $j = jQuery.noConflict(); 8 | import MouseHold from './../dependencies/mousehold'; 9 | MouseHold($j); 10 | class DistributionBuilder { 11 | constructor(o) { 12 | let obj = o ? o : {}; 13 | this.min = obj.hasOwnProperty('minVal') ? obj.minVal : 0; 14 | this.max = obj.hasOwnProperty('maxVal') ? obj.maxVal : 10; 15 | this.nBalls = obj.hasOwnProperty('nBalls') ? obj.nBalls : 10; 16 | this.nRows = obj.hasOwnProperty('nRows') ? obj.nRows : 10; 17 | this.nBuckets = obj.hasOwnProperty('nBuckets') ? obj.nBuckets : 10; 18 | this.onTouch = obj.hasOwnProperty('onTouch') ? obj.onTouch : () => { 19 | }; 20 | this.onChange = obj.hasOwnProperty('onChange') ? obj.onChange : () => { 21 | }; 22 | this.remainingBalls = this.nBalls; 23 | this.distribution = new Array(this.nBuckets).fill(0); 24 | this._$target = $j('
    '); 25 | } 26 | render(target, order, r) { 27 | if (r) { 28 | console.warn("The 'resize' argument has been deprecated."); 29 | } 30 | if ((this._$target)) { // Has already been rendered 31 | this._$target.html(''); 32 | this._$target.removeClass('distbuilder'); 33 | } 34 | let $target = $j('#' + target); // Target Div of Grid 35 | let parts = { 36 | 'grid': this._createGrid($target), 37 | 'labels': this._createLabels($target), 38 | 'buttons': this._createButtons($target) 39 | }; 40 | let validOrder = new RegExp('(buttons-grid-labels)|(grid-labels-buttons)|(labels-grid-buttons)|(labels-buttons-grid)|(grid-buttons-labels)|(buttons-labels-grid)', 'g'); 41 | this._$target = $target; 42 | $target.addClass('distbuilder'); 43 | let o = order ? order : "grid-labels-buttons"; 44 | if (!validOrder.test(o)) { 45 | throw ("The order '" + o + "' could not be understood. Make sure " + 46 | "that the order is any combination of 'labels', 'grid', and " + 47 | "'button, separated by '-'."); 48 | } 49 | else { 50 | let renderorder = order.split('-'); 51 | renderorder.forEach((e) => $target.append(parts[e])); 52 | } 53 | } 54 | labelize(o) { 55 | let obj = o ? o : {}; 56 | let values = []; 57 | if (obj.hasOwnProperty('labels')) { 58 | values = obj.labels; 59 | } 60 | else { 61 | let step = (this.max - this.min) / this.nBuckets; 62 | values = Array.from({ length: this.nBuckets }, (value, key) => this.min + key * step + step / 2); 63 | } 64 | let prefix = obj.hasOwnProperty('prefix') ? obj.prefix : ''; 65 | let suffix = obj.hasOwnProperty('suffix') ? obj.suffix : ''; 66 | let labels = values.map((v) => prefix + v + suffix); 67 | this._setLabels(labels); 68 | } 69 | isComplete() { 70 | return (this.remainingBalls == 0); 71 | } 72 | getRemainingBalls() { 73 | return this.remainingBalls; 74 | } 75 | getDistribution() { 76 | return this.distribution.slice(); 77 | } 78 | _setLabels(labels) { 79 | labels.forEach((l, i) => { 80 | let label = this._$target.find('.label' + i); 81 | label.html(l); 82 | }); 83 | } 84 | _actionCreator(action) { 85 | if (action == 'increment') { 86 | return (bucket) => { 87 | return () => { 88 | this.onTouch(); 89 | if ((this.distribution[bucket] < (this.nRows)) && (this.remainingBalls > 0)) { 90 | let rowIndex = this.distribution[bucket]; 91 | this._$target.find(".row" + rowIndex + ">.col" + bucket).addClass("filled"); 92 | this.distribution[bucket]++; 93 | this.remainingBalls--; 94 | this.onChange(); 95 | } 96 | }; 97 | }; 98 | } 99 | else { 100 | return (bucket) => { 101 | return () => { 102 | this.onTouch(); 103 | if (this.distribution[bucket] > 0) { 104 | this.distribution[bucket]--; 105 | let rowIndex = this.distribution[bucket]; 106 | this._$target.find(".row" + rowIndex + ">.col" + bucket).removeClass("filled"); 107 | this.remainingBalls++; 108 | this.onChange(); 109 | } 110 | }; 111 | }; 112 | } 113 | } 114 | _createGrid($target) { 115 | let nRows = this.nRows; 116 | let nBuckets = this.nBuckets; 117 | let $grid = $j('
    ', { class: "grid" }); //Div holding the grid 118 | for (let row = 0; row < nRows; row++) { // Create as many rows as needed 119 | let rowIndex = (nRows - row - 1); // Row number 0 is the bottom-most row. 120 | let $lineDiv = $j('
    ', { class: "distrow row" + rowIndex }); 121 | for (let col = 0; col < nBuckets; col++) { // Create as many cells as needed 122 | let $colDiv = $j("
    ", { "class": "cell " + "col" + col }); 123 | let $ball = $j("
    ", { "class": "ball " + "col" + col }); 124 | $colDiv.append($ball); 125 | $lineDiv.append($colDiv); // Add each cell to the row 126 | } 127 | $grid.append($lineDiv); // Add each row to the grid div 128 | } 129 | return $grid; 130 | } 131 | _createButtons($target) { 132 | let incrementAction = this._actionCreator('increment'); //Currying functions 133 | let decrementAction = this._actionCreator('decrement'); //Currying functions 134 | let $lineDivButtons = $j("
    ", { class: "distrow" }); 135 | let $buttons = $j('
    ', { class: "buttons" }); //Div holding the buttons 136 | for (let col = 0; col < this.nBuckets; col++) { 137 | let $divButtons = $j("
    ", { "class": "buttongroup" }); 138 | let $addButton = $j('', { class: "btn btn-default distbutton glyphicon glyphicon-plus" }); 139 | let $removeButton = $j('', { class: "btn btn-default distbutton glyphicon glyphicon-minus" }); 140 | $divButtons.append($addButton 141 | .mousehold(200, 100, incrementAction(col)) 142 | .click(incrementAction(col))); 143 | $divButtons.append($removeButton 144 | .mousehold(200, 100, decrementAction(col)) 145 | .click(decrementAction(col))); 146 | $lineDivButtons.append($divButtons); 147 | } 148 | $buttons.append($lineDivButtons); 149 | return $buttons; 150 | } 151 | _createLabels($target) { 152 | let $labels = $j('
    ', { class: "labels" }); //Div holding the buttons 153 | let $lineDivLabels = $j("
    ", { "class": "distrow" }); 154 | for (let col = 0; col < this.nBuckets; col++) { 155 | let $divLabel = $j("
    ", { "class": "label" + " label" + col }); 156 | $lineDivLabels.append($divLabel); 157 | } 158 | $labels.append($lineDivLabels); // Add each row to the grid div 159 | return $labels; 160 | } 161 | } 162 | 163 | export default DistributionBuilder -------------------------------------------------------------------------------- /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 |
    20 |

    distBuilder

    21 |

    A Javascript library adding distribution builders to your experiments.

    22 |
    23 | 24 |
    44 |
    45 | 48 |
    49 |

    1. Introduction

    50 |

    A. Why distBuilder?

    51 |

    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 |

    B. Citing distBuilder

    67 |

    You can cite distBuilder using its Digital Object Identifier 68 | (DOI).

    69 |

    C. About the author

    70 |

    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 |

    2. Installation

    76 |

    To add the library to your projects and start using distBuilder, just follow the instructions 77 | below.

    78 |

    A. Adding to a website

    79 |

    If you are hosting your experiment on a separate website, here is how to add the script:

    80 |
      81 |
    1. 82 | Download the Library using the button at the top of the page. 83 |
    2. 84 |
    3. Add the Javascript file distributionbuilder.min.js and the CSS file distributionbuilder.css 85 | to your html code:

      86 |
      <link rel="stylesheet" href="distributionbuilder.css">
       87 | <script src="distributionbuilder.min.js"></script>
      88 |
    4. 89 |
    5. 90 | That's it! You can now add your own distribution builders to your webpage. No need to include jQuery! 91 | distBuilder automatically injects it, and makes it accessible under the $j alias. 92 | Instead of typing $('#Hello'), type $j('#Hello'). 93 |
    6. 94 |
    95 |

    B. Adding to Qualtrics

    96 |

    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 |
      102 |
    1. Navigate to the "Look and Feel" section of your survey, and click on the "Advanced" tab
    2. 103 |
    3. 104 | Edit the "Header" section, and add the following lines to load the library script and the library 105 | styles:
      106 |
      <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 |
    4. 109 |
    5. 110 | That's it! You can now add your own distribution builders in the "Custom Javascript" section of your 111 | Qualtrics questions. Make sure to wrap the code in the Qualtrics.SurveyEngine.addOnReady section. 112 |
      You also do not need to include jQuery! distBuilder automatically injects it, and makes 113 | it accessible under the $j alias. Instead of typing $('#Hello'), type $j('#Hello'). 114 |
    6. 115 |
    116 | 117 |

    3. Basic Setup

    118 | 119 |

    The library is extremely easy to use: it allows you to add fully functional distribution builders in a few 120 | lines of code.

    121 |
      122 |
    • For a website, add <div id="targetdiv"> to your HTML, and copy the code below between 123 | your two <script> tags. 124 |
    • 125 |
    • For Qualtrics, add a <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 |
    • 130 |
    131 | 132 |
    133 |
    134 |
    135 |
    136 |
    var distbuilder = new DistributionBuilder();
    137 | distbuilder.render("targetdiv");
    138 | distbuilder.labelize();
    139 |
    140 |

    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:

    142 | 143 |
      144 |
    • The grid, an matrix of cells in which the balls allocated are displayed.
    • 145 |
    • The labels, a line matching each bucket to a value.
    • 146 |
    • The buttons, which allow the user to increment or decrement a bucket.
    • 147 |
    148 |

    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 |

    4. Customization

    154 |

    The complete creation of a Distribution Builder object is done in three phases:

    155 |
      156 |
    • A. Initialization
    • 157 |
    • B. Render
    • 158 |
    • C. Labelize.
    • 159 |
    160 |

    A. Initialization

    161 |

    The first step in creating a Distribution Builder is to initialize the object:

    162 |

    myDistBuilder = new DistributionBuilder({})

    163 |

    This function call initializes the internal state of the DistributionBuilder object, with a certain number of 164 | parameters that you can specify:

    165 |
      166 |
    • 167 | 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 |
    • 170 |
    • 171 | nBuckets, (default: 10): The number of buckets (columns) to which balls can be 172 | allocated. 173 |
    • 174 |
    • 175 | minVal, (default: 0): The value corresponding to the first bucket (the smallest 176 | value). 177 |
    • 178 |
    • 179 | maxVal, (default: 10): The value corresponding to the last bucket (the largest 180 | value) 181 |
    • 182 |
    • 183 | nBalls, (default: 10): The total number of balls to allocate. 184 |
    • 185 |
    • 186 | onTouch, (default: function () {}): A JavaScript function that will 187 | be called every time the user clicks a button of the distribution builder. 188 |
    • 189 |
    • 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 |
    • 194 |
    • toggleGridClick, (default: false): Allow/disallow participants to click 195 | on the distBuilder to change the allocation of the balls. 196 |
    • 197 |
    • addTotals, (default: false): Add a row to the bottom of the distBuilder 198 | summarizing how many balls are in each bucket. 199 |
    • 200 |
    201 |
    202 |
    203 |
    204 |
    205 |
    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 |
    223 |

    B. Render

    224 |

    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()

    227 |

    This method requires the argument target, and you can optionally supply the order 228 | argument to further tweak the appearance of the Distribution Builder.

    229 |
      230 |
    • 231 | 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 |
    • 235 |
    • 236 | 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 |
    • 240 |
    241 | Using this method to put the labels at the top of the Distribution Builder: 242 |
    243 |
    244 |
    245 |
    246 |
    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 |
    263 |

    C. Labelize

    264 |

    You generally want to add some labels to the buckets of your distribution builder:

    265 |

    DistributionBuilder.labelize()

    266 |

    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:

    273 |
      274 |
    1. labels, an array of length nBucketscode>. Supplying this argument will 275 | override the default labels. 276 |
    2. 277 |
    3. prefix, a string (e.g. ‘$’) which you would like to see prepended to all the labels. 278 |
    4. 279 |
    5. suffix, a string (e.g. ‘€’) which you would like to see appended to all the labels. 280 |
    6. 281 |
    282 |

    The prefix and suffix arguments are always applied, whether or not you specify 283 | custom labels using the label argument.

    284 |
    285 |
    286 |
    287 |
    288 |
    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 |
    308 |

    5. Changing the Distribution

    309 |

    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)

    313 |

    Here is an example:

    314 |
    315 |
    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 |
    328 |
    329 |
    330 |
    331 | 332 | 333 |

    6. Reading the Data

    334 |

    After setting up the Distribution Builder, its internal state can be conveniently accessed through three 335 | methods:

    336 |
      337 |
    • 338 |

      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].

      342 |
    • 343 |
    • 344 |

      DistributionBuilder.getRemainingBalls() returns the number of balls that have not been 345 | allocated yet.

      346 |
    • 347 |
    • 348 |

      DistributionBuilder.isComplete() returns true is the user has allocated all 349 | the balls, and false otherwise.

      350 |
    • 351 |
    352 |

    The two examples below demonstrate how those three methods can be used in your experiments and studies.

    353 |

    A. Number of balls allocated

    354 |

    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.

    357 | 358 |
    359 |
    360 |
    361 |
    362 |
    363 |
    364 | 365 |
    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 |
    384 |

    B. Validating the distribution

    385 |

    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 |
      389 |
    • 390 | Create a button called “SubmitDistribution” with disabled=true. When clicked, 391 | this button will for now display "Distribution Validated!". 392 |
    • 393 |
    • 394 | Assign a function that will enable or disable the button to the onChange argument of the 395 | DistributionBuilder object. This is done by using the value returned by the DistributionBuilder.isComplete() 396 | method. 397 |
    • 398 |
    399 |

    Together, those two steps are ensuring that the distribution is fully specified before the user can click the 400 | button.

    401 |
    402 | 405 |
    406 |
    407 | 408 |
    409 |
    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 |
    427 |

    C. Accessing the distribution

    428 |

    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().

    433 |
    434 | 437 |
    438 |
    439 | 440 |
    441 |
    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 |
    462 |

    D. Storing the data in Qualtrics

    463 |

    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 |
      466 |
    • In the "Survey Flow" section of your survey, create a new Embedded Data field at the beginning of your 467 | survey flow called "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 |
    • 470 |
    • 471 | You can now use the 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 |
    • 475 |
    • This Qualtrics function requires a string, but the method 476 | 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 |
    • 480 |
    481 |

    Adding the code to the previous example:

    482 |
    483 | 486 |
    487 |
    488 | 489 |
    490 |
    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 |
    514 | 515 |

    7. CSS Specification

    516 |

    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.

    518 |

    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 |

    8. About

    572 |

    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 |
    587 |
    588 | 590 | 591 | 608 | 609 | -------------------------------------------------------------------------------- /docs/fonts/quattrocentosans-bold-webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG webfont generated by Font Squirrel. 6 | Copyright : Copyright c 2011 Pablo Impallari wwwimpallaricomimpallarigmailcomCopyright c 2011 Igino Marini wwwikerncommailiginomarinicomCopyright c 2011 Brenda Gallo gbrenda1987gmailcomwith Reserved Font Name Quattrocento Sans 7 | Designer : Pablo Impallari 8 | Foundry : Pablo Impallari Igino Marini Brenda Gallo 9 | Foundry URL : wwwimpallaricom 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | --------------------------------------------------------------------------------