├── assets └── imgs.ai ├── imgs ├── star.png ├── topleft.png ├── topright.png ├── ajax-loader.gif ├── bottomleft.png └── bottomright.png ├── README.md ├── js ├── thirdparty │ ├── sideburns.js │ ├── underscore-min.js │ ├── tabletop.js │ ├── colorbrewer.js │ └── jquery.1.10.2.min.js └── fourscore.js ├── config.json ├── bookmarklet ├── bookmarklet.min.js └── bookmarklet.js ├── index.html └── css ├── fs-theme.css └── fs-base.css /assets/imgs.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veltman/fourscore/HEAD/assets/imgs.ai -------------------------------------------------------------------------------- /imgs/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veltman/fourscore/HEAD/imgs/star.png -------------------------------------------------------------------------------- /imgs/topleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veltman/fourscore/HEAD/imgs/topleft.png -------------------------------------------------------------------------------- /imgs/topright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veltman/fourscore/HEAD/imgs/topright.png -------------------------------------------------------------------------------- /imgs/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veltman/fourscore/HEAD/imgs/ajax-loader.gif -------------------------------------------------------------------------------- /imgs/bottomleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veltman/fourscore/HEAD/imgs/bottomleft.png -------------------------------------------------------------------------------- /imgs/bottomright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veltman/fourscore/HEAD/imgs/bottomright.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FourScore 2 | ================= 3 | 4 | FourScore is a bootstrapper for letting audience members mark their position on a grid, like the [New York magazine approval matrix](http://nymag.com/arts/all/approvalmatrix/approval-matrix-2013-11-25/) and the [WNYC Sentiment Tracker](http://www.wnyc.org/story/297640-government-surveillance-how-worried-are-you/). 5 | 6 | Read the [full documentation](https://veltman.github.io/fourscore). 7 | 8 | Built by [Noah Veltman](https://github.com/veltman) &[ Michael Keller](https://github.com/mhkeller). 9 | -------------------------------------------------------------------------------- /js/thirdparty/sideburns.js: -------------------------------------------------------------------------------- 1 | // http://bl.ocks.org/aubergene/7791133 2 | 3 | // This is a function 4 | function Normalizer(min, max) { 5 | return function(val) { 6 | return (val - min) / (max - min); 7 | } 8 | } 9 | 10 | // This is another 11 | function Interpolater(min, max, clamp) { 12 | return function(val) { 13 | val = min + (max - min) * val; 14 | return clamp ? Math.min(Math.max(val, min), max) : val; 15 | } 16 | } 17 | 18 | // This is a third 19 | function Scale(minDomain, maxDomain, minRange, maxRange, clamp) { 20 | var normalize = new Normalizer(minDomain, maxDomain); 21 | var interpolate = new Interpolater(minRange, maxRange, clamp); 22 | var _normalize = new Normalizer(minRange, maxRange); 23 | var _interpolate = new Interpolater(minDomain, maxDomain, clamp); 24 | var s = function(val) { 25 | return interpolate(normalize(val)); 26 | }; 27 | s.inverse = function(val) { 28 | return _interpolate(_normalize(val)); 29 | }; 30 | return s; 31 | } -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "dataSource": { 4 | "url": "SPREADSHEET KEY GOES HERE", 5 | "type": "google" 6 | }, 7 | "gridSize": 10, 8 | "xAxis": [ 9 | "Less X", 10 | "More X" 11 | ], 12 | "yAxis": [ 13 | "More Y", 14 | "Less Y" 15 | ], 16 | "gridTarget": "#grid", 17 | "commentsTarget": "#comments", 18 | "colors": { 19 | "name": "Blues", 20 | "number": 5 21 | } 22 | }, 23 | "dataDestination": "GOOGLE FORMS RESPONSE URL GOES HERE", 24 | "fields": [ 25 | { 26 | "name": "X", 27 | "required": true, 28 | "type": "number", 29 | "field": "entry.13245" 30 | }, 31 | { 32 | "name": "Y", 33 | "required": true, 34 | "type": "number", 35 | "field": "entry.12345" 36 | }, 37 | { 38 | "name": "Name", 39 | "required": false, 40 | "type": "text", 41 | "field": "entry.12345" 42 | }, 43 | { 44 | "name": "Comment", 45 | "required": false, 46 | "type": "textarea", 47 | "field": "entry.12345" 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /bookmarklet/bookmarklet.min.js: -------------------------------------------------------------------------------- 1 | javascript:(function(){if(window.jQuery===undefined){var done=false;var script=document.createElement("script");script.src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js";script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;initBookmarklet()}};document.getElementsByTagName("head")[0].appendChild(script)}else{initBookmarklet()}function getInput(el){var item={name:trim($("label .ss-q-title",el).text()),required:!!$(".ss-required-asterisk",el).length},$i=$("input",el),$s=$("select:first",el),$t=$("textarea:first",el);if($i.length&&!$("table",el).length){item.type=$i.first().attr("type");item.field=$i.first().attr("name");if(item.type=="radio"||item.type=="checkbox"){item.choices=$i.map(function(){return $(this).val()}).get()}}else if($s.length){item.type="select";item.field=$s.attr("name");item.choices=$s.find("option").map(function(){return $(this).val()}).get()}else if($t.length){item.type="textarea";item.field=$t.attr("name")}return"type"in item?item:null}function initBookmarklet(){var $div=$("
"),$pre=$("
").css("white-space","pre-wrap"),options={options:{dataSource:{url:"SPREADSHEET KEY GOES HERE",type:"google"},gridSize:10,xAxis:["Less X","More X"],yAxis:["More Y","Less Y"],gridTarget:"#grid",commentsTarget:"#comments",colors:{name:"Blues",number:5}},dataDestination:$("form#ss-form").attr("action"),fields:$("form#ss-form div.ss-form-question div.ss-form-entry").map(function(){return getInput(this)}).get().filter(function(d){return d!==null})};if($.grep(options.fields,function(i){return i.name.toLowerCase()=="x"}).length!=1||$.grep(options.fields,function(i){return i.name.toLowerCase()=="y"}).length!=1){$div.append("

Unable to fetch options. Make sure you have an 'X' and 'Y' field in your form.

")}else{$div.append("

Options for FourScore:

");$pre.append(JSON.stringify(options,null," "));$div.append($pre)}$div.attr("id","fourscore").css({"background-color":"white",border:"1px solid black",padding:"10px",margin:"50px 10px"});$("div#fourscore").remove();$("body").prepend($div)}function trim(s){return(s||"").replace(/^\s+|(\s|[*])+$/g,"")}})(); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | FourScore 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 |
19 | 26 |
27 |
28 |
29 |
30 | 31 | 32 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /css/fs-theme.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0; 3 | padding: 0; 4 | font-family: Lato, Helvetica, Arial, Verdana, sans-serif; 5 | font-size: 10px; 6 | } 7 | 8 | #fs-wrapper{ 9 | max-width: 700px; 10 | margin-left: auto; 11 | margin-right: auto; 12 | margin-top: 20px; 13 | } 14 | 15 | /* Styling for the grid container and comments */ 16 | #grid, #comments-outer { 17 | width: 50%; 18 | float: left; 19 | } 20 | 21 | #comments-outer { 22 | -moz-box-sizing: border-box; 23 | -webkit-box-sizing: border-box; 24 | box-sizing: border-box; 25 | padding-left: 20px; 26 | } 27 | 28 | /* The standard border color separating cells */ 29 | .fs-cell{ 30 | border: 1px solid #fff; 31 | } 32 | 33 | /* 34 | 35 | Axes - specify a different color here to show axes 36 | (Only applies if the grid size is an even number) 37 | 38 | */ 39 | .fs-cell.fs-axis-bottom { 40 | border-bottom-color: #333; 41 | } 42 | 43 | .fs-cell.fs-axis-left { 44 | border-left-color: #333; 45 | } 46 | 47 | .fs-cell.fs-axis-right { 48 | border-right-color: #333; 49 | } 50 | 51 | .fs-cell.fs-axis-top { 52 | border-top-color: #333; 53 | } 54 | 55 | /* Styling for hovering over a cell */ 56 | .fs-grid.submittable .fs-cell:hover{ 57 | opacity: .6; 58 | } 59 | 60 | /* Styling for axis labels */ 61 | 62 | .fs-grid-label{ 63 | background-color: rgba(0,0,0,.5); 64 | letter-spacing: 1px; 65 | font-size: 14px; 66 | color: #fff; 67 | padding: 2px 10px 2px 10px; 68 | } 69 | 70 | /* Styling for a selected cell */ 71 | .fs-grid.submittable .fs-cell.fs-selected,.submittable .fs-cell.fs-selected:hover{ 72 | border: 3px solid #f0a; 73 | opacity: 1; 74 | } 75 | 76 | /* Dot color for minimaps */ 77 | .fs-mm-dot { 78 | background-color: #2646ea; 79 | } 80 | 81 | /* Center the filter buttons for comments */ 82 | #fs-comment-filters { 83 | text-align: center; 84 | margin: 12px 0; 85 | } 86 | 87 | /* 88 | 89 | Styling for the cell they've saved their response to 90 | This version inserts a star in the middle 91 | 92 | */ 93 | .fs-cell.saved { 94 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAMAAAANf8AYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADNQTFRF////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8YBMDAAAABB0Uk5TABAgMEBQYHCAkKCwwNDg8FTgqMgAAACtSURBVBgZ7cHhbsIgGIbR94NCpQg893+1k5isuk0Licn+9BzpdPo007yrpgWcZm2smtUomhQApzkJiJpTgawpns40I9FFzah0WYeWyivF6wXL/C3pjdj4rS16yxV+yqYjF560VQOWyq54DVnYmcYkdlFjKrusIZ5HGpLoSqx0QSMKN0myzM2mAQ5oi7oVaBqwwtV05wsEHSus+mYbmw656vUoVB0y0zNnOp3+0ReObA/KxOPqOgAAAABJRU5ErkJggg=="); 95 | /* TODO TEST base64 in IE filter */ 96 | filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAMAAAANf8AYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADNQTFRF////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8YBMDAAAABB0Uk5TABAgMEBQYHCAkKCwwNDg8FTgqMgAAACtSURBVBgZ7cHhbsIgGIbR94NCpQg893+1k5isuk0Licn+9BzpdPo007yrpgWcZm2smtUomhQApzkJiJpTgawpns40I9FFzah0WYeWyivF6wXL/C3pjdj4rS16yxV+yqYjF560VQOWyq54DVnYmcYkdlFjKrusIZ5HGpLoSqx0QSMKN0myzM2mAQ5oi7oVaBqwwtV05wsEHSus+mYbmw656vUoVB0y0zNnOp3+0ReObA/KxOPqOgAAAABJRU5ErkJggg==', sizingMethod='scale'); 97 | -ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAMAAAANf8AYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADNQTFRF////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8YBMDAAAABB0Uk5TABAgMEBQYHCAkKCwwNDg8FTgqMgAAACtSURBVBgZ7cHhbsIgGIbR94NCpQg893+1k5isuk0Licn+9BzpdPo007yrpgWcZm2smtUomhQApzkJiJpTgawpns40I9FFzah0WYeWyivF6wXL/C3pjdj4rS16yxV+yqYjF560VQOWyq54DVnYmcYkdlFjKrusIZ5HGpLoSqx0QSMKN0myzM2mAQ5oi7oVaBqwwtV05wsEHSus+mYbmw656vUoVB0y0zNnOp3+0ReObA/KxOPqOgAAAABJRU5ErkJggg==', sizingMethod='scale')"; 98 | -webkit-background-size: cover; 99 | background-size: cover; 100 | } 101 | 102 | @media screen and (max-width: 800px) { 103 | .fs-cell.saved { 104 | font-size: 20px; 105 | } 106 | } 107 | 108 | @media screen and (max-width: 600px) { 109 | #grid,#comments-outer { 110 | float: none; 111 | width: 100%; 112 | padding: 0; 113 | } 114 | 115 | #fs-wrapper { 116 | margin-left: 12px; 117 | margin-right: 12px; 118 | } 119 | 120 | #grid { 121 | margin-left: auto; 122 | margin-right: auto; 123 | max-width: 420px; 124 | } 125 | 126 | .fs-cell.saved { 127 | font-size: 32px; 128 | } 129 | } 130 | 131 | @media screen and (max-width: 400px) { 132 | .fs-cell.saved { 133 | font-size: 12px; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /bookmarklet/bookmarklet.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | //get jQuery 4 | if (window.jQuery === undefined) { 5 | var done = false; 6 | var script = document.createElement("script"); 7 | script.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"; 8 | script.onload = script.onreadystatechange = function(){ 9 | if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) { 10 | done = true; 11 | initBookmarklet(); 12 | } 13 | }; 14 | document.getElementsByTagName("head")[0].appendChild(script); 15 | } else { 16 | initBookmarklet(); 17 | } 18 | 19 | function getInput(el) { 20 | 21 | var item = { 22 | "name": trim($("label .ss-q-title",el).text()), 23 | "required": !!$(".ss-required-asterisk",el).length 24 | }, 25 | $i = $("input",el), 26 | $s = $("select:first",el), 27 | $t = $("textarea:first",el); 28 | 29 | //Exclude grids by ignoring tables 30 | if ($i.length && !$("table",el).length) { 31 | 32 | item.type = $i.first().attr("type"); 33 | item.field = $i.first().attr("name"); 34 | 35 | //Get choices for radio and checkbox 36 | if (item.type == "radio" || item.type == "checkbox") { 37 | item.choices = $i.map(function(){ 38 | return $(this).val(); 39 | }) 40 | .get(); 41 | } 42 | 43 | } else if ($s.length) { 44 | 45 | item.type = "select"; 46 | item.field = $s.attr("name"); 47 | 48 | //Get choices for select 49 | item.choices = $s.find("option").map(function(){ 50 | return $(this).val(); 51 | }) 52 | .get(); 53 | 54 | } else if ($t.length) { 55 | 56 | item.type = "textarea"; 57 | item.field = $t.attr("name"); 58 | 59 | } 60 | 61 | return ("type" in item) ? item : null; 62 | 63 | } 64 | 65 | function initBookmarklet() { 66 | 67 | //Empty div, empty pre, form options 68 | var $div = $("
"), 69 | $pre = $("
").css("white-space","pre-wrap"), 70 | options = { 71 | options: { 72 | dataSource: { 73 | url: "SPREADSHEET KEY GOES HERE", 74 | type: "google" 75 | }, 76 | gridSize: 10, 77 | xAxis: ["Less X","More X"], 78 | yAxis: ["More Y","Less Y"], 79 | gridTarget: "#grid", 80 | commentsTarget: "#comments", 81 | colors: { 82 | name: "Blues", 83 | number: 5 84 | } 85 | }, 86 | // Spreadsheet form endpoint 87 | dataDestination: $("form#ss-form").attr("action"), 88 | // Mapped array of {name: "foo", field: "bar"} 89 | // name = human field name (e.g. "ZIP Code") 90 | // field = Google form field ID (e.g. input.a099i12j09ds2) 91 | fields: $("form#ss-form div.ss-form-question div.ss-form-entry").map(function() { 92 | return getInput(this); 93 | }) 94 | .get() 95 | .filter(function(d){ 96 | return d !== null; 97 | }) 98 | }; 99 | 100 | if ($.grep(options.fields,function(i){ return (i.name.toLowerCase() == "x"); }).length != 1 || 101 | $.grep(options.fields,function(i){ return (i.name.toLowerCase() == "y"); }).length != 1) { 102 | 103 | $div.append("

