<div id="app"></div>
@import url("https://fonts.googleapis.com/css2?family=Alexandria&display=swap");
html,
body,
#app {
height: 100vh;
}
body {
margin: 0;
font-family: "Alexandria", sans-serif;
font-size: 1em;
}
h1 {
font-size: 1.3em;
text-align:center;
}
#app {
display: grid;
justify-items: center;
align-content: center;
grid-template-columns: 20% 10% 50% 20%;
grid-template-rows: 60vh 10vh;
grid-gap: 0.5em;
}
form {
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
grid-row: 1/3;
grid-column: 2/4;
width: 90%;
height: 70vh;
background: linear-gradient(
115deg,
rgba(255, 234, 0, 0.5),
rgba(255, 0, 0, 0) 70.71%
),
linear-gradient(217deg, rgba(255, 0, 0, 0.5), rgba(255, 0, 0, 0) 70.71%),
linear-gradient(127deg, rgba(0, 255, 0, 0.5), rgba(0, 255, 0, 0) 70.71%),
linear-gradient(336deg, rgba(0, 0, 255, 0.5), rgba(0, 0, 255, 0) 70.71%);
border-radius: 5px;
}
form label {
display: flex;
flex-direction: column;
}
input {
padding: 0.5%;
border-radius: 0.5em;
border: none;
text-align:center;
}
input.error {
border: 1px solid red;
}
input[type="submit"] {
background: rgb(9, 189, 90);
border: rgb(9, 189, 90);
color: white;
cursor: pointer;
font-weight: bold;
}
input[type="submit"]:hover {
background: rgb(9, 196, 93);
border: rgb(9, 196, 93);
}
input[type="submit"]:active {
background: rgb(8, 158, 75);
border: rgb(8, 158, 75);
}
input[type="submit"]:disabled {
opacity: 0.5;
}
.box {
width: 85%;
height: 65vh;
grid-row: 1/3;
grid-column: 3/4;
overflow-x: auto;
overflow-y: clip;
justify-self: left;
border-left: 1px solid black;
}
.histogram {
height: 50vh;
border-bottom: 3px solid black;
border-left: 3px solid black;
display: flex;
width: max-content;
justify-content: space-around;
align-items: flex-end;
}
.y-axis {
display: flex;
flex-direction: column-reverse;
justify-content: space-evenly;
align-items: center;
height: 50vh;
grid-row: 1/2;
grid-column: 2/3;
justify-self: right;
}
.column-label {
display: flex;
flex-direction: column-reverse;
width: 2vw;
height: inherit;
margin: 0 1em;
}
.column {
width: 2vw;
height: 0px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
cursor: pointer;
}
[class^="label"] {
position: relative;
width: 2em;
height: 2em;
background: white;
color: white;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
visibility: hidden;
opacity: 0;
}
[class^="label"]::after {
content: "";
display: block;
position: absolute;
top: 25px;
left: 7px;
clip-path: polygon(49% 100%, 0 0, 100% 0);
width: 1.05em;
height: 1.05em;
background: inherit;
z-index: 0;
font-weight: bolder;
}
.column-label span {
position: relative;
top: 100%;
margin-bottom: auto;
}
.reset {
padding: 0.5em;
border: 1px solid black;
border-radius: 5px;
text-align: center;
cursor: pointer;
grid-column: 2/4;
grid-row:3/4;
justify-self: center;
display: flex;
justify-content: center;
align-items: center;
}
.reset:hover {
background: rgb(225, 225, 225);
}
@media (max-width: 650px) {
#app {
grid-template-columns: 5% 10% 80% 5%;
grid-gap: 0;
}
form {
width: 95%;
}
.box {
width: 80%;
justify-self: center;
}
.y-axis {
width: 10%;
}
.column-label {
width: 1.75em;
}
.column {
width: 1.75em;
}
.reset {
grid-column: 3/4;
justify-self: center;
}
}
const colors = [
"#FFC313",
"#FF9813",
"#FB1223",
"#E01093",
"#6621D2",
"#2B37D4",
"#198DCC",
"#10D66C",
"#45EA11",
"#BBF812",
];
let i = 0;
function getColor() {
if (i > colors.length - 1) {
i = 0;
}
let currentColor = colors[i];
i++;
return currentColor;
}
function Column(props) {
const hover = (event) => {
event.currentTarget.style.opacity = "0.5";
props.labelRef.current.style.background = props.color;
props.labelRef.current.style.visibility = "visible";
props.labelRef.current.style.opacity = "1";
};
const hoverLeave = (event) => {
event.currentTarget.style.opacity = "1";
props.labelRef.current.style.visibility = "hidden";
props.labelRef.current.style.opacity = "0";
};
return (
<div
className="column"
style={{ height: props.height + "%", background: props.color }}
onMouseEnter={hover}
onMouseLeave={hoverLeave}
onTouchStart={hover}
onTouchEnd={hoverLeave}
></div>
);
}
function ColumnAndLabel(props) {
return (
<div className="column-label">
<Column
labelRef={props.labelRef}
color={getColor()}
height={100 * (props.frequency / props.maxValue)}
/>
<div ref={props.labelRef} className="label">
{/**frequency label visible on hover */}
{props.frequency}
</div>
<span>{props.num}</span> {/**x-axis */}
</div>
);
}
function Box(props) {
return (
<div className="box">
<div className="histogram">
{Object.keys(props.data)
.sort((a, b) => a - b)
.map((num, i) => (
<ColumnAndLabel
key={"column-label" + num}
num={num}
frequency={props.data[num]}
labelRef={props.labelsRef[i]}
maxValue={props.maxValue}
/>
))}
</div>
</div>
);
}
function Form(props) {
return (
<form>
<h1>Data fetching and visualization</h1>
<label htmlFor="amount">
Amount of numbers
<input
type="text"
name="amount"
id="amount"
defaultValue="200"
onInput={props.onInput}
className={
props.formInput.amount &&
!isNaN(props.formInput.amount - "") &&
props.formInput.amount - "" <= 950
? null
: "error"
}
/>
</label>
<label htmlFor="min">
Min number
<input
type="text"
name="min"
id="min-num"
defaultValue="1"
onInput={props.onInput}
className={
props.formInput.min && !isNaN(props.formInput.min - "")
? null
: "error"
}
/>
</label>
<label htmlFor="max">
Max number
<input
type="text"
name="max"
id="max-num"
defaultValue="10"
onInput={props.onInput}
className={
props.formInput.max &&
!isNaN(props.formInput.max - "") &&
props.formInput.max !== props.formInput.min
? null
: "error"
}
/>
</label>
<input
type="submit"
value="Submit"
id="submit"
onClick={props.onSubmit}
disabled={
!props.formInput.amount.length ||
!props.formInput.min.length ||
!props.formInput.max.length ||
isNaN(props.formInput.max - "") ||
isNaN(props.formInput.min - "") ||
isNaN(props.formInput.amount - "") ||
props.formInput.max === props.formInput.min ||
props.formInput.amount - "" > 950
}
/>
</form>
);
}
class App extends React.Component {
state = {
data: {},
labelsRef: [],
formInput: {
min: "1",
max: "10",
amount: "200",
},
};
getData = (e) => {
e.preventDefault();
const amount = this.state.formInput.amount;
const min = this.state.formInput.min;
const max = this.state.formInput.max;
const url = `https://www.random.org/integers/?num=${amount}&min=${min}&max=${max}&col=1&base=10&format=plain&rnd=new`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.text();
})
.then((result) => {
let data = this.state.data;
let labelsRef = this.state.labelsRef;
data = {};
labelsRef = [];
result
.split("\n")
.filter((num) => num !== "")
.forEach((num) => {
if (num in data) {
data[num]++;
} else {
data[num] = 1;
}
});
for (let i = 0; i < Object.keys(data).length; i++) {
labelsRef.push(React.createRef());
}
this.setState({ data, labelsRef });
})
.catch((error) => console.log("Error: " + error));
};
formInputs = (event) => {
const value = event.currentTarget.value;
let formInput = this.state.formInput;
if (event.currentTarget.id === "amount") {
formInput.amount = value;
} else if (event.currentTarget.id === "min-num") {
formInput.min = value;
} else {
formInput.max = value;
}
this.setState({ formInput });
};
reset = () => {
this.setState({
data: {},
formInput: {
min: "1",
max: "10",
amount: "200",
},
});
};
render() {
if (Object.keys(this.state.data).length) {
let maxValue = Math.max(...Object.values(this.state.data));
let yAxis = [];
maxValue = Number.isInteger(maxValue / 10)
? maxValue
: Math.trunc((maxValue + 10) / 10) * 10;
if (maxValue > 10) {
for (let i = 10; i < maxValue; i += 10) {
yAxis.push(<span key={i}>{i}</span>);
}
} else {
yAxis.push(<span key="5">5</span>);
}
return [
<div key="y-axis" className="y-axis">
{yAxis}
</div>,
<Box
key="box-histogram"
data={this.state.data}
labelsRef={this.state.labelsRef}
maxValue={maxValue}
/>,
<div key="reset" className="reset" onClick={this.reset}>
<img src="https://img.icons8.com/ios-glyphs/30/null/reboot.png" />
</div>,
];
} else {
return (
<Form
formInput={this.state.formInput}
onInput={this.formInputs}
onSubmit={this.getData}
/>
);
}
}
}
ReactDOM.render(<App />, document.getElementById("app"));
View Compiled
This Pen doesn't use any external CSS resources.