i wanted to make a game and followed a tutorial to make a Tetris game, while playing it i noticed that when you press the space button the whole game resets, i cant figure out how to fix that. My harddrop function works when i press space so that works but then the game resets to. As far as i know there should be no other function set to the spacebar (keyCode === 32)
If i remove the harddrop function the game still restarts if i press space
this is my code:
const Tetris = () => {
const [dropTime, setDropTime] = useState(null);
const [gameOver, setGameOver] = useState(false);
const [player, updatePlayerPos, resetPlayer, playerRotate] = usePlayer();
const [stage, setStage, rowsCleared] = useStage(player, resetPlayer);
const [score, setScore, rows, setRows, level, setLevel] = useGameStatus(
rowsCleared
);
console.log('re-render');
const movePlayer = dir => {
if (!checkCollision(player, stage, { x: dir, y: 0 })) {
updatePlayerPos({ x: dir, y: 0 });
}
};
const keyUp = ({ keyCode }) => {
if (!gameOver) {
// Activate the interval again when user releases down arrow.
if (keyCode === 40) {
setDropTime(1000 / (level + 1));
}
}
};
const startGame = () => {
// Reset everything
setStage(createStage());
setDropTime(1000);
resetPlayer();
setScore(0);
setLevel(0);
setRows(0);
setGameOver(false);
};
const drop = () => {
// Increase level when player has cleared 10 rows
if (rows > (level + 1) * 10) {
setLevel(prev => prev + 1);
// Also increase speed
setDropTime(1000 / (level + 1) + 200);
}
if (!checkCollision(player, stage, { x: 0, y: 1 })) {
updatePlayerPos({ x: 0, y: 1, collided: false });
} else {
// Game over!
if (player.pos.y < 1) {
console.log('GAME OVER!!!');
setGameOver(true);
setDropTime(null);
}
updatePlayerPos({ x: 0, y: 0, collided: true });
}
};
const dropPlayer = () => {
setDropTime(null);
drop();
};
const hardDrop = () => {
let pot = 0;
while (!checkCollision(player, stage, { x: 0, y: pot })) {
setDropTime(5);
pot += 1;
}
updatePlayerPos({ x: 0, y: pot-1, collided: true });
}
// starts the game
useInterval(() => {
drop();
}, dropTime);
const move = ({ keyCode }) => {
if (!gameOver) {
if (keyCode === 37) {
movePlayer(-1);
} else if (keyCode === 39) {
movePlayer(1);
} else if (keyCode === 40) {
dropPlayer();
} else if (keyCode === 38) {
playerRotate(stage, 1);
} else if (keyCode === 32) {
hardDrop()
}
}
};
return (
<StyledTetrisWrapper
role="button"
tabIndex="0"
onKeyDown={e => move(e)}
onKeyUp={keyUp}
>
<StyledHeader/>
<StyledTetris>
<Stage stage={stage} />
<aside>
{gameOver ? (
<Display gameOver={gameOver} text="Game Over" />
) : (
<div className='aside-container'>
<Display text={`Score: ${score}`} />
<Display text={`rows: ${rows}`} />
<Display text={`Level: ${level}`} />
</div>
)}
<StartButton callback={startGame}></StartButton>
<button className='back-btn'
type='button'
onClick={(e) => {
e.preventDefault();
window.location.href="https://play-it-games.netlify.app/";
}}>Back to Play it!</button>
</aside>
</StyledTetris>
</StyledTetrisWrapper>
);
};
export default Tetris;
Related
I'm implementing sorting via draggable with recoil and react-dnd.
It was confirmed that the index to be replaced and the target index came in normally, but when the onMove function was executed, the data in the questionItemIdList array was initialized to the initial value.
Thinking that the index value was set as id, it was initialized as it is now even after changing the logic with a unique id value instead of the index value.
When you check the console in the 3rd line of the onMove function above, the data array was initialized.
How can I solve this??
const MultipleSelection = ({ questionIndex, partIndex, sysCode, hasNextSectionFlag, ListLength }: IProps) => {
const [questionItemIdList, setQuestionItemIdList] = useRecoilState(qusetionItemIdListAtom(sysCode));
// console.log(questionList);
const onMove = useCallback((dragIndex: number, hoverIndex: number) => {
const dragInput = questionItemIdList[dragIndex];
console.log(questionItemIdList, dragIndex, hoverIndex);
setQuestionItemIdList(
update(questionItemIdList, {
$splice: [
[dragIndex, 1], // Delete
[hoverIndex, 0, dragInput], // Add
],
})
);
// console.log(questionItemIdList);
}, []);
const renderInput = useCallback((id: QuestionItemListID, index: number, arr: QuestionItemListID[]) => {
return (
<InputAndNextPartContainer
key={id}
hasNextSectionFlag={hasNextSectionFlag}
hasDeleteBtn={arr.length > 1}
partId={partIndex}
questionId={questionIndex}
selectionNumber={index + 1}
id={sysCode} //해당 선택지 리스트에 대한 id값
idName={id} //선택지 리스트 안에 있는 고유 id 값
ListLength={ListLength}
moveCard={onMove}
/>
);
}, []);
return (
<DndProvider backend={TouchBackend}>
<Option>{questionItemIdList.map((id, idx, arr) => renderInput(id, idx, arr))}</Option>
</DndProvider>
);
};
//InputAndNextPartContainer
const InputAndNextPartContainer = ({id, moveCard}: IProps) => {
const [showModalState, setshowModalState] = useState(false);
return (
<Draggable handleMove={moveCard} index={id} id={id}>
...
</Draggable>
);
};
const Draggable = ({ children, handleMove, index, id }: IProps) => {
const ref = useRef<HTMLDivElement>(null);
const debounceHoverItem = _.debounce((item: DragItem, monitor: DropTargetMonitor) => {
if (!ref.current) {
return;
}
if (item.index === undefined) return;
const dragIndex = item.index;
const hoverIndex = index;
if (dragIndex === hoverIndex) {
return;
}
const hoverBoundingRect = ref.current?.getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
handleMove(dragIndex, hoverIndex);
item.index = hoverIndex;
}, 70);
const [{ handlerId }, drop] = useDrop<DragItem, void, { handlerId: Identifier | null }>({
accept: ItemTypes.QUESTION_ITEM,
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
};
},
hover(item: DragItem, monitor: DropTargetMonitor) {
debounceHoverItem(item, monitor);
},
});
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.QUESTION_ITEM,
item: () => {
return { id, index };
},
collect: (monitor: any) => ({
isDragging: monitor.isDragging(),
}),
});
drag(drop(ref));
return (
<DraggableWrapper ref={ref} data-handler-id={handlerId} isdragging={isDragging ? 1 : 0}>
{children}
</DraggableWrapper>
);
};
So I'm trying to create an infinite scroll component that will display if the component is scrolling left, right, or not at all. I'm running into alot of issues using hooks. Ive tried combining this https://dirask.com/posts/React-scroll-stop-event-DkBe01 with this https://codesandbox.io/s/icy-river-wr0uk?file=/src/App.js but could not get it functioning. Lastly, any ideas why it smoothly transitions when I scroll right but it stops upon scrolling left? Thank you guys for your help, this is my first post and I appreciate any help from the community!
import React, { Component, useEffect, useRef, useReducer, useState } from 'react'
import Card from './Card'
import './skills.scss'
import myImage from './Images/editIcon.png'
function SkillsFunction() {
const [state, setState] = useReducer(
(state, newState) => ({...state, ...newState}),
{disableScroll: false, scrollWidth: 0, scrollPos: 1, clonesWidth: 0}
);
const scrollContainerRefy = useRef();
function reCalc() {
let scrollPos = scrollPos;
let scrollWidth = scrollContainerRefy.current.clientWidth;
let clonesWidth = getClonesWidth();
if (scrollPos <= 0) {
scrollPos = 1;
}
setState({
scrollPos: scrollPos,
scrollWidth: scrollWidth,
clonesWidth: clonesWidth,
});
};
// function sideScroll(element,direction,speed,distance,step) {
// let scrollAmount = 0;
// var slideTimer = setInterval(() => {
// if(direction == 'left'){
// element.scrollLeft -= step;
// } else {
// element.scrollLeft += step;
// }
// scrollAmount += step;
// if(scrollAmount >= distance){
// window.clearInterval(slideTimer);
// }
// }, speed);
// };
function handleScroll(e) {
const container = e.currentTarget;
const scrollWidth = container.scrollWidth;
const clonesWidth = getClonesWidth();
let scrollPos = container.scrollLeft;
let scrollPosAdd;
container.clientWidth > clonesWidth ? scrollPosAdd = container.clientWidth : scrollPosAdd = clonesWidth;
if (!state.disableScroll) {
if (scrollPos + scrollPosAdd >= scrollWidth) {
setScroll(
// The math floor value helps smooth out the scroll jump,
// I don't know why that particular value works, but it does
// Same goes for the other setScroll call below
container, 1
// + Math.floor(scrollPosAdd/50)
);
setState({
disableScroll: true,
});
} else if (scrollPos <= 0) {
setScroll(
container, scrollWidth - clonesWidth
// - Math.floor(scrollPosAdd/50)
);
setState({
disableScroll: true,
});
}
}
setState({
scrollWidth: container.scrollWidth,
scrollPos: container.scrollLeft,
});
} ;
function getClonesWidth() {
const clones = document.getElementsByClassName('is-clone');
let clonesWidth = 0;
for (let i = 0; i < clones.length; i++) {
clonesWidth = clonesWidth + clones[i].clientWidth;
}
return clonesWidth;
};
function setScroll(element, pos) {
element.scrollLeft = pos;
setState({
scrollPos: element.scrollLeft,
});
};
function scrollNext(e) {
const container = e.currentTarget.previousSibling;
const element = container;
const direction = 'right';
const speed = 10;
const distance = 272;
const step = 10;
let scrollAmount = 0;
var slideTimer = setInterval(() => {
if(direction == 'left'){
element.scrollLeft -= step;
} else {
element.scrollLeft += step;
}
scrollAmount += step;
if(scrollAmount >= distance){
window.clearInterval(slideTimer);
}
}, speed);
};
function scrollPrev(e) {
const container = e.currentTarget.nextSibling;
const element = container;
const direction = 'left';
const speed = 10;
const distance = 272;
const step = 10;
let scrollAmount = 0;
var slideTimer = setInterval(() => {
if(direction == 'left'){
element.scrollLeft -= step;
} else {
element.scrollLeft += step;
}
scrollAmount += step;
if(scrollAmount >= distance){
window.clearInterval(slideTimer);
}
}, speed);
};
// function componentDidUpdate(prevProps, prevState) {
// };
// function componentDidMount() {
// window.addEventListener('resize', reCalc);
// };
// function componentWillUnmount() {
// window.removeEventListener('resize', reCalc);
// };
useEffect(() => {
// Update the document title using the browser API
if (state.disableScroll) {
window.setTimeout(function() {
setState({
disableScroll: false,
});
}, 40)
}
});
const [scrollDir, setScrollDir] = useState("scrolling left");
const containerRef = useRef();
useEffect(() => {
console.log("olay");
const threshold = 0;
let lastScrollX = window.scrollX;
let ticking = false;
const updateScrollDir = () => {
const scrollX = window.scrollX;
if (Math.abs(scrollX - lastScrollX) < threshold) {
ticking = false;
return;
}
setScrollDir(
scrollX > lastScrollX ? "scrolling right" : "scrolling left"
);
lastScrollX = scrollX > 0 ? scrollX : 0;
ticking = false;
};
const onScroll = () => {
if (!ticking) {
window.requestAnimationFrame(updateScrollDir);
ticking = true;
}
};
window.addEventListener("scroll", onScroll);
console.log(scrollDir);
return () => window.removeEventListener("scroll", onScroll);
}, [scrollDir]);
return(
<>
<div className="card-container">
<div className="scroll scroll-prev" onClick={scrollPrev}>
<i className="fas fa-chevron-left"></i>
</div>
<div ref={containerRef} className="scrolling-wrapper" onScroll={handleScroll}>
<Card title={'Card Number 7'} classes={""}/>
<Card title={'Card Number 1'} classes={""}/>
<Card title={'Card Number 2'}/>
<Card title={'Card Number 3'}/>
<Card title={'Card Number 4'}/>
<Card title={'Card Number 5'}/>
<Card title={'Card Number 6'} image={myImage}/>
<Card title={'Card Number 7'} classes={"is-clone is-start"}/>
<Card title={'Card Number 1'} classes={"is-clone"}/>
<Card title={'Card Number 2'} classes={"is-clone"}/>
<Card title={'Card Number 3'} classes={"is-clone"}/>
<Card title={'Card Number 4'} classes={"is-clone"}/>
<Card title={'Card Number 5'} classes={"is-clone"}/>
<Card title={'Card Number 6'} classes={"is-clone"}/>
</div>
<div className="scroll scroll-next" onClick={scrollNext}>
<i className="fas fa-chevron-right"></i>
</div>
</div>
<div>Scrolling </div>
</>
)
}
export default SkillsFunction
I faced an issue with react-p5-wrapper that it is running in background although I have switch to another route in my react app.
For example, I am currently in /game, and the console is logging "running draw", but when I switch to /about-us, it still logging, meaning it is still running the draw function
Here is my code in sandbox
App.js
import "./styles.css";
import { Route, Switch, BrowserRouter as Router, Link } from "react-router-dom";
export default function App() {
return (
<div className="App">
<Router>
<Link to="/game">Game</Link> | <Link to="/about-us">About Us</Link>
<Switch>
<Route path="/about-us" component={require("./abtus").default} />
<Route path="/game" component={require("./game").default} />
</Switch>
</Router>
</div>
);
}
game.js
import { useEffect } from "react";
import { ReactP5Wrapper, P5Instance } from "react-p5-wrapper";
// Sound
let pointSound, endSound;
let playEndSound = false;
/**
* #param {P5Instance} p
*/
const sketch = (p) => {
const MAX_SPEED = 15;
const pickDirections = () => {
return ((Math.floor(Math.random() * 3) % 2 === 0 ? 1 : -1) * (Math.floor(Math.random() * 2) + 1));
};
const randomXPos = () => {
return p.random(30, p.width - 30);
};
const ramdomImgIndex = () => {
return Math.floor(Math.random() * imgs.length);
};
const reset = () => {
score = 0;
speed = 2;
falls = [
{
y: -70,
x: randomXPos(),
rotation: 0,
direction: pickDirections(),
imgIndex: ramdomImgIndex()
}
];
};
const rotate_n_draw_image = (image,img_x,img_y,img_width,img_height,img_angle) => {
p.imageMode(p.CENTER);
p.translate(img_x + img_width / 2, img_y + img_width / 2);
p.rotate((Math.PI / 180) * img_angle);
p.image(image, 0, 0, img_width, img_height);
p.rotate((-Math.PI / 180) * img_angle);
p.translate(-(img_x + img_width / 2), -(img_y + img_width / 2));
p.imageMode(p.CORNER);
};
// Images
let imgs = [],
basket = { img: null, width: 150, height: 150 },
imgSize = 50;
let screen = 0,
falls = [
{
y: -70,
x: randomXPos(),
rotation: 0,
direction: pickDirections(),
imgIndex: ramdomImgIndex()
}
],
score = 0,
speed = 2;
const startScreen = () => {
p.background(205, 165, 142);
p.fill(255);
p.textAlign(p.CENTER);
p.text("WELCOME TO MY CATCHING GAME", p.width / 2, p.height / 2);
p.text("click to start", p.width / 2, p.height / 2 + 20);
reset();
};
const gameOn = () => {
p.background(219, 178, 157);
p.textAlign(p.LEFT);
p.text("score = " + score, 30, 20);
falls = falls.map(({ x, y, rotation, direction, imgIndex }) => {
// rotate while dropping
rotation += direction;
// dropping
y += speed;
return { x, y, rotation, direction, imgIndex };
});
falls.forEach(({ x, y, rotation, imgIndex }, i) => {
// when is lower than the border line
if (y > p.height) {
screen = 2;
playEndSound = true;
}
// when reaching the border line and is within the range
if (
y > p.height - 50 &&
x > p.mouseX - basket.width / 2 &&
x < p.mouseX + basket.width / 2
) {
// Play Sound
pointSound.currentTime = 0;
pointSound.play();
// Increase Score
score += 10;
// Increase Speed
if (speed < MAX_SPEED) {
speed += 0.1;
speed = parseFloat(speed.toFixed(2));
}
// Whether add new item into array or not
if (i === falls.length - 1 && falls.length < 3) {
falls.push({
x: randomXPos(),
y: -70 - p.height / 3,
rotation: 0,
direction: pickDirections(),
imgIndex: ramdomImgIndex()
});
}
falls[i].y = -70;
falls[i].x = randomXPos();
falls[i].imgIndex = ramdomImgIndex();
}
rotate_n_draw_image(imgs[imgIndex], x, y, imgSize, imgSize, rotation);
});
p.imageMode(p.CENTER);
p.image(
basket.img,
p.mouseX,
p.height - basket.height / 2,
basket.width,
basket.height
);
};
const endScreen = () => {
if (playEndSound) {
endSound.play();
playEndSound = false;
}
p.background(205, 165, 142);
p.textAlign(p.CENTER);
p.text("GAME OVER", p.width / 2, p.height / 2);
p.text("SCORE = " + score, p.width / 2, p.height / 2 + 20);
p.text("click to play again", p.width / 2, p.height / 2 + 60);
};
p.preload = () => {
// Load Images
imgs[0] = p.loadImage("https://dummyimage.com/400x400");
imgs[1] = p.loadImage("https://dummyimage.com/400x400");
imgs[2] = p.loadImage("https://dummyimage.com/401x401");
basket.img = p.loadImage("https://dummyimage.com/500x500");
};
p.setup = () => {
p.createCanvas(
window.innerWidth > 400 ? 400 : window.innerWidth,
window.innerHeight > 500 ? 500 : window.innerHeight
);
};
p.draw = () => {
console.log("running draw");
switch (screen) {
case 0:
startScreen();
break;
case 1:
gameOn();
break;
case 2:
endScreen();
break;
default:
}
};
p.mousePressed = () => {
if (screen === 0) {
screen = 1;
} else if (screen === 2) {
screen = 0;
}
};
};
const CatchingGmae = () => {
useEffect(() => {
// eslint-disable-next-line
pointSound = new Audio("/game/points.wav");
// eslint-disable-next-line
endSound = new Audio("/game/end.wav");
pointSound.volume = 0.3;
return () => {
pointSound.muted = true;
endSound.muted = true;
};
});
return (
<div className="mx-auto flex justify-center items-center">
<ReactP5Wrapper sketch={sketch} />
</div>
);
};
export default CatchingGame;
Is there anyway to stop it from running in background when user switches route?
Given your setup, I can see two ways of telling the sketch to stop when route is switched and the Game react component is not rendered anymore.
Alt 1. You can make something similar to react-p5-wrapper
documentation, reacting to props:
In CatchingGmae component:
const [lastRender, setLastRender] = useState(Date.now());
useEffect(() => {
const interval = setInterval(() => setLastRender(Date.now()), 100);
return () => {
clearInterval(interval);
};
}, []);
return (
<>
<div className="mx-auto flex justify-center items-center">
<ReactP5Wrapper sketch={sketch} lastRender={lastRender} />
In sketch:
let lastRender = 0;
p.updateWithProps = (props) => {
lastRender = props.lastRender;
};
p.draw = () => {
if (!(Date.now() > lastRender + 100)) {
console.log("running draw");
☝ The problem with the Alt 1 is that react will do calculations and re-render frequently for no reason.
Alt 2. Use a state outside of React, a very simple side-effect
for the component, for the sketch to poll on.
Add to CatchingGmae component:
useEffect(() => {
window.noLoop = false;
return () => {
window.noLoop = true;
};
}, []);
Inside p.draw:
if (window.noLoop) return p.noLoop();
☝ This works without calculations, but you might want to scope the global within your own namespace or using other state manager.
The title pretty much says it all, but I'll include my somewhat complicated React timer code anyway.
My app is basically an alarm app, and it keeps perfect time most (about 3/4s) of the time. But sometimes when it is in a background tab, the timing is off (15 minutes could take 20 minutes, for example).
So I'm wondering if this is a common problem that anyone knows about. Does my browser send the clock to sleep to save energy perhaps?
I see other browser based alarm clocks on the web, but I haven't been able to find much about this problem.
Thanks,
class Clock extends React.Component {
render() {
return (
<>
<div className="floatLeft">
<div id="timer">
<Countdown updateDB={this.props.updateDB} taskBarOpen={this.props.taskBarOpen} pauseForModal={this.props.pauseForModal} cycle={this.props.cycle} taskBarCounter={this.props.taskBarCounter}>
</Countdown>
</div>
</div>
</>
);
}
}
function Countdown(props) {
const [timer, setTimer] = React.useState({
name: 'timer',
onBreak: false,
firstStart: true,
isPaused: true,
pauseForModal: false,
time: 0,
timeRemaining: 0,
timerHandler: null,
cycle: 0,
timeEqualsTimeRemaning: true,
showToolbar: false
})
const [taskBarCounter] = React.useState({
counter: 0
})
let taskBarProps = props.taskBarCounter
let allowCountdownRestart = (taskBarCounter.counter !== taskBarProps) ? true : false
const context = React.useContext(ApiContext);
let breakDurations = context.prefs
React.useEffect(() => {
if (allowCountdownRestart) {
taskBarCounter.counter = taskBarCounter.counter + 1
allowCountdownRestart = false
} else {
allowCountdownRestart = true
}
}, [props, allowCountdownRestart])
React.useEffect(() => {
if (props.pauseForModal) {
timer.pauseForModal = true
// handlePause()
} else {
setTimeout(() => {
timer.pauseForModal = false
// handleStart()
}, 300);
}
}, [props.pauseForModal])
React.useEffect(() => {
if (allowCountdownRestart) {
if (!timer.pauseForModal) {
setTimer((timer) => ({
...timer,
time: props.cycle * 60,
timeRemaining: props.cycle * 60,
cycle: props.cycle,
onBreak: false
}));
}
if (timer.isPaused) {
// timer.isPaused = false;
}
}
}, [props])
React.useEffect(() => {
if (timer.time === 0 && !timer.firstStart) {
setTimeout(function () {
if (timer.onBreak) {
playBark()
timer.showToolbar = false
timer.onBreak = false
} else {
const breakDuration = breakDurations[timer.cycle] * 60
playTweet()
if (breakDuration !== 0) {
// playTweet()
setTimer((timer) => ({
...timer,
onBreak: true,
time: breakDuration,
timeRemaining: breakDuration
}));
} else {
// playBark()
timer.showToolbar = false
}
props.updateDB(timer.cycle)
}
}, 1000);
} else {
if (timer.time === timer.timeRemaining) {
timer.firstStart = false
timer.showToolbar = true
handleStart()
}
}
}, [timer.time, timer.time === timer.timeRemaining])
React.useEffect(() => {
if (timer.timeRemaining === 0) {
clearInterval(timer.timerHandler)
setTimer((timer) => ({
...timer,
time: 0,
isPaused: true
}));
}
}, [timer.timeRemaining])
const updateTimeRemaining = e => {
setTimer(prev => {
return { ...prev, timeRemaining: prev.timeRemaining - 1 }
})
}
const handleStart = e => {
if (timer.time !== 0) {
clearInterval(timer.timerHandler)
const handle = setInterval(updateTimeRemaining, 1000);
setTimer({ ...timer, isPaused: false, timerHandler: handle })
}
}
const handlePause = e => {
clearInterval(timer.timerHandler)
setTimer({ ...timer, isPaused: true })
}
const timeFormat = (duration) => {
if (duration > 0) {
var hrs = ~~(duration / 3600);
var mins = ~~((duration % 3600) / 60);
var secs = ~~duration % 60;
var ret = "";
if (hrs > 0) {
ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
}
ret += "" + mins + ":" + (secs < 10 ? "0" : "");
ret += "" + secs;
return ret;
} else {
return "00:00"
}
}
const handleSkip = () => {
clearInterval(timer.timerHandler)
setTimer({ ...timer, timeRemaining: 0 })
}
const handleStop = () => {
clearInterval(timer.timerHandler)
setTimer({ ...timer, onBreak: true, cycle: 0, timeRemaining: 0 })
}
const [playBark] = useSound(bark,
{ volume: 0.35 }
);
const [playTweet] = useSound(tweet,
{ volume: 0.20 }
);
const [playGong] = useSound(gong,
{ volume: 0.20 }
);
return <React.Fragment>
{timer.onBreak ?
<div><h2 className="display-timer-header">On Break </h2> <h2 className="display-timer">{timeFormat(timer.timeRemaining)}</h2></div>
: <div><h3 className="display-timer-header"> Time Left </h3> <h3 ref={context.timerRef} className="display-timer">{timeFormat(timer.timeRemaining)}</h3></div>}
<div className="toolbar-container">
<div className={`toolbar-icons ${props.taskBarOpen ? "taskbar-open" : ""}`}>
<i className="tooltip"><Stop className="toolbar-icon" onClick={handleStop}></Stop>
<span className="tooltiptext">Stop</span></i>
{!timer.isPaused ?
<i className="tooltip pause"><PausePresentation className="toolbar-icon" onClick={handlePause}></PausePresentation>
<span className="tooltiptext pause-tooltip">Pause</span></i>
:
<i className="tooltip pause"><PlayCircleOutline className="toolbar-icon" onClick={handleStart}></PlayCircleOutline>
<span className="tooltiptext">Start</span></i>
}
<i className="tooltip"><SkipNext className="toolbar-icon" onClick={handleSkip} ></SkipNext>
<span className="tooltiptext">Skip to Break</span></i>
</div>
</div>
</React.Fragment>
}
export default Clock;
I am not too good at data visualization.I want to create a Sunburst where the user can zoom. I have done the zoom with the help of my friend but I am unable to add text from data. Here is my code of zoomable Sunburst.
import React from "react";
import { Group } from "#vx/group";
import { Arc } from "#vx/shape";
import { Partition } from "#vx/hierarchy";
import { arc as d3arc } from "d3-shape";
import {
scaleLinear,
scaleSqrt,
scaleOrdinal,
schemeCategory20c
} from "d3-scale";
import { interpolate } from "d3-interpolate";
import Animate from "react-move/Animate";
import NodeGroup from "react-move/NodeGroup";
const color = scaleOrdinal(schemeCategory20c);
export default class extends React.Component {
state = {
xDomain: [0, 1],
xRange: [0, 2 * Math.PI],
yDomain: [0, 1],
yRange: [0, this.props.width / 2]
};
xScale = scaleLinear();
yScale = scaleSqrt();
arc = d3arc()
.startAngle(d => Math.max(0, Math.min(2 * Math.PI, this.xScale(d.x0))))
.endAngle(d => Math.max(0, Math.min(2 * Math.PI, this.xScale(d.x1))))
.innerRadius(d => Math.max(0, this.yScale(d.y0)))
.outerRadius(d => Math.max(0, this.yScale(d.y1)));
handleClick = d => {
this.setState({
xDomain: [d.x0, d.x1],
yDomain: [d.y0, 1],
yRange: [d.y0 ? 20 : 0, this.props.width / 2]
});
};
render() {
const {
root,
width,
height,
margin = {
top: 0,
left: 0,
right: 0,
bottom: 0
}
} = this.props;
const { xDomain, xRange, yDomain, yRange } = this.state;
if (width < 10) return null;
const radius = Math.min(width, height) / 2 - 10;
return (
<svg width={width} height={height}>
<Partition top={margin.top} left={margin.left} root={root}>
{({ data }) => {
const nodes = data.descendants();
return (
<Animate
start={() => {
this.xScale.domain(xDomain).range(xRange);
this.yScale.domain(yDomain).range(yRange);
}}
update={() => {
const xd = interpolate(this.xScale.domain(), xDomain);
const yd = interpolate(this.yScale.domain(), yDomain);
const yr = interpolate(this.yScale.range(), yRange);
return {
unused: t => {
this.xScale.domain(xd(t));
this.yScale.domain(yd(t)).range(yr(t));
},
timing: {
duration: 800
}
};
}}
>
{() => (
<Group top={height / 2} left={width / 2}>
{nodes.map((node, i) => (
<path
d={this.arc(node)}
stroke="#fff"
fill={color(
(node.children ? node.data : node.parent.data).name
)}
fillRule="evenodd"
onClick={() => this.handleClick(node)}
text="H"
key={`node-${i}`}
/>
))}
</Group>
)}
</Animate>
);
}}
</Partition>
</svg>
);
}
}
Currently this visualization does not display the name of data from data.js. I want to display that and add a tooltip. How can I achieve that?
class Sunburst extends React.Component {
componentDidMount() {
this.renderSunburst(this.props);
}
componentWillReceiveProps(nextProps) {
if (!isEqual(this.props, nextProps)) {
this.renderSunburst(nextProps);
}
}
arcTweenData(a, i, node, x, arc) {
const oi = d3.interpolate({ x0: (a.x0s ? a.x0s : 0), x1: (a.x1s ? a.x1s : 0) }, a);
function tween(t) {
const b = oi(t);
a.x0s = b.x0;
a.x1s = b.x1;
return arc(b);
}
if (i === 0) {
const xd = d3.interpolate(x.domain(), [node.x0, node.x1]);
return function (t) {
x.domain(xd(t));
return tween(t);
};
} else {
return tween;
}
}
formatNameTooltip(d) {
const name = d.data.name;
return `${name}`;
}
labelName(d) {
const name = d.data.name;
return `${name}`;
}
labelVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03;
}
labelTransform(d) {
const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
const y = (d.y0 + d.y1) / 2 * 130;
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}
update(root, firstBuild, svg, partition, hueDXScale, x, y, radius, arc, node, self) {
if (firstBuild) {
firstBuild = false;
function click(d) {
node = d; // eslint-disable-line
self.props.onSelect && self.props.onSelect(d);
svg.selectAll('path').transition().duration(1000);
}
const tooltipContent = self.props.tooltipContent;
const tooltip = d3.select(`#${self.props.keyId}`)
.append(tooltipContent ? tooltipContent.type : 'div')
.style('position', 'absolute')
.style('z-index', '10')
.style('opacity', '0');
if (tooltipContent) {
Object.keys(tooltipContent.props).forEach((key) => {
tooltip.attr(key, tooltipContent.props[key]);
});
}
svg.selectAll('path')
.data(partition(root).descendants())
.enter()
.append('path')
.style('fill', (d) => {
const current = d;
if (current.depth === 0) {
return '#ffff';
}
if (current.depth === 1) {
return '#3f51b5';
}
if (current.depth > 1) {
return '#f44336';
}
})
.attr('stroke', '#fff') // lines color
.attr('stroke-width', '2') // line width
.on('click', d => click(d, node, svg, self, x, y, radius, arc))
.on('mouseover', function (d) {
if (self.props.tooltip) {
d3.select(this).style('cursor', 'pointer');
tooltip.html(() => { const name = self.formatNameTooltip(d); return name; });
return tooltip.transition().duration(50).style('opacity', 1);
}
return null;
})
.on('mousemove', () => {
if (self.props.tooltip) {
tooltip
.style('top', `${d3.event.pageY - 50}px`)
.style('left', `${self.props.tooltipPosition === 'right' ? d3.event.pageX - 100 : d3.event.pageX - 50}px`);
}
return null;
})
.on('mouseout', function () {
if (self.props.tooltip) {
d3.select(this).style('cursor', 'default');
tooltip.transition().duration(50).style('opacity', 0);
}
return null;
})
} else {
svg.selectAll('path').data(partition(root).descendants());
}
svg.selectAll('path').transition().duration(1000).attrTween('d', (d, i) => self.arcTweenData(d, i, node, x, arc));
}
renderSunburst(props) {
if (props.data) {
const self = this, // eslint-disable-line
gWidth = props.width,
gHeight = props.height,
radius = (Math.min(gWidth, gHeight) / 2) - 10,
svg = d3.select('svg').append('g').attr('transform', `translate(${gWidth / 2},${gHeight / 2})`),
x = d3.scaleLinear().range([0, 2 * Math.PI]),
y = props.scale === 'linear' ? d3.scaleLinear().range([0, radius]) : d3.scaleSqrt().range([0, radius]),
partition = d3.partition(),
arc = d3.arc()
.startAngle(d => Math.max(0, Math.min(2 * Math.PI, x(d.x0))))
.endAngle(d => Math.max(0, Math.min(2 * Math.PI, x(d.x1))))
.innerRadius(d => Math.max(0, y(d.y0)))
.outerRadius(d => Math.max(0, y(d.y1))),
hueDXScale = d3.scaleLinear()
.domain([0, 1])
.range([0, 360]),
rootData = d3.hierarchy(props.data);
const firstBuild = true;
const node = rootData;
rootData.sum(d => d.size);
self.update(rootData, firstBuild, svg, partition, hueDXScale, x, y, radius, arc, node, self); // GO!
}
}
render() {
return (
<div id={this.props.keyId} className="text-center">
<svg style={{ width: parseInt(this.props.width, 10) || 480, height: parseInt(this.props.height, 10) || 400 }} id={`${this.props.keyId}-svg`} />
</div>
);
}
}
export default Sunburst;