Skip to content

Commit

Permalink
Merge pull request #141 from nzzdev/release-1.11.0
Browse files Browse the repository at this point in the history
Release 1.11.0
  • Loading branch information
KiaraEdTech committed Jun 7, 2021
2 parents 6dbbe65 + 150c52a commit f5be400
Show file tree
Hide file tree
Showing 29 changed files with 15,788 additions and 249 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ The tool structure follows the general structure of each Q tool. Further informa

### Question Types

Q-Quiz supports three different question types `multiple choice`, `number guess` and `map point guess`. Each question type takes a question, a correct answer and additional configuration parameters like wrong answers or min and max values. The questions types are implemented as ES6 classes and each follow the same structure.
Q-Quiz supports four question types `multiple choice`, `number guess`, `map point guess` and `number poll`. Each of them requires a question, and have additional configuration parameters, like min or max values. The questions `multiple choice`, `number guess` and `map point guess` require a correct answer and will evaluate results. A version of `number-guess`, called `number-poll` does not require correct answer. The questions types are implemented as ES6 classes and each follow the same structure.

### `/rendering-info/html-js`

Expand Down
15,022 changes: 15,002 additions & 20 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nzz/q-quiz",
"version": "1.10.8",
"version": "1.11.0",
"description": "Q quiz",
"keywords": [
"storytelling",
Expand Down
10 changes: 10 additions & 0 deletions resources/fixtures/data/all.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@
"articleRecommendations": [],
"question": "wo kommt sharon her?"
},
{
"id": "quiz-0-1503673375520-572013999",
"type": "numberPoll",
"image": {},
"articleRecommendations": [],
"question": "Temperatur",
"min": 1,
"max": 35,
"step": 1
},
{
"id": "quiz-0-1503673750136-872919612",
"type": "lastCard",
Expand Down
17 changes: 17 additions & 0 deletions resources/fixtures/data/single-number-poll.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"title": "FIXTURE: Number poll - single question quiz",
"elements": [
{
"id": "quiz-9-1620205109920-228184695",
"type": "numberPoll",
"articleRecommendations": [],
"introduction": "This is the introduction",
"question": "Test Frage",
"questionSubTitle": "Test Subtitle",
"answer": 0,
"min": 1,
"max": 10,
"step": 1
}
]
}
6 changes: 4 additions & 2 deletions resources/helpers/constants.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
module.exports = {
questionTypes: ["multipleChoice", "numberGuess", "mapPointGuess"],
questionTypes: ["multipleChoice", "numberGuess", "mapPointGuess", "numberPoll"],
scoredQuestionTypes: ["multipleChoice", "numberGuess", "mapPointGuess"],
multiplicator: {
multipleChoice: 5,
numberGuess: 10,
mapPointGuess: 10
mapPointGuess: 10,
numberPoll: 10
}
};
6 changes: 6 additions & 0 deletions resources/helpers/scoreHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ function calculateWorstAnswerDifference(question) {
question.max - question.answer
);
}
if (question.type === "numberPoll") {
return 1;
}
if (question.type === "mapPointGuess") {
const bbox = question.answer.bbox;
const correctAnswerCoord = question.answer.geometry.coordinates;
Expand Down Expand Up @@ -84,6 +87,9 @@ function getAnswerQuality(question) {
Math.abs(question.userAnswer - question.answer) / worstAnswerDifference
);
}
if (question.type === "numberPoll") {
return 1;
}
if (
question.type === "mapPointGuess" &&
worstAnswerDifference !== undefined
Expand Down
31 changes: 31 additions & 0 deletions resources/helpers/statsCalculators.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,36 @@ class NumberGuessStatsCalculator {
}
}

