Merge pull request #1 from olehomelchenko/feat/localize-ukrainian

Feat/localize ukrainian
This commit is contained in:
Oleh Omelchenko
2025-01-16 14:15:41 +02:00
committed by GitHub
40 changed files with 615 additions and 406 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.pyc
responses.json

View File

@@ -0,0 +1,4 @@
__pycache__
*.pyc
.dockerignore
Dockerfile

View File

@@ -0,0 +1,17 @@
FROM python:3.9
WORKDIR /app
COPY Pipfile Pipfile.lock ./
RUN pip install pipenv && \
pipenv install --system --deploy && \
pip install flask-cors tinydb
# Create directory for database
RUN mkdir -p /app/data
COPY . .
EXPOSE 5000
CMD ["flask", "run", "--host=0.0.0.0"]

View File

@@ -13,11 +13,18 @@ import secrets
import pandas as pd import pandas as pd
import db_conf import db_conf
from flask import send_file from flask import send_file
from flask_cors import CORS
from tinydb import TinyDB
#from flask_mail import Mail, Message #from flask_mail import Mail, Message
#import firebase_admin #import firebase_admin
#from firebase_admin import credentials, firestore, initialize_app, firebase #from firebase_admin import credentials, firestore, initialize_app, firebase
app = Flask(__name__, static_folder='../frontend/build', static_url_path='/') app = Flask(__name__, static_folder='../frontend/build', static_url_path='/')
CORS(app) # Add this line
db = TinyDB('/app/data/responses.json')
responses_table = db.table('responses')
app.config['MYSQL_DATABASE_HOST'] = db_conf.host app.config['MYSQL_DATABASE_HOST'] = db_conf.host
app.config['MYSQL_DATABASE_USER'] = db_conf.user app.config['MYSQL_DATABASE_USER'] = db_conf.user
app.config['MYSQL_DATABASE_PASSWORD'] = db_conf.password app.config['MYSQL_DATABASE_PASSWORD'] = db_conf.password
@@ -103,10 +110,11 @@ def record_responses_to_db():
# msg = Message("Quiz Response for " + str(session_id),sender='minivlat@gmail.com', recipients=['minivlat@gmail.com']) # msg = Message("Quiz Response for " + str(session_id),sender='minivlat@gmail.com', recipients=['minivlat@gmail.com'])
# msg.body = data_send # msg.body = data_send
# mail.send(msg) # mail.send(msg)
fname = str(session_id)+'.txt' # fname = str(session_id)+'.txt'
fname = './surveys/quiz/' + fname # fname = './surveys/quiz/' + fname
with open(fname, 'w+') as test: responses_table.insert(data)
test.write(json.dumps(data) + "\n") # with open(fname, 'w+') as test:
# test.write(json.dumps(data) + "\n")
print('TODO: Record quiz responses into a file or DB') print('TODO: Record quiz responses into a file or DB')
print('Collected quiz data: ', data) print('Collected quiz data: ', data)

View File

@@ -0,0 +1,4 @@
node_modules
build
.dockerignore
Dockerfile

1
ReactTool/frontend/.env Normal file
View File

@@ -0,0 +1 @@
REACT_APP_API_URL=http://localhost:8000

View File

@@ -0,0 +1 @@
REACT_APP_API_URL=https://mini-vlat-ua.duckdns.org/api

View File

@@ -21,3 +21,5 @@
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
*.pyc

View File

@@ -0,0 +1,12 @@
FROM node:16
WORKDIR /app
COPY package*.json ./
COPY yarn.lock ./
RUN yarn install
COPY . .
EXPOSE 3000
CMD ["yarn", "start"]

View File

@@ -0,0 +1,14 @@
# ReactTool/frontend/Dockerfile.prod
FROM node:16 as build
WORKDIR /app
COPY package*.json ./
COPY yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

View File

@@ -59,5 +59,5 @@
"last 1 safari version" "last 1 safari version"
] ]
}, },
"proxy": "http://localhost:5000" "proxy": "http://localhost:3000"
} }

View File

@@ -6,7 +6,18 @@ import '../App.css';
import data from './data/AreaChart-2.csv'; import data from './data/AreaChart-2.csv';
import img10 from '../components/data/Mini-VLAT/AreaChart.png' import img10 from '../components/data/Mini-VLAT/AreaChart.png'
const ukLocale = {
"dateTime": "%A, %e %B %Y р. %X",
"date": "%d.%m.%Y",
"time": "%H:%M:%S",
"periods": ["AM", "PM"],
"days": ["неділя", "понеділок", "вівторок", "середа", "четвер", "п'ятниця", "субота"],
"shortDays": ["нд", "пн", "вт", "ср", "чт", "пт", "сб"],
"months": ["січень", "лютий", "березень", "квітень", "травень", "червень", "липень", "серпень", "вересень", "жовтень", "листопад", "грудень"],
"shortMonths": ["січ", "лют", "бер", "кві", "тра", "чер", "лип", "сер", "вер", "жов", "лис", "гру"]
};
const formatUk = d3.timeFormatLocale(ukLocale).format("%b %Y");
class AreaChartMini extends Component { class AreaChartMini extends Component {
@@ -43,7 +54,7 @@ class AreaChartMini extends Component {
.attr("width", width + margin.left + margin.right) .attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom) .attr("height", height + margin.top + margin.bottom)
.append("g") .append("g")
svg.append("text").attr("class", 'bubbleTitle').text("Average Coffee Bean Price from 2013 to 2014").style("font-weight", 'bolder').attr('x', 1.2 * margin.top).attr('y', 1.2 * margin.top).style('font-size', 0.04 * height) svg.append("text").attr("class", 'bubbleTitle').text("Середня ціна кавових бобів з 2013 по 2014").style("font-weight", 'bolder').attr('x', 1.2 * margin.top).attr('y', 1.2 * margin.top).style('font-size', 0.04 * height)
var image = svg.append('image').attr('width', 1.2 * width).attr('x', 0).attr('y', margin.top * height / width).attr('xlink:href', img10).attr('height', 1.1 * height) var image = svg.append('image').attr('width', 1.2 * width).attr('x', 0).attr('y', margin.top * height / width).attr('xlink:href', img10).attr('height', 1.1 * height)
@@ -73,7 +84,7 @@ class AreaChartMini extends Component {
svg.append("g") svg.append("g")
.attr("class", "x-axis") .attr("class", "x-axis")
.attr("transform", `translate(0, ${height})`) .attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(xScale)) .call(d3.axisBottom(xScale).tickFormat(formatUk))
var yScale = d3.scaleLinear() var yScale = d3.scaleLinear()
.domain([0.5, 0.9]) .domain([0.5, 0.9])
@@ -120,7 +131,7 @@ class AreaChartMini extends Component {
.attr("x", 0 - (height / 1.9)) .attr("x", 0 - (height / 1.9))
.attr("dy", "1em") .attr("dy", "1em")
.style("text-anchor", "middle") .style("text-anchor", "middle")
.text("Coffee Price ($/lb)") .text("Ціна ($/фунт)")
.style("font-weight", "bold") .style("font-weight", "bold")
function make_x_gridlines() { function make_x_gridlines() {
@@ -153,7 +164,7 @@ class AreaChartMini extends Component {
.attr("class", "title") .attr("class", "title")
.attr("x", width / 3) .attr("x", width / 3)
.attr("y", -length / margin.top) // +20 to adjust position (lower) .attr("y", -length / margin.top) // +20 to adjust position (lower)
.text("Robusta Coffee Price") .text("Ціна кави Робуста")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")

View File

@@ -131,14 +131,14 @@ class BarChartMini extends Component {
.attr("x", 0 - (height / 1.9)) .attr("x", 0 - (height / 1.9))
.attr("dy", "1em") .attr("dy", "1em")
.style("text-anchor", "middle") .style("text-anchor", "middle")
.text("Internet Speed (Mbps)") .text("Швидкість інтернету (Mbps)")
.style("font-weight", "bold") .style("font-weight", "bold")
svg svg
.append("text") .append("text")
.attr("x", width / 4) .attr("x", width / 4)
.attr("y", -3 * length / margin.top) // +20 to adjust position (lower) .attr("y", -3 * length / margin.top) // +20 to adjust position (lower)
.text("Global Internet Speed (Mbps)") .text("Швидкість інтернету у світі (Mbps)")
.attr("class", "title") .attr("class", "title")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")

View File

@@ -191,7 +191,7 @@ class BubbleChartMini extends Component {
} }
}) })
.style("font-weight", "bold") .style("font-weight", "bold")
.text("Ridership") .text("Пасажиропотік")
svg.append("text") svg.append("text")
.attr("class", "legend-title") .attr("class", "legend-title")
@@ -212,7 +212,7 @@ class BubbleChartMini extends Component {
} }
}) })
.style("font-weight", "bold") .style("font-weight", "bold")
.text("(bn per year)") .text("(млрд/рік)")
svg svg
.selectAll("legend") .selectAll("legend")
@@ -339,7 +339,7 @@ class BubbleChartMini extends Component {
.attr("x", 0 - (height / 1.9)) .attr("x", 0 - (height / 1.9))
.attr("dy", "1em") .attr("dy", "1em")
.style("text-anchor", "middle") .style("text-anchor", "middle")
.text("Total System Length (Km)") .text("Загальна протяжність системи (км)")
.style("font-weight", "bold") .style("font-weight", "bold")
svg.append("text") svg.append("text")
@@ -354,14 +354,14 @@ class BubbleChartMini extends Component {
}) })
.style("text-anchor", "middle") .style("text-anchor", "middle")
.style("font-weight", "bold") .style("font-weight", "bold")
.text("Number of Stations") .text("Кількість станцій")
svg svg
.append("text") .append("text")
.attr("class", "title") .attr("class", "title")
.attr("x", width / 3) .attr("x", width / 3)
.attr("y", -length / margin.top) // +20 to adjust position (lower) .attr("y", -length / margin.top) // +20 to adjust position (lower)
.text("Metro Systems of the World") .text("Системи метро у світі")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")
}) })

View File

