├── .gitignore ├── index.js ├── images ├── bar.png ├── dz.jpg ├── beer.png ├── button.jpg ├── line.png ├── line2.png ├── ydi1.png ├── ydi2.png ├── ydi3.png ├── ydi4.png ├── ydi5.png ├── lederhosen.jpg ├── lederhosen2.jpg ├── questionmark.png ├── questionmark2.png ├── multipleChoice.png └── visualReference_small.jpg ├── src ├── helpers │ ├── clamp.js │ ├── getRandom.js │ ├── constants.js │ ├── debounce.js │ ├── function.js │ ├── formatValue.js │ └── referenceValues.js ├── state.js ├── youdrawit.js ├── ydCheckbox.js ├── results │ └── score.js ├── interface.js ├── ydBar.js └── ydLine.js ├── .babelrc ├── rollup.config.js ├── .eslintrc.js ├── package.json ├── LICENSE-original-wdr-version ├── test ├── 3_templateEnglish_bare.html ├── 10_bavarian_quiz.html ├── 5_templateGerman.html ├── 7_funfacts_bare_french.html ├── 1_funfacts_bare.html ├── 2_minecraft_bare.html ├── checkbox_test.html ├── 4_templateEnglish.html ├── 9_funfacts_multipleChoice.html ├── 8_funfacts_referenceValues.html ├── 1_funfacts.html └── 6_datasketches.html ├── LICENSE ├── dist ├── js │ └── d3-license.txt ├── templateFrench.html ├── templateEnglish.html ├── templateGerman.html └── css │ └── style.css ├── info.txt ├── visualReference └── README.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export {default as chart} from "./src/interface.js"; -------------------------------------------------------------------------------- /images/bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/bar.png -------------------------------------------------------------------------------- /images/dz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/dz.jpg -------------------------------------------------------------------------------- /images/beer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/beer.png -------------------------------------------------------------------------------- /images/button.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/button.jpg -------------------------------------------------------------------------------- /images/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/line.png -------------------------------------------------------------------------------- /images/line2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/line2.png -------------------------------------------------------------------------------- /images/ydi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/ydi1.png -------------------------------------------------------------------------------- /images/ydi2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/ydi2.png -------------------------------------------------------------------------------- /images/ydi3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/ydi3.png -------------------------------------------------------------------------------- /images/ydi4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/ydi4.png -------------------------------------------------------------------------------- /images/ydi5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/ydi5.png -------------------------------------------------------------------------------- /images/lederhosen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/lederhosen.jpg -------------------------------------------------------------------------------- /images/lederhosen2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/lederhosen2.jpg -------------------------------------------------------------------------------- /images/questionmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/questionmark.png -------------------------------------------------------------------------------- /images/questionmark2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/questionmark2.png -------------------------------------------------------------------------------- /images/multipleChoice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/multipleChoice.png -------------------------------------------------------------------------------- /src/helpers/clamp.js: -------------------------------------------------------------------------------- 1 | export const clamp = function (a, b, c) { 2 | return Math.max(a, Math.min(b, c)); 3 | }; -------------------------------------------------------------------------------- /images/visualReference_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EE2dev/you-draw-it/HEAD/images/visualReference_small.jpg -------------------------------------------------------------------------------- /src/helpers/getRandom.js: -------------------------------------------------------------------------------- 1 | export function getRandom(min, max) { 2 | return Math.random() * (max - min) + min; 3 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "external-helpers" 12 | ] 13 | } -------------------------------------------------------------------------------- /src/helpers/constants.js: -------------------------------------------------------------------------------- 1 | export const yourData = "yourData"; 2 | export const resultShown = "resultShown"; 3 | export const completed = "completed"; 4 | export const score = "score"; 5 | export const predictionDiff = "predictionDiff"; 6 | export const prediction = "prediction"; 7 | export const truth = "truth"; -------------------------------------------------------------------------------- /src/helpers/debounce.js: -------------------------------------------------------------------------------- 1 | 2 | export const debounce = function (func, wait, immediate) { 3 | let timeout; 4 | return function () { 5 | const context = this, args = arguments; 6 | const later = function () { 7 | timeout = null; 8 | if (!immediate) func.apply(context, args); 9 | }; 10 | const callNow = immediate && !timeout; 11 | clearTimeout(timeout); 12 | timeout = setTimeout(later, wait); 13 | if (callNow) func.apply(context, args); 14 | }; 15 | }; -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | // import nodeResolve from 'rollup-plugin-node-resolve'; 3 | import babel from "rollup-plugin-babel"; 4 | 5 | export default { 6 | entry: "index.js", 7 | dest: "dist/js/youdrawit.js", 8 | format: "umd", 9 | moduleName: "youdrawit", 10 | globals: { 11 | "d3": "d3", 12 | }, 13 | plugins: [ 14 | /* 15 | nodeResolve({ 16 | jsnext: true, 17 | main: true}), 18 | */ 19 | 20 | babel({ 21 | exclude: "node_modules/**"}) 22 | ] 23 | }; -------------------------------------------------------------------------------- /src/helpers/function.js: -------------------------------------------------------------------------------- 1 | export const ƒ = function () { 2 | const functions = arguments; 3 | 4 | //convert all string arguments into field accessors 5 | for (let i = 0; i < functions.length; i++) { 6 | if (typeof(functions[i]) === "string" || typeof(functions[i]) === "number") { 7 | functions[i] = (str => function (d) { 8 | return d[str]; 9 | })(functions[i]); 10 | } 11 | } 12 | 13 | //return composition of functions 14 | return function (d) { 15 | let i = 0, l = functions.length; 16 | while (i++ < l) d = functions[i - 1].call(this, d); 17 | return d; 18 | }; 19 | }; -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | "env": { 4 | "browser": true, 5 | "commonjs": true, 6 | "es6": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "indent": [ 14 | "warn", 15 | 2 16 | ], 17 | "linebreak-style": [ 18 | "warn", 19 | "windows" 20 | ], 21 | "quotes": [ 22 | "warn", 23 | "double" 24 | ], 25 | "no-console": 0, 26 | "semi": [ 27 | "error", 28 | "always" 29 | ] 30 | } 31 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "you-draw-it", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "b-clean-dist": "rm -rf dist && mkdir dist", 11 | "b-rollup": "rollup -c", 12 | "b-uglify": "uglifyjs dist/js/youdrawit.js -c -m -o dist/js/youdrawit.min.js", 13 | "b-eslint": "eslint index.js src", 14 | "b-tape": "node test/youdrawit-test.js | tap-spec", 15 | "build": "npm run -s b-rollup & npm run -s b-eslint & npm run -s b-uglify" 16 | }, 17 | "author": "Mihael Ankerst", 18 | "license": "BSD-3-Clause", 19 | "devDependencies": { 20 | "babel-core": "^6.26.3", 21 | "babel-plugin-external-helpers": "^6.22.0", 22 | "babel-preset-env": "^1.7.0", 23 | "eslint": "^5.2.0", 24 | "rollup": "^0.63.4", 25 | "rollup-plugin-babel": "^3.0.7", 26 | "rollup-plugin-node-resolve": "^3.3.0", 27 | "tap-spec": "^5.0.0", 28 | "tape": "^4.9.1", 29 | "uglify-js": "^3.4.2" 30 | }, 31 | "dependencies": { 32 | "d3": "^5.5.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/helpers/formatValue.js: -------------------------------------------------------------------------------- 1 | import { getLanguage } from "../youdrawit.js"; 2 | 3 | export const formatValue = function(val, unit, precision, defaultPrecision) { 4 | const data = precision ? 5 | Number(val).toFixed(precision) : 6 | (defaultPrecision !== 0) ? 7 | Number(val).toFixed(defaultPrecision) : 8 | (defaultPrecision === 0) ? 9 | Number(val).toFixed() : 10 | val; 11 | // revert decimal and thousands separator based on country 12 | let dataDelimited = numberWithCommas(data); 13 | if (getLanguage() === "de") { 14 | const temp1 = dataDelimited.replace(/\./g, "whatever"); 15 | const temp2 = temp1.replace(/,/g, "."); 16 | dataDelimited = temp2.replace(/whatever/g, ","); 17 | } else if (getLanguage() === "fr") { 18 | const temp1 = dataDelimited.replace(/\./g, "whatever"); 19 | const temp2 = temp1.replace(/,/g, " "); 20 | dataDelimited = temp2.replace(/whatever/g , ","); 21 | } 22 | return dataDelimited + (unit ? " " + unit : ""); 23 | }; 24 | 25 | const numberWithCommas = (x) => { 26 | var parts = x.toString().split("."); 27 | parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); 28 | return parts.join("."); 29 | }; -------------------------------------------------------------------------------- /src/state.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | let state = {}; 3 | stateAPI(); 4 | 5 | function stateAPI(){ state = {};} 6 | 7 | stateAPI.setQuestion = function(question){ 8 | if (!state[question]) { 9 | state[question] = {}; 10 | } 11 | }; 12 | 13 | stateAPI.getQuestion = function(question){ 14 | return state[question]; 15 | }; 16 | 17 | stateAPI.getAllQuestions = function(){ 18 | return Object.keys(state); 19 | }; 20 | 21 | stateAPI.getState = function(){ 22 | return state; 23 | }; 24 | 25 | stateAPI.set = function(question, key, value){ 26 | if (!state[question][key]) { 27 | state[question][key] = {}; 28 | } 29 | state[question][key] = value; 30 | }; 31 | 32 | stateAPI.get = function(question, key){ 33 | return state[question][key]; 34 | }; 35 | 36 | // for calculating the score 37 | stateAPI.getResult = function(question, key){ 38 | const oldArray = state[question][key]; 39 | // remove first element for line charts, which was not a prediction but the starting point for the line 40 | let newArray = oldArray.length > 1 ? oldArray.slice(1) : oldArray; 41 | return newArray; 42 | }; 43 | 44 | return stateAPI; 45 | } -------------------------------------------------------------------------------- /LICENSE-original-wdr-version: -------------------------------------------------------------------------------- 1 | Warning: This license does not apply to the graphics files of this project! 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright © 2017 Westdeutscher Rundfunk, http://wdr.de 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /test/3_templateEnglish_bare.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Mihael Ankerst 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the author nor the names of contributors may be used to 15 | endorse or promote products derived from this software without specific prior 16 | written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /dist/js/d3-license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010-2017 Mike Bostock 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the author nor the names of contributors may be used to 15 | endorse or promote products derived from this software without specific prior 16 | written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/youdrawit.js: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3"; 2 | import { debounce } from "./helpers/debounce"; 3 | import { ydLine } from "./ydLine"; 4 | import { ydBar } from "./ydBar"; 5 | import { ydCheckbox } from "./ydCheckbox"; 6 | import { default as myState } from "./state"; 7 | 8 | let globals = {}; 9 | 10 | export function youdrawit(_globals, questions) { 11 | const isMobile = window.innerWidth < 760; 12 | globals = _globals; 13 | let state = myState(); 14 | 15 | const drawGraphs = function () { 16 | d3.selectAll(".you-draw-it").each(function (d, i) { 17 | const sel = d3.select(this); 18 | const question = questions[i]; 19 | const key = question.key; 20 | const originalData = question.data; 21 | 22 | const data = originalData.map((ele, index) => { 23 | return { 24 | year: index, 25 | timePointIndex: index, 26 | timePoint: Object.keys(ele)[0], 27 | value: ele[Object.keys(ele)[0]] 28 | }; 29 | }); 30 | 31 | const indexedTimepoint = data.map((ele) => ele.timePoint); 32 | const indexedData = data.map((ele) => ele.value); 33 | 34 | state.setQuestion(key); 35 | 36 | if (data.length < 1) { 37 | console.log("No data available for:", key); 38 | return; 39 | } 40 | 41 | if (question.chartType === "barChart") { 42 | ydBar(isMobile, state, sel, key, question, globals, data, indexedTimepoint, indexedData); 43 | } else if (question.chartType === "timeSeries") { 44 | ydLine(isMobile, state, sel, key, question, globals, data, indexedTimepoint, indexedData); 45 | } else if (question.chartType === "multipleChoice") { 46 | ydCheckbox(isMobile, state, sel, key, question, globals, data); 47 | } 48 | }); 49 | }; 50 | 51 | document.addEventListener("DOMContentLoaded", drawGraphs); 52 | 53 | window.addEventListener("resize", debounce(() => { 54 | drawGraphs(); 55 | }, 500)); 56 | } 57 | 58 | export function getLanguage() { 59 | return globals.default; 60 | } -------------------------------------------------------------------------------- /test/10_bavarian_quiz.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 60 | 61 | -------------------------------------------------------------------------------- /test/5_templateGerman.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 78 | 79 | -------------------------------------------------------------------------------- /test/7_funfacts_bare_french.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 91 | 92 | -------------------------------------------------------------------------------- /info.txt: -------------------------------------------------------------------------------- 1 | .your-line 2 | .your-result (label als span) 3 | 4 | Done: 5 | ----- 6 | + refactored into helpers 7 | + change state to own function 8 | + specify data for bar as one value 9 | + renamed options properties 10 | + . instead to , for english 11 | + dynamischer Aufbau der charts über javascript 12 | + compile (babel + minify) 13 | + Start mit einem Punkt 14 | + Start ohne Punkte 15 | + optionale Festlegung der y min und max werte (yAxisMin) 16 | + Graph linie statiger / runder 17 | + Marker schoener 18 | + quiz mit einem Wert: Balken hoch runter 19 | + change graphMaxY based on maxValue + random number 20 | + blue line also curveMonotone 21 | + ES6 modules 22 | + unit not showing 23 | + change conditions interface 24 | + rect.draggable for bars: just shows mouse as pointer when on top on bar (not below) 25 | + display "your guess" title for bars 26 | + transition drawAreaTitle upon dragging overlap (mouse y coord + labelY) 27 | + implement global options: minY, maxY 28 | + increase svg.margin top to show label if top value is selected 29 | 30 | To Do 31 | ----- 32 | 33 | - Umstellen von Y Achse Number auf String (für Mon, tage,..) -> y achsel number lassen nur anzeige : string 34 | - data Umstellung von Object auf Array! number und strings werden in reihenfolge vertauscht!! 35 | 36 | - Text /titel / als Variable) 37 | - Hinweis zu zeichnen -> Text als Variable 38 | - Hinweis zu zeichnen -> bessere Position 39 | - Hinweis zu zeichnen -> schöneres Design (orange, ellipse?) 40 | 41 | - Button (näher, paperCSS) 42 | - Schrift 43 | 44 | - refactoring: 45 | - delete year -> timePoint 46 | 47 | - thousandsseparator + decimal separators bas on locale (-> so) 48 | 49 | - your result (after hr and button) 50 | - implement global options: scoreButtonText, scoreButtonTooltip 51 | - implement global options: scoreHtml() or ( html, start, end) 52 | 53 | - dynamic you-draw-it website like startext 54 | - remove error messages 55 | - clean up css 56 | - refactor score split showScore() 57 | - resultButton höher 58 | - yDLine Punkte bei jeden Wert hinzufügen 59 | - alle ticks bei Line? 60 | - allle html Dateien umstellen 61 | - background 62 | - tooltip bei result 63 | - templateGermanBare starts to show result immediately (no button) 64 | 65 | new: 28.9.2018 66 | - referenceValues 67 | + bar with hidden span first to calc height 68 | + allow for stlying of line (.question-referenceValue) 69 | + add update-font class 70 | + document in readme 71 | + add belgium newspaper to readme 72 | + lines 73 | 74 | - !referenceValuesLine in referenceValues uses an css id for textpath which is not unique (for more graphs) 75 | - !style referenceValues for line 76 | - picture reference in readme 77 | - document multiple choice and add picture 78 | - add instruction: add one or multiple choices 79 | - add waves to multiple choice 80 | - document how to omit evaluation + picture 81 | + correct update after resize (show user choices) 82 | - referenceValues add dotted line for bar charts as default 83 | - select categories 84 | - store state for resize 85 | 86 | 87 | -------------------------------------------------------------------------------- /test/1_funfacts_bare.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 92 | 93 | -------------------------------------------------------------------------------- /dist/templateFrench.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 96 | 97 | -------------------------------------------------------------------------------- /test/2_minecraft_bare.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 121 | 122 | -------------------------------------------------------------------------------- /dist/templateEnglish.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 99 | 100 | -------------------------------------------------------------------------------- /dist/templateGerman.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 99 | 100 | -------------------------------------------------------------------------------- /test/checkbox_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 128 | 129 | 130 |

Custom Checkboxes

