diff --git a/LICENCE-THIRD-PARTY b/LICENCE-THIRD-PARTY index c35283e64..300edb7a7 100644 --- a/LICENCE-THIRD-PARTY +++ b/LICENCE-THIRD-PARTY @@ -183,6 +183,15 @@ licensed under MIT licence. third-party-licences/cytoscape-no-overlap.txt ============================================================================== +============================================================================== +Bias in Big Data +------------------------------------------------------------------------------ +https://github.com/NCC74656/Bias-In-Big-Data-Interactive +Copyright 2019 Mark Henszey Wolgin +licensed under MIT licence. +third-party-licences/bias-in-big-data.txt +============================================================================== + ============================================================================== del ------------------------------------------------------------------------------ diff --git a/csfieldguide/interactives/content/en/interactives.yaml b/csfieldguide/interactives/content/en/interactives.yaml index b3ee91f6e..13bedddc2 100644 --- a/csfieldguide/interactives/content/en/interactives.yaml +++ b/csfieldguide/interactives/content/en/interactives.yaml @@ -46,6 +46,8 @@ confused-buttons: name: Confused Buttons confusing-error: name: Confusing Error +data-bias: + name: Data Bias date-picker: name: Date Picker deceiver: diff --git a/csfieldguide/interactives/content/structure/interactives.yaml b/csfieldguide/interactives/content/structure/interactives.yaml index 1778c9124..4e8805c8a 100644 --- a/csfieldguide/interactives/content/structure/interactives.yaml +++ b/csfieldguide/interactives/content/structure/interactives.yaml @@ -111,6 +111,10 @@ confusing-error: de: interactives/confusing-error.html es: interactives/confusing-error.html is_interactive: false +data-bias: + languages: + en: interactives/data-bias.html + is_interactive: true date-picker: languages: en: interactives/date-picker.html diff --git a/csfieldguide/package.json b/csfieldguide/package.json index adf4c76be..b7b4f4318 100644 --- a/csfieldguide/package.json +++ b/csfieldguide/package.json @@ -23,6 +23,7 @@ "csfg-interactive-city-trip": "file:./static/interactives/city-trip/", "csfg-interactive-cmy-mixer": "file:./static/interactives/cmy-mixer/", "csfg-interactive-colour-matcher": "file:./static/interactives/colour-matcher/", + "csfg-interactive-data-bias": "file:./static/interactives/data-bias/", "csfg-interactive-frequency-anaylsis": "file:./static/interactives/frequency-analysis/", "csfg-interactive-jpeg-compression": "file:./static/interactives/jpeg-compression/", "csfg-interactive-matrix-simplifier": "file:./static/interactives/matrix-simplifier/", diff --git a/csfieldguide/static/interactives/data-bias/README.md b/csfieldguide/static/interactives/data-bias/README.md new file mode 100644 index 000000000..f6844f3aa --- /dev/null +++ b/csfieldguide/static/interactives/data-bias/README.md @@ -0,0 +1,25 @@ +# Data Bias interactive + +**Created by:** + +- Sofia DiGirolamo +- Minji Kong +- Korey Mitchell +- Mark Wolgin + +The original can be found [here](https://github.com/NCC74656/Bias-In-Big-Data-Interactive). + +**Rebuilt by:** Courtney Bracefield + +This interactive demonstrates how small changes in selection and perception can change the result of a study or competition. +The user will learn how to better view data by altering their perspective. + +## The interactive + +The user is presented with a number of coloured circles placed on a coloured background and is asked to click the visible circles. +Some circles are the same colour as the background and therefore near impossible to find. +The interactive then reveals the hidden circles and allows the user to change the background colour via a slider. + +## Licences + +The licence of the original interactive can be found in `LICENCE-THIRD-PARTY` with a full copy available in the `third-party-licences` directory. diff --git a/csfieldguide/static/interactives/data-bias/css/data-bias.scss b/csfieldguide/static/interactives/data-bias/css/data-bias.scss new file mode 100644 index 000000000..474c9f6a3 --- /dev/null +++ b/csfieldguide/static/interactives/data-bias/css/data-bias.scss @@ -0,0 +1,122 @@ +@import "node_modules/nouislider/distribute/nouislider"; +@import "node_modules/bootstrap/scss/functions"; +@import "node_modules/bootstrap/scss/variables"; +@import "node_modules/bootstrap/scss/mixins"; + +.circle { + border-radius: 50%; + width: 3.125rem; + height: 3.125rem; + position: absolute; +} + +#circles-area { + position: relative; + border-radius: 1rem; +} + +#data-bias-container { + padding: 4rem !important; +} + +.noUi-pips { + padding-top: 0.3125rem; +} + +.noUi-target { + border: none; + background: linear-gradient( + to right, + hsl(0,100%,50%), + hsl(60,100%,50%), + hsl(120,100%,50%), + hsl(180,100%,50%), + hsl(240,100%,50%), + hsl(300,100%,50%), + hsl(360,100%,50%) + ); +} + +.noUi-handle { + border: 0.125rem white solid; + height: 1.75rem !important; + width: 1.75rem !important; + border-radius: 50%; + top: -0.375rem !important; +} + +.noUi-handle:before, +.noUi-handle:after { + content: none; +} + +.noUi-connect { + background: none; +} + +.glow { + box-shadow: 0 0 1rem 0.5rem black; +} + +.red { + background: red; +} + +.lime { + background: lime; +} + +.blue { + background: blue; +} + +.yellow { + background: yellow; +} + +.purple { + background: #9d03fc; +} + +.darkorange { + background: darkorange; +} + +.fuchsia { + background: fuchsia; +} + +.deepskyblue { + background: deepskyblue; +} + +.grey { + background: grey !important; +} + +@include media-breakpoint-down(sm) { + #instruction-area, + #background-colour-slider-container { + padding: 0rem !important; + } + + #data-bias-container { + padding: 1rem 3rem !important; + } + + #instruction-text { + margin: 1rem 0rem; + } + + .circle { + width: 2.5rem !important; + height: 2.5rem !important; + } +} + +@include media-breakpoint-up(xl) { + #circles-area { + max-width: 60%; + max-height: 80%; + } +} diff --git a/csfieldguide/static/interactives/data-bias/js/data-bias.js b/csfieldguide/static/interactives/data-bias/js/data-bias.js new file mode 100644 index 000000000..843bb5932 --- /dev/null +++ b/csfieldguide/static/interactives/data-bias/js/data-bias.js @@ -0,0 +1,186 @@ +const noUiSlider = require('nouislider'); +const wNumb = require('wnumb'); + +const COLOURS = ['red', 'lime', 'blue', 'yellow', 'purple', 'darkorange', 'fuchsia', 'deepskyblue']; +const START_TEXT = gettext("Click each dot that you see on the screen, then click 'Next stage' to reveal the answer!"); +const MISSED_CIRCLES_TEXT = gettext('You seem to have missed some dots!