@@ -46,173 +46,10 @@ class ChoroplethMini extends Component {
.attr("height", height + margin.top + margin.bottom) .attr("height", height + margin.top + margin.bottom)
.append("g") .append("g")
svg.append("text").attr("class", 'bubbleTitle').text("Unemployment Rates for States in 2020").style("font-weight", 'bolder').attr('x', 1.2 * margin.top).attr('y', 0.9 * margin.top).style('font-size', 0.04 * height) svg.append("text").attr("class", 'bubbleTitle').text("Рівень безробіття в штатах США у 2020 році").style("font-weight", 'bolder').attr('x', 1.2 * margin.top).attr('y', 0.9 * margin.top).style('font-size', 0.04 * height)
var image = svg.append('image').attr('width', 1.4 * width).attr('x', 0).attr('y', margin.top * height / width).attr('xlink:href', img8).attr('height', 1.1 * height) var image = svg.append('image').attr('width', 1.4 * width).attr('x', 0).attr('y', margin.top * height / width).attr('xlink:href', img8).attr('height', 1.1 * height)
// else {
// const margin = { top: length / 7, right: length / 7, bottom: length / 7, left: length / 7 },
// width = length - margin.left - margin.right,
// height = length - margin.top - margin.bottom;
// // append the svg object to the body of the page
// //d3.select("#graph_box").selectAll("svg").remove();
// d3.select("#graph_box").select("svg").remove();
// const svg = d3.select("#graph_box")
// .append("svg")
// .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("text").attr("class", 'bubbleTitle').text("Unemployment Rates for States in 2015").style("font-weight", 'bolder').attr('x', 1.2 * margin.top).attr('y', 1.2 * margin.top).style('font-size', 0.04 * height)
// // var image = svg.append('image').attr('width', 1.4 * width).attr('x', 0).attr('y', margin.top * height / width).attr('xlink:href', img8).attr('height', 1.1 * height)
// var projection = d3.geoAlbersUsa()
// .translate([width / 2, height / 2])
// .scale(Math.min(e.clientHeight, e.clientWidth));
// var path = d3.geoPath()
// .projection(projection)
// var lowColor = '#f7fbff'
// var highColor = '#084594'
// d3.csv(data).then(function (data) {
// var names = {};
// data.forEach(function (d) {
// d.state = d.state;
// d.value = parseFloat(d.value);
// names[d.id] = d.code;
// })
// var dataArr = [];
// for (var d = 0; d < data.length; d++) {
// dataArr.push(parseFloat(data[d].value))
// }
// var minVal = d3.min(dataArr);
// var maxVal = d3.max(dataArr);
// var ramp = d3.scaleLinear().domain([minVal, maxVal]).range([lowColor, highColor])
// for (var i = 0; i < data.length; i++) {
// var dataState = data[i].state;
// var dataVal = data[i].value;
// var usa = topojson.feature(data_usa, data_usa.objects.states).features;
// for (var j = 0; j < usa.length; j++) {
// var jsonState = usa[j].properties.name;
// if (dataState == jsonState) {
// usa[j].properties.value = dataVal;
// break;
// }
// }
// }
// // Render the U.S. by using the path generator
// svg.selectAll("path")
// .data(usa)
// .enter().append("path")
// .attr("d", path)
// .style("stroke", "#ffff")
// .style("stroke-width", "1")
// .style("fill", function (d) {
// return ramp(d.properties.value)
// });
// svg.selectAll("text").data(usa).enter().append("text")
// .text(function (d) {
// return names[d.id];
// })
// .attr("x", function (d) {
// return path.centroid(d)[0];
// })
// .attr("y", function (d) {
// return path.centroid(d)[1];
// })
// .attr("class", "state-abbr")
// .attr("text-anchor", "middle")
// .attr("fill", "black")
// .style("font-weight", "bold")
// //var w = e.clientWidth/4, h = e.clientHeight/6;
// //d3.select("body").select("svg").remove();
// //d3.selectAll("svg").select(".legend").remove();
// var legend = svg.append("defs")
// .append("svg:linearGradient")
// .attr("id", "gradient")
// .attr("x1", "0%")
// .attr("y1", "100%")
// .attr("x2", "100%")
// .attr("y2", "100%")
// .attr("spreadMethod", "pad");
// legend.append("stop")
// .attr("offset", "0%")
// .attr("stop-color", highColor)
// .attr("stop-opacity", 1);
// legend.append("stop")
// .attr("offset", "100%")
// .attr("stop-color", lowColor)
// .attr("stop-opacity", 1);
// if (width < 350) {
// svg.append("rect")
// .attr("width", length / 4)
// .attr("height", length / 5.5 - margin.top)
// .style("fill", "url(#gradient)")
// .attr("transform", "translate(0," + (length / 5.4 - margin.left / 0.8) + ")");
// svg.append("text").attr("x", 0).attr("y", length / 8.5 - 0.4 * margin.top).text("12%").style("font-weight", "bold").attr("class", "legend-value")
// svg.append("text").attr("x", length / 20).attr("y", length / 8.5 - 0.4 * margin.top).text("10%").style("font-weight", "bold").attr("class", "legend-value")
// svg.append("text").attr("x", length / 10).attr("y", length / 8.5 - 0.4 * margin.top).text("8%").style("font-weight", "bold").attr("class", "legend-value")
// svg.append("text").attr("x", length / 7).attr("y", length / 8.5 - 0.4 * margin.top).text("6%").style("font-weight", "bold").attr("class", "legend-value")
// svg.append("text").attr("x", length / 5).attr("y", length / 8.5 - 0.4 * margin.top).text("4%").style("font-weight", "bold").attr("class", "legend-value")
// }
// else {
// svg.append("rect")
// .attr("width", length / 4)
// .attr("height", length / 5.5 - margin.top)
// .style("fill", "url(#gradient)")
// .attr("transform", "translate(0," + (length / 5.4 - margin.left / 1) + ")");
// svg.append("text").attr("x", 0).attr("y", length / 6.5 - 0.4 * margin.top).text("12%").style("font-weight", "bold").attr("class", "legend-value")
// svg.append("text").attr("x", length / 20).attr("y", length / 6.5 - 0.4 * margin.top).text("10%").style("font-weight", "bold").attr("class", "legend-value")
// svg.append("text").attr("x", length / 10).attr("y", length / 6.5 - 0.4 * margin.top).text("8%").style("font-weight", "bold").attr("class", "legend-value")
// svg.append("text").attr("x", length / 7).attr("y", length / 6.5 - 0.4 * margin.top).text("6%").style("font-weight", "bold").attr("class", "legend-value")
// svg.append("text").attr("x", length / 5).attr("y", length / 6.5 - 0.4 * margin.top).text("4%").style("font-weight", "bold").attr("class", "legend-value")
// }
// /*
// var y = d3.scaleLinear()
// .range([length/4 - length/3.3, length/4.4 - length/(margin.left/4.0)])
// //.domain([maxVal, minVal]);
// //ar formatPercent = d3.format("%");
// var yAxis = d3.axisBottom(y).tickValues(["12%", "11%", "10%", "9%"]);
// svg.append("g")
// //.attr("class", "axis")
// .attr("transform", "translate(" + (length/5.45-margin.left/1.1) + "," + margin.right/0.75 + ")")
// .call(yAxis)
// .style("font-weight", "bold")
// .style("font-size", 1.3*(length/margin.top)); */
// svg
// .append("text")
// .attr("x", width / 5.5)
// .attr("y", -length / margin.top) // +20 to adjust position (lower)
// .text("Unemployment Rate for States in 2020 ")
// .attr("class", "title")
// .attr("fill", "black")
// .style("font-weight", "bold")
// });
// }
} }

View File

@@ -1,25 +1,25 @@
date,value date,value
Jan-2018,88.65 Січ-2018,88.65
Feb-2018,89.24 Лют-2018,89.24
Mar-2018,88.18 Бер-2018,88.18
Apr-2018,88.31 Кві-2018,88.31
May-2018,88.74 Тра-2018,88.74
Jun-2018,86.07 Чер-2018,86.07
Jul-2018,84.42 Лип-2018,84.42
Aug-2018,80.74 Сер-2018,80.74
Sep-2018,76.70 Вер-2018,76.70
Oct-2018,85.32 Жов-2018,85.32
Nov-2018,83.52 Лис-2018,83.52
Dec-2018,77.57 Гру-2018,77.57
Jan-2019,78.24 Січ-2019,78.24
Feb-2019,78.65 Лют-2019,78.65
Mar-2019,76.96 Бер-2019,76.96
Apr-2019,73.28 Кві-2019,73.28
May-2019,71.12 Тра-2019,71.12
Jun-2019,74.02 Чер-2019,74.02
Jul-2019,73.93 Лип-2019,73.93
Aug-2019,70.78 Сер-2019,70.78
Sep-2019,70.64 Вер-2019,70.64
Oct-2019,68.63 Жов-2019,68.63
Nov-2019,73.28 Лис-2019,73.28
Dec-2019,73.22 Гру-2019,73.22
1 date value
2 Jan-2018 Січ-2018 88.65
3 Feb-2018 Лют-2018 89.24
4 Mar-2018 Бер-2018 88.18
5 Apr-2018 Кві-2018 88.31
6 May-2018 Тра-2018 88.74
7 Jun-2018 Чер-2018 86.07
8 Jul-2018 Лип-2018 84.42
9 Aug-2018 Сер-2018 80.74
10 Sep-2018 Вер-2018 76.70
11 Oct-2018 Жов-2018 85.32
12 Nov-2018 Лис-2018 83.52
13 Dec-2018 Гру-2018 77.57
14 Jan-2019 Січ-2019 78.24
15 Feb-2019 Лют-2019 78.65
16 Mar-2019 Бер-2019 76.96
17 Apr-2019 Кві-2019 73.28
18 May-2019 Тра-2019 71.12
19 Jun-2019 Чер-2019 74.02
20 Jul-2019 Лип-2019 73.93
21 Aug-2019 Сер-2019 70.78
22 Sep-2019 Вер-2019 70.64
23 Oct-2019 Жов-2019 68.63
24 Nov-2019 Лис-2019 73.28
25 Dec-2019 Гру-2019 73.22

View File

@@ -1,15 +1,15 @@
Country,Speed Country,Speed
Australia,79.24 Австралія,79.24
China,78.61 Китай,78.61
Hong Kong,43.88 Гонконг,43.88
India,13.45 Індія,13.45
Indonesia,16.16 Індонезія,16.16
Japan,40.51 Японія,40.51
Malaysia,23.74 Малайзія,23.74
New Zealand,92.05 Нова Зеландія,92.05
Singapore,68.32 Сінгапур,68.32
South Korea,98.93 Південна Корея,98.93
Sri Lanka,12.60 Шрі-Ланка,12.60
Taiwan,51.67 Тайвань,51.67
Thailand,31.38 Тайланд,31.38
Vietnam,35.33 Вʼєтнам,35.33
1 Country Speed
2 Australia Австралія 79.24
3 China Китай 78.61
4 Hong Kong Гонконг 43.88
5 India Індія 13.45
6 Indonesia Індонезія 16.16
7 Japan Японія 40.51
8 Malaysia Малайзія 23.74
9 New Zealand Нова Зеландія 92.05
10 Singapore Сінгапур 68.32
11 South Korea Південна Корея 98.93
12 Sri Lanka Шрі-Ланка 12.60
13 Taiwan Тайвань 51.67
14 Thailand Тайланд 31.38
15 Vietnam Вʼєтнам 35.33