131 | 137 | 143 | 149 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /test/4_templateEnglish.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 100 | 101 | -------------------------------------------------------------------------------- /src/ydCheckbox.js: -------------------------------------------------------------------------------- 1 | 2 | import * as d3 from "d3"; 3 | import { yourData, resultShown, completed, score } from "./helpers/constants"; 4 | import { getFinalScore } from "./results/score"; 5 | /* 6 | import { ƒ } from "./helpers/function"; 7 | import { formatValue } from "./helpers/formatValue"; 8 | import { clamp } from "./helpers/clamp"; 9 | import { getRandom } from "./helpers/getRandom"; 10 | import { yourData, resultShown, completed, score, prediction, truth } from "./helpers/constants"; 11 | import { getScore } from "./results/score"; 12 | import { addReferenceValues } from "./helpers/referenceValues"; 13 | */ 14 | 15 | export function ydCheckbox(isMobile, state, sel, key, question, globals, data) { 16 | 17 | sel.html(""); 18 | let selDiv = sel.append("div"); 19 | let selLabel; 20 | let prediction = []; 21 | let cb; 22 | 23 | data.forEach(function(ele, i) { 24 | selLabel = selDiv 25 | .append("label") 26 | .attr("class", "question-multipleChoice update-font answer-container l-" + i) 27 | .html(ele.timePoint); 28 | 29 | // checkbox for answers 30 | selLabel 31 | .append("span") 32 | .attr("class", "answer-checkmark-truth t-" + i) 33 | .append("div") 34 | .attr("class", "input"); 35 | 36 | // checkbox for guesses 37 | cb = selLabel.append("input") 38 | .attr("type", "checkbox") 39 | .attr("name", "cb") 40 | .attr("value", "v" + i) 41 | .on("click", handleClick); 42 | 43 | selLabel.append("span") 44 | .attr("class", "answer-checkmark"); 45 | 46 | // preset the checkboxes with the guesses already made for resize event 47 | prediction[i] = state.get(key, yourData) ? state.get(key, yourData)[i] : false; 48 | cb.node().checked = prediction[i]; 49 | }); 50 | 51 | const resultSection = d3.select(".result." + key); 52 | resultSection.select("button").on("click", showResultChart); 53 | 54 | 55 | if (state.get(key, resultShown)) { 56 | showResultChart(); 57 | } 58 | 59 | function handleClick() { 60 | if (state.get(key, resultShown)) { 61 | return; 62 | } 63 | const index = d3.select(this).attr("value").substring(1); 64 | console.log("Clicked, new value [" + index + "] = " + d3.select(this).node().checked); 65 | prediction[index] = d3.select(this).node().checked; 66 | state.set(key, yourData, prediction); 67 | resultSection.node().classList.add("finished"); 68 | resultSection.select("button").node().removeAttribute("disabled"); 69 | } 70 | 71 | function showResultChart() { 72 | state.set(key, completed, true); 73 | // disable hovers 74 | var css = ".answer-container:hover input ~ .answer-checkmark { background-color: #eee;}"; 75 | css = css + " .answer-container input:checked ~ .answer-checkmark { background-color: orange;}"; 76 | var style = document.createElement("style"); 77 | 78 | if (style.styleSheet) { 79 | style.styleSheet.cssText = css; 80 | } else { 81 | style.appendChild(document.createTextNode(css)); 82 | } 83 | document.getElementsByTagName("head")[0].appendChild(style); 84 | 85 | // disable checkboxes 86 | sel.selectAll("div input").each(function() { 87 | d3.select(this).node().disabled=true; 88 | }); 89 | // display result (with transition) 90 | let correctAnswers = 0; 91 | data.forEach((ele,i) => { 92 | if (ele.value === prediction[i]) { correctAnswers = correctAnswers + 1;} 93 | sel.select("div span.answer-checkmark-truth.t-" + i) 94 | .classed("checked", ele.value) 95 | .style("background-color", "#fff") 96 | .transition() 97 | .ease(ele.value ? d3.easeExpIn : d3.easeLinear) 98 | .duration(100) 99 | .delay(i * 100) 100 | .style("background-color", (ele.value) ? "#00345e" : "#eee"); 101 | 102 | sel.select("div span.answer-checkmark-truth.t-" + i +" div.input") 103 | .classed("checked", ele.value); 104 | 105 | sel.select("div .answer-container.l-" + i) 106 | .transition() 107 | .duration(100) 108 | .delay(i * 100) 109 | .style("color", (ele.value) ? "#00345e" : "#eee"); 110 | }); 111 | 112 | // call getScore 113 | const durationTrans = 100 * (data.length + 1); 114 | setTimeout(() => { 115 | resultSection.node().classList.add("shown"); 116 | if (!state.get(key, score) && globals.showScore) { 117 | const myScore = Math.round(correctAnswers / prediction.length * 100); 118 | console.log("score: " + myScore); 119 | state.set(key, score, myScore); 120 | getFinalScore(key, state, resultSection, globals.scoreTitle, 121 | globals.scoreButtonText, globals.scoreButtonTooltip, globals.scoreHtml); 122 | } 123 | state.set(key, resultShown, true); 124 | }, durationTrans); 125 | } 126 | } -------------------------------------------------------------------------------- /test/9_funfacts_multipleChoice.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 142 | 143 | -------------------------------------------------------------------------------- /test/8_funfacts_referenceValues.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 141 | 142 | -------------------------------------------------------------------------------- /test/1_funfacts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 143 | 144 | -------------------------------------------------------------------------------- /test/6_datasketches.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My quiz 6 | 7 | 8 | 9 | 19 | 20 | 21 | 22 | 23 | 24 | 119 | 120 | -------------------------------------------------------------------------------- /src/helpers/referenceValues.js: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3"; 2 | import { ƒ } from "./function"; 3 | 4 | /* 5 | * params: 6 | * sel: DOM selection for the text label of the reference value. A is added with the text 7 | * svg: SVG for the lines connecting the graph with the label 8 | * referenceValues: question.referenceValues 9 | * c: object constant with graphical DOM selections as properties 10 | */ 11 | export function addReferenceLines (sel, svg, referenceValues, c){ 12 | let gRef; 13 | let data; 14 | const referenceLine = d3.line().x(ƒ("year", c.x)).y(ƒ("value", c.y)).curve(d3.curveMonotoneX); 15 | 16 | referenceValues.forEach(function (ref, i){ 17 | data = ref.value.map((ele, index) => { 18 | return { 19 | year: index, 20 | value: ele[Object.keys(ele)[0]] 21 | }; 22 | }); 23 | data.text = ref.text; 24 | data.anchor = parsePosition(ref.textPosition); 25 | data.offset = getOffset(data.anchor); 26 | 27 | gRef = svg.append("g") 28 | .attr("class", "reference question-referenceValues referenceLine controls line-" + data.text.trim()); 29 | 30 | gRef.append("path").attr("d", referenceLine(data)).attr("class", "line referencePath").attr("id", "curvedPath-" + i); 31 | 32 | gRef.append("text") 33 | .attr("class", "question-referenceText update-font") 34 | .attr("dy", "-5") 35 | .append("textPath") 36 | .attr("class", "referenceTextPath") 37 | .attr("text-anchor",data.anchor) 38 | .attr("startOffset",data.offset) 39 | .attr("xlink:href", "#curvedPath-" + i) 40 | .text(data.text); 41 | }); 42 | } 43 | 44 | function parsePosition(pos) { 45 | if (pos !== "start" && pos !== "end") { 46 | pos = "middle"; 47 | } 48 | return pos; 49 | } 50 | 51 | function getOffset(pos) { 52 | let offset; 53 | if (pos === "start") { 54 | offset = "2%"; 55 | } else if (pos === "end") { 56 | offset = "98%"; 57 | } else { 58 | offset = "50%"; 59 | } 60 | return offset; 61 | } 62 | 63 | function addReferenceValuesDefault (sel, svg, referenceValues, c){ 64 | let gRef; 65 | let len; 66 | 67 | len = svg.select("g.grid").node().getBBox().width / 2; 68 | 69 | referenceValues.forEach(function (ref){ 70 | gRef = svg.append("g") 71 | .attr("class", "reference question-referenceValues referenceLine controls"); 72 | 73 | gRef.append("line") 74 | .attr("x1", 0) 75 | .attr("y1", c.y(ref.value)) 76 | .attr("x2", len) 77 | .attr("y2", c.y(ref.value)) 78 | .attr("class", "line referencePath"); 79 | 80 | sel.append("span") 81 | .style("left", "10px") 82 | .style("right", (len - 10) + "px") 83 | .style("top", (c.y(ref.value) - 18) + "px") 84 | .append("div") 85 | .attr("class", "question-referenceValues update-font") 86 | .style("text-align", "center") 87 | .text(ref.text); 88 | }); 89 | } 90 | 91 | /* 92 | * params: 93 | * sel: DOM selection for the text label of the reference value. A is added with the text 94 | * svg: SVG for the lines connecting the graph with the label 95 | * referenceValues: question.referenceValues 96 | * c: object constant with graphical DOM selections as properties 97 | * line: true or false (= ticks) 98 | */ 99 | export function addReferenceValues (sel, svg, referenceValues, c, line){ 100 | 101 | if (line) { 102 | return addReferenceValuesDefault (sel, svg, referenceValues, c); 103 | } 104 | const len = 10; 105 | const shiftSpan = 8; 106 | let rectHeight = 30; 107 | let data = referenceValues.map(d => c.y(d.value) - shiftSpan); 108 | const positions = getPositions(data, rectHeight, c.height); 109 | let gRef; 110 | 111 | referenceValues.forEach(function (ref, i){ 112 | gRef = svg.append("g") 113 | .attr("class", "reference question-referenceValues controls"); 114 | 115 | gRef.append("line") 116 | .attr("x1", 0) 117 | .attr("y1", c.y(ref.value)) 118 | .attr("x2", len / 2) 119 | .attr("y2", c.y(ref.value)); 120 | 121 | gRef.append("line") 122 | .attr("x1", len / 2) 123 | .attr("y1", c.y(ref.value)) 124 | .attr("x2", len) 125 | .attr("y2", positions[i] + shiftSpan); 126 | 127 | sel.append("span") 128 | .style("left", (len + 3) + "px") 129 | .style("top", positions[i] + "px") 130 | .append("div") 131 | .attr("class", "question-referenceValues update-font") 132 | .text(ref.text); 133 | }); 134 | } 135 | 136 | function getPositions(data, rectHeight) { 137 | let newPositions; 138 | var dataObject = createObject(data, rectHeight); 139 | dataObject = adjustBottoms(dataObject); 140 | newPositions = trimObject(dataObject); 141 | // drawRectangles(g, data2, "after"); 142 | 143 | if (newPositions[newPositions.length-1] < 0) { 144 | dataObject = adjustTops(dataObject); 145 | newPositions = trimObject(dataObject); 146 | // drawRectangles(g, data3, "final"); 147 | } 148 | return newPositions; 149 | } 150 | 151 | function createObject(data, rectHeight, height) { 152 | // setup data structure with rectangles from bottom to the top 153 | var dataObject = []; 154 | var obj = {top: height, bottom: height + rectHeight}; // add dummy rect for lower bound 155 | 156 | dataObject.push(obj); 157 | data.forEach(function(d){ 158 | obj = {top: d, bottom: d + rectHeight}; 159 | dataObject.push(obj); 160 | }); 161 | obj = {top: 0 - rectHeight, bottom: 0}; // add dummy rect for upper bound 162 | dataObject.push(obj); 163 | 164 | return dataObject; 165 | } 166 | 167 | function trimObject(dataObject) { // convert back to original array of values, also remove dummies 168 | var data3 = []; 169 | dataObject.forEach(function(d,i){ 170 | if (!(i === 0 || i === dataObject.length-1)) { 171 | data3.push(d.top); 172 | } 173 | }); 174 | return data3; 175 | } 176 | 177 | function adjustBottoms(dataObject){ 178 | dataObject.forEach(function(d,i){ 179 | if (!(i === 0 || i === dataObject.length-1)) { 180 | var diff = dataObject[i-1].top - d.bottom; 181 | if (diff < 0) { // move rect up 182 | d.top += diff; 183 | d.bottom += diff; 184 | } 185 | } 186 | }); 187 | return dataObject; 188 | } 189 | 190 | function adjustTops(dataObject){ 191 | for (var i = dataObject.length; i-- > 0; ){ 192 | if (!(i === 0 || i === dataObject.length-1)) { 193 | var diff = dataObject[i+1].bottom - dataObject[i].top; 194 | if (diff > 0) { // move rect down 195 | dataObject[i].top += diff; 196 | dataObject[i].bottom += diff; 197 | } 198 | } 199 | } 200 | return dataObject; 201 | } -------------------------------------------------------------------------------- /src/results/score.js: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3"; 2 | import { yourData, score, predictionDiff } from "../helpers/constants"; 3 | 4 | function compareGuess(truth, guess, graphMaxY, graphMinY) { 5 | let maxDiff = 0; 6 | let predDiff = 0; 7 | truth.forEach(function(ele, i) { 8 | maxDiff += Math.max(graphMaxY - ele.value, ele.value - graphMinY); 9 | predDiff += Math.abs(ele.value - guess[i].value); 10 | }); 11 | return {maxDiff: maxDiff, predDiff: predDiff}; 12 | } 13 | 14 | export function getScore(key, truth, state, graphMaxY, graphMinY, resultSection, scoreTitle, scoreButtonText, scoreButtonTooltip, scoreHtml) { 15 | let myScore = 0; 16 | const guess = state.getResult(key, yourData); 17 | const r = compareGuess(truth, guess, graphMaxY, graphMinY); 18 | const maxDiff = r.maxDiff; 19 | const predDiff = r.predDiff; 20 | const scoreFunction = d3.scaleLinear().domain([0, maxDiff]).range([100,0]); 21 | 22 | myScore = scoreFunction(predDiff).toFixed(1); 23 | state.set(key, predictionDiff, predDiff); 24 | state.set(key, score, +myScore); 25 | 26 | getFinalScore(key, state, resultSection, scoreTitle, scoreButtonText, scoreButtonTooltip, scoreHtml); 27 | 28 | console.log(state.get(key, yourData)); 29 | console.log("The pred is: " + predDiff); 30 | console.log("The maxDiff is: " + maxDiff); 31 | console.log("The score is: " + myScore); 32 | console.log(state.getState()); 33 | } 34 | 35 | export function getFinalScore(key, state, resultSection, scoreTitle, scoreButtonText, scoreButtonTooltip, scoreHtml) { 36 | let completed = true; 37 | state.getAllQuestions().forEach((ele) => {completed = completed && (typeof state.get(ele, score) !== "undefined"); }); 38 | if (completed) { 39 | let scores = 0; 40 | state.getAllQuestions().forEach((ele) => {scores = scores + state.get(ele, score);}); 41 | const finalScoreFunction = d3.scaleLinear().domain([0, 100 * state.getAllQuestions().length]).range([0,100]); 42 | let finalScore = finalScoreFunction(scores).toFixed(); 43 | console.log("The final score is: " + finalScore); 44 | 45 | drawScore(+finalScore, resultSection, key, scoreTitle, scoreButtonText, scoreButtonTooltip, scoreHtml); 46 | } 47 | } 48 | 49 | function drawScore(finalScore, resultSection, key, scoreTitle, scoreButtonText, scoreButtonTooltip, scoreHtml) { 50 | // add final result button 51 | const ac = resultSection 52 | .append("div") 53 | .attr("class", "actionContainer finalScore"); 54 | const button = ac.append("button") 55 | .attr("class", "showAction"); 56 | button.append("div") 57 | .attr("class", "globals-scoreButtonText update-font") 58 | .text(scoreButtonText); 59 | 60 | const tt = ac.append("div") 61 | .attr("class", "tooltipcontainer") 62 | .append("span") 63 | .attr("class", "tooltiptext globals-scoreButtonTooltip update-font") 64 | //.attr("class", "tooltiptext") 65 | .text(scoreButtonTooltip); 66 | 67 | // add final result graph 68 | let fs = {}; 69 | fs.div = resultSection.select("div.text") 70 | .append("div") 71 | .attr("class", "finalScore text") 72 | .style("visibility", "hidden"); 73 | 74 | fs.div.append("div") 75 | //.attr("class", "before-finalScore globals-scoreTitle update-font") 76 | .attr("class", "before-finalScore") 77 | .append("strong") 78 | .append("div") 79 | .attr("class", "globals-scoreTitle update-font") 80 | .text(scoreTitle); 81 | 82 | const svgWidth = (window.innerWidth > 500) ? 500 : window.innerWidth - 10; 83 | fs.svg = fs.div.append("svg") 84 | .attr("width", svgWidth) 85 | .attr("height", 75); 86 | 87 | const ch = resultSection.select("div.text").append("div") 88 | .attr("class", "customHtml") 89 | .style("visibility", "hidden") 90 | .style("text-align", "center"); 91 | 92 | if (typeof scoreHtml !== "undefined") { 93 | const sHtml = scoreHtml.filter(d => d.lower <= finalScore && d.upper > finalScore); 94 | ch.selectAll("p") 95 | .data(sHtml) 96 | .enter() 97 | .append("p") 98 | .append("div") 99 | .attr("class", "globals-scoreHtml update-font") 100 | .html(d => d.html); 101 | } 102 | 103 | // adding some space at the bottom to reserved the final display space and 104 | // to have space below the botton (for the tooltip) 105 | // (30 = margin-top from fs.div) , 70 = margin-bottom from div.result.finished.shown) 106 | const h = fs.div.node().offsetHeight + ch.node().offsetHeight + 30 + 70 - ac.node().clientHeight; 107 | fs.div.style("display", "none").style("visibility", "visible"); // reset to avoid taking up space 108 | ch.style("display", "none").style("visibility", "visible"); 109 | 110 | const dummy = resultSection 111 | .append("div") 112 | .attr("class", "dummy") 113 | .style("height", h + "px"); 114 | 115 | button.on("click", function() { 116 | d3.select("div.actionContainer.finalScore").style("display", "none"); 117 | //d3.select(this).style("display", "none"); 118 | tt.style("display", "none"); 119 | dummy.remove(); 120 | showFinalScore(finalScore, resultSection, key, svgWidth); 121 | }); 122 | } 123 | 124 | function showFinalScore(finalScore, resultSection, key, svgWidth) { 125 | 126 | function showText() { 127 | d3.select(".result." + key).select("text.scoreText") 128 | .style("opacity", 1); 129 | resultSection.select("div.customHtml") 130 | .style("visibility", "visible"); 131 | } 132 | 133 | resultSection.select("div.finalScore.text") 134 | .style("display", "block"); 135 | resultSection.select("div.customHtml") 136 | .style("display", "block") 137 | .style("visibility", "hidden"); 138 | 139 | let fs = {}; 140 | 141 | fs.g = resultSection.select(".finalScore.text > svg") 142 | .append("g") 143 | .attr("transform", "translate(5, 10)"); 144 | 145 | // const xScale = d3.scaleLinear().domain([0, 100]).range([0, 400]); 146 | const xScale = d3.scaleLinear().domain([0, 100]).range([0, svgWidth - 80]); 147 | const xAxis = d3.axisBottom(xScale).ticks(4); 148 | fs.g.append("g") 149 | .attr("transform", "translate(0, 45)") 150 | .attr("class", "x axis") 151 | .call(xAxis); 152 | 153 | fs.rect = fs.g.append("rect") 154 | .attr("class", "final-score-result") 155 | .attr("x", 0) 156 | .attr("y", 0) 157 | .attr("height", 40) 158 | .attr("width", 0); 159 | 160 | fs.txt = fs.g.append("text") 161 | .attr("class", "scoreText globals-scoreText update-font") 162 | .attr("x", xScale(finalScore) + 5) 163 | .attr("dy", 27) 164 | .text("(" + finalScore + "/100)"); 165 | 166 | fs.rect.transition() 167 | .duration(3000) 168 | .attr("width", xScale(finalScore)) 169 | .on("end", showText); 170 | } 171 | -------------------------------------------------------------------------------- /visualReference/README.md: -------------------------------------------------------------------------------- 1 | # A visual reference to the configuration options for you-draw-it 2 | 3 | ![visual reference to the configuration options- 1](https://github.com/EE2dev/you-draw-it/blob/master/images/ydi1.png) 4 | 5 | ![visual reference to the configuration options- 2](https://github.com/EE2dev/you-draw-it/blob/master/images/ydi2.png) 6 | 7 | ![visual reference to the configuration options- 1](https://github.com/EE2dev/you-draw-it/blob/master/images/ydi3.png) 8 | 9 | ![visual reference to the configuration options- 1](https://github.com/EE2dev/you-draw-it/blob/master/images/ydi4.png) 10 | 11 | ![visual reference to the configuration options- 1](https://github.com/EE2dev/you-draw-it/blob/master/images/ydi5.png) 12 | 13 | ## API Reference 14 | 15 | ### The configuration object `globals` 16 | 17 | # globals.default 18 | 19 | Sets the several undefined properties of `globals` to default values. If a property is already defined, it is not overridden. 20 | Three values are currently supported: 21 | - globals.default = "en". 22 | Initialization with English defaults. In addition, the thousands separator is set to `,`(comma) and the decimal separator to `.`(dot). The English default is also applied if no default is specified. 23 | ``` 24 | // globals.default = "en" applies the following initialization: 25 | var globals = { 26 | default: "en", 27 | resultButtonText: "Show me the result!", 28 | resultButtonTooltip: "Draw your guess. Upon clicking here, you see if you're right.", 29 | scoreTitle: "Your result:", 30 | scoreButtonText: "Show me how good I am!", 31 | scoreButtonTooltip: "Click here to see your result", 32 | drawAreaTitle: "Your\nguess", 33 | drawLine: "draw the graph\nfrom here to the end", 34 | drawBar: "drag the bar\nto the estimated height", 35 | }; 36 | ``` 37 | - globals.default = "de". 38 | Initialization with German defaults. In addition, the thousands separator is set to `.`(dot) and the decimal separator to `,`(comma). 39 | ``` 40 | // globals.default = "de" applies the following initialization: 41 | var globals = { 42 | default: "de", 43 | resultButtonText: "Zeig mir die Lösung!", 44 | resultButtonTooltip: "Zeichnen Sie Ihre Einschätzung. Der Klick verrät, ob sie stimmt.", 45 | scoreTitle: "Ihr Ergebnis:", 46 | scoreButtonText: "Zeig mir, wie gut ich war!", 47 | scoreButtonTooltip: "Klicken Sie hier, um Ihr Gesamtergebnis zu sehen"", 48 | drawAreaTitle: "Ihre\nEinschätzung", 49 | drawLine: "Zeichnen Sie von hier\nden Verlauf zu Ende", 50 | drawBar: "Ziehen Sie den Balken\nauf die entsprechende Höhe", 51 | }; 52 | ``` 53 | 54 | - globals.default = "fr". 55 | Initialization with French defaults. In addition, the thousands separator is set to ` `(space) and the decimal separator to `,`(comma). Shout-out to Ambroise Carton for the translation! 56 | 57 | ``` 58 | // globals.default = "fr" applies the following initialization: 59 | var globals = { 60 | default: "fr", 61 | resultButtonText: "Montrez-moi le résultat", 62 | resultButtonTooltip: "A vous de dessiner la courbe. Pour voir la bonne réponse, cliquez ici", 63 | scoreTitle: "Votre résultat:", 64 | scoreButtonText: "Montrez-moi la bonne réponse", 65 | scoreButtonTooltip: "Cliquez ici pour obtenir des explications", 66 | drawAreaTitle: "Votre supposition", 67 | drawLine: "Placez votre doigt ou votre souris ici et dessinez la courbe", 68 | drawBar: "Montez la barre jusqu’à la hauteur supposée", 69 | }; 70 | ``` 71 | 72 | # globals.header 73 | 74 | Sets the *text* or *html* containing the header (= first line of the quiz). 75 | 76 | # globals.subHeader 77 | 78 | Sets the *text* or *html* containing the subHeader (= second line of the quiz). 79 | 80 | # globals.drawAreaTitle 81 | 82 | Sets the *text* denoting the area to be drawn by the user. 83 | 84 | # globals.drawLine 85 | 86 | Sets the *text* denoting the call to action for the user to continue drawing the line chart. 87 | 88 | # globals.drawBar 89 | 90 | Sets the *text* denoting the call to action for the user to adjusting the bar chart. 91 | 92 | # globals.resultButtonText 93 | 94 | Sets the *text* denoting the button to reveal the correct answer. 95 | 96 | # globals.resultButtonTooltip 97 | 98 | Sets the *text* denoting the tooltip of the button to reveal the correct answer. 99 | 100 | # globals.showScore 101 | 102 | Determines if the score evaluation should be displayed or not. Default is *true*. 103 | 104 | # globals.scoreButtonText 105 | 106 | Sets the *text* denoting the button to reveal the total score. 107 | 108 | # globals.scoreButtonTooltip 109 | 110 | Sets the *text* denoting the tooltip of the button to reveal the total score. 111 | 112 | # globals.scoreTitle 113 | 114 | Sets the *text* denoting the the headline on top of the score evaluation. 115 | 116 | # globals.scoreHtml 117 | 118 | There are two ways to specify that property: 119 | 1. specify a *text* or *html* for any score: 120 | ``` 121 | var globals = { 122 | ... 123 | scoreHtml: "Next time you can do better!", 124 | }; 125 | ``` 126 | Sets the *text* or *html* shown after the total score is revealed. 127 | 128 | 2. specify a *text* or *html* depending on the score: 129 | ``` 130 | var globals = { 131 | ... 132 | scoreHtml: [{lower: 0, upper: 50, html: "That wasn't much, was it??"}, 133 | {lower: 50, upper: 101, html: "Excellent!!"}], 134 | }; 135 | ``` 136 | Sets the *text* or *html* based on the score. In this case, `g.scoreHtml` is an `array` of `objects`. The array contains as many objects as there are intervalls. Each `object` defines the intervall with its lower and upper bound. It also contains an html property for the *text* or *html* to be displayed in this case. 137 | 138 | ### The configuration object `question` 139 | 140 | # question.data 141 | 142 | Sets the value/ values which is/are the correct response for the question. 143 | - In case a single value is the answer (which is re presented by a bar chart), `data` has to be initialized with the correct *number*. 144 | - In case a sequence of values is the answer (which is represented by a line chart), `data` has to be initialized by an *array* of *objects*. Each *object* is a point in the sequence and has to be initialized by a key (which will be the x coordinate) and its value (which will be the y coordinate) 145 | 146 | Note that the decimal separator has to denoted by a `.`(dot). The display, however, can be modified with `globals.default` (`.`(dot) vs `,`(comma)) 147 | 148 | ``` 149 | // examples for setting question.data 150 | question = { 151 | ... 152 | data: 385, // or 153 | data: 2.545, // or 154 | 155 | data: [ 156 | {"1998 (JP)": 32000}, 157 | {"2002 (US)": 22000}, 158 | {"2006 (IT)": 18000}, 159 | {"2010 (CA)": 18500}, 160 | {"2014 (RU)": 25000}, 161 | {"2018 (CN)": 22400}, 162 | ], // or 163 | 164 | data: [ 165 | {"Mon": 32.0}, 166 | {"Tue": 20.2}, 167 | {"Wed": 18.7}, 168 | {"Thu": 18.3}, 169 | {"Fri": 25.2}, 170 | {"Sat": 22.1}, 171 | {"Son": 22.9}, 172 | ], 173 | ...}; 174 | ``` 175 | 176 | # question.heading 177 | 178 | Sets the *text* or *html* containing the question. 179 | 180 | # question.subHeading 181 | 182 | Sets the *text* or *html* below the `heading`. 183 | 184 | # question.resultHtml 185 | 186 | Sets the *text* or *html* after the user has drawn his guess and the correct result is shown. 187 | 188 | # question.unit 189 | 190 | Sets a *string* which is attached to the values of the y axis and the label of the tooltip when the user makes. 191 | 192 | ``` 193 | // examples for setting question.unit 194 | question = { 195 | ... 196 | unit: "sec", // or 197 | unit: "Mio", // or 198 | unit: "US$", // or 199 | unit: "€", 200 | ...}; 201 | ``` 202 | 203 | # question.precision 204 | 205 | Sets the number of decimal places. The default is 1. 206 | 207 | # question.lastPointShownAt 208 | 209 | Determines the last point shown for the line chart. The user guesses start from the next point. 210 | Default value is the next to last point in the sequence. This leaves the user to guess just the last point. Any point but the last can be specified. 211 | Is irrelevant for the bar chart. 212 | 213 | # question.yAxisMin 214 | 215 | Sets the lowest value for the y axis. 216 | Default value is: 217 | - 0, if all values are positive numbers 218 | - Min(value) - *random number* * Min(value), if min(values) is a negative number. *random number* is a number between 0.4 and 1.0. 219 | 220 | # question.yAxisMax 221 | 222 | Sets the highest value for the y axis. 223 | Default value is: 224 | - Max(value) + *random number* * Max(value). *random number* is a number between 0.4 and 1.0. 225 | -------------------------------------------------------------------------------- /src/interface.js: -------------------------------------------------------------------------------- 1 | import { youdrawit } from "./youdrawit"; 2 | import * as d3 from "d3"; 3 | 4 | export default function () { 5 | "use strict"; 6 | 7 | let options = {}; 8 | options.containerDiv = d3.select("body"); 9 | options.globals = {}; 10 | /* option.globals contain: 11 | g.default 12 | g.header 13 | g.subHeader 14 | g.drawAreaTitle 15 | g.drawLine 16 | g.drawBar 17 | g.resultButtonText 18 | g.resultButtonTooltip 19 | g.showScore 20 | g.scoreTitle 21 | g.scoreButtonText 22 | g.scoreButtonTooltip 23 | g.scoreHtml 24 | */ 25 | options.questions = []; 26 | /* options.questions is an array of question objects q with: 27 | q.data 28 | q.heading 29 | q.subHeading 30 | q.resultHtml 31 | q.unit 32 | q.precision 33 | q.lastPointShownAt 34 | q.yAxisMin 35 | q.yAxisMax 36 | q.referenceValues 37 | q.referenceShape 38 | 39 | // the following are internal properties 40 | q.chartType 41 | q.key 42 | */ 43 | 44 | // API for external access 45 | function chartAPI(selection) { 46 | selection.each(function () { 47 | options.containerDiv = d3.select(this); 48 | if (!options.questions) { console.log("no questions specified!"); } 49 | if (Object.keys(options.globals).length === 0) { setGlobalDefault("English"); } 50 | completeQuestions(); 51 | completeDOM(); 52 | youdrawit(options.globals, options.questions); 53 | }); 54 | return chartAPI; 55 | } 56 | 57 | chartAPI.questions = function(_) { 58 | if (!arguments.length) return options.questions; 59 | options.questions = _; 60 | return chartAPI; 61 | }; 62 | 63 | chartAPI.globals = function(_) { 64 | if (!arguments.length) return options.globals; 65 | for (var key in _) { 66 | if (_.hasOwnProperty(key)) { 67 | options.globals[key] = _[key]; 68 | if (key === "default") { setGlobalDefault(_[key]);} 69 | } 70 | } 71 | return chartAPI; 72 | }; 73 | 74 | function setGlobalDefault(lang) { 75 | let g = options.globals; 76 | g.showScore = (typeof g.showScore === "undefined") ? true : g.showScore; 77 | if (lang === "de") { // de (German) 78 | g.resultButtonText = (typeof g.resultButtonText === "undefined") ? "Zeig mir die Lösung!" : g.resultButtonText; 79 | g.resultButtonTooltip = (typeof g.resultButtonTooltip === "undefined") ? "Zeichnen Sie Ihre Einschätzung. Der Klick verrät, ob sie stimmt." : g.resultButtonTooltip; 80 | g.scoreTitle = (typeof g.scoreTitle === "undefined") ? "Ihr Ergebnis:" : g.scoreTitle; 81 | g.scoreButtonText = (typeof g.scoreButtonText === "undefined") ? "Zeig mir, wie gut ich war!" : g.scoreButtonText; 82 | g.scoreButtonTooltip = (typeof g.scoreButtonTooltip === "undefined") ? "Klicken Sie hier, um Ihr Gesamtergebnis zu sehen" : g.scoreButtonTooltip; 83 | g.drawAreaTitle = (typeof g.drawAreaTitle === "undefined") ? "Ihre\nEinschätzung" : g.drawAreaTitle; 84 | g.drawLine = (typeof g.drawLine === "undefined") ? "Zeichnen Sie von hier\nden Verlauf zu Ende" : g.drawLine; 85 | g.drawBar = (typeof g.drawBar === "undefined") ? "Ziehen Sie den Balken\nauf die entsprechende Höhe" : g.drawBar; 86 | } else if (lang === "fr") { // fr (French) 87 | g.default = "fr"; 88 | g.resultButtonText = (typeof g.resultButtonText === "undefined") ? "Montrez-moi le résultat" : g.resultButtonText; 89 | g.resultButtonTooltip = (typeof g.resultButtonTooltip === "undefined") ? "A vous de dessiner la courbe. Pour voir la bonne réponse, cliquez ici" : g.resultButtonTooltip; 90 | g.scoreTitle = (typeof g.scoreTitle === "undefined") ? "Votre résultat:" : g.scoreTitle; 91 | g.scoreButtonText = (typeof g.scoreButtonText === "undefined") ? "Montrez-moi la bonne réponse" : g.scoreButtonText; 92 | g.scoreButtonTooltip = (typeof g.scoreButtonTooltip === "undefined") ? "Cliquez ici pour obtenir des explications" : g.scoreButtonTooltip; 93 | g.drawAreaTitle = (typeof g.drawAreaTitle === "undefined") ? "Votre\nsupposition" : g.drawAreaTitle; 94 | g.drawLine = (typeof g.drawLine === "undefined") ? "Placez votre doigt\nou votre souris ici\net dessinez la courbe" : g.drawLine; 95 | g.drawBar = (typeof g.drawBar === "undefined") ? "Montez la barre\njusqu’à la hauteur supposée" : g.drawBar; 96 | } 97 | else { // lang === "en" (English) 98 | g.default = "en"; 99 | g.resultButtonText = (typeof g.resultButtonText === "undefined") ? "Show me the result!" : g.resultButtonText; 100 | g.resultButtonTooltip = (typeof g.resultButtonTooltip === "undefined") ? "Draw your guess. Upon clicking here, you see if you're right." : g.resultButtonTooltip; 101 | g.scoreTitle = (typeof g.scoreTitle === "undefined") ? "Your result:" : g.scoreTitle; 102 | g.scoreButtonText = (typeof g.scoreButtonText === "undefined") ? "Show me how good I am!" : g.scoreButtonText; 103 | g.scoreButtonTooltip = (typeof g.scoreButtonTooltip === "undefined") ? "Click here to see your result" : g.scoreButtonTooltip; 104 | g.drawAreaTitle = (typeof g.drawAreaTitle === "undefined") ? "Your\nguess" : g.drawAreaTitle; 105 | g.drawLine = (typeof g.drawLine === "undefined") ? "draw the graph\nfrom here to the end" : g.drawLine; 106 | g.drawBar = (typeof g.drawBar === "undefined") ? "drag the bar\nto the estimated height" : g.drawBar; 107 | } 108 | } 109 | 110 | function completeQuestions() { 111 | if (typeof options.globals.scoreHtml !== "undefined"){ 112 | if (typeof options.globals.scoreHtml === "string" || options.globals.scoreHtml instanceof String) { 113 | if (!checkResult(options.globals.scoreHtml)) { 114 | console.log("invalid scoreHtml!"); 115 | options.globals.scoreHtml = void 0; // set to undefined 116 | } else { 117 | options.globals.scoreHtml = [{lower: 0, upper: 101, html: options.globals.scoreHtml}]; 118 | } 119 | } 120 | else { // options.globals.scoreHtml is an array 121 | if (typeof options.globals.scoreHtml.length !== "undefined") { 122 | options.globals.scoreHtml.forEach(function (range) { 123 | let exp = range.html; 124 | if (!checkResult(exp)) { 125 | console.log("invalid scoreHtml! -> set to empty string"); 126 | range.html = ""; 127 | } 128 | }); 129 | } 130 | } 131 | } 132 | 133 | options.questions.forEach(function(q, index) { 134 | if (!q.data) { console.log("no data specified!"); } 135 | if (!checkResult(q.resultHtml)) { console.log("invalid result!");} 136 | 137 | q.chartType = getChartType(q.data); 138 | q.heading = (typeof q.heading === "undefined") ? "" : q.heading; 139 | q.subHeading = (typeof q.subHeading === "undefined") ? "" : q.subHeading; 140 | q.resultHtml = (typeof q.resultHtml === "undefined") ? "
" : q.resultHtml; 141 | q.unit = (typeof q.unit === "undefined") ? "" : q.unit; 142 | q.precision = (typeof q.precision === "undefined") ? 1 : q.precision; 143 | q.referenceShape = (typeof q.referenceShape === "undefined") ? "line" : q.referenceShape; 144 | q.key = "q" + (index + 1); 145 | 146 | if (q.chartType === "barChart") { 147 | q.data = [{ value: q.data}]; 148 | } 149 | 150 | if (!q.lastPointShownAt) { 151 | if (q.chartType === "timeSeries") { 152 | const nextToLast = q.data[q.data.length - 2]; 153 | q.lastPointShownAt = Object.keys(nextToLast)[0]; 154 | } else if (q.chartType === "barChart") { 155 | const onlyElement = q.data[0]; 156 | q.lastPointShownAt = Object.keys(onlyElement)[0]; 157 | } 158 | } 159 | console.log("display question " + index + " as " + q.chartType); 160 | }); 161 | } 162 | 163 | function getChartType(data) { 164 | let chartType; 165 | if (isNumber(data)) { 166 | chartType = "barChart"; 167 | } else { 168 | const firstObj = data[0]; 169 | let num = true; 170 | for (var key in firstObj) { 171 | if (firstObj.hasOwnProperty(key)) { 172 | num = num && isNumber(firstObj[key]); 173 | } 174 | } 175 | chartType = num ? "timeSeries" : "multipleChoice"; 176 | } 177 | return chartType; 178 | } 179 | 180 | function isNumber(n) { 181 | return !isNaN(parseFloat(n)) && isFinite(n); 182 | } 183 | 184 | function completeDOM() { 185 | const art = options.containerDiv 186 | .append("article") 187 | .attr("id", "content") 188 | .attr("class", "container"); 189 | 190 | const intro = art.append("div") 191 | .attr("class", "intro"); 192 | intro.append("h1") 193 | .append("div") 194 | .attr("class", "globals-header update-font") 195 | .html(options.globals.header); 196 | intro.append("p") 197 | .append("div") 198 | .attr("class", "globals-subHeader update-font") 199 | .html(options.globals.subHeader); 200 | 201 | const questions = art.append("div") 202 | .attr("class", "questions"); 203 | 204 | options.questions.forEach(function(q) { 205 | const question = questions.append("div") 206 | .attr("class", "question"); 207 | question.append("h2") 208 | .append("div") 209 | .attr("class", "question-heading update-font") 210 | .html(q.heading); 211 | question.append("h3") 212 | .append("div") 213 | .attr("class", "question-subHeading update-font") 214 | .html(q.subHeading); 215 | question.append("div") 216 | .attr("class", "you-draw-it " + q.key) 217 | .attr("data-key", q.key); 218 | 219 | const res = question.append("div") 220 | .attr("class", "result " + q.key); 221 | const ac = res.append("div") 222 | .attr("class", "actionContainer"); 223 | ac.append("button") 224 | .attr("class", "showAction") 225 | .attr("disabled", "disabled") 226 | .append("div") 227 | .attr("class", "globals-resultButtonText update-font") 228 | .text(options.globals.resultButtonText); 229 | ac.append("div") 230 | .attr("class", "tooltipcontainer") 231 | .append("span") 232 | .attr("class", "tooltiptext globals-resultButtonTooltip update-font") 233 | .text(options.globals.resultButtonTooltip); 234 | 235 | res.append("div") 236 | .attr("class", "text") 237 | .append("p") 238 | .append("div") 239 | .attr("class", "question-resultHtml update-font") 240 | .html(q.resultHtml); 241 | }); 242 | } 243 | 244 | function checkResult(exp){ // checks if html might contain javascript 245 | if (!exp) { return true; } 246 | const expUC = exp.toUpperCase(); 247 | if (expUC.indexOf("<") !== -1 && expUC.indexOf("SCRIPT") !== -1 && expUC.indexOf(">") !== -1) { 248 | console.log("--- invalid html!"); 249 | console.log("--- expression was: "); 250 | console.log(exp); 251 | return false; 252 | } else { 253 | return true; 254 | } 255 | } 256 | 257 | return chartAPI; 258 | } -------------------------------------------------------------------------------- /src/ydBar.js: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3"; 2 | import { ƒ } from "./helpers/function"; 3 | import { formatValue } from "./helpers/formatValue"; 4 | import { clamp } from "./helpers/clamp"; 5 | import { getRandom } from "./helpers/getRandom"; 6 | import { yourData, resultShown, completed, score, prediction, truth } from "./helpers/constants"; 7 | import { getScore } from "./results/score"; 8 | import { addReferenceValues } from "./helpers/referenceValues"; 9 | 10 | export function ydBar(isMobile, state, sel, key, question, globals, data, indexedTimepoint, indexedData) { 11 | const minX = data[0].timePointIndex; 12 | const maxX = data[data.length - 1].timePointIndex; 13 | const minY = d3.min(data, d => d.value); 14 | const maxY = d3.max(data, d => d.value); 15 | const lastPointShownAtIndex = indexedTimepoint.indexOf(question.lastPointShownAt.toString()); 16 | 17 | const drawAxes = function (c) { 18 | c.axis.append("g") 19 | .attr("class", "x axis") 20 | .attr("transform", "translate(0," + c.height + ")"); 21 | // .call(c.xAxis); 22 | 23 | c.axis.append("g") 24 | .attr("class", "y axis") 25 | .call(c.yAxis); 26 | }; 27 | 28 | const makeLabel = function (pos, addClass) { 29 | const x = c.x(truth) + (c.x.bandwidth() / 2); 30 | const truthValue = data[0].value; 31 | const y = c.y(truthValue); 32 | 33 | const text = formatValue(truthValue, question.unit, question.precision); 34 | 35 | const label = c.labels.append("div") 36 | .classed("data-label", true) 37 | .classed(addClass, true) 38 | .style("opacity", 0) 39 | .style("left", x + "px") 40 | .style("top", y + "px"); 41 | label.append("span") 42 | // .classed("no-dot question-label update-font", true) 43 | .classed("no-dot", true) 44 | .append("div") 45 | .classed("question-label update-font", true) 46 | .text(text); 47 | 48 | if (pos == minX && isMobile) { 49 | label.classed("edge-left", true); 50 | } 51 | if (pos == maxX && isMobile) { 52 | label.classed("edge-right", true); 53 | } 54 | 55 | return [{}, label]; 56 | }; 57 | 58 | const drawChart = function (addClass) { 59 | const group = c.charts.append("g").attr("class", "truth"); 60 | 61 | makeLabel(truth, addClass); 62 | 63 | const truthSelection = group.append("rect") 64 | .attr("class", "bar") 65 | .attr("x", c.x(truth)) 66 | .attr("y", c.height) 67 | .attr("height", 0) 68 | .attr("width", c.x.bandwidth()); 69 | return truthSelection; 70 | }; 71 | 72 | // make visual area empty 73 | sel.html(""); 74 | 75 | const margin = { 76 | top: 40, 77 | // right: isMobile ? 20 : 50, 78 | right: 50, 79 | bottom: 30, 80 | // left: isMobile ? 20 : 100 81 | left: 100 82 | }; 83 | const width = sel.node().offsetWidth; 84 | const height = 400; 85 | const c = { 86 | width: width - (margin.left + margin.right), 87 | height: height - (margin.top + margin.bottom) 88 | }; 89 | 90 | const graphMinY = question.yAxisMin ? question.yAxisMin : 91 | minY >= 0 ? 0 : minY * getRandom(1, 1.5); 92 | const graphMaxY = question.yAxisMax ? question.yAxisMax : 93 | maxY + (maxY - graphMinY) * getRandom(0.4, 1); // add 40 - 100% for segment titles 94 | c.x = d3.scaleBand().rangeRound([0, c.width]).padding(0.1); 95 | c.x.domain([prediction, truth]); 96 | c.y = d3.scaleLinear().range([c.height, 0]); 97 | c.y.domain([graphMinY, graphMaxY]); 98 | 99 | c.svg = sel.append("svg") 100 | .attr("width", width) 101 | .attr("height", height) 102 | .append("g") 103 | .attr("transform", "translate(" + margin.left + "," + margin.top + ")") 104 | .attr("width", c.width) 105 | .attr("height", c.height); 106 | 107 | // gradients 108 | c.defs = d3.select(c.svg.node().parentNode).append("defs"); 109 | ["black", "red", "blue"].forEach(color => { 110 | const gradient = c.defs.append("linearGradient") 111 | .attr("id", "gradient-" + color) 112 | .attr("x1", "0%") 113 | .attr("y1", "0%") 114 | .attr("x2", "0%") 115 | .attr("y2", "100%"); 116 | gradient.append("stop").attr("offset", "0%").attr("class", "start"); 117 | gradient.append("stop").attr("offset", "100%").attr("class", "end"); 118 | }); 119 | 120 | c.defs.append("marker") 121 | .attr("id", "preview-arrowp") 122 | .attr("orient", "auto") 123 | .attr("viewBox", "0 0 10 10") 124 | .attr("markerWidth", 6) 125 | .attr("markerHeight", 6) 126 | .attr("refX", 1) 127 | .attr("refY", 5) 128 | .append("path") 129 | .attr("d", "M 0 0 L 10 5 L 0 10 z"); 130 | 131 | // make background grid 132 | c.grid = c.svg.append("g") 133 | .attr("class", "grid"); 134 | 135 | c.grid.append("g").attr("class", "vertical").call( 136 | d3.axisLeft(c.y) 137 | .tickValues(c.y.ticks(6)) 138 | .tickFormat("") 139 | .tickSize(-c.width) 140 | ); 141 | 142 | const applyMargin = function (sel) { 143 | sel.style("left", margin.left + "px") 144 | .style("top", margin.top + "px") 145 | .style("width", c.width + "px") 146 | .style("height", c.height + "px"); 147 | }; 148 | 149 | setTimeout(() => { 150 | const clientRect = c.svg.node().getBoundingClientRect(); 151 | c.top = clientRect.top + window.scrollY; 152 | c.bottom = clientRect.bottom + window.scrollY; 153 | }, 1000); 154 | 155 | c.labels = sel.append("div") 156 | .attr("class", "labels") 157 | .call(applyMargin); 158 | c.axis = c.svg.append("g"); 159 | c.charts = c.svg.append("g").attr("class", "charts"); 160 | c.xPredictionCenter = c.x(prediction) + (c.x.bandwidth() / 2); 161 | 162 | const userSel = c.svg.append("rect").attr("class", "your-rect"); 163 | 164 | // invisible rect for dragging to work 165 | const dragArea = c.svg.append("rect") 166 | .attr("class", "draggable") 167 | .attr("x", c.x(prediction)) 168 | .attr("width", c.x.bandwidth()) 169 | .attr("height", c.height) 170 | .attr("opacity", 0); 171 | 172 | // configure axes 173 | c.xAxis = d3.axisBottom().scale(c.x); 174 | c.yAxis = d3.axisLeft().scale(c.y).tickValues(c.y.ticks(6)); 175 | c.yAxis.tickFormat(d => formatValue(d, question.unit, question.precision)); 176 | drawAxes(c); 177 | 178 | c.titles = sel.append("div") 179 | .attr("class", "titles") 180 | .call(applyMargin) 181 | .style("top", "0px"); 182 | 183 | // add a preview pointer 184 | const xs = c.xPredictionCenter; 185 | const ys = c.height - 30; 186 | const xArrowStart = xs + 45; 187 | const yArrowStart = ys - 50; 188 | const xTextStart = xArrowStart + 5; 189 | const yTextStart = yArrowStart - 10; 190 | 191 | c.preview = c.svg.append("path") 192 | .attr("class", "controls preview-pointer") 193 | .attr("marker-end", "url(#preview-arrowp)") 194 | .attr("d", "M" + xArrowStart + "," + yArrowStart + 195 | " Q" + xs + "," + yArrowStart + 196 | " " + xs + "," + (ys - 10)); 197 | 198 | // add preview wave 199 | let arc = d3.arc() 200 | .startAngle(0) 201 | .endAngle(2 * Math.PI); 202 | 203 | let nrWaves = initializeWaves(4); 204 | c.wave = c.svg.append("g").attr("class", "wave controls"); 205 | c.wave.append("clipPath") 206 | .attr("id", `wave-clip-${key}`) 207 | .append("rect") 208 | .attr("width", c.width) 209 | .attr("height", c.height); 210 | 211 | c.wave = c.wave 212 | .append("g") 213 | .attr("clip-path", `url(#wave-clip-${key})`) 214 | .append("g") 215 | .attr("transform", "translate(" + xs + ", " + ys + ")") 216 | .selectAll("path") 217 | .data(nrWaves) 218 | .enter() 219 | .append("path") 220 | .attr("class", "wave") 221 | .attr("d", arc); 222 | 223 | moveWave(); 224 | function moveWave(){ 225 | c.wave.style("opacity", .6) 226 | .transition() 227 | .ease(d3.easeLinear) 228 | .delay((d,i) => 1000 + i * 300) 229 | .duration(4000) 230 | .attrTween("d", arcTween()) 231 | .style("opacity", 0) 232 | .on("end", restartWave); 233 | } 234 | 235 | function initializeWaves(nr){ 236 | let nrWaves = []; 237 | for (let i = 0; i < nr; i++) {nrWaves.push({});} 238 | return nrWaves; 239 | } 240 | 241 | function restartWave(d,i){ 242 | if (i === (nrWaves.length - 1)) { // restart after last wave is finished 243 | let nrWaves2 = initializeWaves(4); 244 | c.wave = c.wave.data(nrWaves2); 245 | c.wave.attr("d", arc); 246 | moveWave(); 247 | } 248 | } 249 | 250 | function arcTween() { 251 | return function(d) { 252 | if (sel.classed("drawn")){ 253 | c.wave.interrupt(); 254 | console.log("waves interrupted"); 255 | return; 256 | } 257 | var interpolate = d3.interpolate(0, 100); 258 | return function(t) { 259 | d.innerRadius = interpolate(t); 260 | d.outerRadius = interpolate(t) + 3; 261 | return arc(d); 262 | }; 263 | }; 264 | } 265 | 266 | // add preview notice 267 | c.controls = sel.append("div") 268 | .attr("class", "controls") 269 | .call(applyMargin) 270 | .style("padding-left", c.xPredictionCenter); 271 | 272 | c.controls.append("span") 273 | .style("left", xTextStart + "px") 274 | .style("top", yTextStart + "px") 275 | .append("div") 276 | .attr("class", "globals-drawBar update-font") 277 | .text(globals.drawBar); 278 | 279 | if (typeof question.referenceValues !== "undefined") { 280 | if (question.referenceShape === "tick") { 281 | addReferenceValues (c.controls, c.svg, question.referenceValues, c, false); 282 | } else{ //question.referenceShape === "line" 283 | addReferenceValues (c.controls, c.svg, question.referenceValues, c, true); 284 | } 285 | } 286 | 287 | // make chart 288 | const truthSelection = drawChart("blue"); 289 | 290 | // segment title 291 | c.predictionTitle = c.titles.append("span") 292 | .style("left", "1px") 293 | .style("width", (c.width / 2) - 1 + "px"); 294 | c.predictionTitle 295 | .append("div") 296 | .attr("class", "globals-drawAreaTitle update-font") 297 | .text(globals.drawAreaTitle); 298 | 299 | // Interactive user selection part 300 | userSel.attr("x", c.x(prediction)) 301 | .attr("y", c.height - 30) 302 | .attr("width", c.x.bandwidth()) 303 | .attr("height", 30); 304 | 305 | if(!state.get(key, yourData)){ 306 | const val = data.map(d => ({year: d.year, value: indexedData[lastPointShownAtIndex], defined: 0})) 307 | .filter(d => { 308 | if (d.year == lastPointShownAtIndex) d.defined = true; 309 | return d.year >= lastPointShownAtIndex; 310 | }); 311 | state.set(key, "yourData", val); 312 | } 313 | 314 | const resultSection = d3.select(".result." + key); 315 | const drawUserBar = function(year) { 316 | const h = c.y(state.get(key, yourData)[0].value); 317 | userSel.attr("y", h) 318 | .attr("height", c.height - h); 319 | const d = state.get(key, yourData).filter(d => d.year === year)[0]; 320 | // const dDefined = state.get(key, yourData).filter(d => d.defined && (d.year !== lastPointShownAtIndex)); 321 | 322 | if(!d.defined) { 323 | return; 324 | } 325 | 326 | const yourResult = c.labels.selectAll(".your-result") 327 | .data([d]); 328 | yourResult.enter() 329 | .append("div") 330 | .classed("data-label your-result", true) 331 | .classed("edge-right", isMobile) 332 | .merge(yourResult) 333 | .style("left", c.xPredictionCenter + "px") 334 | .style("top", r => c.y(r.value) + "px") 335 | .html("") 336 | .append("span") 337 | .classed("no-dot", true) 338 | .append("div") 339 | .classed("question-label update-font", true) 340 | .text(r => question.precision ? formatValue(r.value, question.unit, question.precision) 341 | : formatValue(r.value, question.unit, question.precision, 0)); 342 | }; 343 | if (sel.classed("drawn")){ 344 | drawUserBar(lastPointShownAtIndex); 345 | } 346 | 347 | const interactionHandler = function() { 348 | if (state.get(key, resultShown)) { 349 | return; 350 | } 351 | 352 | sel.node().classList.add("drawn"); 353 | 354 | const pos = d3.mouse(c.svg.node()); 355 | // if (pos[1] < margin.top) { return; } 356 | if (pos[1] < 0) { return; } 357 | const value = clamp(c.y.domain()[0], c.y.domain()[1], c.y.invert(pos[1])); 358 | let yearPoint = lastPointShownAtIndex; 359 | 360 | state.get(key, yourData).forEach(d => { 361 | d.value = value; 362 | d.defined = true; 363 | yearPoint = d.year; 364 | }); 365 | 366 | if (pos[1] < 80) { c.predictionTitle.style("opacity", 0); 367 | } else if (pos[1] >= 80) { c.predictionTitle.style("opacity", 1); } 368 | 369 | drawUserBar(yearPoint); 370 | 371 | if (!state.get(key, completed) && d3.mean(state.get(key, yourData), ƒ("defined")) == 1) { 372 | state.set(key, completed, true); 373 | resultSection.node().classList.add("finished"); 374 | resultSection.select("button").node().removeAttribute("disabled"); 375 | } 376 | }; 377 | 378 | c.svg.call(d3.drag().on("drag", interactionHandler)); 379 | c.svg.on("click", interactionHandler); 380 | 381 | const showResultChart = function () { 382 | if(!state.get( key, completed)) { 383 | return; 384 | } 385 | c.labels.selectAll(".your-result").node().classList.add("hideLabels"); 386 | 387 | const h = c.y(data[0].value); 388 | truthSelection.transition() 389 | .duration(1300) 390 | .attr("y", h) 391 | .attr("height", c.height - h); 392 | 393 | dragArea.attr("class", ""); 394 | 395 | setTimeout(() => { 396 | c.labels.select("div.data-label").style("opacity", 1); 397 | resultSection.node().classList.add("shown"); 398 | 399 | if (!state.get(key, score) && globals.showScore) { 400 | const truth = data.filter(d => d.year === lastPointShownAtIndex); 401 | getScore(key, truth, state, graphMaxY, graphMinY, resultSection, 402 | globals.scoreTitle, globals.scoreButtonText, globals.scoreButtonTooltip, globals.scoreHtml); 403 | } 404 | state.set(key, resultShown, true); 405 | }, 1300); 406 | 407 | }; 408 | resultSection.select("button").on("click", showResultChart); 409 | if (state.get(key, resultShown)) { 410 | showResultChart(); 411 | } 412 | } -------------------------------------------------------------------------------- /src/ydLine.js: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3"; 2 | import { ƒ } from "./helpers/function"; 3 | import { formatValue } from "./helpers/formatValue"; 4 | import { clamp } from "./helpers/clamp"; 5 | import { getRandom } from "./helpers/getRandom"; 6 | import { yourData, resultShown, completed, score } from "./helpers/constants"; 7 | import { getScore } from "./results/score"; 8 | import { addReferenceLines } from "./helpers/referenceValues"; 9 | 10 | export function ydLine(isMobile, state, sel, key, question, globals, data, indexedTimepoint, indexedData) { 11 | const minX = data[0].timePointIndex; 12 | const maxX = data[data.length - 1].timePointIndex; 13 | const minY = d3.min(data, d => d.value); 14 | const maxY = d3.max(data, d => d.value); 15 | const lastPointShownAtIndex = indexedTimepoint.indexOf(question.lastPointShownAt.toString()); 16 | 17 | const periods = [ 18 | { year: lastPointShownAtIndex, class: "blue", title: ""}, 19 | { year: maxX, class: "blue", title: globals.drawAreaTitle} 20 | ]; 21 | const segmentBorders = [minX].concat(periods.map(d => d.year)); 22 | 23 | const drawAxes = function (c) { 24 | c.axis.append("g") 25 | .attr("class", "x axis") 26 | .attr("transform", "translate(0," + c.height + ")") 27 | .call(c.xAxis); 28 | 29 | c.axis.append("g") 30 | .attr("class", "y axis") 31 | .call(c.yAxis); 32 | }; 33 | 34 | const makeLabel = function (lowerPos, pos, addClass) { 35 | const x = c.x(pos); 36 | const y = c.y(indexedData[pos]); 37 | const text = formatValue(indexedData[pos], question.unit, question.precision); 38 | 39 | const label = c.labels.append("div") 40 | .classed("data-label", true) 41 | .classed(addClass, true) 42 | .style("left", x + "px") 43 | .style("top", y + "px"); 44 | label.append("span") 45 | .append("div") 46 | .attr("class", "question-label update-font") 47 | .text(text); 48 | 49 | if (pos == minX && isMobile) { 50 | label.classed("edge-left", true); 51 | } 52 | if (pos == maxX && isMobile) { 53 | label.classed("edge-right", true); 54 | } 55 | 56 | let circles; 57 | let counter = 0; 58 | for (let between = lowerPos + 1; between <= pos; between++) { 59 | c.dots.append("circle") 60 | .attr("r", 4.5) 61 | .attr("cx", c.x(between)) 62 | .attr("cy", c.y(indexedData[between])) 63 | .attr("class", addClass); 64 | counter = counter + 1; 65 | } 66 | 67 | circles = c.dots.selectAll("circle:nth-last-child(-n+" + counter + ")"); 68 | /* 69 | return [ 70 | c.dots.append("circle") 71 | .attr("r", 4.5) 72 | .attr("cx", x) 73 | .attr("cy", y) 74 | .attr("class", addClass), 75 | label 76 | ]; 77 | */ 78 | return [circles, label]; 79 | }; 80 | 81 | const drawChart = function (lower, upper, addClass) { 82 | const definedFn = (d) => d.year >= lower && d.year <= upper; 83 | const area = d3.area().curve(d3.curveMonotoneX).x(ƒ("year", c.x)).y0(ƒ("value", c.y)).y1(c.height).defined(definedFn); 84 | const line = d3.area().curve(d3.curveMonotoneX).x(ƒ("year", c.x)).y(ƒ("value", c.y)).defined(definedFn); 85 | 86 | if (lower == minX) { 87 | makeLabel(minX - 1, minX, addClass); 88 | } 89 | const svgClass = addClass + (upper == lastPointShownAtIndex ? " median" : ""); 90 | 91 | const group = c.charts.append("g"); 92 | group.append("path").attr("d", area(data)).attr("class", "area " + svgClass).attr("fill", `url(#gradient-${addClass})`); 93 | group.append("path").attr("d", line(data)).attr("class", "line " + svgClass); 94 | 95 | return [ 96 | group, 97 | ].concat(makeLabel(lower, upper, svgClass)); 98 | }; 99 | 100 | // make visual area empty 101 | sel.html(""); 102 | 103 | const margin = { 104 | top: 44, 105 | // right: isMobile ? 20 : 50, 106 | right: 50, 107 | bottom: 30, 108 | // left: isMobile ? 20 : 100 109 | left: 100 110 | }; 111 | const heightCap = 84; 112 | const width = sel.node().offsetWidth; 113 | const height = 400; 114 | const c = { 115 | width: width - (margin.left + margin.right), 116 | height: height - (margin.top + margin.bottom) 117 | }; 118 | 119 | // configure scales 120 | const graphMinY = question.yAxisMin ? question.yAxisMin : 121 | minY >= 0 ? 0 : minY * getRandom(1, 1.5); 122 | const graphMaxY = question.yAxisMax ? question.yAxisMax : 123 | maxY + (maxY - graphMinY) * getRandom(0.4, 1); // add 40 - 100% for segment titles 124 | c.x = d3.scaleLinear().range([0, c.width]); 125 | c.x.domain([minX, maxX]); 126 | c.y = d3.scaleLinear().range([c.height, 0]); 127 | c.y.domain([graphMinY, graphMaxY]); 128 | 129 | c.svg = sel.append("svg") 130 | .attr("width", width) 131 | .attr("height", height) 132 | .append("g") 133 | .attr("transform", "translate(" + margin.left + "," + margin.top + ")") 134 | .attr("width", c.width) 135 | .attr("height", c.height); 136 | 137 | // gradients 138 | c.defs = d3.select(c.svg.node().parentNode).append("defs"); 139 | ["black", "red", "blue"].forEach(color => { 140 | const gradient = c.defs.append("linearGradient") 141 | .attr("id", "gradient-" + color) 142 | .attr("x1", "0%") 143 | .attr("y1", "0%") 144 | .attr("x2", "0%") 145 | .attr("y2", "100%"); 146 | gradient.append("stop").attr("offset", "0%").attr("class", "start"); 147 | gradient.append("stop").attr("offset", "100%").attr("class", "end"); 148 | }); 149 | 150 | c.defs.append("marker") 151 | .attr("id", "preview-arrowp") 152 | .attr("orient", "auto") 153 | .attr("viewBox", "0 0 10 10") 154 | .attr("markerWidth", 6) 155 | .attr("markerHeight", 6) 156 | .attr("refX", 1) 157 | .attr("refY", 5) 158 | .append("path") 159 | .attr("d", "M 0 0 L 10 5 L 0 10 z"); 160 | 161 | // make background grid 162 | c.grid = c.svg.append("g") 163 | .attr("class", "grid"); 164 | c.grid.append("g").attr("class", "horizontal").call( 165 | d3.axisBottom(c.x) 166 | .tickValues(c.x.ticks(maxX - minX)) 167 | .tickFormat("") 168 | .tickSize(c.height) 169 | ) 170 | .selectAll("line") 171 | .attr("class", (d) => segmentBorders.indexOf(d) !== -1 ? "highlight" : ""); 172 | 173 | c.grid.append("g").attr("class", "vertical").call( 174 | d3.axisLeft(c.y) 175 | .tickValues(c.y.ticks(6)) 176 | .tickFormat("") 177 | .tickSize(-c.width) 178 | ); 179 | 180 | const applyMargin = function (sel) { 181 | sel.style("left", margin.left + "px") 182 | .style("top", margin.top + "px") 183 | .style("width", c.width + "px") 184 | .style("height", c.height + "px"); 185 | 186 | }; 187 | 188 | // invisible rect for dragging to work 189 | const dragArea = c.svg.append("rect") 190 | .attr("class", "draggable") 191 | .attr("x", c.x(lastPointShownAtIndex)) 192 | .attr("width", c.x(maxX) - c.x(lastPointShownAtIndex)) 193 | .attr("height", c.height) 194 | .attr("opacity", 0); 195 | 196 | setTimeout(() => { 197 | const clientRect = c.svg.node().getBoundingClientRect(); 198 | c.top = clientRect.top + window.scrollY; 199 | c.bottom = clientRect.bottom + window.scrollY; 200 | }, 1000); 201 | 202 | c.labels = sel.append("div") 203 | .attr("class", "labels") 204 | .call(applyMargin); 205 | c.axis = c.svg.append("g"); 206 | c.charts = c.svg.append("g"); 207 | 208 | const userSel = c.svg.append("path").attr("class", "your-line"); 209 | c.dots = c.svg.append("g").attr("class", "dots"); 210 | 211 | // configure axes 212 | c.xAxis = d3.axisBottom().scale(c.x); 213 | c.xAxis.tickFormat(d => indexedTimepoint[d]).ticks(maxX - minX); 214 | c.yAxis = d3.axisLeft().scale(c.y);//.tickValues(c.y.ticks(6)); 215 | c.yAxis.tickFormat(d => formatValue(d, question.unit, question.precision)); 216 | drawAxes(c); 217 | 218 | c.titles = sel.append("div") 219 | .attr("class", "titles") 220 | .call(applyMargin) 221 | .style("top", "0px"); 222 | 223 | // add a preview pointer 224 | const xs = c.x(lastPointShownAtIndex); 225 | const ys = c.y(indexedData[lastPointShownAtIndex]); 226 | 227 | const xArrowStart = (ys <= 300) ? (xs + 45) : (xs + 70); 228 | const yArrowStart = (ys <= 300) ? (ys + 30) : (ys - 30); 229 | const yTextStart = (ys <= 300) ? (c.y(indexedData[lastPointShownAtIndex]) + 30) : (c.y(indexedData[lastPointShownAtIndex]) - 65); 230 | const xTextStart = (ys <= 300) ? (c.x(lastPointShownAtIndex) + 30) : (c.x(lastPointShownAtIndex) + 65); 231 | 232 | c.preview = c.svg.append("path") 233 | .attr("class", "controls preview-pointer") 234 | .attr("marker-end", "url(#preview-arrowp)") 235 | .attr("d", "M" + xArrowStart + "," + yArrowStart + 236 | " Q" + xArrowStart + "," + ys + 237 | " " + (xs + 15) + "," + ys); 238 | 239 | // add preview wave 240 | let arc = d3.arc() 241 | .startAngle(0) 242 | .endAngle(Math.PI); 243 | 244 | let nrWaves = initializeWaves(4); 245 | c.wave = c.svg.append("g").attr("class", "wave controls"); 246 | c.wave.append("clipPath") 247 | .attr("id", `wave-clip-${key}`) 248 | .append("rect") 249 | .attr("width", c.width) 250 | .attr("height", c.height); 251 | 252 | c.wave = c.wave 253 | .append("g") 254 | .attr("clip-path", `url(#wave-clip-${key})`) 255 | .append("g") 256 | .attr("transform", "translate(" + xs + ", " + ys + ")") 257 | .selectAll("path") 258 | .data(nrWaves) 259 | .enter() 260 | .append("path") 261 | .attr("class", "wave") 262 | .attr("d", arc); 263 | 264 | moveWave(); 265 | function moveWave(){ 266 | console.log ("moveWave"); 267 | c.wave.style("opacity", .6) 268 | .transition() 269 | .ease(d3.easeLinear) 270 | .delay((d,i) => 1000 + i * 300) 271 | .duration(4000) 272 | .attrTween("d", arcTween()) 273 | .style("opacity", 0) 274 | .on("end", restartWave); 275 | } 276 | 277 | function initializeWaves(nr){ 278 | let nrWaves = []; 279 | for (let i = 0; i < nr; i++) {nrWaves.push({});} 280 | return nrWaves; 281 | } 282 | 283 | function restartWave(d,i){ 284 | if (i === (nrWaves.length - 1)) { // restart after last wave is finished 285 | let nrWaves2 = initializeWaves(4); 286 | c.wave = c.wave.data(nrWaves2); 287 | c.wave.attr("d", arc); 288 | moveWave(); 289 | } 290 | } 291 | 292 | function arcTween() { 293 | return function(d) { 294 | if (sel.classed("drawn")){ 295 | c.wave.interrupt(); 296 | console.log("waves interrupted"); 297 | return; 298 | } 299 | var interpolate = d3.interpolate(0, 100); 300 | return function(t) { 301 | d.innerRadius = interpolate(t); 302 | d.outerRadius = interpolate(t) + 3; 303 | return arc(d); 304 | }; 305 | }; 306 | } 307 | 308 | // add preview notice 309 | c.controls = sel.append("div") 310 | .attr("class", "controls") 311 | .call(applyMargin) 312 | .style("padding-left", c.x(minX) + "px"); 313 | 314 | c.controls.append("span") 315 | .style("left", xTextStart + "px") 316 | .style("top", yTextStart + "px") 317 | .append("div") 318 | .attr("class", "globals-drawLine update-font") 319 | .text(globals.drawLine); 320 | 321 | if (typeof question.referenceValues !== "undefined") { 322 | addReferenceLines (c.controls, c.svg, question.referenceValues, c); 323 | } 324 | 325 | // make chart 326 | const charts = periods.map((entry, key) => { 327 | const lower = key > 0 ? periods[key - 1].year : minX; 328 | const upper = entry.year; 329 | 330 | // segment title 331 | var t = c.titles.append("span") 332 | .style("left", Math.ceil(c.x(lower) + 1) + "px") 333 | .style("width", Math.floor(c.x(upper) - c.x(lower) - 1) + "px"); 334 | t.append("div") 335 | .attr("class", "globals-drawAreaTitle update-font") 336 | .text(entry.title); 337 | 338 | // assign prediction period to variable to use it later in interactionHandler 339 | if (key === 1) { 340 | c.predictionTitle = t; 341 | } 342 | 343 | return drawChart(lower, upper, entry.class); 344 | }); 345 | 346 | const resultChart = charts[charts.length - 1][0]; 347 | const resultClip = c.charts.append("clipPath") 348 | .attr("id", `result-clip-${key}`) 349 | .append("rect") 350 | .attr("width", c.x(lastPointShownAtIndex)) 351 | .attr("height", c.height); 352 | const resultLabel = charts[charts.length - 1].slice(1, 3); 353 | resultChart.attr("clip-path", `url(#result-clip-${key})`) 354 | .append("rect") 355 | .attr("width", c.width) 356 | .attr("height", c.height) 357 | .attr("fill", "none"); 358 | resultLabel.map(e => e.style("opacity", 0)); 359 | 360 | // Interactive user selection part 361 | const userLine = d3.line().x(ƒ("year", c.x)).y(ƒ("value", c.y)).curve(d3.curveMonotoneX); 362 | 363 | if(!state.get(key, yourData)){ 364 | const val = data.map(d => ({year: d.year, value: indexedData[lastPointShownAtIndex], defined: 0})) 365 | .filter(d => { 366 | if (d.year == lastPointShownAtIndex) d.defined = true; 367 | return d.year >= lastPointShownAtIndex; 368 | }); 369 | state.set(key, "yourData", val); 370 | } 371 | 372 | const resultSection = d3.select(".result." + key); 373 | 374 | const drawUserLine = function(year) { 375 | userSel.attr("d", userLine.defined(ƒ("defined"))(state.get(key, yourData))); 376 | const d = state.get(key, yourData).filter(d => d.year === year)[0]; 377 | const dDefined = state.get(key, yourData).filter(d => d.defined && (d.year !== lastPointShownAtIndex)); 378 | 379 | if(!d.defined) { 380 | return; 381 | } 382 | 383 | const dot = c.dots.selectAll("circle.result") 384 | .data(dDefined); 385 | dot.enter() 386 | .append("circle") 387 | .merge(dot) 388 | .attr("r", 4.5) 389 | .attr("cx", de => c.x(de.year)) 390 | .attr("cy", de => c.y(de.value)) 391 | .attr("class", "result"); 392 | 393 | const yourResult = c.labels.selectAll(".your-result") 394 | .data([d]); 395 | yourResult.enter() 396 | .append("div") 397 | .classed("data-label your-result", true) 398 | .classed("edge-right", isMobile) 399 | .merge(yourResult) 400 | .style("z-index", () => (year === lastPointShownAtIndex) ? 1 : 2) // should always be != , z-index=2 401 | .style("left", () => c.x(year) + "px") 402 | .style("top", r => c.y(r.value) + "px") 403 | .html("") 404 | .append("span") 405 | .append("div") 406 | .attr("class", "question-label update-font") 407 | .text(r => question.precision ? formatValue(r.value, question.unit, question.precision) 408 | : formatValue(r.value, question.unit, question.precision, 0)); 409 | }; 410 | drawUserLine(lastPointShownAtIndex); 411 | 412 | const interactionHandler = function() { 413 | if (state.get(key, resultShown)) { 414 | return; 415 | } 416 | 417 | sel.node().classList.add("drawn"); 418 | 419 | const pos = d3.mouse(c.svg.node()); 420 | // if (pos[1] < margin.top + 4) { return; } 421 | if (pos[1] < 0) { return; } 422 | const year = clamp(lastPointShownAtIndex, maxX, c.x.invert(pos[0])); 423 | const value = clamp(c.y.domain()[0], c.y.domain()[1], c.y.invert(pos[1])); 424 | let yearPoint = lastPointShownAtIndex; 425 | 426 | state.get(key, yourData).forEach(d => { 427 | if(d.year > lastPointShownAtIndex) { 428 | if(Math.abs(d.year - year) < .5) { 429 | d.value = value; 430 | yearPoint = d.year; 431 | } 432 | if(d.year - year < 0.5) { 433 | d.defined = true; 434 | yearPoint = d.year; 435 | } 436 | } 437 | }); 438 | 439 | if (pos[1] < heightCap) { c.predictionTitle.style("opacity", 0); 440 | } else if (pos[1] >= heightCap) { c.predictionTitle.style("opacity", 1); } 441 | 442 | drawUserLine(yearPoint); 443 | 444 | if (!state.get(key, completed) && d3.mean(state.get(key, yourData), ƒ("defined")) == 1) { 445 | state.set(key, completed, true); 446 | resultSection.node().classList.add("finished"); 447 | resultSection.select("button").node().removeAttribute("disabled"); 448 | } 449 | }; 450 | 451 | c.svg.call(d3.drag().on("drag", interactionHandler)); 452 | c.svg.on("click", interactionHandler); 453 | 454 | const showResultChart = function () { 455 | if(!state.get( key, completed)) { 456 | return; 457 | } 458 | c.labels.selectAll(".your-result").node().classList.add("hideLabels"); 459 | resultClip.transition() 460 | .duration(700) 461 | .attr("width", c.x(maxX)); 462 | dragArea.attr("class", ""); 463 | resultLabel[0] 464 | .transition() 465 | .duration(30) 466 | .delay((d,i) => (i + 1) / resultLabel[0].size() * 700) 467 | .style("opacity", 1); 468 | 469 | setTimeout(() => { 470 | resultLabel.map(e => e.style("opacity", 1)); 471 | resultSection.node().classList.add("shown"); 472 | 473 | if (!state.get(key, score) && globals.showScore) { 474 | const truth = data.filter(d => d.year > lastPointShownAtIndex); 475 | getScore(key, truth, state, graphMaxY, graphMinY, resultSection, 476 | globals.scoreTitle, globals.scoreButtonText, globals.scoreButtonTooltip, globals.scoreHtml); 477 | } 478 | state.set(key, resultShown, true); 479 | }, 700); 480 | }; 481 | resultSection.select("button").on("click", showResultChart); 482 | if (state.get(key, resultShown)) { 483 | showResultChart(); 484 | } 485 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # you-draw-it 2 | ![Image of you-draw-it bar chart](https://github.com/EE2dev/you-draw-it/blob/master/images/bar.png)![Image of you-draw-it line chart](https://github.com/EE2dev/you-draw-it/blob/master/images/line.png)![Image of you-draw-it multiple choice chart](https://github.com/EE2dev/you-draw-it/blob/master/images/multipleChoice.png) 3 | 4 | You-draw-it lets you configure a quiz with questions. This quiz supports answers for a single value, sequence of values and multiple choice. The user can specify the numeric answers (single number or sequence of values, e.g. time series) by drawing interactively. 5 | 6 | ### Credits 7 | - This you-draw-it implementation is adapted from the great work at https://github.com/wdr-data/you-draw-it 8 | - Original idea developed by [the New York Times](https://www.nytimes.com/interactive/2015/05/28/upshot/you-draw-it-how-family-income-affects-childrens-college-chances.html) 9 | - The visualization and interaction features were implemented using the amazing library [d3.js](https://d3js.org/) 10 | 11 | ### Examples 12 | - [fun facts](https://bl.ocks.org/EE2dev/8cc9d3a19df00f30cf011a8fd5f3d7e4/) 13 | - [datasketches](https://bl.ocks.org/ee2dev/fef9374c83cd2d860e52ca392ea22bf5) 14 | - [minecraft](https://bl.ocks.org/EE2dev/d2fe539e84c7fa27566bf4c1a1b16eeb/) 15 | - [minecraft - custom font](https://bl.ocks.org/EE2dev/17460b7600768ca9aca47090f0b85bd4/) 16 | - [newspaper article in rtbf in French](https://www.rtbf.be/info/societe/detail_vacances-salaire-vos-prejuges-sur-les-profs-correspondent-ils-a-la-realite?id=10011363) 17 | - [example with reference values](https://bl.ocks.org/EE2dev/93c813e9099a2783235883f00ebbb9f1) 18 | - [example with individually styled reference values](https://bl.ocks.org/ee2dev/9d2f6823eb9914578bf1480f2a88902d) 19 | - [example with multiple choice questions](https://bl.ocks.org/EE2dev/f0cd67b9b68d79f5824af8656254f5e7/) 20 | 21 | ### Templates 22 | - [English-bare template](https://bl.ocks.org/ee2dev/9e1984c29d946b2912beb62df680ff9a) 23 | - [English template](https://bl.ocks.org/ee2dev/5e553c5b50d2b12d2d3d707c89c849f2) 24 | - [German template](https://bl.ocks.org/ee2dev/e085741d2376c4c12800c855f381266d) 25 | 26 | 27 | ## 1. How to use you-draw-it 28 | ### 1.1 Using you-draw-it (with internet connection) 29 | The easiest way to start off is to create an html file with the following content: 30 | ``` 31 | 32 | 33 | 34 | 35 | My quiz 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 84 | 85 | 86 | ``` 87 | ### 1.2 Using you-draw-it (local version - no internet required) 88 | All you need to do is 89 | 1. download the zipped repository by clicking on the green button![button image](images/button.jpg) at the top part of the repository page and select ![download zip image](images/dz.jpg) 90 | 2. unzip the downloaded repository 91 | 3. navigate to the *dist* directory 92 | 4. open one of the three templates in a browser (e.g. templateEnglish.html) to see it running locally 93 | 5. make a copy of the template and adjust/add/remove the questions and answers in a text editor 94 | 95 | ### 1.3 General usage 96 | In the javascript portion three variables are defined: 97 | - `questions` which is an *array* of `question` *objects*. 98 | - `question` which is an *object* containing all infos about that question 99 | - `globals` which is an *object* containing global settings, mainly *text* or *html* strings, which are displayed during the course of the quiz. 100 | 101 | All you need to do is 102 | 1. to adjust the two properties of each `question` 103 | - `heading` refering to a *string* with one particular quiz question. This *string* can contain *text* or *html* (in case want to format your question in a certain way). 104 | - `data` refering to the value or values of the correct answer. 105 | - In case a single value is the answer (which is represented by a bar chart), `data` has to be initialized with the correct *number*. 106 | - In case a sequence of values is the answer (which is represented by a line chart), `data` has to be initialized by an *array* of *objects*. Each *object* is a point in the sequence and has to be initialized by a key (which will be the x coordinate) and its value (which will be the y coordinate) 107 | - In case multiple choice (of text) is the answer, `data` has to be initialized by an *array* of *objects*. Each *object* is a possible answer and has to be initialized by a key (which will be the possible answer) and a *boolean* value (which indicates if the answer is correct) 108 | 2. to add more `question`'s you can simply copy the block commented with ... `copy FROM here` until ... `copy TO here`, adjust the properties and you are ready to go! 109 | 110 | For specific adjustments take a look at [tips & tricks](https://github.com/EE2dev/you-draw-it#14-tips--tricks) and [the API reference](https://github.com/EE2dev/you-draw-it#2-api-reference). 111 | 112 | ### 1.4 Tips & tricks 113 | - **number of digits** 114 | It is recommended using at most 4 digits for any value. The value is displayed with all thousands as well as the decimal separator. The number of displayed digits after the decimal spearator can be specified with [`question.precision`](https://github.com/EE2dev/you-draw-it#q-precision) 115 | - ***text* vs *html*** 116 | The following options can be set with either *text* or *html*: 117 | - [`globals.header`](#g-header) 118 | - [`globals.subHeader`](#g-subHeader) 119 | - [`globals.scoreHtml`](#g-scoreHtml) 120 | - [`question.heading`](#q-heading) 121 | - [`question.subHeading`](#q-subHeading) 122 | - [`question.resultHtml`](#q-resultHtml) 123 | 124 | - **final score** 125 | You can add a *text* or *html* after the final score is shown. In addition you can show a different *text* or *html* based on the final score. See [`globals.scoreHtml`](#g-scoreHtml) for details. 126 | - **using a different font** 127 | See section [Using a different font](#30-using-a-different-font) 128 | - **template** 129 | You can use [this template](https://bl.ocks.org/ee2dev/5e553c5b50d2b12d2d3d707c89c849f2) which lists all `globals` and `question` options. 130 | 131 | ## 2. API Reference 132 | 133 | ### 2.1 A visual reference to the configuration options 134 | 135 | [![visual reference of the configuration options](https://github.com/EE2dev/you-draw-it/blob/master/images/visualReference_small.jpg)](https://github.com/EE2dev/you-draw-it/tree/master/visualReference) 136 | 137 | [Click here](https://github.com/EE2dev/you-draw-it/tree/master/visualReference) to the see the visual reference to the configuration options. 138 | 139 | ### 2.2 The configuration object `globals` 140 | 141 | # globals.default 142 | 143 | Sets the several undefined properties of `globals` to default values. If a property is already defined, it is not overridden. 144 | Three values are currently supported: 145 | - globals.default = "en". 146 | Initialization with English defaults. In addition, the thousands separator is set to `,`(comma) and the decimal separator to `.`(dot). The English default is also applied if no default is specified. 147 | ``` 148 | // globals.default = "en" applies the following initialization: 149 | var globals = { 150 | default: "en", 151 | resultButtonText: "Show me the result!", 152 | resultButtonTooltip: "Draw your guess. Upon clicking here, you see if you're right.", 153 | scoreTitle: "Your result:", 154 | scoreButtonText: "Show me how good I am!", 155 | scoreButtonTooltip: "Click here to see your result", 156 | drawAreaTitle: "Your\nguess", 157 | drawLine: "draw the graph\nfrom here to the end", 158 | drawBar: "drag the bar\nto the estimated height", 159 | }; 160 | ``` 161 | - globals.default = "de". 162 | Initialization with German defaults. In addition, the thousands separator is set to `.`(dot) and the decimal separator to `,`(comma). 163 | ``` 164 | // globals.default = "de" applies the following initialization: 165 | var globals = { 166 | default: "de", 167 | resultButtonText: "Zeig mir die Lösung!", 168 | resultButtonTooltip: "Zeichnen Sie Ihre Einschätzung. Der Klick verrät, ob sie stimmt.", 169 | scoreTitle: "Ihr Ergebnis:", 170 | scoreButtonText: "Zeig mir, wie gut ich war!", 171 | scoreButtonTooltip: "Klicken Sie hier, um Ihr Gesamtergebnis zu sehen"", 172 | drawAreaTitle: "Ihre\nEinschätzung", 173 | drawLine: "Zeichnen Sie von hier\nden Verlauf zu Ende", 174 | drawBar: "Ziehen Sie den Balken\nauf die entsprechende Höhe", 175 | }; 176 | ``` 177 | 178 | - globals.default = "fr". 179 | Initialization with French defaults. In addition, the thousands separator is set to ` `(space) and the decimal separator to `,`(comma). Shout-out to Ambroise Carton for the translation! 180 | 181 | ``` 182 | // globals.default = "fr" applies the following initialization: 183 | var globals = { 184 | default: "fr", 185 | resultButtonText: "Montrez-moi le résultat", 186 | resultButtonTooltip: "A vous de dessiner la courbe. Pour voir la bonne réponse, cliquez ici", 187 | scoreTitle: "Votre résultat:", 188 | scoreButtonText: "Montrez-moi la bonne réponse", 189 | scoreButtonTooltip: "Cliquez ici pour obtenir des explications", 190 | drawAreaTitle: "Votre supposition", 191 | drawLine: "Placez votre doigt ou votre souris ici et dessinez la courbe", 192 | drawBar: "Montez la barre jusqu’à la hauteur supposée", 193 | }; 194 | ``` 195 | 196 | # globals.header 197 | 198 | Sets the *text* or *html* containing the header (= first line of the quiz). 199 | 200 | # globals.subHeader 201 | 202 | Sets the *text* or *html* containing the subHeader (= second line of the quiz). 203 | 204 | # globals.drawAreaTitle 205 | 206 | Sets the *text* denoting the area to be drawn by the user. 207 | 208 | # globals.drawLine 209 | 210 | Sets the *text* denoting the call to action for the user to continue drawing the line chart. 211 | 212 | # globals.drawBar 213 | 214 | Sets the *text* denoting the call to action for the user to adjusting the bar chart. 215 | 216 | # globals.resultButtonText 217 | 218 | Sets the *text* denoting the button to reveal the correct answer. 219 | 220 | # globals.resultButtonTooltip 221 | 222 | Sets the *text* denoting the tooltip of the button to reveal the correct answer. 223 | 224 | # globals.showScore 225 | 226 | Determines if the score evaluation should be displayed or not. Default is *true*. 227 | 228 | # globals.scoreButtonText 229 | 230 | Sets the *text* denoting the button to reveal the total score. 231 | 232 | # globals.scoreButtonTooltip 233 | 234 | Sets the *text* denoting the tooltip of the button to reveal the total score. 235 | 236 | # globals.scoreTitle 237 | 238 | Sets the *text* denoting the the headline on top of the score evaluation. 239 | 240 | # globals.scoreHtml 241 | 242 | There are two ways to specify that property: 243 | 1. specify a *text* or *html* for any score: 244 | ``` 245 | var globals = { 246 | ... 247 | scoreHtml: "Next time you can do better!", 248 | }; 249 | ``` 250 | Sets the *text* or *html* shown after the total score is revealed. 251 | 252 | 2. specify a *text* or *html* depending on the score: 253 | ``` 254 | var globals = { 255 | ... 256 | scoreHtml: [{lower: 0, upper: 50, html: "That wasn't much, was it??"}, 257 | {lower: 50, upper: 101, html: "Excellent!!"}], 258 | }; 259 | ``` 260 | Sets the *text* or *html* based on the score. In this case, `globals.scoreHtml` is an `array` of `objects`. The array contains as many objects as there are intervalls. Each `object` defines the intervall with its lower and upper bound. It also contains an html property for the *text* or *html* to be displayed in this case. 261 | 262 | 263 | ### 2.3 The configuration object `question` 264 | 265 | # question.data 266 | 267 | Sets the value/ values which is/are the correct response for the question. 268 | - In case a single value is the answer (which is re presented by a bar chart), `data` has to be initialized with the correct *number*. 269 | - In case a sequence of values is the answer (which is represented by a line chart), `data` has to be initialized by an *array* of *objects*. Each *object* is a point in the sequence and has to be initialized by a key (which will be the x coordinate) and its value (which will be the y coordinate) 270 | - In case multiple choice (of text) is the answer, `data` has to be initialized by an *array* of *objects*. Each *object* is a possible answer and has to be initialized by a key (which will be the possible answer) and a *boolean* value (which indicates if the answer is correct) 271 | 272 | Note that the decimal separator has to denoted by a `.`(dot). The display, however, can be modified with `globals.default` (`.`(dot) vs `,`(comma)) 273 | 274 | ``` 275 | // examples for setting question.data 276 | question = { 277 | ... 278 | // data for single value looks like this: 279 | data: 385, // or 280 | data: 2.545, // or 281 | 282 | // data for a sequence of values looks like this: 283 | data: [ 284 | {"1998 (JP)": 32000}, 285 | {"2002 (US)": 22000}, 286 | {"2006 (IT)": 18000}, 287 | {"2010 (CA)": 18500}, 288 | {"2014 (RU)": 25000}, 289 | {"2018 (CN)": 22400}, 290 | ], // or 291 | 292 | data: [ 293 | {"Mon": 32.0}, 294 | {"Tue": 20.2}, 295 | {"Wed": 18.7}, 296 | {"Thu": 18.3}, 297 | {"Fri": 25.2}, 298 | {"Sat": 22.1}, 299 | {"Son": 22.9}, 300 | ], //or 301 | 302 | // data for multiple choice questions look like this: 303 | data: [ 304 | {"San Francisco": false}, 305 | {"Boston": false}, 306 | {"New York": false}, 307 | {"Paris": true}, 308 | ], // or 309 | 310 | data: [ 311 | {"Mike Bostock": false}, 312 | {"Jim Vallandingham": true}, 313 | {"Amanda Cox": false}, 314 | ], 315 | ...}; 316 | ``` 317 | 318 | # question.heading 319 | 320 | Sets the *text* or *html* containing the question. 321 | 322 | # question.subHeading 323 | 324 | Sets the *text* or *html* below the `heading`. 325 | 326 | # question.resultHtml 327 | 328 | Sets the *text* or *html* after the user has drawn his guess and the correct result is shown. 329 | 330 | # question.unit 331 | 332 | Sets a *string* which is attached to the values of the y axis and the label of the tooltip when the user makes. 333 | 334 | ``` 335 | // examples for setting question.unit 336 | question = { 337 | ... 338 | unit: "sec", // or 339 | unit: "Mio", // or 340 | unit: "US$", // or 341 | unit: "€", 342 | ...}; 343 | ``` 344 | 345 | # question.precision 346 | 347 | Sets the number of decimal places. The default is 1. 348 | 349 | # question.lastPointShownAt 350 | 351 | Determines the last point shown for the line chart. The user guesses start from the next point. 352 | Default value is the next to last point in the sequence. This leaves the user to guess just the last point. Any point but the last can be specified. 353 | Is irrelevant for the bar chart. 354 | 355 | # question.yAxisMin 356 | 357 | Sets the lowest value for the y axis. 358 | Default value is: 359 | - 0, if all values are positive numbers 360 | - Min(value) - *random number* * Min(value), if min(values) is a negative number. *random number* is a number between 0.4 and 1.0. 361 | 362 | # question.yAxisMax 363 | 364 | Sets the highest value for the y axis. 365 | Default value is: 366 | - Max(value) + *random number* * Max(value). *random number* is a number between 0.4 and 1.0. 367 | 368 | # question.referenceValues 369 | 370 | Sets reference values to be shown before the interaction starts. 371 | 372 | For the bar chart: `referenceValues` has to be initialized by an *array* of *objects*. Each *object* has two properties: 373 | 1. `text` (containing the label) 374 | 2. `value` (which contains the y coordinate). 375 | 376 | E.g.: 377 | ``` 378 | question = { 379 | ... 380 | referenceValues: [ 381 | {text: "right-handers (12.0 sec)", value: 12}, 382 | {text: "right-handers2 (12.5 sec)", value: 12.5}, 383 | {text: "right-handers3 (13 sec)", value: 13}, 384 | ], 385 | ``` 386 | 387 | `referenceValues` for the bar chart can be styled as follows: 388 | ``` 389 | 395 | ``` 396 | 397 | For the line chart: `referenceValues` has to be initialized by an *array* of *objects*. Each *object* has three properties: 398 | 1. `text` (containing the label) 399 | 2. `textPosition` optional (which contains the offset on the path. Possible values are: `start`, `middle` and `end`. The default is `middle`) 400 | 3. `value` (which contains an *array* with the data for the line) 401 | 402 | E.g.: 403 | ``` 404 | question = { 405 | ... 406 | referenceValues: [ 407 | {text: "myLine", textPosition: "end", value: [ 408 | {"1998": 12000}, 409 | {"2002": 12000}, 410 | {"2006": 8000}, 411 | {"2010": 8500}, 412 | {"2014": 15000}, 413 | {"2018": 2400}, 414 | ] 415 | }, 416 | {text: "your Line", textPosition: "start", value: [ 417 | {"2010": 28500}, 418 | {"2014": 5000}, 419 | {"2018": 16400}, 420 | ] 421 | }, 422 | ], 423 | ``` 424 | 425 | `referenceValues` for the line chart can be styled as follows: 426 | ``` 427 | 433 | ``` 434 | The selector for `referenceValues` for the line chart is `.question-referenceValues.referenceLine`. 435 | In case you want to style each line separately you the class `.line-`*the name of your line*. Any whitespaces in your name (= the text property) will be trimmed. 436 | 437 | # question.referenceShape 438 | 439 | Sets the shape of the reference for the bar chart (single value). 440 | Default value is "line", only alternative is "tick" which prevents overlapping. 441 | 442 | ## 3.0 Using a different font 443 | 444 | The font of the quiz can be changed in the `````` of the html document by 445 | 1. importing the new font 446 | 2. assigning the font to the desired text elements 447 | 448 | Three possible choices are: 449 | ### 3.1 Using a different font for all text elements 450 | 451 | ``` 452 | 453 | ... 454 | 455 | 461 | 462 | ``` 463 | 464 | ### 3.2 Using a different font for all text elements but the axes 465 | 466 | ``` 467 | 468 | ... 469 | 470 | 476 | 477 | ``` 478 | 479 | ### 3.3 Using a different font for just the call-to-action text elements 480 | 481 | ``` 482 | 483 | ... 484 | 485 | 491 | 492 | ``` 493 | 494 | ### 3.4 Table with the css classes of the text elements 495 | 496 | You can CSS style any text element by applying class selectors as referenced below: 497 | 498 | | object key | class1 | class2 | 499 | | ------------- |:-------------|:-----| 500 | | globals.header | globals-header | update-font | 501 | | globals.subHeader | globals-subHeader | update-font | 502 | | globals.resultButtonText | globals-resultButtonText | update-font | 503 | | globals.resultButtonTooltip | globals-resultButtonTooltip | update-font | 504 | | globals.drawAreaTitle | globals-drawAreaTitle | update-font | 505 | | globals.drawLine | globals-drawLine | update-font | 506 | | globals.drawBar | globals-drawBar | update-font | 507 | | globals.scoreButtonText | globals-scoreButtonText | update-font | 508 | | globals.scoreButtonTooltip | globals-scoreButtonTooltip | update-font | 509 | | globals.scoreTitle | globals-scoreTitle | update-font | 510 | | globals.scoreHtml | globals-scoreHtml | update-font | 511 | | () → .scoreText | globals-scoreText | update-font | 512 | | question.heading | question-heading | update-font | 513 | | question.subHeading | question-subHeading | update-font | 514 | | question.resultHtml | question-resultHtml | update-font | 515 | | question.referenceValues | question-referenceValues | update-font | 516 | | (question.unit) → label | question-label | update-font | 517 | | (multiple choice question) → label | question-multipleChoice | update-font | 518 | 519 | ## 4. Calculating the final score 520 | The final score is calculated as follows: 521 | - for each question a score is calculated by 522 | - (if line chart) 523 | - compute the max possible distance for all points 524 | - give score (0 - 100) for this question based on absolute distances 525 | - (if bar chart) 526 | - compute the max possible distance 527 | - give score (0 - 100) for this question based on absolute distance 528 | - final score is mean of all individual scores 529 | 530 | ## 5. License 531 | This code is released under the [BSD license](https://github.com/EE2dev/you-draw-it//blob/master/LICENSE). 532 | -------------------------------------------------------------------------------- /dist/css/style.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html { 2 | font-family: sans-serif; 3 | line-height: 1.15; 4 | -ms-text-size-adjust: 100%; 5 | -webkit-text-size-adjust: 100%} 6 | body { 7 | margin: 0; 8 | } 9 | article, aside, footer, header, nav, section { 10 | display: block; 11 | } 12 | h1 { 13 | font-size: 2em; 14 | margin: .67em 0; 15 | } 16 | figcaption, figure, main { 17 | display: block; 18 | } 19 | figure { 20 | margin: 1em 40px; 21 | } 22 | hr { 23 | box-sizing: content-box; 24 | height: 0; 25 | overflow: visible; 26 | } 27 | pre { 28 | font-family: monospace, monospace; 29 | font-size: 1em; 30 | } 31 | a { 32 | background-color: transparent; 33 | -webkit-text-decoration-skip: objects; 34 | } 35 | a:active, a:hover { 36 | outline-width: 0; 37 | } 38 | abbr[title] { 39 | border-bottom: none; 40 | text-decoration: underline; 41 | text-decoration: underline dotted; 42 | } 43 | b, strong { 44 | font-weight: inherit; 45 | } 46 | b, strong { 47 | font-weight: bolder; 48 | } 49 | code, kbd, samp { 50 | font-family: monospace, monospace; 51 | font-size: 1em; 52 | } 53 | dfn { 54 | font-style: italic; 55 | } 56 | mark { 57 | background-color: #ff0; 58 | color: #000; 59 | } 60 | small { 61 | font-size: 80%} 62 | sub, sup { 63 | font-size: 75%; 64 | line-height: 0; 65 | position: relative; 66 | vertical-align: baseline; 67 | } 68 | sub { 69 | bottom: -.25em; 70 | } 71 | sup { 72 | top: -.5em; 73 | } 74 | audio, video { 75 | display: inline-block; 76 | } 77 | audio:not([controls]) { 78 | display: none; 79 | height: 0; 80 | } 81 | img { 82 | border-style: none; 83 | } 84 | svg:not(:root) { 85 | overflow: hidden; 86 | } 87 | /* 88 | button, input, optgroup, select, textarea { 89 | font-family: sans-serif; 90 | font-size: 100%; 91 | line-height: 1.15; 92 | margin: 0; 93 | } 94 | */ 95 | button, input { 96 | overflow: visible; 97 | } 98 | button, select { 99 | text-transform: none; 100 | } 101 | [type=reset], [type=submit], button, html [type=button] { 102 | -webkit-appearance: button; 103 | } 104 | [type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner { 105 | border-style: none; 106 | padding: 0; 107 | } 108 | [type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring { 109 | outline: 1px dotted ButtonText; 110 | } 111 | fieldset { 112 | border: 1px solid silver; 113 | margin: 0 2px; 114 | padding: .35em .625em .75em; 115 | } 116 | legend { 117 | box-sizing: border-box; 118 | color: inherit; 119 | display: table; 120 | max-width: 100%; 121 | padding: 0; 122 | white-space: normal; 123 | } 124 | progress { 125 | display: inline-block; 126 | vertical-align: baseline; 127 | } 128 | textarea { 129 | overflow: auto; 130 | } 131 | [type=checkbox], [type=radio] { 132 | box-sizing: border-box; 133 | padding: 0; 134 | } 135 | [type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button { 136 | height: auto; 137 | } 138 | [type=search] { 139 | -webkit-appearance: textfield; 140 | outline-offset: -2px; 141 | } 142 | [type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration { 143 | -webkit-appearance: none; 144 | } 145 | ::-webkit-file-upload-button { 146 | -webkit-appearance: button; 147 | font: inherit; 148 | } 149 | details, menu { 150 | display: block; 151 | } 152 | summary { 153 | display: list-item; 154 | } 155 | canvas { 156 | display: inline-block; 157 | } 158 | template { 159 | display: none; 160 | } 161 | [hidden] { 162 | display: none; 163 | } 164 | 165 | * { 166 | box-sizing: border-box; 167 | } 168 | .icon { 169 | line-height: 36px; 170 | margin-left: 40px; 171 | } 172 | .icon:before { 173 | font-size: 36px; 174 | height: 36px; 175 | width: 36px; 176 | margin-left: -40px; 177 | } 178 | .icon:before { 179 | color: #fdfdfc; 180 | font-family: "WDR Iconfont", helvetica, arial, sans-serif; 181 | text-decoration: none!important; 182 | position: absolute; 183 | font-weight: 400; 184 | display: inline-block; 185 | } 186 | .icon.weather:before { 187 | content: "w"} 188 | .icon.traffic:before { 189 | content: "v"} 190 | .icon.search:before { 191 | content: "s"} 192 | .icon.hamburger:before { 193 | content: "h"} 194 | .icon.arrow-right:before { 195 | content: ">"} 196 | 197 | body { 198 | font-family: Thesis, Helvetica, Arial, sans-serif; 199 | /* font-family: 'Indie Flower', cursive; 200 | background: #f0f0f0;*/ 201 | } 202 | /* 203 | .controls, button, .tooltipcontainer { 204 | font-family: 'Indie Flower', cursive; 205 | } */ 206 | 207 | h1 { 208 | font-size: 25px; 209 | margin: 10px 0 15px 0; 210 | } 211 | h2 { 212 | font-size: 20px; 213 | text-align: center; 214 | margin: 8px 0 10px 0; 215 | } 216 | p { 217 | line-height: 1.3; 218 | } 219 | a { 220 | text-decoration: none; 221 | color: #00345e; 222 | } 223 | a:hover { 224 | text-decoration: underline; 225 | } 226 | /* 227 | ul { 228 | list-style: none; 229 | margin: 0; 230 | padding: 0; 231 | }*/ 232 | .question .result .text ul { 233 | text-align: left; 234 | } 235 | .hidden { 236 | display: none!important; 237 | } 238 | .pull-right { 239 | float: right; 240 | } 241 | .container { 242 | max-width: 996px; 243 | margin-left: auto; 244 | margin-right: auto; 245 | } 246 | #header { 247 | background: #00345e; 248 | overflow: hidden; 249 | padding-top: 25px; 250 | color: #fff; 251 | font-family: WDRSlab, serif; 252 | } 253 | #header ul { 254 | margin: 0; 255 | padding: 0; 256 | list-style: none; 257 | } 258 | #header ::placeholder { 259 | font-family: WDRSlab, serif; 260 | } 261 | #header .logo { 262 | display: inline-block; 263 | } 264 | #header .servicenavi { 265 | display: inline-block; 266 | } 267 | #header .servicenavi li { 268 | display: inline-block; 269 | border-radius: 2px; 270 | } 271 | #header .servicenavi li:hover { 272 | background: #fff; 273 | color: #00345e; 274 | } 275 | #header .servicenavi li:hover .icon:before { 276 | color: #00345e; 277 | } 278 | #header .servicenavi li a { 279 | padding: 5px; 280 | color: inherit; 281 | text-decoration: none; 282 | margin-right: 10px; 283 | display: inline-block; 284 | } 285 | #header .searchBox { 286 | display: inline-block; 287 | margin-left: 20px; 288 | } 289 | #header .searchBox form { 290 | display: inline-block; 291 | border-radius: 3px; 292 | background: #fff; 293 | color: #000; 294 | padding: 10px; 295 | font-size: 16px; 296 | } 297 | #header .searchBox form input { 298 | border: none; 299 | outline: 0; 300 | width: 230px; 301 | } 302 | #header .searchBox form button { 303 | border: none; 304 | background: 0 0; 305 | padding: 0; 306 | cursor: pointer; 307 | } 308 | #header .searchBox form button:before { 309 | color: #00345e; 310 | margin-top: -24px; 311 | margin-left: -30px; 312 | font-size: 30px; 313 | } 314 | #header .mobileButtons { 315 | display: none; 316 | } 317 | #header .mobileButtons .mobileButton { 318 | display: inline-block; 319 | width: 0; 320 | overflow: hidden; 321 | height: 48px; 322 | cursor: pointer; 323 | line-height: 26px; 324 | margin-left: 30px; 325 | margin-left: 48px; 326 | } 327 | #header .mobileButtons .mobileButton:before { 328 | font-size: 26px; 329 | height: 26px; 330 | width: 26px; 331 | margin-left: -30px; 332 | } 333 | #header .mobileButtons .mobileButton:before { 334 | margin-top: -2px; 335 | margin-left: -44px; 336 | background: #fff; 337 | color: #00345e; 338 | padding: 8px; 339 | border-radius: 3px; 340 | border: 1px solid transparent; 341 | } 342 | #header #searchState:checked~.mobileButtons .searchToggle:before { 343 | border: 1px solid #fff; 344 | background: 0 0; 345 | color: #fff; 346 | } 347 | #header #naviState:checked~.mobileButtons .naviToggle:before { 348 | border: 1px solid #fff; 349 | background: 0 0; 350 | color: #fff; 351 | } 352 | #header .naviCont { 353 | color: #fff; 354 | font-size: 20px; 355 | margin-top: 35px; 356 | } 357 | #header .naviCont a { 358 | color: inherit; 359 | text-decoration: none; 360 | padding: 6px 10px; 361 | line-height: 1.6; 362 | display: inline-block; 363 | border-radius: 3px 3px 0 0; 364 | } 365 | #header .naviCont a.active, #header .naviCont a:hover { 366 | background: #fff; 367 | color: #00345e; 368 | } 369 | #header .naviCont li { 370 | display: inline-block; 371 | } 372 | #header .naviCont .subnavi { 373 | display: inline-block; 374 | } 375 | #header .naviCont .masternavi { 376 | float: right; 377 | font-weight: 800; 378 | } 379 | @media (max-width:996px) { 380 | #header { 381 | padding-top: 15px; 382 | } 383 | #header .searchBox form input { 384 | width: 170px; 385 | } 386 | #header .naviCont { 387 | margin-top: 15px; 388 | font-size: 18px; 389 | } 390 | #header .naviCont .masternavi, #header .naviCont .subnavi { 391 | display: block; 392 | margin-left: -10px; 393 | margin-right: -10px; 394 | } 395 | #header .naviCont .subnavi a { 396 | border-radius: 3px; 397 | } 398 | #header .naviCont .masternavi { 399 | float: none; 400 | } 401 | }@media (max-width:760px) { 402 | #header .toolsTop { 403 | float: none; 404 | } 405 | #header .servicenavi { 406 | display: none; 407 | } 408 | #header .searchBox { 409 | display: none; 410 | } 411 | #header #searchState:checked~.toolsTop .searchBox { 412 | display: block; 413 | padding: 10px 0; 414 | margin-left: 0; 415 | } 416 | #header #searchState:checked~.toolsTop .searchBox form { 417 | margin-top: 10px; 418 | width: 100%; 419 | display: block; 420 | } 421 | #header #searchState:checked~.toolsTop .searchBox form input { 422 | width: 100%; 423 | padding-right: 36px; 424 | display: block; 425 | } 426 | #header #searchState:checked~.toolsTop .searchBox form button { 427 | float: right; 428 | } 429 | #header #searchState:checked~.toolsTop .searchBox form button:before { 430 | margin-top: -28px; 431 | } 432 | #header .mobileButtons { 433 | display: inline-block; 434 | } 435 | #header .naviCont { 436 | display: none; 437 | } 438 | #header .naviCont .subnavi li { 439 | border-bottom: 1px solid #fff; 440 | } 441 | #header .naviCont .subnavi li a { 442 | border-radius: 0; 443 | } 444 | #header .naviCont ul li { 445 | display: block; 446 | } 447 | #header .naviCont ul li a { 448 | border-radius: 0; 449 | display: block; 450 | padding-left: 15px; 451 | padding-right: 15px; 452 | } 453 | #header #naviState:checked~.naviCont { 454 | display: block; 455 | } 456 | }.you-draw-it { 457 | margin: 30px 0 20px 0; 458 | position: relative; 459 | overflow: hidden; 460 | } 461 | .you-draw-it #gradient-black .start { 462 | stop-color: #000; 463 | stop-opacity: .3; 464 | } 465 | .you-draw-it #gradient-black .end { 466 | stop-color: #000; 467 | stop-opacity: 0; 468 | } 469 | .you-draw-it #gradient-red .start { 470 | stop-color: red; 471 | stop-opacity: .3; 472 | } 473 | .you-draw-it #gradient-red .end { 474 | stop-color: red; 475 | stop-opacity: 0; 476 | } 477 | 478 | .you-draw-it #gradient-blue .start { 479 | stop-color: #00345e; 480 | stop-opacity: .3; 481 | } 482 | .you-draw-it #gradient-blue .end { 483 | stop-color: #00345e; 484 | stop-opacity: 0; 485 | } 486 | .line.blue { 487 | stroke: #00345e; 488 | } 489 | .you-draw-it .labels .data-label.blue span { 490 | background-color: #00345e; 491 | } 492 | .you-draw-it .labels .data-label.blue:after { 493 | border-top-color: #00345e; 494 | } 495 | 496 | .you-draw-it .axis { 497 | opacity: .5; 498 | } 499 | /*.you-draw-it .axis text {*/ 500 | .you-draw-it .axis .tick { 501 | font-size: 15px; 502 | } 503 | .you-draw-it .line { 504 | fill: none; 505 | stroke-width: 3; 506 | } 507 | .you-draw-it .line.black { 508 | stroke: #000; 509 | } 510 | .you-draw-it .line.red { 511 | stroke: red; 512 | } 513 | .you-draw-it .dots circle { 514 | transition: opacity .3s; 515 | } 516 | .you-draw-it .dots circle.median { 517 | cursor: pointer; 518 | } 519 | .you-draw-it .dots circle.black { 520 | fill: #000; 521 | } 522 | .you-draw-it .dots circle.red { 523 | fill: red; 524 | } 525 | .you-draw-it .dots circle.result { 526 | fill: #ff9900; 527 | } 528 | .result .final-score-result { 529 | fill: rgba(255,153,0,0.3); 530 | stroke: #ff9900; 531 | stroke-width: 1px; 532 | } 533 | 534 | .you-draw-it .labels { 535 | position: absolute; 536 | top: 0; 537 | left: 0; 538 | bottom: 0; 539 | right: 0; 540 | pointer-events: none; 541 | } 542 | .you-draw-it .labels .data-label { 543 | position: absolute; 544 | transition: opacity .3s; 545 | z-index: 2; 546 | } 547 | .you-draw-it .labels .data-label span { 548 | position: relative; 549 | margin-top: -43px; 550 | left: -50%; 551 | padding: 5px 15px; 552 | color: #fff; 553 | display: block; 554 | text-align: center; 555 | line-height: 16px; 556 | white-space: nowrap; 557 | font-weight: 700; 558 | } 559 | 560 | .you-draw-it .labels .data-label span.no-dot { 561 | margin-top: -38px; 562 | } 563 | .you-draw-it .labels .data-label.edge-left span { 564 | left: -10px; 565 | } 566 | /* 567 | .you-draw-it .labels .data-label.edge-right span { 568 | left: auto; 569 | right: 86% 570 | } 571 | */ 572 | .you-draw-it .labels .data-label:after { 573 | border: solid transparent; 574 | content: " "; 575 | height: 0; 576 | width: 0; 577 | position: absolute; 578 | pointer-events: none; 579 | border-color: rgba(136, 183, 213, 0); 580 | border-width: 12px 6px; 581 | margin-left: -6px; 582 | } 583 | .you-draw-it .labels .data-label.black span { 584 | background-color: #000; 585 | } 586 | .you-draw-it .labels .data-label.black:after { 587 | border-top-color: #000; 588 | } 589 | .you-draw-it .labels .data-label.red span { 590 | background-color: red; 591 | } 592 | .you-draw-it .labels .data-label.red:after { 593 | border-top-color: red; 594 | } 595 | .you-draw-it .labels .data-label.your-result { 596 | position: absolute; 597 | z-index: 1; 598 | } 599 | .you-draw-it .labels .data-label.your-result span { 600 | background-color: #ff9900; 601 | } 602 | .you-draw-it .labels .data-label.your-result:after { 603 | border-top-color: #ff9900; 604 | } 605 | 606 | .question .result.finished .tooltipcontainer { 607 | display: none; 608 | } 609 | 610 | .question-referenceValues { 611 | color: grey; 612 | stroke: grey; 613 | } 614 | 615 | .question-referenceValues.referenceLine .referencePath{ 616 | stroke-width: 1; 617 | stroke-dasharray: 5, 5; 618 | } 619 | .you-draw-it .question-referenceValues.referenceLine { 620 | fill: grey; 621 | stroke: grey; 622 | 623 | font-family: Thesis, Helvetica, Arial, sans-serif; 624 | stroke-width: 0.01; 625 | font-size: 14px; 626 | } 627 | 628 | .you-draw-it .wave { 629 | fill: #ff9900; 630 | fill: grey; 631 | } 632 | .you-draw-it .titles { 633 | position: absolute; 634 | top: 0; 635 | left: 0; 636 | bottom: 0; 637 | right: 0; 638 | pointer-events: none; 639 | padding-top: 20px; 640 | } 641 | .you-draw-it .titles span { 642 | display: block; 643 | position: absolute; 644 | text-align: center; 645 | text-transform: uppercase; 646 | white-space: pre-line; 647 | line-height: 1.5; 648 | font-size: 14px; 649 | background-color: #ffe0b3; 650 | color: grey; 651 | } 652 | 653 | .you-draw-it .controls { 654 | position: absolute; 655 | top: 0; 656 | left: 0; 657 | bottom: 0; 658 | right: 0; 659 | pointer-events: none; 660 | text-align: center; 661 | color: #696969; 662 | } 663 | .you-draw-it .controls span { 664 | text-align: left; 665 | white-space: pre-line; 666 | font-size: 14px; 667 | display: block; 668 | position: absolute; 669 | } 670 | 671 | .you-draw-it .controls .box { 672 | margin-top: 230px; 673 | display: inline-block; 674 | border: 2px dotted #696969; 675 | padding: 10px; 676 | } 677 | .you-draw-it .grid .domain { 678 | stroke-width: 0; 679 | } 680 | .you-draw-it .grid .tick line { 681 | stroke-dasharray: 1, 1; 682 | opacity: .2; 683 | } 684 | .you-draw-it .grid .tick line.highlight { 685 | opacity: .7; 686 | } 687 | .you-draw-it .draggable { 688 | cursor: pointer; 689 | } 690 | .you-draw-it .preview-line, .you-draw-it .your-line { 691 | stroke-width: 4; 692 | stroke-dasharray: 1 7; 693 | stroke-linecap: round; 694 | fill: none; 695 | } 696 | 697 | .you-draw-it .your-rect { 698 | fill: orange; 699 | } 700 | 701 | .you-draw-it .truth rect { 702 | fill: #00345e; 703 | } 704 | 705 | .you-draw-it .preview-pointer{ 706 | fill: none; 707 | stroke: #aaa; 708 | stroke-width: 1.5px; 709 | } 710 | .you-draw-it #preview-arrowp{ 711 | fill: #aaa; 712 | } 713 | 714 | .you-draw-it .your-line { 715 | stroke: #ff9900; 716 | } 717 | .you-draw-it .preview-line { 718 | opacity: 0; 719 | stroke: #aaa; 720 | } 721 | .you-draw-it:hover .preview-line { 722 | opacity: 1; 723 | } 724 | .drawn .controls, .hideLabels { 725 | display: none; 726 | } 727 | .drawn:hover .preview-line { 728 | opacity: 0; 729 | } 730 | #preview-arrow path { 731 | fill: #aaa; 732 | } 733 | 734 | /* The container */ 735 | .answer-container { 736 | display: block; 737 | position: relative; 738 | padding-left: 135px; 739 | margin-bottom: 12px; 740 | cursor: pointer; 741 | font-size: 1.17em; 742 | -webkit-user-select: none; 743 | -moz-user-select: none; 744 | -ms-user-select: none; 745 | user-select: none; 746 | } 747 | 748 | /* Hide the browser's default checkbox */ 749 | .answer-container input { 750 | position: absolute; 751 | opacity: 0; 752 | cursor: pointer; 753 | height: 0; 754 | width: 0; 755 | } 756 | 757 | /* Create a custom checkbox */ 758 | .answer-checkmark { 759 | position: absolute; 760 | top: 0; 761 | left: 100px; 762 | height: 20px; /* 25px */ 763 | width: 20px; /* 25px */ 764 | background-color: #eee; 765 | } 766 | 767 | /* On mouse-over, add a grey background color */ 768 | .answer-container:hover input ~ .answer-checkmark { 769 | background-color: #ccc; 770 | } 771 | 772 | /* When the checkbox is checked, add a blue background */ 773 | .answer-container input:checked ~ .answer-checkmark { 774 | background-color: orange; 775 | } 776 | 777 | /* Create the checkmark/indicator (hidden when not checked) */ 778 | .answer-checkmark:after { 779 | content: ""; 780 | position: absolute; 781 | display: none; 782 | } 783 | 784 | /* Show the checkmark when checked */ 785 | .answer-container input:checked ~ .answer-checkmark:after { 786 | display: block; 787 | } 788 | 789 | /* Style the checkmark/indicator */ 790 | .answer-container .answer-checkmark:after { 791 | left: 6px; /* 9px */ 792 | top: 2px; /* 5px */ 793 | width: 5px; 794 | height: 10px; 795 | border: solid white; 796 | border-width: 0 3px 3px 0; 797 | -webkit-transform: rotate(45deg); 798 | -ms-transform: rotate(45deg); 799 | transform: rotate(45deg); 800 | } 801 | 802 | /* Hide the browser's default checkbox 803 | .answer-container div.input { 804 | position: absolute; 805 | opacity: 0; 806 | cursor: pointer; 807 | height: 0; 808 | width: 0; 809 | }*/ 810 | 811 | /* Create a custom checkbox for the true result */ 812 | .answer-checkmark-truth { 813 | position: absolute; 814 | display: block; 815 | top: 0; 816 | left: 75px; 817 | height: 20px; /* 25px */ 818 | width: 20px; /* 25px */ 819 | /* background-color: #eee; */ 820 | } 821 | 822 | /* When the checkbox is checked, add a blue background */ 823 | /* .answer-container input:checked ~ .answer-checkmark-truth { 824 | .answer-container .answer-checkmark-truth.checked { 825 | background-color: #00345e; 826 | } 827 | */ 828 | 829 | /* Show the checkmark when checked */ 830 | /*.answer-container input:checked ~ .answer-checkmark-truth:after {*/ 831 | /* .answer-container .answer-checkmark-truth.checked { 832 | .answer-container .answer-checkmark-truth { 833 | display: block; 834 | } */ 835 | 836 | /* Style the checkmark/indicator 837 | .answer-container .answer-checkmark-truth.checked:after { 838 | left: 6px; 839 | top: 2px; 840 | width: 5px; 841 | height: 10px; 842 | border: solid white; 843 | border-width: 0 3px 3px 0; 844 | -webkit-transform: rotate(45deg); 845 | -ms-transform: rotate(45deg); 846 | transform: rotate(45deg); 847 | }*/ 848 | 849 | .answer-container div.input.checked { 850 | display: block; 851 | position: relative; 852 | box-sizing: initial; 853 | left: 6px; /* 9px */ 854 | top: 2px; /* 5px */ 855 | width: 5px; 856 | height: 10px; 857 | border: solid white; 858 | border-width: 0 3px 3px 0; 859 | -webkit-transform: rotate(45deg); 860 | -ms-transform: rotate(45deg); 861 | transform: rotate(45deg); 862 | } 863 | 864 | /* 865 | .breadcrumb { 866 | background-color: #fdfdfc; 867 | margin: 20px 0; 868 | padding: 8px; 869 | display: inline-block; 870 | } 871 | .breadcrumb ul { 872 | width: 100%} 873 | .breadcrumb ul li { 874 | display: inline-block; 875 | width: auto; 876 | height: 20px; 877 | position: relative; 878 | padding: 0; 879 | overflow: visible; 880 | margin: 0 10px 0 0; 881 | color: #1d2124; 882 | text-decoration: none; 883 | line-height: 16px; 884 | margin-left: 20px; 885 | line-height: 20px; 886 | } 887 | .breadcrumb ul li:before { 888 | font-size: 16px; 889 | height: 16px; 890 | width: 16px; 891 | margin-left: -20px; 892 | } 893 | .breadcrumb ul li:before { 894 | color: #00345e; 895 | } 896 | footer { 897 | background-color: #00345e; 898 | padding: 0 15px; 899 | overflow: hidden; 900 | color: #fff; 901 | } 902 | footer a { 903 | color: inherit; 904 | padding: 3px 6px; 905 | display: inline-block; 906 | } 907 | footer .foot { 908 | margin: 24px auto; 909 | overflow: hidden; 910 | } 911 | footer .foot .left { 912 | display: inline-block; 913 | } 914 | footer .foot .left ul { 915 | margin-bottom: 5px; 916 | } 917 | footer .foot .left ul li { 918 | display: inline-block; 919 | margin-right: 18px; 920 | } 921 | footer .foot .left ul li a:hover { 922 | background: #004e86; 923 | } 924 | footer .foot .left p { 925 | margin: 0; 926 | } 927 | footer .foot .logo { 928 | float: right; 929 | margin-top: 5px; 930 | } 931 | */ 932 | body { 933 | /*background: url(../images/landtagswahl-bg.jpg) fixed;*/ 934 | background-size: cover; 935 | } 936 | #content { 937 | overflow: hidden; 938 | } 939 | #content.container { 940 | padding: 50px 30px 20px 30px; 941 | margin: 0 auto; 942 | background: #fff; 943 | } 944 | .category { 945 | overflow: hidden; 946 | background: url(../images/wahlkreuz.png) no-repeat 60% center; 947 | background-size: auto 80%} 948 | .category a { 949 | display: block; 950 | text-decoration: none; 951 | padding: 10px 0; 952 | margin: 30px 0 20px 0; 953 | color: #fff; 954 | font-weight: 700; 955 | font-size: 20px; 956 | } 957 | .category a:hover { 958 | text-decoration: underline; 959 | } 960 | .intro { 961 | max-width: 498px; 962 | text-align: center; 963 | margin: 0 auto 80px auto; 964 | } 965 | .intro h1 { 966 | font-size: 35px; 967 | } 968 | .intro p { 969 | line-height: 1.6; 970 | } 971 | .question h2, .question h3 { 972 | max-width: 796.8px; 973 | margin: 10px auto; 974 | text-align: center; 975 | } 976 | .question h2 { 977 | font-size: 24px; 978 | } 979 | .question .accessibility { 980 | margin: 20px 0 10px 0; 981 | text-align: right; 982 | } 983 | .question .result { 984 | position: relative; 985 | margin-top: 50px; 986 | margin-bottom: 70px; 987 | /* 988 | margin-top: 0px; 989 | margin-bottom: 50px; */ 990 | } 991 | .question .result .actionContainer { 992 | position: absolute; 993 | left: 0; 994 | right: 0; 995 | } 996 | /*.question .result .actionContainer button {*/ 997 | .actionContainer button { 998 | display: block; 999 | margin: 20px auto; 1000 | /* 1001 | margin-top: 0px; 1002 | margin-bottom: 20px; 1003 | margin-left: auto; 1004 | margin-right: auto; 1005 | */ 1006 | padding: 10px 15px; 1007 | border: none; 1008 | background: #00345e; 1009 | color: #fff; 1010 | font-size: 20px; 1011 | cursor: pointer; 1012 | outline: 0; 1013 | border: 2px solid white; 1014 | border-radius: 25px; 1015 | } 1016 | .actionContainer.finalScore button { 1017 | background: orange; 1018 | color: white; 1019 | } 1020 | /*.question .result .actionContainer button[disabled] {*/ 1021 | .actionContainer button[disabled] { 1022 | background: #ccc; 1023 | cursor: not-allowed; 1024 | } 1025 | .question .result .actionContainer .tooltipcontainer { 1026 | display: none; 1027 | left: 50%; 1028 | position: absolute; 1029 | opacity: 0; 1030 | padding-top: 7px; 1031 | } 1032 | .question .result .actionContainer .tooltipcontainer .tooltiptext { 1033 | position: relative; 1034 | background-color: #555; 1035 | color: #fff; 1036 | text-align: center; 1037 | border-radius: 6px; 1038 | padding: 8px 12px; 1039 | z-index: 1; 1040 | left: -50%; 1041 | transition: opacity 1s; 1042 | white-space: nowrap; 1043 | } 1044 | .question .result .text { 1045 | max-width: 498px; 1046 | margin: 10px auto; 1047 | visibility: hidden; 1048 | text-align: center; 1049 | font-size: 20px; 1050 | } 1051 | .question .result .text p { 1052 | line-height: 1.5; 1053 | } 1054 | .question .result .text a, .question .result .text .finalScore { 1055 | display: block; 1056 | text-align: left; 1057 | padding: .3em 0; 1058 | border-bottom: 1px solid #606365; 1059 | color: #1d2124; 1060 | position: relative; 1061 | left: 0; 1062 | right: 0; 1063 | } 1064 | .question .result .scoreText{ 1065 | opacity: 0; 1066 | font-weight: bold; 1067 | } 1068 | .question .result .text a:first-of-type, .question .result .text .finalScore:first-of-type{ 1069 | border-top: 1px solid #606365; 1070 | margin-top: 30px; 1071 | } 1072 | .question .result .text a:hover, .question .result .text .finalScore:hover { 1073 | background-color: #f0f0f0; 1074 | } 1075 | .question .result .text .before-finalScore { 1076 | display: block; 1077 | padding-left: 5px; 1078 | } 1079 | .question .result.finished .tooltipcontainer { 1080 | display: block; 1081 | } 1082 | .question .result.shown .actionContainer { 1083 | display: none; 1084 | } 1085 | .question .result.shown .actionContainer.finalScore { 1086 | display: block; 1087 | } 1088 | .question .result.shown .text { 1089 | visibility: visible; 1090 | } 1091 | .question .tooltiptext::after { 1092 | bottom: 100%; 1093 | left: 50%; 1094 | border: solid transparent; 1095 | content: " "; 1096 | height: 0; 1097 | width: 0; 1098 | position: absolute; 1099 | pointer-events: none; 1100 | border-color: rgba(136, 183, 213, 0); 1101 | border-bottom-color: #555; 1102 | border-width: 15px; 1103 | margin-left: -15px; 1104 | } 1105 | .question .showAction:hover+.tooltipcontainer { 1106 | opacity: 1; 1107 | } 1108 | .credits h3, .credits h4 { 1109 | margin: 30px 0 10px 0; 1110 | } 1111 | .credits p { 1112 | margin: 0 0 5px 0; 1113 | } 1114 | .credits a { 1115 | display: block; 1116 | } 1117 | .credits p+a { 1118 | margin-bottom: 20px; 1119 | } 1120 | .updatedAt { 1121 | margin-top: 20px; 1122 | text-align: right; 1123 | } 1124 | @media (max-width:996px) { 1125 | .container { 1126 | padding-left: 10px; 1127 | padding-right: 10px; 1128 | } 1129 | }@media (max-width:760px) { 1130 | .you-draw-it { 1131 | margin: 0 -30px; 1132 | } 1133 | 1134 | } 1135 | --------------------------------------------------------------------------------