Forced perspective like this can be used in data representation and cause bias in the overall results.'); +const SLIDER_TEXT = gettext('Click and drag the slider to change the background colour.

What do you notice is happening?') +const SLIDER_MIN = 0; +const SLIDER_MAX = 360; +const PERENTAGE_BOUNDARY_UPPER = 85; // 85% +const PERCENTAGE_ADJUSTMENT = 15; // 15% +const PERENTAGE_BOUNDARY_LOWER = 5; // 5% +const NUM_CIRCLES_TO_ADD = 8; // in addition to the 3 circles created that are the same colour as the background colour +// below dictionary holds the 'H' value of the HSL colours. +// these numbers correspond to the slider value of that colour, e.g blue is at value 240 on the slider. +const SLIDER_COLOUR_VALUES = { + 'red': 0, + 'lime': 120, + 'blue': 240, + 'yellow': 60, + 'purple': 277, + 'darkorange': 33, + 'fuchsia': 300, + 'deepskyblue': 195 +} +var firstStage = true; +var bgColourSlider = $('#background-colour-slider'); +var sliderStartPos = 0; +var startColour = 'red'; + + +$(document).ready(function() { + init(); + $('#next-stage').click(loadNextStage); + $('#start-again').click(restartInteractive); +}); + + +/** + * Returns everything to the inital 'page loaded' state. + */ +function init() { + // get random background colour to start + startColour = getRandomColour(); + sliderStartPos = SLIDER_COLOUR_VALUES[startColour]; + $('#circles-area').addClass(startColour); + // make sure we have at least 2 circles that are the same as the background colour + // a third circle is added later in loadNextStage to make sure the user NEVER finds all of the circles + createCircle(startColour); + createCircle(startColour); + // generate 8 more randomly coloured circles + for (i=0; i < NUM_CIRCLES_TO_ADD; i++) { + createCircle(); + } + $('#next-stage').removeClass('d-none'); + $('#start-again').addClass('d-none'); + createSlider(); + bgColourSlider[0].noUiSlider.on('update', updateSlider); +} + + +/** + * Creates slider that controls background colour. + */ +function createSlider() { + noUiSlider.create(bgColourSlider[0], { + start: sliderStartPos, + step: 1, + connect: "lower", + orientation: "horizontal", + range: { + 'min': SLIDER_MIN, + 'max': SLIDER_MAX + }, + format: wNumb({ + decimals: 0 + }) + }); +} + + +/** + * Updates background colour when slider is moved. + */ +function updateSlider() { + var value = bgColourSlider[0].noUiSlider.get(); + hslColour = 'hsl(' + value + ', 100%, 50%)'; + $('#circles-area').css('background', hslColour); + $('.noUi-handle').css('background', hslColour); +} + + +/** + * Returns a random position in the form of {top: ..., left: ...} to randomly place circles. + */ +function getRandomPosition() { + var circlesAreaHeight = $('#circles-area').height(); + var circlesAreaWidth = $('#circles-area').width(); + var randHeight = Math.floor((Math.random() * circlesAreaHeight)); + var randWidth = Math.floor((Math.random() * circlesAreaWidth)); + + // convert px to % + heightInPercentage = Math.floor((randHeight / circlesAreaHeight) * 100); + widthInPercentage = Math.floor((randWidth / circlesAreaWidth) * 100); + + // reduces or increases percentage by 5% to prevent circles going outside of parent + if (heightInPercentage >= PERENTAGE_BOUNDARY_UPPER) { + heightInPercentage -= PERCENTAGE_ADJUSTMENT; + } else if (heightInPercentage <= PERENTAGE_BOUNDARY_LOWER) { + heightInPercentage += PERCENTAGE_ADJUSTMENT + } + if (widthInPercentage >= PERENTAGE_BOUNDARY_UPPER) { + widthInPercentage -= PERCENTAGE_ADJUSTMENT; + } else if (widthInPercentage <= PERENTAGE_BOUNDARY_LOWER) { + widthInPercentage += PERCENTAGE_ADJUSTMENT; + } + + return { + top: heightInPercentage + '%', + left: widthInPercentage + '%' + }; +} + + +/** + * Creates a circle div and adds it to the page. + */ +function createCircle(colour) { + var colour = colour || getRandomColour(); + var $circle = $("
").addClass('circle ' + colour); + $circle.css(getRandomPosition()); + // so overlapping circles don't give away the hidden circles + // brings non hidden circles up 1 layer + if (colour !== startColour) { + $circle.css('z-index', 1); + } + $('#circles-area').append($circle); + $circle.click(function() { + // toggle glow around clicked circle + $circle.toggleClass('glow'); + }); +} + + +/** + * Loads the next stage which is either revealing the hidden circles or adjusting the background colour with the slider. + */ +function loadNextStage() { + if (firstStage) { + // add a sneaky extra circle in the off chance they find all of the hidden circles :P + createCircle(startColour); + $('#circles-area').removeClass(startColour); + $('#circles-area').addClass('grey'); + $('#instruction-text').html(MISSED_CIRCLES_TEXT); + firstStage = false; + } else { + $('#instruction-text').html(SLIDER_TEXT); + $('.circle').removeClass('glow'); + $('#circles-area').removeClass('grey'); + $('#circles-area').addClass(startColour); + $('#next-stage').addClass('d-none'); + $('#start-again').removeClass('d-none'); + $('#background-colour-slider-container').removeClass('d-none'); + } +} + + +/** + * Resets the interactive and calls init() to return page to the 'page loaded' state. + */ +function restartInteractive() { + $('.circle').remove(); + $('#background-colour-slider-container').addClass('d-none'); + firstStage = true; + $('#instruction-text').html(START_TEXT); + bgColourSlider[0].noUiSlider.destroy(); + init(); +} + + +/** + * Returns a random colour from the COLOURS array. + */ +function getRandomColour() { + return COLOURS[Math.floor(Math.random() * COLOURS.length)]; +} diff --git a/csfieldguide/static/interactives/data-bias/package.json b/csfieldguide/static/interactives/data-bias/package.json new file mode 100644 index 000000000..0f41688d2 --- /dev/null +++ b/csfieldguide/static/interactives/data-bias/package.json @@ -0,0 +1,9 @@ +{ + "name": "csfg-interactive-data-bias", + "version": "1.0.0", + "private": true, + "dependencies": { + "nouislider": "13.1.5", + "wnumb": "1.2.0" + } +} diff --git a/csfieldguide/templates/appendices/contributors.html b/csfieldguide/templates/appendices/contributors.html index 7bd3e9fca..59b0d31db 100644 --- a/csfieldguide/templates/appendices/contributors.html +++ b/csfieldguide/templates/appendices/contributors.html @@ -139,6 +139,10 @@