View File

@@ -1,12 +1,12 @@
City,Length,NumberofStations,Ridership City,Length,NumberofStations,Ridership
Delhi,230,348.12,1.7 Делі,230,348.12,1.7
Guangzhow,247,531.1,2.4 Гуанчжоу,247,531.1,2.4
Tokyo,249,316.3,3.6 Токіо,249,316.3,3.6
Mexico City,163,200.9,0.93 Мехіко,163,200.9,0.93
Moscow,198,412.1,1.6 Москва,198,412.1,1.6
London,317,439.2,0.335 Лондон,317,439.2,0.335
Seoul,436,547.9,2.7 Сеул,436,547.9,2.7
Paris,304,219.9,2.3 Париж,304,219.9,2.3
Beijing,342,727,0.75 Пекін,342,727,0.75
Shanghai,369,743,2.8 Шанхай,369,743,2.8
N.Y.C.,458,443.7,0.67 Нью-Йорк.,458,443.7,0.67
1 City Length NumberofStations Ridership
2 Delhi Делі 230 348.12 1.7
3 Guangzhow Гуанчжоу 247 531.1 2.4
4 Tokyo Токіо 249 316.3 3.6
5 Mexico City Мехіко 163 200.9 0.93
6 Moscow Москва 198 412.1 1.6
7 London Лондон 317 439.2 0.335
8 Seoul Сеул 436 547.9 2.7
9 Paris Париж 304 219.9 2.3
10 Beijing Пекін 342 727 0.75
11 Shanghai Шанхай 369 743 2.8
12 N.Y.C. Нью-Йорк. 458 443.7 0.67

View File

@@ -1,13 +1,13 @@
Month,Price Month,Price
Jan-2020,57.52 Січ-2020,57.52
Feb-2020,50.54 Лют-2020,50.54
Mar-2020,29.21 Бер-2020,29.21
Apr-2020,16.55 Кві-2020,16.55
May-2020,28.56 Тра-2020,28.56
Jun-2020,38.31 Чер-2020,38.31
Jul-2020,40.71 Лип-2020,40.71
Aug-2020,42.34 Сер-2020,42.34
Sep-2020,39.63 Вер-2020,39.63
Oct-2020,39.4 Жов-2020,39.4
Nov-2020,40.94 Лис-2020,40.94
Dec-2020,47.02 Гру-2020,47.02
1 Month Price
2 Jan-2020 Січ-2020 57.52
3 Feb-2020 Лют-2020 50.54
4 Mar-2020 Бер-2020 29.21
5 Apr-2020 Кві-2020 16.55
6 May-2020 Тра-2020 28.56
7 Jun-2020 Чер-2020 38.31
8 Jul-2020 Лип-2020 40.71
9 Aug-2020 Сер-2020 42.34
10 Sep-2020 Вер-2020 39.63
11 Oct-2020 Жов-2020 39.4
12 Nov-2020 Лис-2020 40.94
13 Dec-2020 Гру-2020 47.02

View File

@@ -4,4 +4,4 @@ Xiaomi,16
Apple,15 Apple,15
Oppo,10 Oppo,10
Vivo,10 Vivo,10
Others,24 Інші,24
1 Brand Share
4 Apple 15
5 Oppo 10
6 Vivo 10
7 Others Інші 24

View File

@@ -1,11 +1,11 @@
City,Sandwich,Water,Peanut,Soda,Vodka City,Sandwich,Water,Peanut,Soda,Vodka
Helsinki,38.2,6.4,12.1,6.4,5.5 Хельсінкі,38.2,6.4,12.1,6.4,5.5
Oslo,29.4,7.4,15.8,7.1,5.1 Осло,29.4,7.4,15.8,7.1,5.1
Seoul,27.4,7.4,6.1,7.9,9.9 Сеул,27.4,7.4,6.1,7.9,9.9
Zurich,31.3,5.9,10.8,5.6,4.3 Цюріх,31.3,5.9,10.8,5.6,4.3
Stockholm,28.4,4.1,11.3,4.9,5 Стокгольм,28.4,4.1,11.3,4.9,5
Paris,23.6,6.8,12.6,6.8,7.0 Париж,23.6,6.8,12.6,6.8,7.0
N.Y.C.,24.2,3.9,16.8,3.9,7.1 Нью-Йорк.,24.2,3.9,16.8,3.9,7.1
Singapore,19.4,5.2,14.8,6.6,6.2 Сінгапур,19.4,5.2,14.8,6.6,6.2
Toronto,20.8,5.4,18.2,3.4,8.0 Торонто,20.8,5.4,18.2,3.4,8.0
Copenhagen,24.3,2.8,10.8,5.0,3.7 Копенгаген,24.3,2.8,10.8,5.0,3.7
1 City Sandwich Water Peanut Soda Vodka
2 Helsinki Хельсінкі 38.2 6.4 12.1 6.4 5.5
3 Oslo Осло 29.4 7.4 15.8 7.1 5.1
4 Seoul Сеул 27.4 7.4 6.1 7.9 9.9
5 Zurich Цюріх 31.3 5.9 10.8 5.6 4.3
6 Stockholm Стокгольм 28.4 4.1 11.3 4.9 5
7 Paris Париж 23.6 6.8 12.6 6.8 7.0
8 N.Y.C. Нью-Йорк. 24.2 3.9 16.8 3.9 7.1
9 Singapore Сінгапур 19.4 5.2 14.8 6.6 6.2
10 Toronto Торонто 20.8 5.4 18.2 3.4 8.0
11 Copenhagen Копенгаген 24.3 2.8 10.8 5.0 3.7

View File