Unable to fetch options. Make sure you have an 'X' and 'Y' field in your form.

"); 104 | 105 | } else { 106 | // Add a header and styling for clarity 107 | $div.append("

Options for FourScore:

"); 108 | 109 | // Stringify the options with spacing 110 | $pre.append(JSON.stringify(options,null," ")); 111 | $div.append($pre); 112 | 113 | } 114 | 115 | $div.attr("id","fourscore") 116 | .css({ 117 | "background-color": "white", 118 | "border": "1px solid black", 119 | "padding": "10px", 120 | "margin": "50px 10px", //Try to get it below the "Edit this form" button 121 | }); 122 | 123 | // Remove an existing one 124 | $("div#fourscore").remove(); 125 | 126 | // Insert at the top of the page 127 | $("body").prepend($div); 128 | 129 | 130 | } 131 | 132 | function trim(s){ 133 | return ( s || '' ).replace( /^\s+|(\s|[*])+$/g, '' ); 134 | } 135 | 136 | })(); -------------------------------------------------------------------------------- /css/fs-base.css: -------------------------------------------------------------------------------- 1 | .fs-grid{ 2 | text-align: center; 3 | vertical-align: center; 4 | position: relative; 5 | } 6 | 7 | .fs-row{ 8 | clear: both; 9 | width: 100%; 10 | } 11 | 12 | .fs-cell{ 13 | height: 100%; 14 | float:left; 15 | -moz-box-sizing: border-box; 16 | -webkit-box-sizing: border-box; 17 | box-sizing: border-box; 18 | -webkit-touch-callout: none; 19 | -webkit-user-select: none; 20 | -khtml-user-select: none; 21 | -moz-user-select: -moz-none; 22 | -ms-user-select: none; 23 | user-select: none; 24 | } 25 | 26 | .fs-grid.submittable .fs-cell:hover{ 27 | cursor: pointer; 28 | cursor: hand; 29 | } 30 | 31 | .fs-grid.submittable .fs-cell.fs-selected,.submittable .fs-cell.fs-selected:hover{ 32 | cursor: default; 33 | } 34 | 35 | .fs-missing label, .fs-missing input, .fs-missing select, .fs-missing span { 36 | color: red; 37 | } 38 | 39 | .fs-iframe { 40 | display: none; 41 | } 42 | 43 | .fs-tooltip,.fs-form { 44 | z-index: 990; 45 | position: absolute; 46 | background-color: white; 47 | border: 2px solid #ccc; 48 | color: #333; 49 | text-align: left; 50 | padding: 10px; 51 | display: none; 52 | } 53 | 54 | .fs-form.loading #fs-form-submit{ 55 | display: none; 56 | } 57 | 58 | #fs-form-submit-loading{ 59 | display: none; 60 | } 61 | 62 | .fs-form.loading #fs-form-submit-loading{ 63 | display: block; 64 | } 65 | 66 | .fs-form.loading #fs-form-submit-loading:hover{ 67 | cursor: default; 68 | } 69 | 70 | .fs-form.loading #fs-form-submit-loading img{ 71 | position: relative; 72 | top: 3px; 73 | } 74 | 75 | .fs-tooltip { 76 | pointer-events: none; 77 | } 78 | 79 | .fs-tooltip.open { 80 | display: block; 81 | } 82 | 83 | .fs-grid.open .fs-tooltip, .fs-grid.open .fs-tooltip.open { 84 | display: none; 85 | } 86 | 87 | .fs-grid.open .fs-form { 88 | display: block; 89 | } 90 | 91 | .fs-form { 92 | z-index: 999; 93 | max-width: 300px; 94 | } 95 | 96 | .fs-form .fs-close { 97 | float: right; 98 | cursor: pointer; 99 | } 100 | 101 | .fs-close{ 102 | opacity: .5; 103 | } 104 | 105 | .fs-close:hover{ 106 | cursor: pointer; 107 | cursor: hand; 108 | opacity: 1; 109 | } 110 | 111 | .fs-form input[type='text'],input[type='number'],select,textarea { 112 | margin: 0; 113 | border: 1px solid #333; 114 | background-color: #fff; 115 | color: #333; 116 | } 117 | 118 | .fs-form option { 119 | background-color: #fff; 120 | } 121 | 122 | .fs-form label { 123 | font-weight: bold; 124 | display: block; 125 | margin-bottom: 4px; 126 | } 127 | 128 | .fs-form form > div { 129 | margin: 10px 0px; 130 | } 131 | 132 | .fs-form form div div span { 133 | margin: 0px 18px 0px 6px; 134 | } 135 | 136 | .fs-grid-label{ 137 | position: absolute; 138 | } 139 | 140 | .fs-grid-label[data-location="left"]{ 141 | -webkit-transform: rotate(-90deg); 142 | -ms-transform: rotate(-90deg); 143 | transform: rotate(-90deg); 144 | } 145 | 146 | .fs-grid-label[data-location="right"]{ 147 | -webkit-transform: rotate(90deg); 148 | -ms-transform: rotate(90deg); 149 | transform: rotate(90deg); 150 | } 151 | 152 | .fs-comment-container{ 153 | position: relative; 154 | width: 100%; 155 | border: 1px solid #ccc; 156 | background-color: #fff; 157 | margin-right: 10px; 158 | margin-bottom: 10px; 159 | padding: 10px; 160 | overflow: auto; 161 | -moz-box-sizing: border-box; 162 | -webkit-box-sizing: border-box; 163 | box-sizing: border-box; 164 | } 165 | 166 | .fs-mini-map{ 167 | position: relative; 168 | width: 50px; 169 | height: 50px; 170 | margin-right: 10px; 171 | float: left; 172 | } 173 | 174 | .fs-mm-quadrant{ 175 | width:50%; 176 | height:50%; 177 | float: left; 178 | border-left: 1px solid #ccc; 179 | border-top: 1px solid #ccc; 180 | -moz-box-sizing: border-box; 181 | -webkit-box-sizing: border-box; 182 | box-sizing: border-box; 183 | } 184 | 185 | .fs-mm-quadrant:nth-child(even){ 186 | border-right: 1px solid #ccc; 187 | } 188 | 189 | .fs-mm-quadrant:nth-child(3),.fs-mm-quadrant:nth-child(4){ 190 | border-bottom: 1px solid #ccc; 191 | border-left: 1px solid #ccc; 192 | } 193 | 194 | .fs-name{ 195 | font-size: 18px; 196 | margin-bottom: 5px; 197 | } 198 | 199 | .fs-comment{ 200 | font-size: 12px; 201 | font-style: italic; 202 | } 203 | 204 | .fs-paragraph{ 205 | margin-top: 10px; 206 | font-size: 12px; 207 | } 208 | 209 | .fs-mm-dot{ 210 | position: absolute; 211 | width: 5px; 212 | height: 5px; 213 | border-radius: 50%; 214 | margin-right: 10px; 215 | } 216 | 217 | /* COMMENT FILTER STYLES, REMOVE IF YOU WANT TO CUSTOMIZE */ 218 | #fs-comment-filters{ 219 | list-style: none; 220 | overflow: auto; 221 | padding: 0; 222 | display: none; 223 | } 224 | 225 | #fs-comment-filters li{ 226 | display: inline-block; 227 | padding: 5px; 228 | margin-right: 18px; 229 | } 230 | 231 | #fs-comment-filters li:not(.fs-comment-filter){ 232 | font-size: 16px; 233 | display: block; 234 | } 235 | 236 | #fs-comment-filters .fs-comment-filter{ 237 | border: 1px solid #686868; 238 | background-color: #C4C4C4; 239 | color: #FFF; 240 | border-radius: 3px; 241 | margin: 0 5px 5px 5px; 242 | background-repeat: no-repeat; 243 | background-position: center; 244 | width: 31px; 245 | height: 31px; 246 | } 247 | 248 | .fs-comment-filter[data-quadrant="topleft"]{ 249 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfAQMAAAAlYIR6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFAAAA////pdmf3QAAABhJREFUCNdjYICA/3+oQQBB/T8gi2ICBADm2UxL22xMYgAAAABJRU5ErkJggg=="); 250 | } 251 | .fs-comment-filter[data-quadrant="topright"]{ 252 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfAQMAAAAlYIR6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFAAAA////pdmf3QAAABdJREFUCNdjYACB+n/UIKBG/f9DOQECAPtCRWdU9xbuAAAAAElFTkSuQmCC"); 253 | } 254 | .fs-comment-filter[data-quadrant="bottomright"]{ 255 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfAQMAAAAlYIR6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFAAAA////pdmf3QAAABdJREFUCNdjYACB+n///1BOQI2iBgECABtHRWcrDwDhAAAAAElFTkSuQmCC"); 256 | } 257 | .fs-comment-filter[data-quadrant="bottomleft"]{ 258 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfAQMAAAAlYIR6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFAAAA////pdmf3QAAABlJREFUCNdjYACB+n///1BOQAGYRTHBwAAAAfRMSx8fTlkAAAAASUVORK5CYII="); 259 | } 260 | 261 | #fs-comment-filters .fs-comment-filter.fs-hide{ 262 | background-color: #fff; 263 | color: #000; 264 | opacity: .35; 265 | } 266 | 267 | #fs-comment-filters .fs-comment-filter:hover{ 268 | cursor: pointer; 269 | cursor: hand; 270 | text-decoration: underline; 271 | -webkit-touch-callout: none; 272 | -webkit-user-select: none; 273 | -khtml-user-select: none; 274 | -moz-user-select: -moz-none; 275 | -ms-user-select: none; 276 | user-select: none; 277 | } 278 | 279 | .#fs-comment-filters .fs-comment-filter img { 280 | width: 100%; 281 | } 282 | 283 | .fs-row .clear { 284 | clear: both; 285 | } 286 | -------------------------------------------------------------------------------- /js/thirdparty/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.6.0 2 | // http://underscorejs.org 3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.6.0";var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&"constructor"in n&&"constructor"in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};T.unescape=j.invert(T.escape);var I={escape:new RegExp("["+j.keys(T.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(T.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),"function"==typeof define&&define.amd&&define("underscore",[],function(){return j})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /js/thirdparty/tabletop.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | "use strict"; 3 | 4 | var inNodeJS = false; 5 | if (typeof module !== 'undefined' && module.exports) { 6 | inNodeJS = true; 7 | var request = require('request'); 8 | } 9 | 10 | var supportsCORS = false; 11 | var inLegacyIE = false; 12 | try { 13 | var testXHR = new XMLHttpRequest(); 14 | if (typeof testXHR.withCredentials !== 'undefined') { 15 | supportsCORS = true; 16 | } else { 17 | if ("XDomainRequest" in window) { 18 | supportsCORS = true; 19 | inLegacyIE = true; 20 | } 21 | } 22 | } catch (e) { } 23 | 24 | // Create a simple indexOf function for support 25 | // of older browsers. Uses native indexOf if 26 | // available. Code similar to underscores. 27 | // By making a separate function, instead of adding 28 | // to the prototype, we will not break bad for loops 29 | // in older browsers 30 | var indexOfProto = Array.prototype.indexOf; 31 | var ttIndexOf = function(array, item) { 32 | var i = 0, l = array.length; 33 | 34 | if (indexOfProto && array.indexOf === indexOfProto) return array.indexOf(item); 35 | for (; i < l; i++) if (array[i] === item) return i; 36 | return -1; 37 | }; 38 | 39 | /* 40 | Initialize with Tabletop.init( { key: '0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc' } ) 41 | OR! 42 | Initialize with Tabletop.init( { key: 'https://docs.google.com/spreadsheet/pub?hl=en_US&hl=en_US&key=0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc&output=html&widget=true' } ) 43 | OR! 44 | Initialize with Tabletop.init('0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc') 45 | */ 46 | 47 | var Tabletop = function(options) { 48 | // Make sure Tabletop is being used as a constructor no matter what. 49 | if(!this || !(this instanceof Tabletop)) { 50 | return new Tabletop(options); 51 | } 52 | 53 | if(typeof(options) === 'string') { 54 | options = { key : options }; 55 | } 56 | 57 | this.callback = options.callback; 58 | this.wanted = options.wanted || []; 59 | this.key = options.key; 60 | this.simpleSheet = !!options.simpleSheet; 61 | this.parseNumbers = !!options.parseNumbers; 62 | this.wait = !!options.wait; 63 | this.reverse = !!options.reverse; 64 | this.postProcess = options.postProcess; 65 | this.debug = !!options.debug; 66 | this.query = options.query || ''; 67 | this.orderby = options.orderby; 68 | this.endpoint = options.endpoint || "https://spreadsheets.google.com"; 69 | this.singleton = !!options.singleton; 70 | this.simple_url = !!options.simple_url; 71 | this.callbackContext = options.callbackContext; 72 | 73 | if(typeof(options.proxy) !== 'undefined') { 74 | // Remove trailing slash, it will break the app 75 | this.endpoint = options.proxy.replace(/\/$/,''); 76 | this.simple_url = true; 77 | this.singleton = true; 78 | // Let's only use CORS (straight JSON request) when 79 | // fetching straight from Google 80 | supportsCORS = false 81 | } 82 | 83 | this.parameterize = options.parameterize || false; 84 | 85 | if(this.singleton) { 86 | if(typeof(Tabletop.singleton) !== 'undefined') { 87 | this.log("WARNING! Tabletop singleton already defined"); 88 | } 89 | Tabletop.singleton = this; 90 | } 91 | 92 | /* Be friendly about what you accept */ 93 | if(/key=/.test(this.key)) { 94 | this.log("You passed an old Google Docs url as the key! Attempting to parse."); 95 | this.key = this.key.match("key=(.*?)&")[1]; 96 | } 97 | 98 | if(/pubhtml/.test(this.key)) { 99 | alert("You passed a new Google Spreadsheets url as the key! This won't work yet, you'll need to change back to old Sheets."); 100 | this.key = this.key.match("d\\/(.*?)\\/pubhtml")[1]; 101 | console.log(this.key); 102 | } 103 | 104 | if(!this.key) { 105 | this.log("You need to pass Tabletop a key!"); 106 | return; 107 | } 108 | 109 | this.log("Initializing with key " + this.key); 110 | 111 | this.models = {}; 112 | this.model_names = []; 113 | 114 | this.base_json_path = "/feeds/worksheets/" + this.key + "/public/basic?alt="; 115 | 116 | if (inNodeJS || supportsCORS) { 117 | this.base_json_path += 'json'; 118 | } else { 119 | this.base_json_path += 'json-in-script'; 120 | } 121 | 122 | if(!this.wait) { 123 | this.fetch(); 124 | } 125 | }; 126 | 127 | // A global storage for callbacks. 128 | Tabletop.callbacks = {}; 129 | 130 | // Backwards compatibility. 131 | Tabletop.init = function(options) { 132 | return new Tabletop(options); 133 | }; 134 | 135 | Tabletop.sheets = function() { 136 | this.log("Times have changed! You'll want to use var tabletop = Tabletop.init(...); tabletop.sheets(...); instead of Tabletop.sheets(...)"); 137 | }; 138 | 139 | Tabletop.prototype = { 140 | 141 | fetch: function(callback) { 142 | if(typeof(callback) !== "undefined") { 143 | this.callback = callback; 144 | } 145 | this.requestData(this.base_json_path, this.loadSheets); 146 | }, 147 | 148 | /* 149 | This will call the environment appropriate request method. 150 | 151 | In browser it will use JSON-P, in node it will use request() 152 | */ 153 | requestData: function(path, callback) { 154 | if (inNodeJS) { 155 | this.serverSideFetch(path, callback); 156 | } else { 157 | //CORS only works in IE8/9 across the same protocol 158 | //You must have your server on HTTPS to talk to Google, or it'll fall back on injection 159 | var protocol = this.endpoint.split("//").shift() || "http"; 160 | if (supportsCORS && (!inLegacyIE || protocol === location.protocol)) { 161 | this.xhrFetch(path, callback); 162 | } else { 163 | this.injectScript(path, callback); 164 | } 165 | } 166 | }, 167 | 168 | /* 169 | Use Cross-Origin XMLHttpRequest to get the data in browsers that support it. 170 | */ 171 | xhrFetch: function(path, callback) { 172 | //support IE8's separate cross-domain object 173 | var xhr = inLegacyIE ? new XDomainRequest() : new XMLHttpRequest(); 174 | xhr.open("GET", this.endpoint + path); 175 | var self = this; 176 | xhr.onload = function() { 177 | try { 178 | var json = JSON.parse(xhr.responseText); 179 | } catch (e) { 180 | console.error(e); 181 | } 182 | callback.call(self, json); 183 | }; 184 | xhr.send(); 185 | }, 186 | 187 | /* 188 | Insert the URL into the page as a script tag. Once it's loaded the spreadsheet data 189 | it triggers the callback. This helps you avoid cross-domain errors 190 | http://code.google.com/apis/gdata/samples/spreadsheet_sample.html 191 | 192 | Let's be plain-Jane and not use jQuery or anything. 193 | */ 194 | injectScript: function(path, callback) { 195 | var script = document.createElement('script'); 196 | var callbackName; 197 | 198 | if(this.singleton) { 199 | if(callback === this.loadSheets) { 200 | callbackName = 'Tabletop.singleton.loadSheets'; 201 | } else if (callback === this.loadSheet) { 202 | callbackName = 'Tabletop.singleton.loadSheet'; 203 | } 204 | } else { 205 | var self = this; 206 | callbackName = 'tt' + (+new Date()) + (Math.floor(Math.random()*100000)); 207 | // Create a temp callback which will get removed once it has executed, 208 | // this allows multiple instances of Tabletop to coexist. 209 | Tabletop.callbacks[ callbackName ] = function () { 210 | var args = Array.prototype.slice.call( arguments, 0 ); 211 | callback.apply(self, args); 212 | script.parentNode.removeChild(script); 213 | delete Tabletop.callbacks[callbackName]; 214 | }; 215 | callbackName = 'Tabletop.callbacks.' + callbackName; 216 | } 217 | 218 | var url = path + "&callback=" + callbackName; 219 | 220 | if(this.simple_url) { 221 | // We've gone down a rabbit hole of passing injectScript the path, so let's 222 | // just pull the sheet_id out of the path like the least efficient worker bees 223 | if(path.indexOf("/list/") !== -1) { 224 | script.src = this.endpoint + "/" + this.key + "-" + path.split("/")[4]; 225 | } else { 226 | script.src = this.endpoint + "/" + this.key; 227 | } 228 | } else { 229 | script.src = this.endpoint + url; 230 | } 231 | 232 | if (this.parameterize) { 233 | script.src = this.parameterize + encodeURIComponent(script.src); 234 | } 235 | 236 | document.getElementsByTagName('script')[0].parentNode.appendChild(script); 237 | }, 238 | 239 | /* 240 | This will only run if tabletop is being run in node.js 241 | */ 242 | serverSideFetch: function(path, callback) { 243 | var self = this 244 | request({url: this.endpoint + path, json: true}, function(err, resp, body) { 245 | if (err) { 246 | return console.error(err); 247 | } 248 | callback.call(self, body); 249 | }); 250 | }, 251 | 252 | /* 253 | Is this a sheet you want to pull? 254 | If { wanted: ["Sheet1"] } has been specified, only Sheet1 is imported 255 | Pulls all sheets if none are specified 256 | */ 257 | isWanted: function(sheetName) { 258 | if(this.wanted.length === 0) { 259 | return true; 260 | } else { 261 | return (ttIndexOf(this.wanted, sheetName) !== -1); 262 | } 263 | }, 264 | 265 | /* 266 | What gets send to the callback 267 | if simpleSheet === true, then don't return an array of Tabletop.this.models, 268 | only return the first one's elements 269 | */ 270 | data: function() { 271 | // If the instance is being queried before the data's been fetched 272 | // then return undefined. 273 | if(this.model_names.length === 0) { 274 | return undefined; 275 | } 276 | if(this.simpleSheet) { 277 | if(this.model_names.length > 1 && this.debug) { 278 | this.log("WARNING You have more than one sheet but are using simple sheet mode! Don't blame me when something goes wrong."); 279 | } 280 | return this.models[ this.model_names[0] ].all(); 281 | } else { 282 | return this.models; 283 | } 284 | }, 285 | 286 | /* 287 | Add another sheet to the wanted list 288 | */ 289 | addWanted: function(sheet) { 290 | if(ttIndexOf(this.wanted, sheet) === -1) { 291 | this.wanted.push(sheet); 292 | } 293 | }, 294 | 295 | /* 296 | Load all worksheets of the spreadsheet, turning each into a Tabletop Model. 297 | Need to use injectScript because the worksheet view that you're working from 298 | doesn't actually include the data. The list-based feed (/feeds/list/key..) does, though. 299 | Calls back to loadSheet in order to get the real work done. 300 | 301 | Used as a callback for the worksheet-based JSON 302 | */ 303 | loadSheets: function(data) { 304 | var i, ilen; 305 | var toLoad = []; 306 | this.foundSheetNames = []; 307 | 308 | for(i = 0, ilen = data.feed.entry.length; i < ilen ; i++) { 309 | this.foundSheetNames.push(data.feed.entry[i].title.$t); 310 | // Only pull in desired sheets to reduce loading 311 | if( this.isWanted(data.feed.entry[i].content.$t) ) { 312 | var sheet_id = data.feed.entry[i].link[3].href.substr( data.feed.entry[i].link[3].href.length - 3, 3); 313 | var json_path = "/feeds/list/" + this.key + "/" + sheet_id + "/public/values?sq=" + this.query + '&alt=' 314 | if (inNodeJS || supportsCORS) { 315 | json_path += 'json'; 316 | } else { 317 | json_path += 'json-in-script'; 318 | } 319 | if(this.orderby) { 320 | json_path += "&orderby=column:" + this.orderby.toLowerCase(); 321 | } 322 | if(this.reverse) { 323 | json_path += "&reverse=true"; 324 | } 325 | toLoad.push(json_path); 326 | } 327 | } 328 | 329 | this.sheetsToLoad = toLoad.length; 330 | for(i = 0, ilen = toLoad.length; i < ilen; i++) { 331 | this.requestData(toLoad[i], this.loadSheet); 332 | } 333 | }, 334 | 335 | /* 336 | Access layer for the this.models 337 | .sheets() gets you all of the sheets 338 | .sheets('Sheet1') gets you the sheet named Sheet1 339 | */ 340 | sheets: function(sheetName) { 341 | if(typeof sheetName === "undefined") { 342 | return this.models; 343 | } else { 344 | if(typeof(this.models[ sheetName ]) === "undefined") { 345 | // alert( "Can't find " + sheetName ); 346 | return; 347 | } else { 348 | return this.models[ sheetName ]; 349 | } 350 | } 351 | }, 352 | 353 | /* 354 | Parse a single list-based worksheet, turning it into a Tabletop Model 355 | 356 | Used as a callback for the list-based JSON 357 | */ 358 | loadSheet: function(data) { 359 | var model = new Tabletop.Model( { data: data, 360 | parseNumbers: this.parseNumbers, 361 | postProcess: this.postProcess, 362 | tabletop: this } ); 363 | this.models[ model.name ] = model; 364 | if(ttIndexOf(this.model_names, model.name) === -1) { 365 | this.model_names.push(model.name); 366 | } 367 | this.sheetsToLoad--; 368 | if(this.sheetsToLoad === 0) 369 | this.doCallback(); 370 | }, 371 | 372 | /* 373 | Execute the callback upon loading! Rely on this.data() because you might 374 | only request certain pieces of data (i.e. simpleSheet mode) 375 | Tests this.sheetsToLoad just in case a race condition happens to show up 376 | */ 377 | doCallback: function() { 378 | if(this.sheetsToLoad === 0) { 379 | this.callback.apply(this.callbackContext || this, [this.data(), this]); 380 | } 381 | }, 382 | 383 | log: function(msg) { 384 | if(this.debug) { 385 | if(typeof console !== "undefined" && typeof console.log !== "undefined") { 386 | Function.prototype.apply.apply(console.log, [console, arguments]); 387 | } 388 | } 389 | } 390 | 391 | }; 392 | 393 | /* 394 | Tabletop.Model stores the attribute names and parses the worksheet data 395 | to turn it into something worthwhile 396 | 397 | Options should be in the format { data: XXX }, with XXX being the list-based worksheet 398 | */ 399 | Tabletop.Model = function(options) { 400 | var i, j, ilen, jlen; 401 | this.column_names = []; 402 | this.name = options.data.feed.title.$t; 403 | this.elements = []; 404 | this.raw = options.data; // A copy of the sheet's raw data, for accessing minutiae 405 | 406 | if(typeof(options.data.feed.entry) === 'undefined') { 407 | options.tabletop.log("Missing data for " + this.name + ", make sure you didn't forget column headers"); 408 | this.elements = []; 409 | return; 410 | } 411 | 412 | for(var key in options.data.feed.entry[0]){ 413 | if(/^gsx/.test(key)) 414 | this.column_names.push( key.replace("gsx$","") ); 415 | } 416 | 417 | for(i = 0, ilen = options.data.feed.entry.length ; i < ilen; i++) { 418 | var source = options.data.feed.entry[i]; 419 | var element = {}; 420 | for(var j = 0, jlen = this.column_names.length; j < jlen ; j++) { 421 | var cell = source[ "gsx$" + this.column_names[j] ]; 422 | if (typeof(cell) !== 'undefined') { 423 | if(options.parseNumbers && cell.$t !== '' && !isNaN(cell.$t)) 424 | element[ this.column_names[j] ] = +cell.$t; 425 | else 426 | element[ this.column_names[j] ] = cell.$t; 427 | } else { 428 | element[ this.column_names[j] ] = ''; 429 | } 430 | } 431 | if(element.rowNumber === undefined) 432 | element.rowNumber = i + 1; 433 | if( options.postProcess ) 434 | options.postProcess(element); 435 | this.elements.push(element); 436 | } 437 | 438 | }; 439 | 440 | Tabletop.Model.prototype = { 441 | /* 442 | Returns all of the elements (rows) of the worksheet as objects 443 | */ 444 | all: function() { 445 | return this.elements; 446 | }, 447 | 448 | /* 449 | Return the elements as an array of arrays, instead of an array of objects 450 | */ 451 | toArray: function() { 452 | var array = [], 453 | i, j, ilen, jlen; 454 | for(i = 0, ilen = this.elements.length; i < ilen; i++) { 455 | var row = []; 456 | for(j = 0, jlen = this.column_names.length; j < jlen ; j++) { 457 | row.push( this.elements[i][ this.column_names[j] ] ); 458 | } 459 | array.push(row); 460 | } 461 | return array; 462 | } 463 | }; 464 | 465 | if(inNodeJS) { 466 | module.exports = Tabletop; 467 | } else { 468 | global.Tabletop = Tabletop; 469 | } 470 | 471 | })(this); 472 | -------------------------------------------------------------------------------- /js/thirdparty/colorbrewer.js: -------------------------------------------------------------------------------- 1 | // This product includes color specifications and designs developed by Cynthia Brewer (http://colorbrewer.org/). 2 | var colorbrewer = {YlGn: { 3 | 3: ["#f7fcb9","#addd8e","#31a354"], 4 | 4: ["#ffffcc","#c2e699","#78c679","#238443"], 5 | 5: ["#ffffcc","#c2e699","#78c679","#31a354","#006837"], 6 | 6: ["#ffffcc","#d9f0a3","#addd8e","#78c679","#31a354","#006837"], 7 | 7: ["#ffffcc","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#005a32"], 8 | 8: ["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#005a32"], 9 | 9: ["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"] 10 | },YlGnBu: { 11 | 3: ["#edf8b1","#7fcdbb","#2c7fb8"], 12 | 4: ["#ffffcc","#a1dab4","#41b6c4","#225ea8"], 13 | 5: ["#ffffcc","#a1dab4","#41b6c4","#2c7fb8","#253494"], 14 | 6: ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#2c7fb8","#253494"], 15 | 7: ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"], 16 | 8: ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"], 17 | 9: ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"] 18 | },GnBu: { 19 | 3: ["#e0f3db","#a8ddb5","#43a2ca"], 20 | 4: ["#f0f9e8","#bae4bc","#7bccc4","#2b8cbe"], 21 | 5: ["#f0f9e8","#bae4bc","#7bccc4","#43a2ca","#0868ac"], 22 | 6: ["#f0f9e8","#ccebc5","#a8ddb5","#7bccc4","#43a2ca","#0868ac"], 23 | 7: ["#f0f9e8","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"], 24 | 8: ["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"], 25 | 9: ["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081"] 26 | },BuGn: { 27 | 3: ["#e5f5f9","#99d8c9","#2ca25f"], 28 | 4: ["#edf8fb","#b2e2e2","#66c2a4","#238b45"], 29 | 5: ["#edf8fb","#b2e2e2","#66c2a4","#2ca25f","#006d2c"], 30 | 6: ["#edf8fb","#ccece6","#99d8c9","#66c2a4","#2ca25f","#006d2c"], 31 | 7: ["#edf8fb","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#005824"], 32 | 8: ["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#005824"], 33 | 9: ["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#006d2c","#00441b"] 34 | },PuBuGn: { 35 | 3: ["#ece2f0","#a6bddb","#1c9099"], 36 | 4: ["#f6eff7","#bdc9e1","#67a9cf","#02818a"], 37 | 5: ["#f6eff7","#bdc9e1","#67a9cf","#1c9099","#016c59"], 38 | 6: ["#f6eff7","#d0d1e6","#a6bddb","#67a9cf","#1c9099","#016c59"], 39 | 7: ["#f6eff7","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016450"], 40 | 8: ["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016450"], 41 | 9: ["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016c59","#014636"] 42 | },PuBu: { 43 | 3: ["#ece7f2","#a6bddb","#2b8cbe"], 44 | 4: ["#f1eef6","#bdc9e1","#74a9cf","#0570b0"], 45 | 5: ["#f1eef6","#bdc9e1","#74a9cf","#2b8cbe","#045a8d"], 46 | 6: ["#f1eef6","#d0d1e6","#a6bddb","#74a9cf","#2b8cbe","#045a8d"], 47 | 7: ["#f1eef6","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#034e7b"], 48 | 8: ["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#034e7b"], 49 | 9: ["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#045a8d","#023858"] 50 | },BuPu: { 51 | 3: ["#e0ecf4","#9ebcda","#8856a7"], 52 | 4: ["#edf8fb","#b3cde3","#8c96c6","#88419d"], 53 | 5: ["#edf8fb","#b3cde3","#8c96c6","#8856a7","#810f7c"], 54 | 6: ["#edf8fb","#bfd3e6","#9ebcda","#8c96c6","#8856a7","#810f7c"], 55 | 7: ["#edf8fb","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#6e016b"], 56 | 8: ["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#6e016b"], 57 | 9: ["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#810f7c","#4d004b"] 58 | },RdPu: { 59 | 3: ["#fde0dd","#fa9fb5","#c51b8a"], 60 | 4: ["#feebe2","#fbb4b9","#f768a1","#ae017e"], 61 | 5: ["#feebe2","#fbb4b9","#f768a1","#c51b8a","#7a0177"], 62 | 6: ["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#c51b8a","#7a0177"], 63 | 7: ["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177"], 64 | 8: ["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177"], 65 | 9: ["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177","#49006a"] 66 | },PuRd: { 67 | 3: ["#e7e1ef","#c994c7","#dd1c77"], 68 | 4: ["#f1eef6","#d7b5d8","#df65b0","#ce1256"], 69 | 5: ["#f1eef6","#d7b5d8","#df65b0","#dd1c77","#980043"], 70 | 6: ["#f1eef6","#d4b9da","#c994c7","#df65b0","#dd1c77","#980043"], 71 | 7: ["#f1eef6","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"], 72 | 8: ["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"], 73 | 9: ["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#980043","#67001f"] 74 | },OrRd: { 75 | 3: ["#fee8c8","#fdbb84","#e34a33"], 76 | 4: ["#fef0d9","#fdcc8a","#fc8d59","#d7301f"], 77 | 5: ["#fef0d9","#fdcc8a","#fc8d59","#e34a33","#b30000"], 78 | 6: ["#fef0d9","#fdd49e","#fdbb84","#fc8d59","#e34a33","#b30000"], 79 | 7: ["#fef0d9","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#990000"], 80 | 8: ["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#990000"], 81 | 9: ["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#b30000","#7f0000"] 82 | },YlOrRd: { 83 | 3: ["#ffeda0","#feb24c","#f03b20"], 84 | 4: ["#ffffb2","#fecc5c","#fd8d3c","#e31a1c"], 85 | 5: ["#ffffb2","#fecc5c","#fd8d3c","#f03b20","#bd0026"], 86 | 6: ["#ffffb2","#fed976","#feb24c","#fd8d3c","#f03b20","#bd0026"], 87 | 7: ["#ffffb2","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#b10026"], 88 | 8: ["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#b10026"], 89 | 9: ["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"] 90 | },YlOrBr: { 91 | 3: ["#fff7bc","#fec44f","#d95f0e"], 92 | 4: ["#ffffd4","#fed98e","#fe9929","#cc4c02"], 93 | 5: ["#ffffd4","#fed98e","#fe9929","#d95f0e","#993404"], 94 | 6: ["#ffffd4","#fee391","#fec44f","#fe9929","#d95f0e","#993404"], 95 | 7: ["#ffffd4","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#8c2d04"], 96 | 8: ["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#8c2d04"], 97 | 9: ["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#993404","#662506"] 98 | },Purples: { 99 | 3: ["#efedf5","#bcbddc","#756bb1"], 100 | 4: ["#f2f0f7","#cbc9e2","#9e9ac8","#6a51a3"], 101 | 5: ["#f2f0f7","#cbc9e2","#9e9ac8","#756bb1","#54278f"], 102 | 6: ["#f2f0f7","#dadaeb","#bcbddc","#9e9ac8","#756bb1","#54278f"], 103 | 7: ["#f2f0f7","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#4a1486"], 104 | 8: ["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#4a1486"], 105 | 9: ["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#54278f","#3f007d"] 106 | },Blues: { 107 | 3: ["#deebf7","#9ecae1","#3182bd"], 108 | 4: ["#eff3ff","#bdd7e7","#6baed6","#2171b5"], 109 | 5: ["#eff3ff","#bdd7e7","#6baed6","#3182bd","#08519c"], 110 | 6: ["#eff3ff","#c6dbef","#9ecae1","#6baed6","#3182bd","#08519c"], 111 | 7: ["#eff3ff","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#084594"], 112 | 8: ["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#084594"], 113 | 9: ["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"] 114 | },Greens: { 115 | 3: ["#e5f5e0","#a1d99b","#31a354"], 116 | 4: ["#edf8e9","#bae4b3","#74c476","#238b45"], 117 | 5: ["#edf8e9","#bae4b3","#74c476","#31a354","#006d2c"], 118 | 6: ["#edf8e9","#c7e9c0","#a1d99b","#74c476","#31a354","#006d2c"], 119 | 7: ["#edf8e9","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#005a32"], 120 | 8: ["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#005a32"], 121 | 9: ["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#006d2c","#00441b"] 122 | },Oranges: { 123 | 3: ["#fee6ce","#fdae6b","#e6550d"], 124 | 4: ["#feedde","#fdbe85","#fd8d3c","#d94701"], 125 | 5: ["#feedde","#fdbe85","#fd8d3c","#e6550d","#a63603"], 126 | 6: ["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#e6550d","#a63603"], 127 | 7: ["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"], 128 | 8: ["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"], 129 | 9: ["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#a63603","#7f2704"] 130 | },Reds: { 131 | 3: ["#fee0d2","#fc9272","#de2d26"], 132 | 4: ["#fee5d9","#fcae91","#fb6a4a","#cb181d"], 133 | 5: ["#fee5d9","#fcae91","#fb6a4a","#de2d26","#a50f15"], 134 | 6: ["#fee5d9","#fcbba1","#fc9272","#fb6a4a","#de2d26","#a50f15"], 135 | 7: ["#fee5d9","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#99000d"], 136 | 8: ["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#99000d"], 137 | 9: ["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15","#67000d"] 138 | },Greys: { 139 | 3: ["#f0f0f0","#bdbdbd","#636363"], 140 | 4: ["#f7f7f7","#cccccc","#969696","#525252"], 141 | 5: ["#f7f7f7","#cccccc","#969696","#636363","#252525"], 142 | 6: ["#f7f7f7","#d9d9d9","#bdbdbd","#969696","#636363","#252525"], 143 | 7: ["#f7f7f7","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525"], 144 | 8: ["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525"], 145 | 9: ["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525","#000000"] 146 | },PuOr: { 147 | 3: ["#f1a340","#f7f7f7","#998ec3"], 148 | 4: ["#e66101","#fdb863","#b2abd2","#5e3c99"], 149 | 5: ["#e66101","#fdb863","#f7f7f7","#b2abd2","#5e3c99"], 150 | 6: ["#b35806","#f1a340","#fee0b6","#d8daeb","#998ec3","#542788"], 151 | 7: ["#b35806","#f1a340","#fee0b6","#f7f7f7","#d8daeb","#998ec3","#542788"], 152 | 8: ["#b35806","#e08214","#fdb863","#fee0b6","#d8daeb","#b2abd2","#8073ac","#542788"], 153 | 9: ["#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788"], 154 | 10: ["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"], 155 | 11: ["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"] 156 | },BrBG: { 157 | 3: ["#d8b365","#f5f5f5","#5ab4ac"], 158 | 4: ["#a6611a","#dfc27d","#80cdc1","#018571"], 159 | 5: ["#a6611a","#dfc27d","#f5f5f5","#80cdc1","#018571"], 160 | 6: ["#8c510a","#d8b365","#f6e8c3","#c7eae5","#5ab4ac","#01665e"], 161 | 7: ["#8c510a","#d8b365","#f6e8c3","#f5f5f5","#c7eae5","#5ab4ac","#01665e"], 162 | 8: ["#8c510a","#bf812d","#dfc27d","#f6e8c3","#c7eae5","#80cdc1","#35978f","#01665e"], 163 | 9: ["#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e"], 164 | 10: ["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"], 165 | 11: ["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"] 166 | },PRGn: { 167 | 3: ["#af8dc3","#f7f7f7","#7fbf7b"], 168 | 4: ["#7b3294","#c2a5cf","#a6dba0","#008837"], 169 | 5: ["#7b3294","#c2a5cf","#f7f7f7","#a6dba0","#008837"], 170 | 6: ["#762a83","#af8dc3","#e7d4e8","#d9f0d3","#7fbf7b","#1b7837"], 171 | 7: ["#762a83","#af8dc3","#e7d4e8","#f7f7f7","#d9f0d3","#7fbf7b","#1b7837"], 172 | 8: ["#762a83","#9970ab","#c2a5cf","#e7d4e8","#d9f0d3","#a6dba0","#5aae61","#1b7837"], 173 | 9: ["#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837"], 174 | 10: ["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"], 175 | 11: ["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"] 176 | },PiYG: { 177 | 3: ["#e9a3c9","#f7f7f7","#a1d76a"], 178 | 4: ["#d01c8b","#f1b6da","#b8e186","#4dac26"], 179 | 5: ["#d01c8b","#f1b6da","#f7f7f7","#b8e186","#4dac26"], 180 | 6: ["#c51b7d","#e9a3c9","#fde0ef","#e6f5d0","#a1d76a","#4d9221"], 181 | 7: ["#c51b7d","#e9a3c9","#fde0ef","#f7f7f7","#e6f5d0","#a1d76a","#4d9221"], 182 | 8: ["#c51b7d","#de77ae","#f1b6da","#fde0ef","#e6f5d0","#b8e186","#7fbc41","#4d9221"], 183 | 9: ["#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221"], 184 | 10: ["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"], 185 | 11: ["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"] 186 | },RdBu: { 187 | 3: ["#ef8a62","#f7f7f7","#67a9cf"], 188 | 4: ["#ca0020","#f4a582","#92c5de","#0571b0"], 189 | 5: ["#ca0020","#f4a582","#f7f7f7","#92c5de","#0571b0"], 190 | 6: ["#b2182b","#ef8a62","#fddbc7","#d1e5f0","#67a9cf","#2166ac"], 191 | 7: ["#b2182b","#ef8a62","#fddbc7","#f7f7f7","#d1e5f0","#67a9cf","#2166ac"], 192 | 8: ["#b2182b","#d6604d","#f4a582","#fddbc7","#d1e5f0","#92c5de","#4393c3","#2166ac"], 193 | 9: ["#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac"], 194 | 10: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"], 195 | 11: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"] 196 | },RdGy: { 197 | 3: ["#ef8a62","#ffffff","#999999"], 198 | 4: ["#ca0020","#f4a582","#bababa","#404040"], 199 | 5: ["#ca0020","#f4a582","#ffffff","#bababa","#404040"], 200 | 6: ["#b2182b","#ef8a62","#fddbc7","#e0e0e0","#999999","#4d4d4d"], 201 | 7: ["#b2182b","#ef8a62","#fddbc7","#ffffff","#e0e0e0","#999999","#4d4d4d"], 202 | 8: ["#b2182b","#d6604d","#f4a582","#fddbc7","#e0e0e0","#bababa","#878787","#4d4d4d"], 203 | 9: ["#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d"], 204 | 10: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"], 205 | 11: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"] 206 | },RdYlBu: { 207 | 3: ["#fc8d59","#ffffbf","#91bfdb"], 208 | 4: ["#d7191c","#fdae61","#abd9e9","#2c7bb6"], 209 | 5: ["#d7191c","#fdae61","#ffffbf","#abd9e9","#2c7bb6"], 210 | 6: ["#d73027","#fc8d59","#fee090","#e0f3f8","#91bfdb","#4575b4"], 211 | 7: ["#d73027","#fc8d59","#fee090","#ffffbf","#e0f3f8","#91bfdb","#4575b4"], 212 | 8: ["#d73027","#f46d43","#fdae61","#fee090","#e0f3f8","#abd9e9","#74add1","#4575b4"], 213 | 9: ["#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4"], 214 | 10: ["#a50026","#d73027","#f46d43","#fdae61","#fee090","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"], 215 | 11: ["#a50026","#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"] 216 | },Spectral: { 217 | 3: ["#fc8d59","#ffffbf","#99d594"], 218 | 4: ["#d7191c","#fdae61","#abdda4","#2b83ba"], 219 | 5: ["#d7191c","#fdae61","#ffffbf","#abdda4","#2b83ba"], 220 | 6: ["#d53e4f","#fc8d59","#fee08b","#e6f598","#99d594","#3288bd"], 221 | 7: ["#d53e4f","#fc8d59","#fee08b","#ffffbf","#e6f598","#99d594","#3288bd"], 222 | 8: ["#d53e4f","#f46d43","#fdae61","#fee08b","#e6f598","#abdda4","#66c2a5","#3288bd"], 223 | 9: ["#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd"], 224 | 10: ["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"], 225 | 11: ["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"] 226 | },RdYlGn: { 227 | 3: ["#fc8d59","#ffffbf","#91cf60"], 228 | 4: ["#d7191c","#fdae61","#a6d96a","#1a9641"], 229 | 5: ["#d7191c","#fdae61","#ffffbf","#a6d96a","#1a9641"], 230 | 6: ["#d73027","#fc8d59","#fee08b","#d9ef8b","#91cf60","#1a9850"], 231 | 7: ["#d73027","#fc8d59","#fee08b","#ffffbf","#d9ef8b","#91cf60","#1a9850"], 232 | 8: ["#d73027","#f46d43","#fdae61","#fee08b","#d9ef8b","#a6d96a","#66bd63","#1a9850"], 233 | 9: ["#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850"], 234 | 10: ["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"], 235 | 11: ["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"] 236 | },Accent: { 237 | 3: ["#7fc97f","#beaed4","#fdc086"], 238 | 4: ["#7fc97f","#beaed4","#fdc086","#ffff99"], 239 | 5: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0"], 240 | 6: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f"], 241 | 7: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17"], 242 | 8: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17","#666666"] 243 | },Dark2: { 244 | 3: ["#1b9e77","#d95f02","#7570b3"], 245 | 4: ["#1b9e77","#d95f02","#7570b3","#e7298a"], 246 | 5: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e"], 247 | 6: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02"], 248 | 7: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d"], 249 | 8: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"] 250 | },Paired: { 251 | 3: ["#a6cee3","#1f78b4","#b2df8a"], 252 | 4: ["#a6cee3","#1f78b4","#b2df8a","#33a02c"], 253 | 5: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99"], 254 | 6: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c"], 255 | 7: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f"], 256 | 8: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00"], 257 | 9: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6"], 258 | 10: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a"], 259 | 11: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99"], 260 | 12: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99","#b15928"] 261 | },Pastel1: { 262 | 3: ["#fbb4ae","#b3cde3","#ccebc5"], 263 | 4: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4"], 264 | 5: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6"], 265 | 6: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc"], 266 | 7: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd"], 267 | 8: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec"], 268 | 9: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"] 269 | },Pastel2: { 270 | 3: ["#b3e2cd","#fdcdac","#cbd5e8"], 271 | 4: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4"], 272 | 5: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9"], 273 | 6: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae"], 274 | 7: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc"], 275 | 8: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc","#cccccc"] 276 | },Set1: { 277 | 3: ["#e41a1c","#377eb8","#4daf4a"], 278 | 4: ["#e41a1c","#377eb8","#4daf4a","#984ea3"], 279 | 5: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00"], 280 | 6: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33"], 281 | 7: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628"], 282 | 8: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf"], 283 | 9: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf","#999999"] 284 | },Set2: { 285 | 3: ["#66c2a5","#fc8d62","#8da0cb"], 286 | 4: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3"], 287 | 5: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854"], 288 | 6: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f"], 289 | 7: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494"], 290 | 8: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494","#b3b3b3"] 291 | },Set3: { 292 | 3: ["#8dd3c7","#ffffb3","#bebada"], 293 | 4: ["#8dd3c7","#ffffb3","#bebada","#fb8072"], 294 | 5: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3"], 295 | 6: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462"], 296 | 7: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69"], 297 | 8: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5"], 298 | 9: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9"], 299 | 10: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd"], 300 | 11: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5"], 301 | 12: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5","#ffed6f"] 302 | }}; -------------------------------------------------------------------------------- /js/fourscore.js: -------------------------------------------------------------------------------- 1 | var FourScore = function(opt){ 2 | 3 | var existing_data, 4 | $tooltip, 5 | $grid; 6 | 7 | /********************************/ 8 | 9 | function range(start, stop, step) { 10 | if (arguments.length < 3) { 11 | step = 1; 12 | if (arguments.length < 2) { 13 | stop = start; 14 | start = 0; 15 | } 16 | } 17 | if ((stop - start) / step === Infinity) throw new Error('infinite range'); 18 | var range = [], 19 | k = range_integerScale(Math.abs(step)), 20 | i = -1, 21 | j; 22 | start *= k, stop *= k, step *= k; 23 | if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); 24 | else while ((j = start + step * ++i) < stop) range.push(j / k); 25 | return range; 26 | }; 27 | 28 | function range_integerScale(x) { 29 | var k = 1; 30 | while (x * k % 1) k *= 10; 31 | return k; 32 | } 33 | 34 | var isArray = Array.isArray || $.isArray; 35 | 36 | // http://bl.ocks.org/aubergene/7791133 37 | 38 | // This is a function 39 | function Normalizer(min, max) { 40 | return function(val) { 41 | return (val - min) / (max - min); 42 | } 43 | } 44 | 45 | // This is another 46 | function Interpolater(min, max, clamp) { 47 | return function(val) { 48 | val = min + (max - min) * val; 49 | return clamp ? Math.min(Math.max(val, min), max) : val; 50 | } 51 | } 52 | 53 | // This is a third 54 | function Scale(minDomain, maxDomain, minRange, maxRange, clamp) { 55 | var normalize = new Normalizer(minDomain, maxDomain); 56 | var interpolate = new Interpolater(minRange, maxRange, clamp); 57 | var _normalize = new Normalizer(minRange, maxRange); 58 | var _interpolate = new Interpolater(minDomain, maxDomain, clamp); 59 | var s = function(val) { 60 | return interpolate(normalize(val)); 61 | }; 62 | s.inverse = function(val) { 63 | return _interpolate(_normalize(val)); 64 | }; 65 | return s; 66 | } 67 | 68 | 69 | /********************************/ 70 | 71 | function findGridMax(submissions, scale){ 72 | var max = 0, 73 | idx_max; 74 | for (var i = 0; i < submissions.length; i++){ 75 | idx_max = Math.max(Math.round(scale(submissions[i].x)), Math.round(scale(submissions[i].y))); 76 | if (idx_max > max) max = idx_max; 77 | } 78 | return max + 1; // Add one because this returns the highest index, but we want to know the length 79 | } 80 | 81 | function makeGridArray(data, size) { 82 | var extent; 83 | // If they haven't specified a custom input range then make it a one-to-one based on grid size 84 | if (!data.inputExtents){ 85 | extent = Math.floor(size/2); 86 | data.inputExtents = [extent * -1, extent]; 87 | } 88 | var userValueToGridIdx = new Scale(data.inputExtents[0], data.inputExtents[1], 0, size - 1), 89 | grid = $.map( 90 | range(0,size), 91 | function(c) { 92 | return [$.map(range(0,size), 93 | function(b) { 94 | return { 95 | submission_value: [Math.round(userValueToGridIdx.inverse(b)),Math.round(userValueToGridIdx.inverse(c))], 96 | count: 0, 97 | ids: [] 98 | } 99 | } 100 | )] 101 | } 102 | ), 103 | grid_x, 104 | grid_y, 105 | grid_xy, 106 | max = 0, 107 | cell; 108 | 109 | var grid_max = findGridMax(data.submissions, userValueToGridIdx); 110 | var possible_input_extent = Math.round((grid_max + 1) / 2); 111 | 112 | for (var i = 0; i < data.submissions.length; i++){ 113 | grid_x = Math.round(userValueToGridIdx(data.submissions[i].x)); 114 | grid_y = Math.round(userValueToGridIdx(data.submissions[i].y)); 115 | grid_xy = [grid_x, grid_y]; 116 | try { 117 | cell = grid[grid_xy[1]][grid_xy[0]]; 118 | cell.count++; 119 | cell.ids.push(data.submissions[i].uid); 120 | if (cell.count > max) max = cell.count; 121 | } catch(e){ 122 | throw 'Input data outside of grid range. Please make your grid larger than ' + grid_max + ' or manually add inputExtents in your config file that cover the range of input values. Perhaps `inputExtents: [-'+possible_input_extent+','+possible_input_extent+'],` will work for you.'; 123 | } 124 | } 125 | return {grid: grid, extents: [0, max]} 126 | } 127 | 128 | function convertNameToSelector(selector){ 129 | if (typeof selector == 'string') { 130 | 131 | if (selector.match(/#?[A-Za-z][A-Za-z0-9_-]+$/)) { 132 | return $('#' + selector.replace(/^#/,'')); 133 | } 134 | return $(selector); 135 | } 136 | 137 | //ADD: if it's an element, return $(el); 138 | 139 | return selector; 140 | } 141 | 142 | function colorScaleFactory($grid, color_info, extents){ 143 | var color_brewer_name; 144 | var bins; 145 | var colorScale; 146 | var hex; 147 | var fn; 148 | 149 | // If it's an array then it's a custom list, if not, it's color brewer styles 150 | if (!isArray(color_info)){ 151 | cb_name = color_info.name; 152 | bins = color_info.number; 153 | colorScale = new Scale(extents[0], extents[1], 0, bins - 1); 154 | fn = function(val){ 155 | return colorbrewer[cb_name][bins][Math.round(colorScale(val))] 156 | } 157 | } else { 158 | colorScale = new Scale(extents[0], extents[1], 0, color_info.length - 1); 159 | fn = function(val){ 160 | return color_info[Math.round(colorScale(val))] 161 | } 162 | } 163 | return fn 164 | } 165 | 166 | function gridArrayToMarkup(grid_selector, color_info, Grid){ 167 | $grid = convertNameToSelector(grid_selector); 168 | var grid = Grid.grid, 169 | extents = Grid.extents, 170 | grid_width = $grid.width(), 171 | square_value, 172 | submission_value, 173 | $cells, 174 | ids; 175 | 176 | $grid.find('.fs-row').remove(); 177 | 178 | $grid.hide() 179 | .addClass('fs-grid'); 180 | 181 | var colorScale = colorScaleFactory($grid, color_info, extents); // This will take a value and return a hex code 182 | 183 | // For every row in the grid, make a row element 184 | for (var i = 0; i < grid.length; i++ ){ 185 | 186 | $('
').appendTo($grid); 187 | 188 | // Now make a cell with the aggregate data 189 | for (var j = 0; j < grid.length; j++){ 190 | 191 | square_value = grid[i][j].count; 192 | submission_value = JSON.stringify(grid[i][j].submission_value); 193 | ids = JSON.stringify(grid[i][j].ids); 194 | fill_color = colorScale(square_value); 195 | 196 | $('
').css("width",((100 / grid.length) + "%")) // Subtract one for the margin on the right between cells 197 | .attr('data-submission-value', submission_value) 198 | .attr('data-ids', ids) 199 | .attr('data-cell-id', grid[i][j].submission_value[0] + '-' + grid[i][j].submission_value[1]) 200 | .css('background-color', fill_color) 201 | .toggleClass('fs-axis-right',(!(grid.length % 2) && j == grid.length/2 - 1)) 202 | .toggleClass('fs-axis-left',(!(grid.length % 2) && j == grid.length/2)) 203 | .toggleClass('fs-axis-bottom',(!(grid.length % 2) && i == grid.length/2 - 1)) 204 | .toggleClass('fs-axis-top',(!(grid.length % 2) && i == grid.length/2)) 205 | .appendTo($($grid.find('.fs-row')[i])); 206 | 207 | } 208 | 209 | $('
').appendTo($($grid.find('.fs-row')[i])); 210 | 211 | } 212 | 213 | if (localStorage.getItem('fs-cell')) { 214 | 215 | $('div[data-cell-id="' + JSON.parse(localStorage.getItem('fs-cell')).join('-') + '"]').addClass('saved'); 216 | 217 | } 218 | 219 | $cells = $grid.find('div.fs-cell'); 220 | 221 | $(window).on('resize',function(){ 222 | $cells.css("height",$cells.first().outerWidth()+"px"); 223 | }); 224 | 225 | $grid.show(); 226 | 227 | $cells.css("height",$cells.first().outerWidth()+"px"); 228 | 229 | 230 | } 231 | 232 | function addGridLabels(grid_selector, x_labels, y_labels){ 233 | var $grid = convertNameToSelector(grid_selector); 234 | var label_width; 235 | 236 | var grid_width = $grid.width(); 237 | var grid_height = $grid.height(); 238 | 239 | /* X-Labels */ 240 | // Left 241 | $('
').hide().appendTo($grid).html(x_labels[0]); 242 | label_height_perc = $('.fs-grid-label[data-location="left"]').outerHeight() / grid_height / 2 * 100; 243 | label_height_padding_px = $('.fs-grid-label[data-location="left"]').outerHeight() - $('.fs-grid-label[data-location="left"]').height(); 244 | label_width_px = $('.fs-grid-label[data-location="left"]').width() / 2; 245 | $('.fs-grid-label[data-location="left"]').css({'left': '-' + (label_width_px + label_height_padding_px - 1) + 'px', 'top': (50 - label_height_perc) + '%', }); 246 | // Right 247 | $('
').hide().appendTo($grid).html(x_labels[1]); 248 | label_height_padding_px = $('.fs-grid-label[data-location="right"]').outerHeight() - $('.fs-grid-label[data-location="right"]').height(); 249 | label_width_px = $('.fs-grid-label[data-location="right"]').width() / 2; 250 | 251 | $('.fs-grid-label[data-location="right"]').css({'right': '-'+ (label_width_px + label_height_padding_px - 2) + 'px', 'top': (50 - label_height_perc) + '%', }); 252 | 253 | /* Y-Labels */ 254 | // Top 255 | $('
').hide().appendTo($grid).html(y_labels[0]); 256 | label_width_perc = $('.fs-grid-label[data-location="top"]').outerWidth() / grid_width / 2 * 100; 257 | $('.fs-grid-label[data-location="top"]').css({'top': 0, 'left': (50 - label_width_perc ) + '%'}); 258 | // Bottom 259 | $('
').hide().appendTo($grid).html(y_labels[1]); 260 | label_width_perc = $('.fs-grid-label[data-location="bottom"]').outerWidth() / grid_width / 2 * 100; 261 | $('.fs-grid-label[data-location="bottom"]').css({'bottom': 0, 'left': (50 - label_width_perc ) + '%'}); 262 | 263 | $('.fs-grid-label').show(); 264 | } 265 | 266 | function submissionsToGridMarkup(subm_data, conf){ 267 | var Grid = makeGridArray(subm_data, conf.options.gridSize); 268 | gridArrayToMarkup(conf.options.gridTarget, conf.options.colors, Grid); 269 | addGridLabels(conf.options.gridTarget, conf.options.xAxis, conf.options.yAxis) 270 | } 271 | 272 | function applyCommentFilters(){ 273 | $('.fs-comment-filter').each(function(i, el){ 274 | var $el = $(el); 275 | var is_hidden= $el.hasClass('fs-hide'); 276 | var quadrant = $el.attr('data-quadrant'); 277 | 278 | var $quadrant_comments = $('.fs-comment-container[data-quadrant="'+quadrant+'"]'); 279 | if (!is_hidden){ 280 | $quadrant_comments.show(); 281 | }else{ 282 | $quadrant_comments.hide(); 283 | } 284 | }) 285 | } 286 | 287 | function bindHandlers(formExists){ 288 | 289 | 290 | //Listeners for quadrant filters 291 | $('.fs-comment-filter').on('click', function(){ 292 | var $el = $(this); 293 | var quadrant = $el.attr('data-quadrant'); 294 | 295 | $el.toggleClass('fs-hide'); 296 | applyCommentFilters(); 297 | }); 298 | 299 | //Don't bind the rest if they've already submitted 300 | if (localStorage.getItem('fs-cell')) return true; 301 | 302 | //Move the tooltip 303 | $grid.on('mouseover.tooltip', '.fs-cell', function(e){ 304 | 305 | var gridOffset = $grid.offset(); 306 | 307 | $tooltip.css({ 308 | left: e.pageX+2 - gridOffset.left, 309 | top: e.pageY+2 - gridOffset.top 310 | }).addClass('open'); 311 | 312 | }); 313 | 314 | //Move the tooltip 315 | $grid.on('mousemove.tooltip', '.fs-cell', function(e){ 316 | 317 | var gridOffset = $grid.offset(); 318 | 319 | $tooltip.css({ 320 | left: e.pageX+2 - gridOffset.left, 321 | top: e.pageY+2 - gridOffset.top 322 | }); 323 | }); 324 | 325 | //Close the tooltip 326 | $grid.on('mouseleave.tooltip', function(){ 327 | $tooltip.removeClass('open'); 328 | }); 329 | 330 | //Open the form when a cell is clicked, or submit the form if there are no extra fields 331 | $grid.on('click.form', '.fs-cell', function(e){ 332 | 333 | var $this = $(this), 334 | gridOffset = $grid.offset(), 335 | gridWidth = $grid.outerWidth(), 336 | $formDiv = $('div.fs-form'), 337 | formWidth = $formDiv.outerWidth(), 338 | formLeft = e.pageX + 2, 339 | submission_values = JSON.parse($this.attr('data-submission-value')); 340 | 341 | $('input.x').val(submission_values[0]); 342 | $('input.y').val(submission_values[1]); 343 | 344 | $('.fs-selected').removeClass('fs-selected'); 345 | $this.addClass('fs-selected'); 346 | 347 | if (!formExists) { 348 | $formDiv.find("form").submit(); 349 | return true; 350 | } 351 | 352 | //Math for where to position the form 353 | if (e.pageX + 2 + formWidth > gridOffset.left + gridWidth) formLeft -= 4 + formWidth; 354 | 355 | $formDiv 356 | .css({ 357 | top: e.pageY + 2 - gridOffset.top, 358 | left: formLeft - gridOffset.left 359 | }); 360 | 361 | $grid.addClass('open'); 362 | 363 | }); 364 | 365 | } 366 | 367 | //Take off all the grid listeners 368 | function unbindHandlers() { 369 | $tooltip.remove(); 370 | $grid.removeClass('submittable').off('mouseover.tooltip mousemove.tooltip mouseleave.tooltip click.form'); 371 | 372 | } 373 | 374 | function updateGrid(new_data,config){ 375 | submissionsToGridMarkup(new_data, config); 376 | } 377 | 378 | function whichQuadrant(x, y) { 379 | x = +x; 380 | y = +y; 381 | if (x < 0 && y < 0) { 382 | return 'topleft' 383 | } else if (x > 0 && y < 0) { 384 | return 'topright' 385 | } else if (x > 0 && y > 0) { 386 | return 'bottomright' 387 | } else if (x < 0 && y > 0) { 388 | return 'bottomleft' 389 | } 390 | } 391 | 392 | function submissionsToCommentsMarkup(data, config){ 393 | var submissions = data.submissions, 394 | extent = data.inputExtents[1], // Find the range to later calc the percentage of this comment 395 | $comments_container = convertNameToSelector(config.options.commentsTarget); 396 | 397 | if ($comments_container.length){ 398 | var $template = $('#fs-comment-template'), 399 | commentTemplateFactory, 400 | comment_markup; 401 | 402 | switch($template.data("template-type").toLowerCase()) { 403 | case "underscore": 404 | commentTemplateFactory = _.template($template.html()); 405 | break; 406 | case "handlebars": 407 | commentTemplateFactory = Handlebars.compile($template.html()); 408 | break; 409 | } 410 | 411 | // Hide it so that it renders faster 412 | $comments_container.hide(); 413 | // Add comments 414 | for (var i = 0; i < submissions.length; i++) { 415 | comment_markup = commentTemplateFactory(submissions[i]); 416 | $comments_container.append(comment_markup); 417 | } 418 | 419 | // Mini-map stuff 420 | // This scale would normally work using the range being 0 to 100 421 | // But you have to take into account the width of the circle 422 | // So subtract the dimensions of the circle (as a percentage of the total mini-map dimentions) 423 | var map_width = $('.fs-mini-map').width(); 424 | var map_height = $('.fs-mini-map').height(); 425 | 426 | // Make a dummy circle first so we can measure its dimensions 427 | $('body').append('
'); 428 | var dot_width_perc = $('.fs-mm-dot').width() / map_width * 100; 429 | var dot_height_perc = $('.fs-mm-dot').height() / map_height * 100; 430 | 431 | var userValueToCssPercentageLeft = new Scale(-1, 1, 0, (100 - dot_width_perc)); 432 | var userValueToCssPercentageTop = new Scale(-1, 1, 0, (100 - dot_height_perc)); 433 | 434 | // Remove the dummy circle 435 | $('.fs-mm-dot').remove(); 436 | 437 | // Make the map 438 | $('.fs-mini-map').each(function(idx, el){ 439 | 440 | var i = submissions.length - 1 - idx; 441 | 442 | var $el = $(el), 443 | x_val = submissions[i].x, 444 | y_val = submissions[i].y, 445 | x_pos = x_val / extent, 446 | y_pos = y_val / extent; 447 | 448 | $el.append('
') 449 | .append('
') 450 | .append('
') 451 | .append('
'); 452 | 453 | $('
') 454 | .css('left', userValueToCssPercentageLeft(x_pos) + '%') 455 | .css('top', userValueToCssPercentageTop(y_pos) + '%').appendTo($el); 456 | 457 | // Say what quadrant you're in 458 | var quadrant = whichQuadrant(x_val, y_val); 459 | $el.parents('.fs-comment-container').attr('data-quadrant', quadrant) 460 | 461 | }); 462 | // Once the appends are done, show it 463 | $comments_container.show(); 464 | 465 | // Show the coment filters if they've put this as the name 466 | if ($('#fs-comment-filters').length) $('#fs-comment-filters').show(); 467 | } 468 | 469 | } 470 | 471 | function createViz(data, config){ 472 | // Create the Grid Viz! 473 | submissionsToGridMarkup(data, config); 474 | 475 | // Create the comments section 476 | submissionsToCommentsMarkup(data, config); 477 | 478 | bindHandlers($.grep(config.fields || [],function(d){ 479 | return d.name.toLowerCase() != "x" && d.name.toLowerCase() != "y"; 480 | }).length); 481 | 482 | } 483 | 484 | /* 485 | 486 | Generate a form element based on a form item in the config options. 487 | 488 | Returns a
with a label and whatever the element is. 489 | 490 | The element is one of: 491 | 492 | a ') 520 | .attr('name',item.field); 521 | 522 | } else if (item.type == 'select') { 523 | $el = $('') 524 | .attr('name',item.field); 525 | 526 | $.each(item.choices,function(i,c){ 527 | 528 | $el.append( 529 | 530 | $('').text(c) 531 | 532 | ); 533 | 534 | }); 535 | 536 | } else if (item.type == 'radio' || item.type == 'checkbox') { 537 | 538 | $el = $('
'); 539 | 540 | $.each(item.choices,function(i,c){ 541 | 542 | var $i = $('').attr({ 543 | name: item.field, 544 | type: item.type, 545 | value: c 546 | }), 547 | $s = $('').text(c); 548 | 549 | $el.append($i); 550 | $el.append($s); 551 | 552 | }); 553 | } else { 554 | 555 | $el = $('').attr({ 556 | name: item.field, 557 | type: item.type, 558 | value: '' 559 | }); 560 | 561 | } 562 | 563 | $outer.append($el); 564 | 565 | return $outer; 566 | 567 | } 568 | 569 | /* 570 | Called upon successful form submission 571 | */ 572 | function submitted(x,y,config) { 573 | 574 | try { 575 | //Big random number into localStorage to mark that they submitted it 576 | localStorage.setItem('fs-cell',JSON.stringify([x,y])); 577 | } catch(e) {} 578 | 579 | //Take hover/click listeners off the grid 580 | existing_data.submissions.push({ 581 | x: x, 582 | y: y 583 | }); 584 | 585 | updateGrid(existing_data,config); 586 | 587 | $('div[data-cell-id="' + x + '-' + y + '"]').addClass('saved'); 588 | $grid.removeClass('open'); 589 | $('div.fs-form').remove(); 590 | } 591 | 592 | function stageData(grid_data,config) { 593 | grid_data = $.map(grid_data,function(d){ 594 | d.x = ('x' in d) ? +d.x : +d.X; 595 | d.y = ('y' in d) ? +d.y : +d.Y; 596 | return d; 597 | }); 598 | 599 | existing_data = {submissions: grid_data}; 600 | if (config.inputExtents) existing_data.inputExtents = config.inputExtents 601 | 602 | // Create the Grid 603 | createViz(existing_data,config); 604 | 605 | } 606 | 607 | /* 608 | Initialize everything from a set of config options 609 | */ 610 | function initFromConfig(config) { 611 | 612 | var $form = $('
').attr('target','fs-iframe'), 613 | $form_outer = $('
').addClass('fs-form'), 614 | $iframe = $('