backgroundColor doesn't re-render - React - javascript

i'm building a sorting algorithm visualizer. And every one of these items is a div with the backgroundColor set to white.
When the algorithm is running he sets the backgroundColor to orange to display which items have changed.
But the problems happens when i reset the array using setState(), because react re-renders the new array perfectly fine, but he never renders the backgroundColor back to white.
This is my component:
import React from 'react'
import './SortingVisualizer.css'
import InsertionSort from '../SortingAlgorithms/InsertionSort'
const NORMAL_COLOR = 'white';
const CHANGED_COLOR = 'red';
const AFTER_CHANGE_COLOR = 'orange';
export default class SortingVisualizer extends React.Component{
constructor(props){
super(props);
this.state = {
arrayToSort: []
};
}
componentDidMount(){
this.resetArray();
}
resetArray(){
const arrayToSort = [];
for (let i = 0; i < 200; i++) {
arrayToSort.push(this.RandomIntBetweenRange(5, 1000));
}
this.setState({ arrayToSort });
}
insertionSort(){
let sortedArrayAnim = InsertionSort(this.state.arrayToSort);
let arrayToSort = this.state.arrayToSort;
let arrayBars = document.getElementsByClassName('array-item');
let arrayBarsWithColorChanged = [];
//loop through all the animations
for (let index = 0; index < sortedArrayAnim.length; index++) {
const [i,j] = sortedArrayAnim[index];
//setTimeout(() => {
//set changed colors back to normal
if(index !== 0){
arrayBarsWithColorChanged.forEach((element, index) => {
arrayBars[element].style.backgroundColor = AFTER_CHANGE_COLOR;
});
arrayBarsWithColorChanged = [];
}
let temp = arrayToSort[i];
//change array
arrayToSort[i] = arrayToSort[j];
arrayToSort[j] = temp;
//change div bar colors, unl
if(index != sortedArrayAnim.length - 1){
arrayBars[i].style.backgroundColor = CHANGED_COLOR;
arrayBars[j].style.backgroundColor = CHANGED_COLOR;
arrayBarsWithColorChanged.push(i);
arrayBarsWithColorChanged.push(j);
}
this.setState({ arrayToSort })
//}, 10);
}
}
render() {
const {arrayToSort} = this.state;
return (
<div className="main-div">
{arrayToSort.map((value, idx) => (
<div className="array-item" key={idx} style={{height: value, backgroundColor: 'white'}}>
</div>
))}
<button onClick={() => this.resetArray()}>Generate new array</button>
<button onClick={() => this.insertionSort()}>Insertion Sort</button>
</ div>
);
}
RandomIntBetweenRange(min, max){
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}

In React you don't manipulate DOM directly and you don't mutate the state (arrayToSort[i] = arrayToSort[j] for example). You change the model (state, props), and the view changes accordingly.
So in your case, you need to include in the state, the array of column values (arrayToSort), the array of pairs to swap (sortedArrayAnim), and a Set of previous changed values (prevChanged). Whenever something changes, the view will update according to those state values.
Demo (see comments in code):
const NORMAL_COLOR = 'white';
const CHANGED_COLOR = 'red';
const AFTER_CHANGE_COLOR = 'orange';
/** this function return the color **/
const getColor = (idx, prevChanged, [current]) => {
if(current && current.includes(idx)) return CHANGED_COLOR; // if it's in current changed pair [i, j]
if(prevChanged.has(idx)) return AFTER_CHANGE_COLOR; // if it was changed before
return NORMAL_COLOR;
}
class SortingVisualizer extends React.Component {
state = {
arrayToSort: [],
sortedArrayAnim: [],
prevChanged: new Set()
};
timeout = null;
componentDidMount() {
this.resetArray();
}
resetArray = () => {
clearTimeout(this.timeout);
const arrayToSort = [];
for (let i = 0; i < 50; i++) {
arrayToSort.push(this.RandomIntBetweenRange(1, 100));
}
this.setState({
arrayToSort,
sortedArrayAnim: [],
prevChanged: new Set()
});
}
animate = (sortedArrayAnim) => {
this.setState(
({
prevChanged,
arrayToSort
}) => {
if(!sortedArrayAnim.length) return { sortedArrayAnim };
const [current] = sortedArrayAnim;
const newArrayToSort = [...arrayToSort]; // clone newArrayToSort
/** flip the values according to current change [i, j] **/
newArrayToSort[current[0]] = arrayToSort[current[1]];
newArrayToSort[current[1]] = arrayToSort[current[0]];
return ({
arrayToSort: newArrayToSort,
sortedArrayAnim,
prevChanged: new Set([...prevChanged, ...current]) // add changed items to the Set
});
},
() => { // when state change is done
const { sortedArrayAnim } = this.state;
// if there are more items to change wait and call animate again
if(sortedArrayAnim.length) {
this.timeout = setTimeout(() => this.animate(sortedArrayAnim.slice(1)), 1000);
}
}
);
}
insertionSort = () => {
const sortedArrayAnim = [[1, 5], [10, 15], [20, 13], [17, 48], [20, 13], [45, 17]]; // InsertionSort(this.state.arrayToSort); // I've used a dummy array
this.animate(sortedArrayAnim);
}
render() {
const { arrayToSort, sortedArrayAnim, prevChanged } = this.state;
return (
<div className="main-div">
{arrayToSort.map((value, idx) => (
<div className="array-item" key={idx} style={{
height: `${value}vh`,
backgroundColor: getColor(idx, prevChanged, sortedArrayAnim)
}}>
</div>
))}
<button onClick={this.resetArray}>Generate new array</button>
<button onClick={this.insertionSort}>Insertion Sort</button>
</ div>
);
}
RandomIntBetweenRange(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
ReactDOM.render(
<SortingVisualizer />,
root
);
.main-div {
display: flex;
align-items: flex-end;
justify-content: space-between;
background: black;
height: 100vh;
padding: 1vmax 1vmax 0 ;
box-sizing: border-box;
}
.array-item {
width: 1vw;
transition: height 0.3s;
}
button {
align-self: flex-start;
}
body {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

You're going to want to keep everything in state in order for react to re-render based on any changes to state.
state = {
backgroundColor: 'white'
}
...
// element
<div className="array-item" key={idx} style={{height: value, backgroundColor: this.state.backgroundColor}}>
replace arrayBars[i].style.backgroundColor = 'orange'; with this.setState({ backgroundColor: 'orange' });
and update:
resetArray(){
const arrayToSort = [];
for (let i = 0; i < 200; i++) {
arrayToSort.push(this.RandomIntBetweenRange(5, 1000));
}
this.setState({ arrayToSort, backgroundColor: 'white' }); // reset back to white
}

Related

Updating state twice when the button is clicked, React

I want to create simple 2048 game application. My initial board is composed of 16 array elements.
The generate() function generates a value of '2' in a random empty element
It works just fine and creates one random '2' for me, but the problem starts when I want to call it twice in a handler like this:
const handleNewGame = () => {
generate()
generate()
}
I've read about prevState but have no idea how to implement it in this batch of code to work properly.
Here is my game component:
const width = 4;
const Game = () => {
const [Board, setBoard] = useState([]);
const createBoard = () => {
let initialBoard = [];
for (let i = 0; i < width * width; i++) {
initialBoard.push("");
}
return initialBoard;
};
const generate = () => {
let board = [...Board];
let randomNumber = Math.floor(Math.random() * Board.length);
console.log(randomNumber);
if (board[randomNumber] === "") {
board[randomNumber] = 2;
setBoard(board);
} else generate()
};
const handleNewGame = () => {
generate()
generate()
}
useEffect(() => {
setBoard(createBoard);
console.log(`Board Created!`);
}, []);
return (
<div className="game-container">
<button onClick={handleNewGame}>NewGame</button>
<div className="game">
{Board.map((value, index) => (
<div className="tile" key={index}>
{value}
</div>
))}
</div>
</div>
);
};
export default Game;
I'll be glad for the answer.
setState(), named setBoard() in your code is asynchronous, whatch this great video on the Event Loop for you to understand more: https://www.youtube.com/watch?v=8aGhZQkoFbQ.
See if this will suit your needs:
import { useEffect, useState } from 'react';
const width = 4;
const Game = () => {
const [Board, setBoard] = useState([]);
const createBoard = () => {
let initialBoard = [];
for (let i = 0; i < width * width; i++) {
initialBoard.push('');
}
return initialBoard;
};
const randomBoardPosition = () => {
return Math.floor(Math.random() * Board.length);
};
const generate = () => {
let board = createBoard();
let randomNumber = randomBoardPosition();
let positionsFilled = 0;
while (positionsFilled < 2) {
if (board[randomNumber] === '') {
board[randomNumber] = 2;
positionsFilled++;
} else {
randomNumber = randomBoardPosition();
}
}
setBoard(board);
};
const handleNewGame = () => {
generate();
};
useEffect(() => {
setBoard(createBoard);
console.log(`Board Created!`);
}, []);
return (
<div className="game-container">
<button onClick={handleNewGame}>NewGame</button>
<div className="game">
{Board.map((value, index) => (
<div className="tile" key={index}>
{value}
</div>
))}
</div>
</div>
);
};
export default Game;
Just us function instead of value
const generate = () => {
setBoard(Board => {
let board = [...Board];
while (true) {
let randomNumber = Math.floor(Math.random() * Board.length);
console.log(randomNumber);
if (board[randomNumber] === "") {
board[randomNumber] = 2;
break
}
}
}
}

Looping to clear all numbers colors in react

I have this working function to change all children button color on click.
Now im doing a "ClearGame" button, to change all button background color to its original state '#ADC0C4'. How can i do that with key or id from the children?
const newBet: React.FC = () => {
const clearGame = () => {
let spliceRangeJSON = gamesJson[whichLoteriaIsVar].range;
totalNumbers.splice(0, spliceRangeJSON);
for (let i = 0; i <= spliceRangeJSON; i++) {
//Looping to change the backgroundColor using Id or Key
}
};
const NumbersParent = (props: any) => {
const [numbersColor, setNumbersColor] = useState('#ADC0C4');
const changeButtonColor = () => {
if (numbersColor === '#ADC0C4') {
setNumbersColor(gamesJson[whichLoteriaIsVar].color);
totalNumbers.push(props.id);
} else {
setNumbersColor('#ADC0C4');
let searchTotalNumbers = totalNumbers.indexOf(props.id);
totalNumbers.splice(searchTotalNumbers, 1);
}
};
return (
<Numbers style={{ backgroundColor: numbersColor }} onClick={changeButtonColor}>
{props.children}
</Numbers>
);
};
return (
<NumbersContainer>
{numbersList.map((num) => (
<NumbersParent key={num} id={num}>
{formatNumber(num)}
</NumbersParent>
))}
</NumbersContainer>
<ClearGame onClick{clearGame}>Clear Game</ClearGame >
);
};
Since you want to modify the state of children from the parent component,
Create a State Object in the parent.
Pass it to the children as prop
You can change it on clear.
Or try something like recoil.
Move the useState and changeButtonColor method to the parent component and make some changes:
const [numbersColor, setNumbersColor] = useState({});
const changeButtonColor = (color) => {
if (!numbersColor[color] || numbersColor[color] === '#ADC0C4') {
setNumbersColor({...numbersColors,
[color]: gamesJson[whichLoteriaIsVar].color
});
totalNumbers.push(props.id);
} else {
setNumbersColor({...numbersColors,
[color]: '#ADC0C4'
});
let searchTotalNumbers = totalNumbers.indexOf(props.id);
totalNumbers.splice(searchTotalNumbers, 1);
}
};
Change the map to that:
<NumbersContainer>
{numbersList.map((num, index) => (
<NumbersParent key={num} id={num} index={index} changeButtonColor={(color) => {changeButtonColor(color)}}>
{formatNumber(num)}
</NumbersParent>
))}
</NumbersContainer>
And on the return of NumbersParent component changes to be like that:
<Numbers style={{ backgroundColor: numbersColor[`color${props.index}`] }} onClick={props.changeButtonColor(`color${props.index}`)}>
{props.children}
</Numbers>
Finally, change clearGame function like this:
const clearGame = () => {
let spliceRangeJSON = gamesJson[whichLoteriaIsVar].range;
for (let i = 0; i <= spliceRangeJSON; i++) {
changeButtonColor(`color${i}`)
}
totalNumbers.splice(0, spliceRangeJSON);
};

React useState to update a specific item in its array

I have an array of images inside refData. I then map them into an array of img objects with RefItem. I then want thouse specifc images to change to the next one in a line - so img 0 becomes 1, and 1 becomes 2 - as written in refbox. I just cannot figure this one out.. I just want to update the individual numbers in each array item?
import React, {useState, useEffect} from 'react'
import refData from './refData'
import RefItem from './refItem'
function ReferencesPP(){
const refItems = refData.map(item => <RefItem key={item.id} pitem={item}/>)
const [refs, setRefs] = useState([0, 1, 2, 3])
useEffect(() => {
const intervalId = setTimeout(() => {
for(let i = 0; i < refs.length; i++){
if(refs[i] !== refData.length){
setRefs(...refs[i], refs[i] = refs[i] + 1)
} else {
setRefs(...refs[i], refs[i] = 0)
}
}
}, 2000);
return () => clearTimeout(intervalId);
}, [refs]);
return(
<div className="referencespp-container">
<div className="background-container" />
<div className="content-container">
<div id="refbox">
{refItems[refs[0]]}
{refItems[refs[1]]}
{refItems[refs[2]]}
{refItems[refs[3]]}
</div>
</div>
</div>
)
}
export default ReferencesPP
I thought it would be as simple, as writing
const refs = [0, 1, 2, 3]
useEffect(() => {
const intervalId = setInterval(() => {
for(let i = 0; i < refs.length; i++){
if(refs[i] !== refData.length){
refs[i] = refs[i] + 1;
} else {
refs[i] = 0;
}
}
}, 2000);
return () => clearInterval(intervalId);
}, []);
but doing that only updates the const and not the {refItems[refs[0]]} elements?
Have a look at this https://jsfiddle.net/gwbo4pdu/ I think it's what you want to get
React.useEffect(() => {
const intervalId = setTimeout(() => {
const newRefs = [...refs]
const i = newRefs.shift()
newRefs.push(i)
setRefs(newRefs);
}, 2000);
return () => clearTimeout(intervalId);
}, [refs]);
P.S. you can do just newRefs.push(newRefs.shift())

Refs return as null

I have an array of 'selected cards' stored in a 'selectedCards' state, which updates depending on whether the user has referenced the 'Card' ID or not (using a checkbox).
When the component mounts, a default array is mapped using the 'Card' component, and added into the stack. They are accessed using refs and using a tinder-style interaction, users can throw out left or right by keyboard or dragging out of the stack.
When the component updates and adds new cards to the stack, the refs are returned as null. Why is this?
export default class Cards extends Component {
constructor(props, context) {
super(props, context);
this.config = {
throwOutDistance: () => Math.max(window.innerWidth, window.innerHeight),
throwOutConfidence: () => 1,
allowedDirections: [
Direction.LEFT,
Direction.RIGHT,
]
};
const fakeEvent = {
target: {
id: 'digiDesign',
}
};
this.cardRefs = new Map();
this.state = {
selectedCategories: ['digiDesign', 'branding'],
selectedCards: this.filterArray(fakeEvent),
cardStack: cardData,
currentCard: cardData.length - 1,
cardIndex: 0,
checked: false,
};
}
componentDidMount() {
const { selectedCards } = this.state;
//Intiates key listener
document.addEventListener("keydown", this.onDirection);
//Configures Swing.js
this.stack = Stack(this.config);
this.stack.on("dragstart", () => this.setState({ dragging: true }));
this.stack.on("dragend", () => this.setState({ dragging: false }));
this.stack.on("throwout", this.onThrowOut.bind(this));
//Config stack of cards
this.createStack();
//Logs initial cards
console.log(selectedCards, 'selected cards')
}
handleChange(e){
const { selectedCards } = this.state;
if (e.target.checked){
for (let i = 0; i < this.filterArray(e).length; i++){
selectedCards.push(this.filterArray(e)[i])
}
} if (!e.target.checked){
this.removeArray(e);
}
this.setState({
selectedCards
})
console.log(selectedCards)
}
createStack(){
const { selectedCards } = this.state;
this.cardRefs.forEach((ref) => {
const el = ReactDOM.findDOMNode(ref);
this.stack.createCard(el);
});
this.setState({
currentCard: selectedCards.length - 1
})
}
destroyStack(){
this.cardRefs.forEach((ref) => {
const el = ReactDOM.findDOMNode(ref);
const card = this.stack.getCard(el);
card.destroy();
});
}
componentWillUnmount() {
this.destroyStack()
}
//Controls keycodes
onDirection = e => {
if(e.keyCode === 37){
this.throwoutLeft();
} if (e.keyCode === 39) {
this.throwoutRight();
}
}
//Controls throwout right with key
throwoutRight(){
const el = ReactDOM.findDOMNode(
this.cardRefs.get(this.state.currentCard)
);
const card = this.stack.getCard(el);
card.throwOut(1000, 0);
}
//Controls throwout left with key
throwoutLeft(){
const el = ReactDOM.findDOMNode(
this.cardRefs.get(this.state.currentCard)
);
const card = this.stack.getCard(el);
card.throwOut(-1000, 0);
}
//Controls swipe/grab
onThrowOut() {
const activeCard = this.state.currentCard
if (activeCard > 0){
this.setState({
currentCard: activeCard - 1
})
}
if (activeCard === 0){
this.setState({
currentCard: activeCard - 1
})
this.resetDeck();
}
}
//Resets the deck on finish
resetDeck() {
const { selectedCards } = this.state
this.setState({
currentCard:selectedCards.length - 1,
resetting: true
});
this.createStack()
this.setState({
resetting: false,
});
}
//Controls checkboxes
filterArray(e){
let filteredArray = cardData.filter(function(x){
return x.id === e.target.id
});
return filteredArray
}
removeArray(e){
const{ selectedCards } = this.state;
for (let i = selectedCards.length-1; i >= 0; i--){
if(selectedCards[i].id === e.target.id){
selectedCards.splice(i, 1)
}
}
}
render() {
const { selectedCards } = this.state;
return (
<div className='container'>
<Viewport>
<CardStack className='stack'>
{selectedCards.map((card, i) => {
return (
<Card
selectedCategories={this.state.selectedCategories}
key={card.advice}
ref={c => this.cardRefs.set(i, c)}
advice={card.advice}
id={card.id}
tag={card.tag}
isSelected={this.state.selectedCategories.indexOf(card.id) > -1}
active={i === this.state.currentCard}
next={i === this.state.currentCard - 1}
previous={i > this.state.currentCard}
dragging={
(i === this.state.currentCard && this.state.dragging) ||
this.state.resetting
}
/>
)
})}
</CardStack>
<FilterContainer>
{
filterData.map((item, i) => {
const active = i === 0;
return (
<Filters
ref='input'
key={item.id}
id={item.id}
label={item.label}
onChange={(e) => this.handleChange(e)}
active={active}
/>
)
})
}
</FilterContainer>
</Viewport>
</div>
);
}
}
The expected result is for the new cards to receive the 'throwIn' function, but seeing as though the new refs are returning as null, this cannot be achieved.
Since you're using a Map, an Array-iterable to store your refs. You'll have to transform the Map back into an array before you can use methods like forEach.
createStack(){
const { selectedCards } = this.state;
let allRefs = Array.from(this.refs.values())
allRefs.forEach((ref) => {
const el = ReactDOM.findDOMNode(ref);
this.stack.createCard(el);
});
this.setState({
currentCard: selectedCards.length - 1
})
}
destroyStack(){
let allRefs = Array.from(this.refs.values())
allRefs.forEach((ref) => {
const el = ReactDOM.findDOMNode(ref);
const card = this.stack.getCard(el);
card.destroy();
});
}
Hope that helps!

Where to hold state for something that exist until after a callback runs

I have the following page in React:
#connect((store) => {
return {
all_orders_data: store.trends.all_orders_data,
...etc...
};
})
class OrdersController extends MyComponent {
constructor(props){
let custom_methods = ['refreshData', 'runDispatch'];
super(props, custom_methods);
this.state = {last_updated_at: new Date()};
}
componentWillMount() {
this.runDispatch();
// this.props.dispatch(fetchOrderData());
}
componentDidMount() {
this.refreshTimerID = setInterval(
() => this.refreshData(),
300 * 1000 // 5 minutes
);
}
refreshData() {
console.log('running refresh');
this.runDispatch(true);
this.setState({last_updated_at: new Date()});
}
runDispatch(refresh=false) {
this.props.dispatch(fetchOrderTrendingAllOrdersData(refresh));
}
render() {
return (
<div>
<h1>Order Trending</h1>
{last_updated_at}
<br/>
<div className="card col-md-12">
<h2 className="style-1">All Orders</h2>
<LineChart
data={all_orders_data}
fetched={fetched_all_orders_data}
error={error_in_fetching_all_orders_data}
/>
</div>
</div>
)
}
In the render I unpack the props, the last_updated header, and render a line chart.
I want to be able to click buttons to toggle lines on the chart. To do this, I have to keep track of the keys for the data variable to show as lines.
I can't put these line options in the constructor because the runDispatch isn't fired off until componentWillMount.
I can't put it in componentWillMount because I don't know when runDispatch will return for data until it does return.
I can get the keys for the all_orders_data in the reducer and pass it to OrdersController as a prop, but props can't be changed and I want this page of our app to control the current lines showing.
I don't want to put it on the chart component because it get's new props every time to refresh runs, and I don't know if it will maintain the proper state after refresh.
The toggle setting doesn't need to be retained later, only while the controller is active (if they pick a new link I don't care if it resets).
My gut is to put state on the line chart since it doesn't have to be permanent.
Where is the correct place to keep the state of these line keys, like:
{
all_orders: ['Online orders', 'Web orders', ...]
}
to let the user toggle what lines he wants to see on graph? It can be on the LineChart, the controller, a new redux prop, etc.
I decided to put it on the LineChart itself, since the buttons toggle lines on it. It wasn't working, but you can protect the state in componentWillReceiveProps:
import React from 'react';
import ReactLoading from 'react-loading';
import {
VictoryChart,
VictoryLine,
VictoryTheme,
VictoryAxis,
VictoryTooltip,
VictoryBar,
VictoryVoronoiContainer
} from 'victory';
import _ from 'underscore';
import MyComponent from '../main/MyComponent';
const COLOR_OPTIONS = [
'#c43a31', // dark red
'blue',
'green',
'yellow',
'purple',
'teal',
'orange'
];
function getTimestringFromUnixTimestamp(timestamp) {
// just default it to AM for now
let period = 'AM'
let date = new Date(timestamp);
let hours = date.getHours();
let minutes = date.getMinutes();
if (hours >= 12) {
period = 'PM';
}
if (hours == 0) {
hours += 12;
} else if (hours >= 13) {
hours -= 12;
}
hours = "0" + hours;
minutes = "0" + minutes;
// Will display time in 10:30 AM format
let formattedTime = `${hours.substr(-2)}:${minutes.substr(-2)} ${period}`;
return formattedTime
}
function displayTooltips(data) {
// x is the unix timestamp, y is the order count
let { x, y } = data;
let formattedTime = getTimestringFromUnixTimestamp(x);
// https://stackoverflow.com/questions/847185/convert-a-unix-timestamp-to-time-in-javascript
return `Time - ${formattedTime}\nOrder Count - ${y}`
}
export default class LineChart extends MyComponent {
constructor(props) {
let custom_methods = [
'generateVictoryLines',
'generateToggleButtons',
'toggleItemOnclick',
'toggleAllLines',
];
super(props, custom_methods);
this.state = {
active_line_keys: [],
available_line_keys: []
};
}
componentWillReceiveProps(nextProps) {
console.log('\n\ncomponentWillReceiveProps:');
let data = nextProps.data;
console.log(data);
if (data) {
let line_keys = Object.keys(data);
console.log('line_keys:');
console.log(line_keys);
console.log('this.state.available_line_keys:');
console.log( this.state.available_line_keys);
let is_equal = _.isEqual(_.sortBy(line_keys), _.sortBy(this.state.available_line_keys));
if (!is_equal) {
console.log('line keys are diff; need to update state');
this.setState({
available_line_keys: line_keys,
active_line_keys: line_keys
});
}
}
}
generateVictoryLines() {
return this.state.active_line_keys.map((key, index) => {
let this_keys_permanent_index = this.state.available_line_keys.indexOf(key);
let color = COLOR_OPTIONS[this_keys_permanent_index];
return (
<VictoryLine
labels={displayTooltips}
labelComponent={<VictoryTooltip/>}
style={{
data: { stroke: `${color}` },
parent: { border: `1px solid #ccc`}
}}
data={this.props.data[key]}
/>
)
});
}
generateToggleButtons() {
return this.state.available_line_keys.map((key, index) => {
let this_keys_permanent_index = this.state.available_line_keys.indexOf(key);
let color = COLOR_OPTIONS[this_keys_permanent_index];
console.log(key);
return (
<button onClick={this.toggleItemOnclick.bind(null, key)} style={ {color: color}}>{key}</button>
);
})
}
toggleItemOnclick(name) {
console.log('\ntoggleItemOnclick:');
console.log(name);
console.log(this.state);
let is_in_active_line_keys = this.state.active_line_keys.indexOf(name) != -1;
console.log(is_in_active_line_keys);
let new_active_line_keys;
if (is_in_active_line_keys) {
new_active_line_keys = this.state.active_line_keys.filter(e => e !== name); // e is each item in the list; filter
// this.setState({active_line_keys: new_active_line_keys});
} else {
new_active_line_keys = this.state.active_line_keys.slice();
new_active_line_keys.push(name);
}
console.log(new_active_line_keys);
this.setState({active_line_keys: new_active_line_keys});
// arr = arr.filter(e => e !== el);
}
toggleAllLines() {
if (this.state.active_line_keys.length < this.state.available_line_keys.length) {
this.setState({active_line_keys: this.state.available_line_keys});
} else {
this.setState({active_line_keys: []});
}
}
// addAllLines() {
// this.setState({active_line_keys: this.state.available_line_keys});
// }
render() {
let graph_body;
let { data, error, fetched } = this.props; // we don't need data here
let victory_lines = this.generateVictoryLines();
let buttons = this.generateToggleButtons();
if (error) {
// alert(error);
console.log('\n\nerror:');
console.log(error);
graph_body = (<h2 className="centered">Error retrieving data</h2>);
} else if (fetched == false) {
graph_body = (
<div className="card col-md-12">
<span><h2 style={{fontWeight: "bold", fontSize: "25px", color: "#F96302"}}>Just a moment...Your request is being processed.</h2>
<ReactLoading type="cylon" color="#F96302" /></span>
</div>
)
} else {
try {
// in the victoryLine, interpolation="natural" will smooth graph out
graph_body = (
<div>
<VictoryChart
theme={VictoryTheme.material}
scale={{x: "time", y: "linear"}}
animate={{duration: 650}}
containerComponent={<VictoryVoronoiContainer/>}
width={1500}
height={600}
>
<VictoryAxis style={ { tickLabels: {fontSize: '20px'} } }/>
<VictoryAxis
dependentAxis
tickValues={[20, 40, 60, 80, 100]}
style={ { tickLabels: {fontSize: '20px'} } }
/>
{victory_lines}
</VictoryChart>
<button onClick={this.toggleAllLines}>Toggle lines</button>
{buttons}
</div>
)
} catch(err) {
graph_body = (<h2 className="centered">Error using response: {`${err}`}</h2>);
}
}
return (
<div>
{graph_body}
</div>
)
}
}

Categories

Resources