class NumberPollStatsCalculator {
constructor(answersStats, correctAnswer, userAnswer) {
// for consistency, we keep the correctAnswer argument
this.answersStats = answersStats;
this.userAnswer = userAnswer;
}

getStats() {
let numberOfSameAnswers;

let totalAnswers = this.answersStats.reduce((prev, current) => {
return prev + current.count;
}, 0);

if (this.userAnswer) {
numberOfSameAnswers = this.answersStats.reduce((prev, current) => {
if (this.userAnswer.value === current.value) {
return prev + current.count - 1;
}
return prev;
}, 0);
}

return {
numberOfSameAnswers: numberOfSameAnswers,
totalAnswers: totalAnswers
};
}
}

class MultipleChoiceStatsCalculator {
constructor(answersStats, correctAnswer, userAnswer) {
this.answersStats = answersStats;
Expand Down Expand Up @@ -120,6 +150,7 @@ class MapPointGuessStatsCalculator {

module.exports = {
numberGuess: NumberGuessStatsCalculator,
numberPoll: NumberPollStatsCalculator,
multipleChoice: MultipleChoiceStatsCalculator,
mapPointGuess: MapPointGuessStatsCalculator
};
236 changes: 236 additions & 0 deletions resources/helpers/svgHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@

const jsdom = require("jsdom");
const { JSDOM } = jsdom;

const getPrecision = require("../../resources/helpers/utils.js").getPrecision;

let d3 = {
scaleLinear: require("d3-scale").scaleLinear,
scaleBand: require("d3-scale").scaleBand,
max: require("d3-array").max,
select: require("d3-selection").select,
};

function getStripplotSvg(data, stats, plotWidth) {
return new Promise((resolve, reject) => {
// in order to work with jsdom like that let jsdom version at 9.5
// should be rebuilt and upgraded in the future
const dom = new JSDOM("");
if (!stats) {
reject("stats is undefined");
return;
}

let margin = { top: 0, right: 0, bottom: 30, left: 0 },
width = plotWidth - margin.left - margin.right,
height = 90 - margin.top - margin.bottom;

let xScale = d3
.scaleLinear()
.domain([data.min, data.max])
.range([0, width]);

let yScale = d3
.scaleLinear()
.domain([0, d3.max(stats, (d) => parseInt(d.count))])
.range([height, 0]);

let element = dom.window.document.createElement("div");

let svg = d3
.select(element)
.append("svg")
.datum(stats)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

svg
.append("rect")
.attr("x", 0)
.attr("y", margin.top)
.attr("width", width)
.attr("height", 60)
.attr("fill", "transparent");

svg
.append("text")
.text(data.min)
.attr(
"class",
"s-font-note-s s-font-note-s--light s-font-note--tabularnums"
)
.attr("text-anchor", "start")
.attr("x", 3)
.attr("y", margin.top + 60)
.attr("dy", "1em");
svg
.append("line")
.attr("class", "s-color-gray-6 q-quiz-answer-chart-min-line")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", margin.top + height + 2)
.attr("y2", margin.top + height + 8);

svg
.append("text")
.text(data.max)
.attr(
"class",
"s-font-note-s s-font-note-s--light s-font-note--tabularnums"
)
.attr("text-anchor", "end")
.attr("x", width - 3)
.attr("y", margin.top + 60)
.attr("dy", "1em");
svg
.append("line")
.attr("class", "s-color-gray-6 q-quiz-answer-chart-max-line")
.attr("x1", width)
.attr("x2", width)
.attr("y1", margin.top + height + 2)
.attr("y2", margin.top + height + 8);

let lines = svg
.selectAll("line.q-quiz-answer-chart-line")
.data(stats)
.enter()
.append("line")
.attr("class", "q-quiz-answer-chart-line s-color-gray-8")
.attr("style", (d) => {
return `opacity: ${0.1 * d.count > 0.9 ? 0.9 : 0.1 * d.count}`;
})
.attr("x1", (d) => xScale(d.value))
.attr("x2", (d) => xScale(d.value))
.attr("y1", margin.top)
.attr("y2", margin.top + 60);

resolve(element.innerHTML);
});
}

function getBarchartSvg(data, stats, chartWidth) {
return new Promise((resolve, reject) => {
const dom = new JSDOM("");
if (!stats) {
reject("stats is undefined");
return;
}

let margin = { top: 0, right: 0, bottom: 30, left: 0 },
width = chartWidth - margin.left - margin.right,
height = 90 - margin.top - margin.bottom;

let precision = getPrecision(data.step);

let xDomain = [];
for (
let i = data.min;
i <= data.max;
i = parseFloat((i + data.step).toFixed(precision))
) {
xDomain.push(i);
}

let xScale = d3
.scaleBand()
.domain(xDomain)
// we probably want to have this padding, its removed to have it easier on the client to calculate the position for the overlaying correct and users answer
// .paddingInner(0.1)
.range([0, width]);

let yScale = d3
.scaleLinear()
.domain([
0,
Math.max(
200 / stats.length,
d3.max(stats, (d) => parseInt(d.count))
),
])
.range([height, 0]);

let element = dom.window.document.createElement("div");

let svg = d3
.select(element)
.append("svg")
.datum(stats)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

svg
.append("rect")
.attr("x", 0)
.attr("y", margin.top)
.attr("width", width)
.attr("height", 60)
.attr("fill", "transparent");

svg
.append("text")
.text(data.min)
.attr(
"class",
"s-font-note-s s-font-note-s--light s-font-note--tabularnums"
)
.attr("text-anchor", "start")
.attr("x", 3)
.attr("y", margin.top + 60)
.attr("dy", "1em");
svg
.append("line")
.attr("class", "s-color-gray-6 q-quiz-answer-chart-min-line")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", margin.top + height + 2)
.attr("y2", margin.top + height + 8);

svg
.append("text")
.text(data.max)
.attr(
"class",
"s-font-note-s s-font-note-s--light s-font-note--tabularnums"
)
.attr("text-anchor", "end")
.attr("x", width - 3)
.attr("y", margin.top + 60)
.attr("dy", "1em");
svg
.append("line")
.attr("class", "s-color-gray-6 q-quiz-answer-chart-max-line")
.attr("x1", width)
.attr("x2", width)
.attr("y1", margin.top + height + 2)
.attr("y2", margin.top + height + 8);

let bars = svg
.selectAll(".bar")
.data(stats)
.enter()
.append("rect")
.attr("class", "s-color-gray-4")
.attr("fill", "currentColor")
.attr("x", function (d) {
return xScale(d.value);
})
.attr("width", xScale.bandwidth())
.attr("y", function (d) {
return yScale(d.count);
})
.attr("height", function (d) {
return Math.max(1, height - yScale(d.count));
});

resolve(element.innerHTML);
});
}

module.exports = {
getBarchartSvg: getBarchartSvg,
getStripplotSvg: getStripplotSvg,
};
1 change: 1 addition & 0 deletions resources/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ function getAnswers(type, questionId, options) {
}
if (
type === "numberGuess" ||
type === "numberPoll" ||
type === "mapPointGuess" ||
type === "multipleChoice"
) {
Expand Down
1 change: 1 addition & 0 deletions resources/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"Tell the user in prose, which answer is correct and ideally still provides some explanation.",
"Anmerkungen": "Notes",
"Zahl schätzen": "Number guess",
"Zahl Umfrage": "Number poll",
"Einheit Plural": "Unit plural",
"Was für eine Zahl ist zu schätzen? Meter? Personen? Franken? etc.":
"What is the unit of the number? Meter? People? Franken? etc.",
Expand Down
1 change: 1 addition & 0 deletions resources/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"Dites à l'utilisateur en prose, quelle est la réponse correcte et idéalement fournit toujours quelques explications.",
"Anmerkungen": "Remarques",
"Zahl schätzen": "Numéro d'estimation",
"Zahl Umfrage": "Sondage numérique",
"Einheit Plural": "Unité pluriel",
"Was für eine Zahl ist zu schätzen? Meter? Personen? Franken? etc.":
"Quelle est l'unité de nombre? Mètre? Les gens? Franken? etc.",
Expand Down
Loading

0 comments on commit f5be400

Please sign in to comment.