@@ -1,7 +1,7 @@
{ {
"children": [ "children": [
{ {
"name": "Search/Portal", "name": "Пошук/Портал",
"children": [ "children": [
{ {
"name": "Google", "name": "Google",
@@ -31,7 +31,7 @@
"colname": "level2" "colname": "level2"
}, },
{ {
"name": "Social Network", "name": "Соцмережа",
"children": [ "children": [
{ {
"name": "Facebook", "name": "Facebook",
@@ -61,7 +61,7 @@
"colname": "level2" "colname": "level2"
}, },
{ {
"name": "Software", "name": "Програмне забезпечення",
"children": [ "children": [
{ {
"name": "Mozilla", "name": "Mozilla",
@@ -97,7 +97,7 @@
"colname": "level2" "colname": "level2"
}, },
{ {
"name": "Retail", "name": "Онлайн-магазини",
"children": [ "children": [
{ {
"name": "eBay", "name": "eBay",
@@ -133,7 +133,7 @@
"colname": "level2" "colname": "level2"
}, },
{ {
"name": "Computer", "name": "Копмʼютери",
"children": [ "children": [
{ {
"name": "Apple", "name": "Apple",

View File

@@ -146,7 +146,7 @@ class HistogramMini extends Component {
.attr("x", - (height / 1.9)) .attr("x", - (height / 1.9))
.attr("dy", "1em") .attr("dy", "1em")
.style("text-anchor", "middle") .style("text-anchor", "middle")
.text("Number of Customers") .text("Кількість користувачів")
.style("font-weight", "bold") .style("font-weight", "bold")
svg.append("text") svg.append("text")
@@ -164,14 +164,14 @@ class HistogramMini extends Component {
}) })
.style("text-anchor", "middle") .style("text-anchor", "middle")
.style("font-weight", "bold") .style("font-weight", "bold")
.text("Distance (in Km)") .text("Відстань (км)")
svg svg
.append("text") .append("text")
.attr("class", "title") .attr("class", "title")
.attr("x", width / 3) .attr("x", width / 3)
.attr("y", -length / margin.top) // +20 to adjust position (lower) .attr("y", -length / margin.top) // +20 to adjust position (lower)
.text("Trip Distance and Customers") .text("Відстань та користувачі")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")
}) })

View File

@@ -5,6 +5,18 @@ import { Container, Col, Row, Navbar, Button, ButtonGroup, ToggleButton, Form, I
import '../App.css'; import '../App.css';
import img12 from '../components/data/Mini-VLAT/LineChart.png'; import img12 from '../components/data/Mini-VLAT/LineChart.png';
const ukLocale = {
"dateTime": "%A, %e %B %Y р. %X",
"date": "%d.%m.%Y",
"time": "%H:%M:%S",
"periods": ["AM", "PM"],
"days": ["неділя", "понеділок", "вівторок", "середа", "четвер", "п'ятниця", "субота"],
"shortDays": ["нд", "пн", "вт", "ср", "чт", "пт", "сб"],
"months": ["січень", "лютий", "березень", "квітень", "травень", "червень", "липень", "серпень", "вересень", "жовтень", "листопад", "грудень"],
"shortMonths": ["січ", "лют", "бер", "кві", "тра", "чер", "лип", "сер", "вер", "жов", "лис", "гру"]
};
const formatUk = d3.timeFormatLocale(ukLocale).format("%B");
class LineChartMini extends Component { class LineChartMini extends Component {
@@ -72,7 +84,7 @@ class LineChartMini extends Component {
svg.append("g") svg.append("g")
//.attr("class", "axis") //.attr("class", "axis")
.attr("transform", `translate(0, ${height})`) .attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(xScale).tickFormat(d3.timeFormat("%B"))) .call(d3.axisBottom(xScale).tickFormat(formatUk))
/* /*
.style("font-size", function() { .style("font-size", function() {
if (length < 700){ if (length < 700){
@@ -206,7 +218,7 @@ class LineChartMini extends Component {
.attr("x", 0 - (height / 1.9)) .attr("x", 0 - (height / 1.9))
.attr("dy", "1em") .attr("dy", "1em")
.style("text-anchor", "middle") .style("text-anchor", "middle")
.text("Oil Price ($)") .text("Ціна на нафту ($)")
.style("font-weight", "bold") .style("font-weight", "bold")
svg svg
@@ -214,7 +226,7 @@ class LineChartMini extends Component {
.attr("class", "title") .attr("class", "title")
.attr("x", width / 2.5) .attr("x", width / 2.5)
.attr("y", -length / margin.top) // +20 to adjust position (lower) .attr("y", -length / margin.top) // +20 to adjust position (lower)
.text("Oil Prices in 2020") .text("Ціни на нафту в 2020")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")
@@ -233,7 +245,7 @@ class LineChartMini extends Component {
}) })
.style("text-anchor", "middle") .style("text-anchor", "middle")
.style("font-weight", "bold") .style("font-weight", "bold")
.text("Month") .text("Місяць")
} }

View File

@@ -60,7 +60,7 @@ class PieChartMini extends Component {
.append("g") .append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`); .attr("transform", `translate(${width / 2}, ${height / 2})`);
var data = { Samsung: 17.6, Xiaomi: 15.5, Apple: 15.0, Oppo: 10.2, Vivo: 9.8, Others: 31.9 } var data = { Samsung: 17.6, Xiaomi: 15.5, Apple: 15.0, Oppo: 10.2, Vivo: 9.8, Інші: 31.9 }
const color = d3.scaleOrdinal() const color = d3.scaleOrdinal()
.range(['#0868ac', '#f03b20', '#feb24c', '#78c679', '#ffffb2', '#756bb1']) .range(['#0868ac', '#f03b20', '#feb24c', '#78c679', '#ffffb2', '#756bb1'])
@@ -119,7 +119,7 @@ class PieChartMini extends Component {
.attr("class", "title") .attr("class", "title")
.attr("x", -width / 5.5) .attr("x", -width / 5.5)
.attr("y", -width / 3) // +20 to adjust position (lower) .attr("y", -width / 3) // +20 to adjust position (lower)
.text("Global Smartphone Market Share in 2021") .text("Світовий ринок смартфонів в 2021")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")

View File

@@ -1,6 +1,6 @@
const questions = [ const questions = [
{ {
question: "What was the price range of a barrel of oil in 2020?", question: "Яким був ціновий діапазон бареля нафти у 2020 році?",
optionA: "$16.55 - $57.52", optionA: "$16.55 - $57.52",
optionB: "$19.52 - $59.00", optionB: "$19.52 - $59.00",
optionC: "$23.43 - $60.72", optionC: "$23.43 - $60.72",
@@ -9,7 +9,7 @@ const questions = [
}, },
{ {
question: "What is the range of the average internet speed in Asia?", question: "Який діапазон середньої швидкості інтернету в Азії?",
optionA: "5.50Mbps - 30.60Mbps", optionA: "5.50Mbps - 30.60Mbps",
optionB: "7.00Mbps - 29.40Mbps", optionB: "7.00Mbps - 29.40Mbps",
optionC: "6.40Mbps - 27.38Mbps", optionC: "6.40Mbps - 27.38Mbps",
@@ -17,7 +17,7 @@ const questions = [
correctOption: "optionD", correctOption: "optionD",
}, },
{ {
question: "What is the cost of peanuts in Las Vegas?", question: "Яка ціна арахісу в Лас Веґасі?",
optionA: "$9.0", optionA: "$9.0",
optionB: "$6.1", optionB: "$6.1",
optionC: "$10.3", optionC: "$10.3",
@@ -26,7 +26,7 @@ const questions = [
}, },
{ {
question: "What is the approval rating of Republicans among the people who have the education level of Postgraduate Study?", question: "Який рейтинг схвалення республіканців серед людей, які мають освітній рівень післядипломної освіти?",
optionA: "35%", optionA: "35%",
optionB: "27%", optionB: "27%",
optionC: "23%", optionC: "23%",
@@ -35,7 +35,7 @@ const questions = [
}, },
{ {
question: "About what is the global smartphone market share of Samsung?", question: "Яку приблизно частку ринку смартфонів займає Samsung?",
optionA: "20%", optionA: "20%",
optionB: "25%", optionB: "25%",
optionC: "30%", optionC: "30%",
@@ -44,7 +44,7 @@ const questions = [
}, },
{ {
question: "How many people have rated the taxi between 4.2 and 4.4?", question: "Скільки людей оцінили таксі рейтингом між 4.2 та 4.4?",
optionA: "270", optionA: "270",
optionB: "190", optionB: "190",
optionC: "300", optionC: "300",
@@ -53,7 +53,7 @@ const questions = [
}, },
{ {
question: "There is a negative linear relationship between the height and the weight of the 85 males.", question: "Існує негативна лінійна залежність між ростом і вагою 85 чоловіків.",
optionA: "True", optionA: "True",
optionB: "False", optionB: "False",
optionC: "", optionC: "",
@@ -62,7 +62,7 @@ const questions = [
}, },
{ {
question: "What was the average price of a pound of coffee beans in September 2013?", question: "Якою була середня ціна фунту кавових бобів в вересні 2013?",
optionA: "$5.15", optionA: "$5.15",
optionB: "$6.2", optionB: "$6.2",
optionC: "$4.8", optionC: "$4.8",
@@ -71,7 +71,7 @@ const questions = [
}, },
{ {
question: "What was the number of girls named 'Olivia' in 2010 in the UK?", question: "Яку кількість дівчаток назвали «Олівія» у 2010 році у Великобританії?",
optionA: "2000", optionA: "2000",
optionB: "2500", optionB: "2500",
optionC: "1700", optionC: "1700",
@@ -80,7 +80,7 @@ const questions = [
}, },
{ {
question: "What is the total length of the metro system in Beijing?", question: "Яка загальна протяжність системи метро в Пекіні?",
optionA: "525 km", optionA: "525 km",
optionB: "495 km", optionB: "495 km",
optionC: "305 km", optionC: "305 km",
@@ -89,7 +89,7 @@ const questions = [
}, },
{ {
question: "In 2015, the unemployment rate for Washington (WA) was higher than that of Wisconsin (WI)", question: "У 2015 році рівень безробіття у Вашингтоні (WA) був вищим, ніж у Вісконсині (WI).",
optionA: "True", optionA: "True",
optionB: "False", optionB: "False",
optionC: "", optionC: "",
@@ -98,7 +98,7 @@ const questions = [
}, },
{ {
question: "For which website was the number of unique visitors the largest in 2010?", question: "Для якого сайту кількість унікальних відвідувачів була найбільшою в 2010 році?",
optionA: "Amazon", optionA: "Amazon",
optionB: "Chase", optionB: "Chase",
optionC: "PayPal", optionC: "PayPal",

View File

@@ -135,7 +135,7 @@ class ScatterPlotMini extends Component {
.attr("x", 0 - (height / 1.9)) .attr("x", 0 - (height / 1.9))
.attr("dy", "1em") .attr("dy", "1em")
.style("text-anchor", "middle") .style("text-anchor", "middle")
.text("Weight (kg)") .text("Маса (кг)")
.style("font-weight", "bold") .style("font-weight", "bold")
svg svg
@@ -143,7 +143,7 @@ class ScatterPlotMini extends Component {
.attr("class", "title") .attr("class", "title")
.attr("x", width / 4) .attr("x", width / 4)
.attr("y", -length / margin.top) // +20 to adjust position (lower) .attr("y", -length / margin.top) // +20 to adjust position (lower)
.text("Weight and Height of 85 Individuals") .text("Маса та висота 85 осіб")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")
@@ -159,7 +159,7 @@ class ScatterPlotMini extends Component {
}) })
.style("text-anchor", "middle") .style("text-anchor", "middle")
.style("font-weight", "bold") .style("font-weight", "bold")
.text("Height (cm)") .text("Висота (см)")
}) })

View File

@@ -152,7 +152,7 @@ class StackedBarChartMini extends Component {
.attr("x", 0 - (height / 1.9)) .attr("x", 0 - (height / 1.9))
.attr("dy", "1em") .attr("dy", "1em")
.style("text-anchor", "middle") .style("text-anchor", "middle")
.text("Olympic Medals (%)") .text("Олімпійські Медалі (%)")
.style("font-weight", "bold") .style("font-weight", "bold")
svg.append("text") svg.append("text")
@@ -167,7 +167,7 @@ class StackedBarChartMini extends Component {
}) })
.style("text-anchor", "middle") .style("text-anchor", "middle")
.style("font-weight", "bold") .style("font-weight", "bold")
.text("Countries") .text("Країни")
//var legend = svg.append('g').attr('class', 'legend').attr('transform', 'translate(' + (margin.left/2) + ',0)'); //var legend = svg.append('g').attr('class', 'legend').attr('transform', 'translate(' + (margin.left/2) + ',0)');
@@ -274,7 +274,7 @@ class StackedBarChartMini extends Component {
.attr("fill", "#feb24c") .attr("fill", "#feb24c")
svg.append("text") svg.append("text")
.text("Bronze") .text("Бронза")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (5.5 * width / margin.left) return width + (5.5 * width / margin.left)
@@ -300,7 +300,7 @@ class StackedBarChartMini extends Component {
.attr("class", "legend-value") .attr("class", "legend-value")
svg.append("text") svg.append("text")
.text("Silver") .text("Срібло")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (5.5 * width / margin.left) return width + (5.5 * width / margin.left)
@@ -326,7 +326,7 @@ class StackedBarChartMini extends Component {
.attr("class", "legend-value") .attr("class", "legend-value")
svg.append("text") svg.append("text")
.text("Gold") .text("Золото")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (5.5 * width / margin.left) return width + (5.5 * width / margin.left)
@@ -363,7 +363,7 @@ class StackedBarChartMini extends Component {
} }
}) })
.attr("y", -length / margin.top) // +20 to adjust position (lower) .attr("y", -length / margin.top) // +20 to adjust position (lower)
.text("Tokyo 2020 Olympics Performance Summary") .text("Підсумок Олімпіади 2020 в Токіо")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")

View File

@@ -123,7 +123,7 @@ class StackedAreaPlotMini extends Component {
.attr("x", 0 - (height / 1.9)) .attr("x", 0 - (height / 1.9))
.attr("dy", "1em") .attr("dy", "1em")
.style("text-anchor", "middle") .style("text-anchor", "middle")
.text("Number of Girls") .text("Кількість дівчат")
.style("font-weight", "bold") .style("font-weight", "bold")
.style('font-size', function () { .style('font-size', function () {
if (width < 500) { if (width < 500) {
@@ -143,7 +143,7 @@ class StackedAreaPlotMini extends Component {
}) })
.style("text-anchor", "middle") .style("text-anchor", "middle")
.style("font-weight", "bold") .style("font-weight", "bold")
.text("Year") .text("Рік")
const areaChart = svg.append('g') const areaChart = svg.append('g')
.attr("clip-path", "url(#clip)") .attr("clip-path", "url(#clip)")
@@ -276,7 +276,7 @@ class StackedAreaPlotMini extends Component {
.attr("fill", "#3182bd") .attr("fill", "#3182bd")
svg.append("text") svg.append("text")
.text("Olivia") .text("Олівія")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (2 * width / margin.left) + length / 25; return width + (2 * width / margin.left) + length / 25;
@@ -296,7 +296,7 @@ class StackedAreaPlotMini extends Component {
.attr("class", "legend-value") .attr("class", "legend-value")
svg.append("text") svg.append("text")
.text("Isla") .text("Айле")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (2 * width / margin.left) + length / 25; return width + (2 * width / margin.left) + length / 25;
@@ -317,7 +317,7 @@ class StackedAreaPlotMini extends Component {
.attr("class", "legend-value") .attr("class", "legend-value")
svg.append("text") svg.append("text")
.text("Amelia") .text("Амелія")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (2 * width / margin.left) + length / 25; return width + (2 * width / margin.left) + length / 25;
@@ -349,7 +349,7 @@ class StackedAreaPlotMini extends Component {
} }
}) })
.attr("y", -length / margin.top) // +20 to adjust position (lower) .attr("y", -length / margin.top) // +20 to adjust position (lower)
.text("Popular Girls' names in the UK") .text("Популярні жіночі імена в Великій Британії")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")
}) })

View File

@@ -172,7 +172,7 @@ class StackedBarChart2Mini extends Component {
.attr("x", - (height / 1.9)) .attr("x", - (height / 1.9))
.attr("dy", "1em") .attr("dy", "1em")
.style("text-anchor", "middle") .style("text-anchor", "middle")
.text("Cost ($)") .text("Ціна ($)")
.style("font-weight", "bold") .style("font-weight", "bold")
svg.append("text") svg.append("text")
@@ -187,7 +187,7 @@ class StackedBarChart2Mini extends Component {
}) })
.style("text-anchor", "middle") .style("text-anchor", "middle")
.style("font-weight", "bold") .style("font-weight", "bold")
.text("Cities") .text("Міста")
var dataNormalized = [] var dataNormalized = []
data.forEach(function (d) { data.forEach(function (d) {
@@ -339,7 +339,7 @@ class StackedBarChart2Mini extends Component {
.attr("fill", "#7fc97f") .attr("fill", "#7fc97f")
svg.append("text") svg.append("text")
.text("Vodka") .text("Горілка")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (5 * width / margin.left) return width + (5 * width / margin.left)
@@ -365,7 +365,7 @@ class StackedBarChart2Mini extends Component {
.attr("class", "legend-value") .attr("class", "legend-value")
svg.append("text") svg.append("text")
.text("Soda") .text("Содова")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (5 * width / margin.left) return width + (5 * width / margin.left)
@@ -391,7 +391,7 @@ class StackedBarChart2Mini extends Component {
.attr("class", "legend-value") .attr("class", "legend-value")
svg.append("text") svg.append("text")
.text("Peanut") .text("Арахіс")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (5 * width / margin.left) return width + (5 * width / margin.left)
@@ -417,7 +417,7 @@ class StackedBarChart2Mini extends Component {
.attr("class", "legend-value") .attr("class", "legend-value")
svg.append("text") svg.append("text")
.text("Water") .text("Вода")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (5 * width / margin.left) return width + (5 * width / margin.left)
@@ -443,7 +443,7 @@ class StackedBarChart2Mini extends Component {
.attr("class", "legend-value") .attr("class", "legend-value")
svg.append("text") svg.append("text")
.text("Sandwich") .text("Сендвіч")
.attr("x", function () { .attr("x", function () {
if (width < 500 && width > 400) { if (width < 500 && width > 400) {
return width + (5 * width / margin.left) return width + (5 * width / margin.left)
@@ -473,7 +473,7 @@ class StackedBarChart2Mini extends Component {
.attr("class", "title") .attr("class", "title")
.attr("x", width / 3) .attr("x", width / 3)
.attr("y", -length / margin.top) // +20 to adjust position (lower) .attr("y", -length / margin.top) // +20 to adjust position (lower)
.text("Room Service Prices") .text("Вартість обслуговування номера")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")
}) })

View File

@@ -200,7 +200,7 @@ class TreeMapMini extends Component {
.attr("class", "title") .attr("class", "title")
.attr("x", width / margin.left) .attr("x", width / margin.left)
.attr("y", -length / margin.top) // +20 to adjust position (lower) .attr("y", -length / margin.top) // +20 to adjust position (lower)
.text("The Number of Unique Visitors for Websites") .text("Кількість унікальних відвідувачів вебсайтів")
.attr("fill", "black") .attr("fill", "black")
.style("font-weight", "bold") .style("font-weight", "bold")

View File

@@ -47,58 +47,36 @@ class Intro extends Component {
return ( return (
<> <>
<Row className={'justify-content-center no-margin-row'}> <Row className={'justify-content-center no-margin-row'}>
<div className='heading-term'>Consent Form</div> <div className='heading-term'>Згода на участь в дослідженні з візуалізації даних</div>
<div className={'terms-container'}> <div className={'terms-container'}>
<div className='terms'> <div className='terms'>
<p>We invite you to participate in a research study being conducted by investigators from Washington University in St. Louis. You are being asked to participate in this research study because you are an English-speaking adult in the United States. The purpose of the study is to develop an instrument that measures how well people can read, understand, and use data visualizations to solve problems.</p> <p>
<p>If you agree to participate, we would like you to questions about a series of data visualization. For each question, you will see a data visualization and a problem to solve. Choose the BEST answer to the questions. If you are unsure, Select ''Skip'' instead of guessing. You are also free to skip any questions that you prefer not to answer. In the end, you will complete a brief demographic survey.</p> Запрошуємо взяти участь в дослідженні, що проводять викладачі та студенти Київської школи економіки. Ви запрошені взяти участь у цьому дослідженні, оскільки є дорослою людиною, яка володіє українською мовою. Мета дослідження полягає в адаптації інструменту, який вимірює, наскільки добре люди можуть зчитувати, розуміти та використовувати візуалізацію даних для вирішення проблем.
<p>We would like to use the data we are obtaining in this study for studies going on right now as well as studies that are conducted in the future.</p>
<p>These studies may provide additional information that will be helpful in developing better visual communication tools. It is unlikely that what we learn from these studies will have a direct benefit to you. There are no plans to provide financial compensation to you should this occur. By allowing us to use your data you give up any property rights you may have in the data.</p>
<p>We will share your data with other researchers. They may be doing research in areas similar to this research or in other unrelated areas. These researchers may be at Washington University, at other research centers and institutions, or industry sponsors of research. We may also share your research data with large data repositories (a repository is a database of information) for broad sharing with the research community. If your individual research data is placed in one of these repositories only qualified researchers, who have received prior approval from individuals that monitor the use of the data, will be able to look at your information.</p>
<p>Your data will be stored without your name or any other kind of link that would enable us to identify which data are yours. Therefore, it will be available indefinitely for use in future research studies without your additional consent and cannot be removed.</p>
{/* <p>Approximately 5100 people will take part in this study at Washington University.</p> */}
<p>There are no known risks from being in this study.</p>
<p>You will not benefit personally. However, we hope that others may benefit in the future from what we learn as a result of this study. You will not have any costs for being in this research study.</p>
<p>You will be paid for being in this research study. You will receive a pay of $8 for completing this quiz. At the end of the study, copy your survey code back to Prolific to receive your payment.</p>
<p>We will keep the information you provide confidentially. This survey is completely anonymous; we will not collect any personally identifiable information. We will only have access to your Prolific ID only, which we will use solely for payment purposes. </p>
<p>Any report or article that we write will not include information that can directly identify you. The journals that publish these reports or articles require that we share the information that was collected for this study with others to make sure the results of this study are correct and help develop new ideas for research. Your information will be shared in a way that cannot directly identify you.</p>
<p>Federal regulatory agencies and Washington University, including the Washington University Institutional Review Board (a committee that reviews and approves research studies) and the Human Research Protection Office, may inspect and copy records pertaining to this research.
Your participation in this study is completely voluntary. You may choose not to take part at all. If you decide to participate in the study, you may stop participating at any time. Any data that was collected as part of this study will remain as part of the study records and cannot be removed. If you decide not to take part in the study or if you stop participating at any time, you wont be penalized or lose any benefits for which you otherwise qualify.
</p> </p>
<p>If you do not wish to participate in this study or want to end your participation in the study, close the browser tab without answering any of the questions.</p> <p>
Якщо ви погоджуєтеся взяти участь, запропонуємо вам дати відповіді щодо змісту декількох візуалізацій даних. Для кожного питання ви побачите візуалізацію даних та запитання, на яке потрібно дати відповідь. Виберіть відповідь на питання, яку вважаєте НАЙТОЧНІШОЮ. Якщо ви не впевнені, виберіть «Пропустити» замість того, щоб вгадувати. Ви також можете пропустити будь-які питання, на які ви вважаєте за краще не відповідати. У кінці ми попросимо вас заповнити короткий демографічний опитувальник.
<p> We encourage you to ask questions. If you have any questions about the research study itself, please contact Saugat Pandey (p.saugat@wustl.edu). If you feel you have been harmed from being in the study, please contact Alvitta Ottley (alvitta@wustl.edu). If you have questions, concerns, or complaints about your rights as a research participant, please contact the Human Research Protection Office at 1-(800)-438-0445 or email hrpo@wustl.edu. General information about being a research participant can be found on the Human Research Protection Office website, http://hrpo.wustl.edu. To offer input about your experiences as a research participant or to speak to someone other than the research staff, call the Human Research Protection Office at the number above.</p> </p>
<p>
<p>Thank you very much for your consideration of this research study.</p> Дані, які ми зберемо протягом цього опитування, будуть використані в поточному та подальших дослідженнях грамотності в сфері візуалізації даних.
</p>
{/* <p>
Ці дослідження можуть надати додаткову інформацію, яка буде корисною для розробки кращих інструментів візуальної комунікації. Скоріш за все знахідки з цих досліджень не будуть для вас прямої користі. Немає планів надавати вам фінансову компенсацію, якщо це станеться. Дозволивши нам використовувати ваші дані, ви відмовляєтеся від будь-яких прав власності, які ви можете мати на ці дані.
</p> */}
<p>
Зібрані дані будуть передаті іншим дослідникам, які проводитимуть дослідження в цій або суміжних сферах. Також ми можемо опублікувати зібрані дані в репозиторіях даних для широкого доступу до наукової спільноти. Якщо ваші індивідуальні дані будуть розміщені в одному з цих репозиторіїв, лише кваліфіковані дослідники, які отримали попередню згоду від осіб, які контролюють використання даних, зможуть переглядати вашу інформацію.
</p>
<p>
Ваші дані будуть збережені без вашого імені чи будь-якої іншої інформації, яка дозволила б нам ідентифікувати вас персонально. Тому вони будуть доступні безстроково для використання в майбутніх дослідженнях без вашої додаткової згоди та не можуть бути видалені.
</p>
<p>Участь в цьому дослідженні не містить для вас жодних відомих потенційних ризиків.</p>
<p>
Якщо ви маєте будь-які питання щодо змісту та деталей дослідження, будь-ласка напишіть Олегу Омельченко (o_omelchenko@kse.org.ua).
</p>
<p>Дякуємо за зацікавленість дослідженням!</p>
</div> </div>
{/* <p>Taking part in this research study is completely voluntary. You may choose not to take part at all.
If you decide to be in this study, you may stop participating at any time. Any data that was collected
as part of your participation in the study will remain as part of the study records and cannot be removed.
As a part of this study:
<ul>
<li><b>We will not collect your name or any identifying information about you. It will not be possible
to link you to your responses on the survey.</b></li>
<li>We will store you response to every question along with the time taken to solve every question and total score.</li>
{/* <li>We will store information about your mouse interaction (e.g. what you clicked) when answering the survey questions.</li>
<li>We may allow other researchers to use the interaction data that we collect.
Researchers from other universities can request to use the data.</li>
</ul>
</p>
<p>
We encourage you to ask questions. If you have any questions about the research study itself, please contact: Alvitta Ottley (alvitta@wustl.edu). If you have questions, concerns, or complaints about your rights as a research participant, please contact the Human Research Protection Office at 660 South Euclid Avenue, Campus Box 8089, St. Louis, MO 63110, 1-(800)-438-0445 or email hrpo@wusm.wustl.edu. General information about being a research participant can be found on the Human Research Protection Office web site, http://hrpo.wustl.edu/. To offer input about your experiences as a research participant or to speak to someone other than the research staff, call the Human Research Protection Office at the number above.
Thank you very much for your consideration of this research study.
</p> */}
<div className={'text-center'}><Button onClick={this.go_to_tutorial.bind(this)} className={'btn-sm'} variant={"success"}> <div className={'text-center'}><Button onClick={this.go_to_tutorial.bind(this)} className={'btn-sm'} variant={"success"}>
I agree to participate. Я погоджуюсь взяти участь в дослідженні.
</Button></div> </Button></div>
</div> </div>

View File

@@ -6,7 +6,6 @@ import { record_ques } from './visualization_quiz';
class ThankYou extends Component { class ThankYou extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
} }
@@ -24,8 +23,8 @@ class ThankYou extends Component {
<table style={{ borderCollapse: 'collapse', margin: 'auto' }}> <table style={{ borderCollapse: 'collapse', margin: 'auto' }}>
<thead> <thead>
<tr> <tr>
<th style={{ border: '1px solid black', padding: '5px' }}>Question</th> <th style={{ border: '1px solid black', padding: '5px' }}>Запитання</th>
<th style={{ border: '1px solid black', padding: '5px' }}>Result</th> <th style={{ border: '1px solid black', padding: '5px' }}>Результат</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -33,7 +32,7 @@ class ThankYou extends Component {
<tr key={question}> <tr key={question}>
<td style={{ border: '1px solid black', padding: '5px' }}>{question}</td> <td style={{ border: '1px solid black', padding: '5px' }}>{question}</td>
<td style={{ border: '1px solid black', padding: '5px' }}> <td style={{ border: '1px solid black', padding: '5px' }}>
{answer === 'Correct' ? <span>&#10004;</span> : answer === 'Wrong' ? <span>&#10060;</span> : answer} {answer === 'Вірно' ? <span>&#10004;</span> : answer === 'Невірно' ? <span>&#10060;</span> : answer}
</td> </td>
</tr> </tr>
))} ))}
@@ -48,17 +47,17 @@ class ThankYou extends Component {
console.log("The values of the dictionary is: " + Object.values(record_ques)) console.log("The values of the dictionary is: " + Object.values(record_ques))
if (this.props.location.state == null) { if (this.props.location.state == null) {
return (<p>Unknown session. Please start from the <a href={'#/'}> consent page</a></p>) return (<p>Невизначена сесія. Будь ласка поверніться на <a href={'/'}> сторінку згоди</a></p>)
} }
if (this.state == null) { if (this.state == null) {
return (<p>Loading...</p>) return (<p>Завантаження...</p>)
} }
return ( return (
<Row className={'justify-content-center no-margin-row'}> <Row className={'justify-content-center no-margin-row'}>
<Col lg={6} className={'text-box text-justify'}> <Col lg={6} className={'text-box text-justify'}>
<h3 style={{ marginBottom: '20px' }}>You scored {score_2} out of 12. Thank you for participating in our study. Your responses have been recorded.</h3> <h3 style={{ marginBottom: '20px' }}>Ваш результат: {score_2} із 12. Дякую за те що взяли участь в дослідженні. Ваші відповіді були збережені.</h3>
{this.renderTable()} {this.renderTable()}
</Col> </Col>
</Row> </Row>

View File

@@ -31,7 +31,7 @@ class Tutorial extends Component {
componentDidMount() { componentDidMount() {
fetch('./new_session_id', { fetch(`${process.env.REACT_APP_API_URL}/new_session_id`, {
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
@@ -54,7 +54,7 @@ class Tutorial extends Component {
render() { render() {
if (this.props.location.state == null) { if (this.props.location.state == null) {
return (<p>Unknown session. Please start from the <a href={'#/'}> consent page</a></p>) return (<p>Невизначена сесія. Будь ласка поверніться на <a href={'/'}> сторінку згоди</a></p>)
} }
return ( return (
@@ -62,20 +62,24 @@ class Tutorial extends Component {
<Row className={'justify-content-center tutorial-body'}> <Row className={'justify-content-center tutorial-body'}>
<Col lg={6} className={'text-box text-justify'}> <Col lg={6} className={'text-box text-justify'}>
<p className='head_1'>Intructions</p> <p className='head_1'>Інструкції</p>
<ul> <ul>
<li className='int_1'>You will be given 12 multiple-choice questions</li> <li className='int_1'>
<li className='int_1'>Answer to the best of your ability. If you are unsure,you may skip the questions instead of guessing.</li> Вам буде запропоновано 12 питань з вибором однієї правильної відповіді.</li>
{/* <li className='int_1'>Please do not take this test if you are color blind. Here is a link to a separate test: <a href='https://colormax.org/color-blind-test/' target="_blank">https://colormax.org/color-blind-test/</a></li> */} <li className='int_1'>
{/* <li>We will store information about your mouse interaction (e.g. what you clicked) when answering the survey questions.</li> */} Дайте на них відповідь, якщо ви впевнені в своїй відповіді. Якщо ви не впевнені, ви можете пропустити питання, а не вгадувати.
</li>
</ul> </ul>
<p className='head_2'><b>Important: You will have 25 seconds to answer each question.</b> Answer to the best of your ability. You may <b>skip the questions instead of guessing</b> if you are unsure.</p> <p className='head_2'>
<b>Важливо: на відповідь до кожного питання ви маєте 25 секунд.</b>
Відповідайте на кожне питання якнайкраще. Якщо ви не впевнені, ви можете <b>пропустити питання, а не вгадувати</b>.
</p>
<div className={'text-center'}> <div className={'text-center'}>
<Button onClick={this.on_experiment_click.bind(this)} <Button onClick={this.on_experiment_click.bind(this)}
className={'btn-sm'} variant={"success"}> className={'btn-sm'} variant={"success"}>
Start the experiment. Розпочати
</Button> </Button>
</div> </div>

View File

@@ -34,18 +34,94 @@ import img12 from '../components/data/VLAT-Pics/LineChart.png'
let minivis = [ let minivis = [
{ 'vis': BarChartMini, 'type': 'Bar Chart', 'question': 'What is the average internet speed in Japan?', 'options': ["42.30 Mbps", "40.51 Mbps", "35.25 Mbps", "16.16 Mbps", "Skip"], 'correct_answer': 1, 'cimage': img9 }, {
{ 'vis': AreaChartMini, 'type': 'Area Chart', 'question': 'What was the average price of pount of coffee beans in October 2019?', 'options': ["$0.71", "$0.90", "$0.80", "$0.63", "Skip"], 'correct_answer': 0, 'cimage': img10 }, 'vis': LineChartMini,
{ 'vis': BubbleChartMini, 'type': 'Bubble Chart', 'question': 'Which city\'s metro system has the largest number of stations?', 'options': ['Beijing', 'Shanghai', 'London', 'Seoul', "Skip"], 'correct_answer': 1, 'cimage': img3 }, 'type': 'Лінійна діаграма',
{ 'vis': ChoroplethMini, 'type': 'Choropleth', 'question': 'In 2020, the unemployment rate for Washington (WA) was higher than that of Wisconsin (WI).', 'options': ['True', 'False', "Skip"], 'correct_answer': 0, 'cimage': img8 }, 'question': 'Яким був ціновий діапазон бареля нафти у 2020 році?',
{ 'vis': HistogramMini, 'type': 'Histogram', 'question': 'What distance have customers traveled in the taxi the most?', 'options': ["60 - 70 Km", "30 - 40 Km", "20 - 30 Km", "50 - 60 Km", "Skip"], 'correct_answer': 1, 'cimage': img6 }, 'options': ["$16.55 - $57.52", "$19.52 - $59.00", "$23.43 - $60.72", "$21.82 - $87.52", "Пропустити"],
{ 'vis': LineChartMini, 'type': 'Line Chart', 'question': 'What was the price of a barrel of oil in February 2020?', 'options': ["$50.54", "$47.02", "$42.34", "$42.34", "Skip"], 'correct_answer': 0, 'cimage': img12 }, 'correct_answer': 0,
{ 'vis': TreeMapMini, 'type': 'Treemap', 'question': 'eBay is nested in the Software category.', 'options': ['True', 'False', 'Skip'], 'correct_answer': 1, 'cimage': img4 }, 'cimage': img12
{ 'vis': ScatterPlotMini, 'type': 'Scatterplot', 'question': 'There is a negative linear relationship between the height and the weight of the 85 males.', 'options': ['True', 'False', 'Skip'], 'correct_answer': 1, 'cimage': img1 }, },
{ 'vis': StackedBarChartMini, 'type': '100% Stacked Bar', 'question': 'Which country has the lowest proportion of Gold medals?', 'options': ["Great Britain", "U.S.A.", "Japan", "Australia", 'Skip'], 'correct_answer': 0, 'cimage': img5 }, {
{ 'vis': StackedAreaPlotMini, 'type': 'Stacked Area', 'question': 'What was the ratio of girls named \'Isla\' to girls named \'Amelia\' in 2012 in the UK?', 'options': ["1 to 1", "1 to 2", "1 to 3", "1 to 4", "Skip"], 'correct_answer': 1, 'cimage': img7 }, 'vis': BarChartMini,
{ 'vis': StackedBarChart2Mini, 'type': 'Stacked Bar', 'question': 'What is the cost of peanuts in Seoul?', 'options': ["$6.1", "$5.2", "$7.5", "$4.5", "Skip"], 'correct_answer': 0, 'cimage': img2 }, 'type': 'Стовпчикова діаграма (Bar Chart)',
{ 'vis': PieChartMini, 'type': 'Pie Chart', 'question': 'What is the approximate global smartphone market share of Samsung?', 'options': ["17.6%", "25.3%", "10.9%", "35.2%", 'Skip'], 'correct_answer': 0, 'cimage': img11 } 'question': 'Який діапазон середньої швидкості інтернету в Азії?',
'options': ["5.50Mbps - 30.60Mbps", "7.00Mbps - 29.40Mbps", "6.40Mbps - 27.38Mbps", "5.50Mbps - 28.60Mbps", "Пропустити"],
'correct_answer': 3,
'cimage': img9
},
{
'vis': StackedBarChart2Mini,
'type': 'Накопичувальна стовпчикова діаграма (Stacked Bar)',
'question': 'Яка ціна арахісу в Лас Веґасі?',
'options': ["$9.0", "$6.1", "$10.3", "$4.3", "Пропустити"],
'correct_answer': 1,
'cimage': img2
},
{
'vis': PieChartMini,
'type': 'Кругова діаграма (Pie Chart)',
'question': 'Яку приблизно частку ринку смартфонів займає Samsung?',
'options': ["20%", "25%", "30%", "15%", "Пропустити"],
'correct_answer': 1,
'cimage': img11
},
{
'vis': HistogramMini,
'type': 'Гістограма (Histogram)',
'question': 'Скільки людей оцінили таксі рейтингом між 4.2 та 4.4?',
'options': ["270", "190", "300", "290", "Пропустити"],
'correct_answer': 1,
'cimage': img6
},
{
'vis': ScatterPlotMini,
'type': 'Точкова діаграма (Scatterplot)',
'question': 'Існує негативна лінійна залежність між ростом і вагою 85 чоловіків.',
'options': ['Вірно', 'Невірно', "Пропустити"],
'correct_answer': 1,
'cimage': img1
},
{
'vis': AreaChartMini,
'type': 'Площинна діаграма (Area Chart)',
'question': 'Якою була середня ціна фунту кавових бобів в вересні 2013?',
'options': ["$5.15", "$6.2", "$4.8", "$4.3", "Пропустити"],
'correct_answer': 0,
'cimage': img10
},
{
'vis': StackedAreaPlotMini,
'type': 'Накопичувальна площинна діаграма (Stacked Area)',
'question': 'Яку кількість дівчаток назвали «Олівія» у 2010 році у Великобританії?',
'options': ["2000", "2500", "1700", "2400", "Пропустити"],
'correct_answer': 2,
'cimage': img7
},
{
'vis': BubbleChartMini,
'type': 'Бульбашкова діаграма (Bubble Chart)',
'question': 'Яка загальна протяжність системи метро в Пекіні?',
'options': ["525 км", "495 км", "305 км", "475 км", "Пропустити"],
'correct_answer': 0,
'cimage': img3
},
{
'vis': ChoroplethMini,
'type': 'Хороплетна мапа (Choropleth)',
'question': 'У 2015 році рівень безробіття у Вашингтоні (WA) був вищим, ніж у Вісконсині (WI).',
'options': ['Вірно', 'Невірно', "Пропустити"],
'correct_answer': 0,
'cimage': img8
},
{
'vis': TreeMapMini,
'type': 'Деревоподібна діаграма (Treemap)',
'question': 'Для якого сайту кількість унікальних відвідувачів була найбільшою в 2010 році?',
'options': ["Amazon", "Chase", "PayPal", "Citibank", "Пропустити"],
'correct_answer': 3,
'cimage': img4
}
]; ];
var record_ques = {} var record_ques = {}
@@ -143,13 +219,13 @@ class VisQuiz extends Component {
score_2 = this.state.mini_score score_2 = this.state.mini_score
if (response === minivis[this.state.current_mini_index]['options'][truth]) { if (response === minivis[this.state.current_mini_index]['options'][truth]) {
record_ques[type] = 'Correct' record_ques[type] = '✅ Вірно'
} }
else if (response === 'Skip') { else if (response === 'Skip') {
record_ques[type] = 'Skip' record_ques[type] = '⏩ Пропущено'
} }
else { else {
record_ques[type] = 'Wrong' record_ques[type] = '❌ Невірно'
} }
console.log("The dictionary is: ", record_ques) console.log("The dictionary is: ", record_ques)
} }
@@ -178,7 +254,7 @@ class VisQuiz extends Component {
on_survey_click() { on_survey_click() {
fetch('./record_responses_to_db', { fetch(`${process.env.REACT_APP_API_URL}//record_responses_to_db`, {
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
@@ -212,7 +288,7 @@ class VisQuiz extends Component {
this.props.history.push(pageType) this.props.history.push(pageType)
} }
timeout() { timeout() {
alert("Time is up! Please select 'Ok' to proceed to the next question.") alert('Чакс вийшов! Натисніть "ОК" щоб перейти до наступного запитання.')
this.setState({ this.setState({
current_visualization_index: this.state.current_visualization_index + 1, current_visualization_index: this.state.current_visualization_index + 1,
}) })
@@ -221,7 +297,7 @@ class VisQuiz extends Component {
return Math.random(); return Math.random();
} }
minitimeout() { minitimeout() {
alert("Time is up! Please select 'Ok' to proceed to the next question.") alert('Чакс вийшов! Натисніть "ОК" щоб перейти до наступного запитання.')
this.setState({ this.setState({
current_mini_index: this.state.current_mini_index + 1, current_mini_index: this.state.current_mini_index + 1,
}) })
@@ -236,7 +312,7 @@ class VisQuiz extends Component {
if (this.props.location.state == undefined) { if (this.props.location.state == undefined) {
window.location.href = "/"; window.location.href = "/";
return (<p>Unknown session. Please start from the <a href={'/'}> consent page</a></p>) return (<p>Невизначена сесія. Будь ласка поверніться на <a href={'/'}> сторінку згоди</a></p>)
} }
let ages = [] let ages = []
for (var i = 18; i < 100; i++) { for (var i = 18; i < 100; i++) {
@@ -265,7 +341,7 @@ class VisQuiz extends Component {
<Col lg={6} className={'quiz-column'}> <Col lg={6} className={'quiz-column'}>
<div className='timeStamp'> <div className='timeStamp'>
<Countdown date={Date.now() + 25000} renderer={({ minutes, seconds, completed }) => { <Countdown date={Date.now() + 25000} renderer={({ minutes, seconds, completed }) => {
return <span>Time Remaining: {minutes}:{seconds}</span>; return <span>Залишилось часу: {minutes}:{seconds}</span>;
}} autoStart={true} key={Date.now()} onComplete={() => this.minitimeout()} /> }} autoStart={true} key={Date.now()} onComplete={() => this.minitimeout()} />
{/* <CountDownTimer hoursMinSecs={hoursMinSecs} /> */} {/* <CountDownTimer hoursMinSecs={hoursMinSecs} /> */}
</div> </div>
@@ -295,7 +371,7 @@ class VisQuiz extends Component {
<Form.Group className={'question'}> <Form.Group className={'question'}>
<Form.Label>Please select your age.</Form.Label> <Form.Label>Будь ласка вкажіть свій вік.</Form.Label>
<Form.Select as="select" id={'age'} onChange={this.handleDemographicChange.bind(this)}> <Form.Select as="select" id={'age'} onChange={this.handleDemographicChange.bind(this)}>
<option value={null} selected={true} disabled={true}></option> <option value={null} selected={true} disabled={true}></option>
{ages.map((d, i) => ( {ages.map((d, i) => (
@@ -306,54 +382,54 @@ class VisQuiz extends Component {
<hr /> <hr />
<Form.Group className={'question'}> <Form.Group className={'question'}>
<Form.Label>Please select your gender.</Form.Label> <Form.Label>Будь ласка вкажіть ваш ґендер.</Form.Label>
<Form.Select as="select" id={'sex'} onChange={this.handleDemographicChange.bind(this)}> <Form.Select as="select" id={'sex'} onChange={this.handleDemographicChange.bind(this)}>
<option value={null} selected={true} disabled={true}></option> <option value={null} selected={true} disabled={true}></option>
<option key={'male'} value={'male'}>Male</option> <option key={'male'} value={'male'}>Чоловік</option>
<option key={'female'} value={'female'}>Female</option> <option key={'female'} value={'female'}>Жінка</option>
<option key={'other'} value={'other'}>Other</option> <option key={'other'} value={'other'}>Небінарна персона</option>
<option key={'withdraw'} value={'withdraw'}>I do not wish to disclose.</option> <option key={'withdraw'} value={'withdraw'}>Не хочу розголошувати цю інформацію</option>
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
<hr /> <hr />
<Form.Group className={'question'}> <Form.Group className={'question'}>
<Form.Label>Please select your highest level of completed education.</Form.Label> <Form.Label>Будь ласка, оберіть ваш найвищий рівень завершеної освіти.</Form.Label>
<Form.Select as="select" id={'education'} onChange={this.handleDemographicChange.bind(this)}> <Form.Select as="select" id={'education'} onChange={this.handleDemographicChange.bind(this)}>
<option value={null} selected={true} disabled={true}></option> <option value={null} selected={true} disabled={true}></option>
<option value={'highschool'}>High School Diploma / GED</option> <option value={'highschool'}>Повна загальна середня освіта</option>
<option value={'associate'}>Associate Degree</option> <option value={'associate'}>Фахова передвища освіта</option>
<option value={'bachelors'}>Bachelors Degree</option> <option value={'bachelors'}>Бакалаврський ступінь</option>
<option value={'masters'}>Masters Degree</option> <option value={'masters'}>Магістерський ступінь</option>
<option value={'doctorate'}>Doctorate Degree</option> <option value={'doctorate'}>Докторський ступінь</option>
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
<hr /> <hr />
<Form.Group className={'question'}> <Form.Group className={'question'}>
<Form.Label>Are you color-blind?</Form.Label> <Form.Label>Чи маєте ви колірну сліпоту (дальтонізм)?</Form.Label>
<Form.Select as="select" id={'color-blind'} onChange={this.handleDemographicChange.bind(this)}> <Form.Select as="select" id={'color-blind'} onChange={this.handleDemographicChange.bind(this)}>
<option value={null} selected={true} disabled={true}></option> <option value={null} selected={true} disabled={true}></option>
<option value={'yes'}>Yes</option> <option value={'yes'}>Так</option>
<option value={'no'}>No</option> <option value={'no'}>Ні</option>
<option value={'maybe'}>I do not wish to disclose.</option> <option value={'maybe'}>Не хочу розголошувати цю інформацію</option>
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
<hr /> <hr />
<Form.Group className={'question'}> <Form.Group className={'question'}>
<Form.Label>Please select your familiarity with visualization.</Form.Label> <Form.Label>Будь ласка опишіть ваш досвід з візуалізацією даних.</Form.Label>
<Form.Select as="select" id={'familiarity'} onChange={this.handleDemographicChange.bind(this)}> <Form.Select as="select" id={'familiarity'} onChange={this.handleDemographicChange.bind(this)}>
<option value={null} selected={true} disabled={true}></option> <option value={null} selected={true} disabled={true}></option>
<option value={'not_familiar'}>I have never created a visualization.</option> <option value={'not_familiar'}>Я ніколи не працював професійно з візуалізацією даних</option>
<option value={'somewhat'}>I am somewhat familiar.</option> <option value={'somewhat'}>Я дещо знайомий з візуалізацією даних</option>
<option value={'very_familiar'}>I have created visualization systems before. </option> <option value={'very_familiar'}>Я створював візуалізації даних самостійно</option>
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
<hr /> <hr />
<Form.Group> <Form.Group>
<Form.Label>Please include any additional comments below. (optional)</Form.Label> <Form.Label>(опціонально) Будь ласка, залиште коментар якщо бажаєте.</Form.Label>
<Form.Control as="textarea" id={'comments'} rows={3} onChange={this.handleTextChange.bind(this)}> <Form.Control as="textarea" id={'comments'} rows={3} onChange={this.handleTextChange.bind(this)}>
</Form.Control> </Form.Control>
</Form.Group> </Form.Group>
@@ -365,7 +441,7 @@ class VisQuiz extends Component {
onClick={this.on_survey_click.bind(this)} onClick={this.on_survey_click.bind(this)}
disabled={(this.state.form_incomplete || this.state.demographics_incomplete)} disabled={(this.state.form_incomplete || this.state.demographics_incomplete)}
id={'survey_submit-btn'}> id={'survey_submit-btn'}>
Submit Responses Відправити відповіді
</Button></div> </Button></div>
<p className={'text-box'}></p> <p className={'text-box'}></p>

61
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,61 @@
# docker-compose.prod.yml
version: '3.8'
services:
traefik:
image: traefik:v2.10
command:
- "--api.insecure=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
- "--certificatesresolvers.myresolver.acme.email=ptrvtch@protonmail.com"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./letsencrypt:/letsencrypt"
networks:
- web
frontend:
build:
context: ./ReactTool/frontend
args:
- NODE_ENV=production
labels:
- "traefik.enable=true"
- "traefik.http.routers.frontend.rule=Host(`mini-vlat-ua.duckdns.org`)"
- "traefik.http.routers.frontend.entrypoints=websecure"
- "traefik.http.routers.frontend.tls.certresolver=myresolver"
- "traefik.http.services.frontend.loadbalancer.server.port=80"
networks:
- web
depends_on:
- backend
backend:
build:
context: ./ReactTool/backend
volumes:
- ./data/db:/app/data
environment:
- FLASK_ENV=production
labels:
- "traefik.enable=true"
- "traefik.http.routers.backend.rule=Host(`mini-vlat-ua.duckdns.org`) && PathPrefix(`/api`)"
- "traefik.http.routers.backend.entrypoints=websecure"
- "traefik.http.routers.backend.tls.certresolver=myresolver"
- "traefik.http.services.backend.loadbalancer.server.port=5000"
- "traefik.http.middlewares.backend-strip.stripprefix.prefixes=/api"
- "traefik.http.routers.backend.middlewares=backend-strip@docker"
networks:
- web
networks:
web:
driver: bridge

28
docker-compose.yml Normal file
View File

@@ -0,0 +1,28 @@
# docker-compose.yml (development)
version: '3.8'
services:
frontend:
build:
context: ./ReactTool/frontend
args:
- NODE_ENV=development
ports:
- "3000:3000"
volumes:
- ./ReactTool/frontend:/app
- /app/node_modules
environment:
- NODE_ENV=development
depends_on:
- backend
backend:
build:
context: ./ReactTool/backend
ports:
- "8000:5000"
volumes:
- ./ReactTool/backend:/app
- ./data/db:/app/data
environment:
- FLASK_ENV=development

137
mini-vlat-tree.txt Normal file
View File

@@ -0,0 +1,137 @@
.
├── README.md
├── ReactTool
│   ├── README.md
│   ├── backend
│   │   ├── Pipfile
│   │   ├── Pipfile.lock
│   │   ├── README.md
│   │   ├── __pycache__
│   │   │   ├── app.cpython-39.pyc
│   │   │   └── db_conf.cpython-39.pyc
│   │   ├── app.py
│   │   ├── db_conf.py
│   │   └── surveys
│   │   └── quiz
│   │   └── 9xdbxs46sp1p5X9yqSe_uQ.txt
│   └── frontend
│   ├── README.md
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   │   ├── favicon.ico
│   │   ├── index.html
│   │   ├── logo192.png
│   │   ├── logo512.png
│   │   ├── manifest.json
│   │   └── robots.txt
│   ├── src
│   │   ├── App.css
│   │   ├── App.js
│   │   ├── App.test.js
│   │   ├── components
│   │   │   ├── areaChart-mini.js
│   │   │   ├── barChart-mini.js
│   │   │   ├── bubbleChart-mini.js
│   │   │   ├── choropleth-mini.js
│   │   │   ├── data
│   │   │   │   ├── 100StackedBarGraph.csv
│   │   │   │   ├── AreaChart-2.csv
│   │   │   │   ├── AreaChart.csv
│   │   │   │   ├── BarGraph.csv
│   │   │   │   ├── BubbleChart.csv
│   │   │   │   ├── Choropleth.csv
│   │   │   │   ├── Histogram-3-2.csv
│   │   │   │   ├── Histogram.csv
│   │   │   │   ├── Images
│   │   │   │   │   ├── 1.png
│   │   │   │   │   ├── 10.png
│   │   │   │   │   ├── 11.png
│   │   │   │   │   ├── 12.png
│   │   │   │   │   ├── 2.png
│   │   │   │   │   ├── 3.png
│   │   │   │   │   ├── 4.png
│   │   │   │   │   ├── 5.png
│   │   │   │   │   ├── 6.png
│   │   │   │   │   ├── 7.png
│   │   │   │   │   ├── 8.png
│   │   │   │   │   ├── 9.png
│   │   │   │   │   ├── RightAns.png
│   │   │   │   │   ├── WrongAns.png
│   │   │   │   │   └── skip.png
│   │   │   │   ├── LineChart.csv
│   │   │   │   ├── Mini-VLAT
│   │   │   │   │   ├── AreaChart.png
│   │   │   │   │   ├── BarChart.png
│   │   │   │   │   ├── BubbleChart.png
│   │   │   │   │   ├── Choropleth.png
│   │   │   │   │   ├── Choropleth_New.png
│   │   │   │   │   ├── Histogram.png
│   │   │   │   │   ├── LineChart.png
│   │   │   │   │   ├── PieChart.png
│   │   │   │   │   ├── Scatterplot.png
│   │   │   │   │   ├── Stacked100.png
│   │   │   │   │   ├── StackedArea.png
│   │   │   │   │   ├── StackedBar.png
│   │   │   │   │   └── TreeMap.png
│   │   │   │   ├── PieChart.csv
│   │   │   │   ├── Scatterplot.csv
│   │   │   │   ├── StackedArea.csv
│   │   │   │   ├── StackedBarGraph.csv
│   │   │   │   ├── Treemap.json
│   │   │   │   ├── USA.json
│   │   │   │   ├── VLAT-Pics
│   │   │   │   │   ├── AreaChart.png
│   │   │   │   │   ├── BarGraph.png
│   │   │   │   │   ├── BubbleChart.png
│   │   │   │   │   ├── Choropleth.png
│   │   │   │   │   ├── Histogram.png
│   │   │   │   │   ├── LineChart.png
│   │   │   │   │   ├── Pie.png
│   │   │   │   │   ├── Scatterplot.png
│   │   │   │   │   ├── StackedArea.png
│   │   │   │   │   ├── StackedBar.png
│   │   │   │   │   ├── StackedBar100.png
│   │   │   │   │   └── TreeMap.png
│   │   │   │   ├── scatter-2.csv
│   │   │   │   ├── scatter.csv
│   │   │   │   └── us.json
│   │   │   ├── histogram-mini.js
│   │   │   ├── linechart-mini.js
│   │   │   ├── pieChart-mini.js
│   │   │   ├── quiz.js
│   │   │   ├── scatterplot-mini.js
│   │   │   ├── stacked100bar-mini.js
│   │   │   ├── stackedArea-mini.js
│   │   │   ├── stackedbar-mini.js
│   │   │   └── treeMap-mini.js
│   │   ├── index.css
│   │   ├── index.js
│   │   ├── logo.svg
│   │   ├── pages
│   │   │   ├── introduction.js
│   │   │   ├── thank_you.js
│   │   │   ├── tutorial.js
│   │   │   └── visualization_quiz.js
│   │   ├── reportWebVitals.js
│   │   └── setupTests.js
│   └── yarn.lock
├── Test
│   └── Mini-VLAT(QuestionsBank)_Updated.pdf
├── VisImages
│   ├── AreaChart.png
│   ├── BarChart.png
│   ├── BubbleChart.png
│   ├── Choropleth.png
│   ├── Choropleth_New.png
│   ├── Histogram.png
│   ├── LineChart.png
│   ├── PieChart.png
│   ├── Scatterplot.png
│   ├── Stacked100.png
│   ├── StackedArea.png
│   ├── StackedBar.png
│   └── TreeMap.png
└── mini-vlat-tree.txt
17 directories, 118 files