{% trans 'Comm
  • gmohler213 (Greg Mohler)
  • Rachel Muzzelo
  • aenkirch
  • +
  • sdigiro (Sofia DiGirolamo)
  • +
  • mkong001 (Minji Kong)
  • +
  • koreymitchell (Korey Mitchell)
  • +
  • NCC74656 (Mark Wolgin)
  • {% trans 'Note: If there is an error in the list, please contact Jack Morgan' %}

    diff --git a/csfieldguide/templates/interactives/data-bias.html b/csfieldguide/templates/interactives/data-bias.html new file mode 100644 index 000000000..992a015da --- /dev/null +++ b/csfieldguide/templates/interactives/data-bias.html @@ -0,0 +1,29 @@ +{% extends interactive_mode_template %} + +{% load i18n %} +{% load static %} + +{% block html %} +
    +

    {% trans "Data Bias" %}

    +
    +
    {% trans "Click each dot that you see on the screen, then click 'Next stage' to reveal the answer!" %}
    +
    +
    +
    +
    +
    + + +
    +
    +
    +{% endblock html %} + +{% block css %} + +{% endblock css %} + +{% block js %} + +{% endblock js %} diff --git a/third-party-licences/bias-in-big-data.txt b/third-party-licences/bias-in-big-data.txt new file mode 100644 index 000000000..ad2628b07 --- /dev/null +++ b/third-party-licences/bias-in-big-data.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Mark Henszey Wolgin + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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.