mirror of
https://github.com/olehomelchenko/Mini-VLAT-ua.git
synced 2025-12-21 13:12:22 +00:00
463 lines
20 KiB
JavaScript
463 lines
20 KiB
JavaScript
import React, { Component, useLayoutEffect, useState, useEffect, FunctionComponent } from 'react';
|
||
import { Container, Col, Row, Navbar, Button, ButtonGroup, ToggleButton, Form, InputGroup } from 'react-bootstrap';
|
||
import '../App.css';
|
||
import ProgressBar from "@ramonak/react-progress-bar";
|
||
import Countdown from 'react-countdown';
|
||
|
||
import AreaChartMini from "../components/areaChart-mini";
|
||
import BarChartMini from "../components/barChart-mini";
|
||
import BubbleChartMini from "../components/bubbleChart-mini";
|
||
import ChoroplethMini from "../components/choropleth-mini";
|
||
import HistogramMini from "../components/histogram-mini";
|
||
import LineChartMini from "../components/linechart-mini";
|
||
import PieChartMini from "../components/pieChart-mini";
|
||
import ScatterPlotMini from "../components/scatterplot-mini";
|
||
import StackedBarChartMini from "../components/stacked100bar-mini";
|
||
import StackedAreaPlotMini from "../components/stackedArea-mini";
|
||
import StackedBarChart2Mini from "../components/stackedbar-mini";
|
||
import TreeMapMini from "../components/treeMap-mini";
|
||
import axios from 'axios';
|
||
|
||
import img1 from '../components/data/VLAT-Pics/Scatterplot.png'
|
||
import img2 from '../components/data/VLAT-Pics/StackedBar.png'
|
||
import img3 from '../components/data/VLAT-Pics/BubbleChart.png'
|
||
import img4 from '../components/data/VLAT-Pics/TreeMap.png'
|
||
import img5 from '../components/data/VLAT-Pics/StackedBar100.png'
|
||
import img6 from '../components/data/VLAT-Pics/Histogram.png'
|
||
import img7 from '../components/data/VLAT-Pics/StackedArea.png'
|
||
import img8 from '../components/data/VLAT-Pics/Choropleth.png'
|
||
import img9 from '../components/data/VLAT-Pics/BarGraph.png'
|
||
import img10 from '../components/data/VLAT-Pics/AreaChart.png'
|
||
import img11 from '../components/data/VLAT-Pics/Pie.png'
|
||
import img12 from '../components/data/VLAT-Pics/LineChart.png'
|
||
|
||
|
||
|
||
let minivis = [
|
||
{
|
||
'vis': LineChartMini,
|
||
'type': 'Лінійна діаграма',
|
||
'question': 'Яким був ціновий діапазон бареля нафти у 2020 році?',
|
||
'options': ["$16.55 - $57.52", "$19.52 - $59.00", "$23.43 - $60.72", "$21.82 - $87.52", "Пропустити"],
|
||
'correct_answer': 0,
|
||
'cimage': img12
|
||
},
|
||
{
|
||
'vis': BarChartMini,
|
||
'type': 'Стовпчикова діаграма (Bar Chart)',
|
||
'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 score_2 = 0
|
||
let initTime = 0
|
||
let endTime = 0
|
||
var num = 12
|
||
|
||
|
||
class VisQuiz extends Component {
|
||
|
||
constructor(props) {
|
||
super(props);
|
||
}
|
||
|
||
componentDidMount() {
|
||
this.setState({
|
||
session_id: this.props.location.state.data.session_id,
|
||
current_visualization_index: 0,
|
||
score: 0,
|
||
current_mini_index: 0,
|
||
list_of_min_vis: this.shuffle(minivis),
|
||
responses: {},
|
||
mini_responses: {},
|
||
resize_bool: true,
|
||
device_info: '',
|
||
form_incomplete: false,
|
||
demographic_questions: {
|
||
'sex': null,
|
||
'age': null,
|
||
'education': null,
|
||
'familiarity': null
|
||
},
|
||
demographics_incomplete: true,
|
||
comment: '',
|
||
width: 0,
|
||
height: 0,
|
||
mini_score: 0,
|
||
ip_address: "",
|
||
}
|
||
)
|
||
|
||
window.addEventListener('resize', this.handleWindowResize.bind(this))
|
||
}
|
||
|
||
handleWindowResize(e) {
|
||
this.setState({
|
||
resize_bool: !this.state.resize_bool
|
||
})
|
||
}
|
||
handleTextChange(e) {
|
||
this.setState({ comment: e.target.value })
|
||
}
|
||
|
||
handleDemographicChange(e) {
|
||
console.log(this.state)
|
||
var new_dq = this.state.demographic_questions
|
||
new_dq[e.target.id] = e.target.value
|
||
|
||
var incomplete = false
|
||
for (var key in new_dq) {
|
||
if (new_dq[key] == null) {
|
||
incomplete = true
|
||
}
|
||
}
|
||
if (e.value == 'oth') {
|
||
alert('Hello')
|
||
}
|
||
|
||
this.setState({ demographic_questions: new_dq, demographics_incomplete: incomplete })
|
||
}
|
||
getData = async () => {
|
||
//https://medium.com/how-to-react/how-to-get-user-ip-address-in-react-js-73eb295720d0
|
||
const res = await axios.get('https://geolocation-db.com/json/')
|
||
console.log("IP Address: ", res.data);
|
||
this.setState({
|
||
ip_address: res.data.IPv4
|
||
})
|
||
}
|
||
|
||
clicked_mini_answer(type, question, response, truth, time) {
|
||
this.getData()
|
||
if (response === minivis[this.state.current_mini_index]['options'][truth]) {
|
||
this.state.mini_score = this.state.mini_score + 1
|
||
}
|
||
this.setState({
|
||
current_mini_index: this.state.current_mini_index + 1,
|
||
})
|
||
endTime = Math.abs((Date.now() - initTime) / 1000)
|
||
this.state.mini_responses[question] = { response: response, truth: truth, time: endTime }
|
||
this.setState({
|
||
device_info: navigator.userAgent
|
||
})
|
||
score_2 = this.state.mini_score
|
||
|
||
if (response === minivis[this.state.current_mini_index]['options'][truth]) {
|
||
record_ques[type] = '✅ Вірно'
|
||
}
|
||
else if (response === 'Skip') {
|
||
record_ques[type] = '⏩ Пропущено'
|
||
}
|
||
else {
|
||
record_ques[type] = '❌ Невірно'
|
||
}
|
||
console.log("The dictionary is: ", record_ques)
|
||
}
|
||
|
||
|
||
|
||
shuffle(array) {
|
||
//https://bost.ocks.org/mike/shuffle/
|
||
var m = array.length, t, i;
|
||
|
||
// While there remain elements to shuffle…
|
||
while (m) {
|
||
|
||
// Pick a remaining element…
|
||
i = Math.floor(Math.random() * m--);
|
||
|
||
// And swap it with the current element.
|
||
t = array[m];
|
||
array[m] = array[i];
|
||
array[i] = t;
|
||
}
|
||
return array;
|
||
}
|
||
|
||
|
||
|
||
on_survey_click() {
|
||
|
||
fetch(`${process.env.REACT_APP_API_URL}//record_responses_to_db`, {
|
||
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
session_id: this.state.session_id,
|
||
responses: this.state.responses,
|
||
mini_responses: this.state.mini_responses,
|
||
score: this.state.score,
|
||
mini_score: this.state.mini_score,
|
||
device: this.state.device_info,
|
||
demographic_responses: this.state.demographic_questions,
|
||
comment: this.state.comment,
|
||
height: window.innerHeight,
|
||
width: window.innerWidth,
|
||
ipaddress: this.state.ip_address
|
||
})
|
||
})
|
||
.then(res => res.json()).then(data => {
|
||
//var message = 'Warning!\n\nNavigating away from this page will delete your text if you haven\'t already saved it.';
|
||
//e.returnValue = message;
|
||
//return message;
|
||
})
|
||
|
||
var pageType = {
|
||
pathname: '/thank_you',
|
||
state: {
|
||
data: {
|
||
'session_id': this.state.session_id,
|
||
}
|
||
}
|
||
}
|
||
this.props.history.push(pageType)
|
||
}
|
||
timeout() {
|
||
alert('Чакс вийшов! Натисніть "ОК" щоб перейти до наступного запитання.')
|
||
this.setState({
|
||
current_visualization_index: this.state.current_visualization_index + 1,
|
||
})
|
||
}
|
||
getRandom() {
|
||
return Math.random();
|
||
}
|
||
minitimeout() {
|
||
alert('Чакс вийшов! Натисніть "ОК" щоб перейти до наступного запитання.')
|
||
this.setState({
|
||
current_mini_index: this.state.current_mini_index + 1,
|
||
})
|
||
}
|
||
|
||
|
||
render() {
|
||
const hoursMinSecs = { hours: 0, minutes: 0, seconds: 10 }
|
||
initTime = Date.now()
|
||
console.log("Starting Time is : " + initTime)
|
||
console.log('render')
|
||
|
||
if (this.props.location.state == undefined) {
|
||
window.location.href = "/";
|
||
return (<p>Невизначена сесія. Будь ласка поверніться на <a href={'/'}> сторінку згоди</a></p>)
|
||
}
|
||
let ages = []
|
||
for (var i = 18; i < 100; i++) {
|
||
ages.push(i)
|
||
}
|
||
|
||
if (this.state == null) {
|
||
return (<p>Loading...</p>)
|
||
}
|
||
if (this.state.current_mini_index < this.state.list_of_min_vis.length) {
|
||
const options = minivis[this.state.current_mini_index]['options'].map((item, i) =>
|
||
|
||
<Button variant="secondary" size="sm" className={'question-option'} id={`button_option_${i}`} key={`button_option_${i}`} onClick={() =>
|
||
this.clicked_mini_answer(minivis[this.state.current_mini_index]['type'], minivis[this.state.current_mini_index]['question'], item, minivis[this.state.current_mini_index]['correct_answer'], 'timeTaken')}>
|
||
{item}
|
||
</Button>
|
||
)
|
||
let VisComp = minivis[this.state.current_mini_index]['vis']
|
||
//console.log(VisComp)
|
||
return (
|
||
<Container className={'container-class'} fluid>
|
||
<Row className={'vis-quiz-row'}>
|
||
<Col lg={6} className={'vis-column'}>
|
||
<VisComp width={window.innerWidth} height={window.innerHeight} resized={this.state.resize_bool}></VisComp>
|
||
</Col>
|
||
<Col lg={6} className={'quiz-column'}>
|
||
<div className='timeStamp'>
|
||
<Countdown date={Date.now() + 25000} renderer={({ minutes, seconds, completed }) => {
|
||
return <span>Залишилось часу: {minutes}:{seconds}</span>;
|
||
}} autoStart={true} key={Date.now()} onComplete={() => this.minitimeout()} />
|
||
{/* <CountDownTimer hoursMinSecs={hoursMinSecs} /> */}
|
||
</div>
|
||
<div className={'question-container'}>
|
||
<div className={'question-text'}>
|
||
<p>{minivis[this.state.current_mini_index]['question']}</p>
|
||
</div>
|
||
|
||
<div className={'question-options d-grid gap-2 btn-block'}>
|
||
{options}
|
||
</div>
|
||
</div>
|
||
|
||
</Col>
|
||
</Row>
|
||
<Row className={'progress-bar-row'}>
|
||
<ProgressBar completed={(parseInt(this.state.current_mini_index)).toString()} maxCompleted={num.toString()} length={Math.min(window.innerWidth, window.innerHeight)} />
|
||
</Row>
|
||
</Container>
|
||
);
|
||
}
|
||
else {
|
||
return (
|
||
<>
|
||
<Row className={'justify-content-center no-margin-row'}>
|
||
<Col lg={6} className={'text-box text-justify'}>
|
||
|
||
|
||
<Form.Group className={'question'}>
|
||
<Form.Label>Будь ласка вкажіть свій вік.</Form.Label>
|
||
<Form.Select as="select" id={'age'} onChange={this.handleDemographicChange.bind(this)}>
|
||
<option value={null} selected={true} disabled={true}></option>
|
||
{ages.map((d, i) => (
|
||
<option key={d} value={d}>{d}</option>
|
||
))}
|
||
</Form.Select>
|
||
</Form.Group>
|
||
<hr />
|
||
|
||
<Form.Group className={'question'}>
|
||
<Form.Label>Будь ласка вкажіть ваш ґендер.</Form.Label>
|
||
<Form.Select as="select" id={'sex'} onChange={this.handleDemographicChange.bind(this)}>
|
||
<option value={null} selected={true} disabled={true}></option>
|
||
<option key={'male'} value={'male'}>Чоловік</option>
|
||
<option key={'female'} value={'female'}>Жінка</option>
|
||
<option key={'other'} value={'other'}>Небінарна персона</option>
|
||
<option key={'withdraw'} value={'withdraw'}>Не хочу розголошувати цю інформацію</option>
|
||
</Form.Select>
|
||
</Form.Group>
|
||
<hr />
|
||
|
||
<Form.Group className={'question'}>
|
||
<Form.Label>Будь ласка, оберіть ваш найвищий рівень завершеної освіти.</Form.Label>
|
||
<Form.Select as="select" id={'education'} onChange={this.handleDemographicChange.bind(this)}>
|
||
<option value={null} selected={true} disabled={true}></option>
|
||
<option value={'highschool'}>Повна загальна середня освіта</option>
|
||
<option value={'associate'}>Фахова передвища освіта</option>
|
||
<option value={'bachelors'}>Бакалаврський ступінь</option>
|
||
<option value={'masters'}>Магістерський ступінь</option>
|
||
<option value={'doctorate'}>Докторський ступінь</option>
|
||
</Form.Select>
|
||
</Form.Group>
|
||
<hr />
|
||
<Form.Group className={'question'}>
|
||
<Form.Label>Чи маєте ви колірну сліпоту (дальтонізм)?</Form.Label>
|
||
<Form.Select as="select" id={'color-blind'} onChange={this.handleDemographicChange.bind(this)}>
|
||
<option value={null} selected={true} disabled={true}></option>
|
||
<option value={'yes'}>Так</option>
|
||
<option value={'no'}>Ні</option>
|
||
<option value={'maybe'}>Не хочу розголошувати цю інформацію</option>
|
||
</Form.Select>
|
||
</Form.Group>
|
||
<hr />
|
||
|
||
<Form.Group className={'question'}>
|
||
<Form.Label>Будь ласка опишіть ваш досвід з візуалізацією даних.</Form.Label>
|
||
<Form.Select as="select" id={'familiarity'} onChange={this.handleDemographicChange.bind(this)}>
|
||
<option value={null} selected={true} disabled={true}></option>
|
||
<option value={'not_familiar'}>Я ніколи не працював професійно з візуалізацією даних</option>
|
||
<option value={'somewhat'}>Я дещо знайомий з візуалізацією даних</option>
|
||
<option value={'very_familiar'}>Я створював візуалізації даних самостійно</option>
|
||
</Form.Select>
|
||
</Form.Group>
|
||
<hr />
|
||
|
||
|
||
<Form.Group>
|
||
<Form.Label>(опціонально) Будь ласка, залиште коментар якщо бажаєте.</Form.Label>
|
||
<Form.Control as="textarea" id={'comments'} rows={3} onChange={this.handleTextChange.bind(this)}>
|
||
</Form.Control>
|
||
</Form.Group>
|
||
<hr />
|
||
|
||
|
||
<div className={'text-center'}><Button className={'btn-sm'}
|
||
variant={"success"}
|
||
onClick={this.on_survey_click.bind(this)}
|
||
disabled={(this.state.form_incomplete || this.state.demographics_incomplete)}
|
||
id={'survey_submit-btn'}>
|
||
Відправити відповіді
|
||
</Button></div>
|
||
|
||
<p className={'text-box'}></p>
|
||
</Col>
|
||
|
||
</Row>
|
||
</>
|
||
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
export default VisQuiz;
|
||
export { score_2 };
|
||
export { record_ques };
|