I currently have a Radar chart in chart.js using the react integration.
I was suprised to note that, when I updated the data, instead of showing a completely new plot, it just transitioned smoothly from the previous dataset to the new one.
What I am interested in is to understand how it works under the hood, which honestly I can't understand, at least from looking at the code.
First: my understanding of React is that, when a prop or state changes, it computes the new DOM, and then merges the new DOM and the current DOM, applying only the difference between the two. However, chartjs seem to be implemented as a Canvas element.
The chartjs integration with react does not do much. Taking the Radar plot, this is what it does
export const Radar = /* #__PURE__ */ createTypedChart('radar', RadarController);
which is nothing but declare a <Chart> element and leave it to ChartJS to plot it. In fact, in ChartJS, we have this code, which basically manages the Canvas element and it is smart to perform transitions using animations and so on. This I understand (relatively): a lot of animation and transition helper functions, but this makes sense to me. However, this part is pure JavaScript. There's nothing that is aware of React.
What does not make sense is therefore how the react synchronization system is integrated with this JavaScript library so that the state invalidation of the props/state is synchronised to an animation, instead of a complete rewrite of the Canvas element. I don't seem to find where this magic happens in react-chartjs-2.
As you explained the canvas element does not get changed so it gets reused. To animate the chart chart.js itself has an update method. React-chartjs-2 uses a useeffect function that checks if the data you pass it has changed. If this is the case it calls the update function from chart.js itself and they handle the animations and updates itself:
useEffect(() => {
if (!chartRef.current) return;
if (redraw) {
destroyChart();
setTimeout(renderChart);
} else {
chartRef.current.update();
}
}, [redraw, options, data.labels, data.datasets]);
https://github.com/reactchartjs/react-chartjs-2/blob/4a010540ac01b1e4b299705ddd93f412df4875d1/src/chart.tsx#L78-L87
This is my understanding of the whole process after diving into the code base quite a bit. I've tried to be as detailed as possible with links to the exact line of code I am talking about. Hope this helps:
Beginning with the code snippet you shared:
export const Radar = /* #__PURE__ */ createTypedChart('radar', RadarController);
If you follow the RadarController via the import statement, you see that it is fetched from chart.js
Now we move to the Chart.js code and look for this controller RadarController. It is found in a file called src/controllers/controller.radar.js.
Within that file, you see an update function
This function then calls updateElements with the points information
This function gets the new point position which is then set in properties and passed into the updateElement function
This updateElement function directly takes us to the core.datasetController
Here you see a condition to check if the chart is in directUpdateMode. If not, it calls a function to _resolveAnimations
Within this function, you will see the new Animations(args) object
This eventually brings us to the core.animations file which consists of all the animation related information and processing.
One interesting bit I found here was: this is what seems to be making the beautiful movement of points to the changed location.
You can explore this Animations class further for more detailed understanding
So yeah essentially, it is the js part under the hood that facilitates the smooth transitions and this is how it does it. React code is essentially just like a wrapper of Chart.js calling this update method with the new values.
You can see here: https://github.com/reactchartjs/react-chartjs-2/blob/master/src/chart.tsx
The react-chartjs-2 library creates a component that adds a canvas and when the props update the component creates/updates an internal Chart object that uses the rendered canvas.
From what I saw the animation starts when the props are changed.
The path is props->react-chartjs-2 component->chart object->animation
Related
I want to use some transition effects in my react js app. I am using function components in my app.
How do I include transition effects in app according to the business requirement these days?
I want to use animation such that on every render I can see the effect. It would be great if someone can help me out with an example.
If you want to use a library, I would suggest react-spring
https://react-spring.io/ it is based on spring physics, If you want to read about that more check this out https://www.joshwcomeau.com/animation/a-friendly-introduction-to-spring-physics/
And there is also another good option which is framer motion https://www.framer.com/motion/ which apparently offers more possibilities maybe out of the box (I personally have never tried it before)
For examples you can check their websites they have good examples.
I'm not sure what effect you are trying to generate.
css can be used by itself to generate animations or transitions.
You want to see the effect on each render?
i.e. You want to tie the effect to the react render cycle?
non-memoized values will change on every render
You could use a simple statement like const trigger = {};
Then react to trigger with a useEffect
useEffect(() => { do something }, [trigger]);
finally, visual effect.. apply a class based on state and use setTimeout to remove the state (and therefore the class)
This could be overly involved for exactly what you are trying to achieve but this works for all possible flows based on the question.
Here is one example with div element is moving to according vertical scroll position .
Look carefully.
First, Set the position using useState and define the window.onscroll function.
const [cardTop, setCardTop] = useState(0);
window.onscroll = function() {
if (window.pageYOffset < 30) {
setCardTop(window.pageYOffset + 'px');
}
};
Second, Set the style's top as state variable.
<div className='card t-card' id='tCard' style={{top:`${cardTop}`}}> ...
Congratulations. It probably act exactly.
It's similar to use Jquery or another Javascript, Only use state variable.
Thanks.
I have two questions about anychart.
(1) I have implemented drawing tools like below (except for "Label") with React
and am wondering if it is possible to implement a functionality for "Label" with React,
i.e. let user modify an existing label's text.
https://www.anychart.com/products/anystock/overview/
Drawing Tools And Annotations
(2) If I select (click on) an existing label, it seems getSelectedAnnotation() can't get the selected one.
The following code is in React functional component, probably is this the reason of that?
chart.listen(`annotationSelect`, function (e) {
if (e.annotation.type === `label`) {
console.log(e.annotation) // this prints e.annotaion object
const s = chart.annotations().getSelectedAnnotation()
console.log(s) // this prints "null"
}
})
A1 - you can implement any logic you need to modify the label annotation and UI approach. The library has all the required API. For example, you can listen for keyboard inputs in the hidden text area and apply text to the label. For details, check the live sample.
Simply select the label and start typing the text.
A similar approach is used in the demo app.
A2 - You need to wait until the next round of the JS event loop. For example, with setTimeout with zero time to schedule a task on the next round. Here is the modified sample.
Trying to implement a custom cell framework renderer with React and it seems simple enough. Create the React component, register it with frameworkComponents.
The data that populates rowData is coming from my redux store. The custom cell renderer is a functional react component.
The issue is that because I'm using a frameworkComponent - a React component in my case - as a cellRenderer, it seems that any change in the data for the grid the I'm getting via useSelector(selectorForMyData) causes a re-render of my frameworkComponent, which on the browser, looks like a random, annoying flicker. The application is heavily wired into redux
Two questions:
1 - How come when I natively use ag grid to render this cell using a AgGridColumn without any custom cell renderers, it doesn't cause this re-rendering behavior on the same store changes? I have a click event bound to the main page that toggles a flag to false (in the case a snackbar alert was open).
2 - Is there any way to get around this? I've tried wrapping my return statement in the framework cell renderer component with a useMemo with the params as a dependency, but that doesn't seem to work. Also tried making a render function via useCallback with the same idea as useMemo and that doesn't help either :/
Thanks
pseudo-code for situation:
App.tsx:
<MyAgGrid/>
MyAgrid.tsx:
const MyAgGrid = () => {
const data = useSelector(selectorForData);
return (
<AgGridReact
rowData={data}
frameworkComponents={
{'myCustomRenderer': CustomRendererComponent}
}
columnDefs={
['field': 'aField', cellRenderer: 'myCustomRenderer']
} />
);
};
CustomCellRendererComponent.tsx:
const CustomCellRendererComponent = (params) => {
console.log("params", params) //logs on every redux store update
return (
<div>HELLO WORLD</div>
);
};
The cells that are rendered via the CustomCellRendererComponent are re-rendered on any redux store change. I'm guessing it's due to useSelector causing the re-render on store changes, which is expected, but then it doesn't answer my first question.
EDIT:
I went "function as class" route shown here ("MyCellRenderer") and so far am not seeing the re-rendering issue, so I will stick with that for now even though it's god ugly. This leads me to believe my issue is trying to fit a React component/hooks, with its lifecycle nuances, as a cell renderer is causing problems. Still, I feel like there should be a way to prevent the behavior of constant re-rendering, otherwise it's a pretty useless feature
EDIT pt 2:
I dug deeper and while I haven't found an out of the box solution for it, I added the reselect library to memoize some of my selectors. The selector I use to get rowData is now memoized, and I'm no longer seeing this issue. Will mark as answer in a few days if no one provides a better, ideally out of the box (with redux or ag grid), solution for it.
As I stated in one of my edits. I figured it out, kind of.
I added the library reselect to the application, and it fixes the symptoms and I'm content with it going forward. It allows you to memoize your selectors, so that it only registers changes/"fires" (leading to a re-render) only if the any of the dependency selectors you hook it into changes/fires, so now, my grid doesn't "flicker" until the actual row data changes since my selectorForRowData is memoized!
I'm still uncertain why prior to using a frameworkComponent (React Component) as a cell renderer I wasn't seeing the symptoms, but I'm happy to just assume Ag-Grid has a lot of performance tricks that clients may lose when plugging in their own custom functionality, as is the case in a custom cell renderer.
I am trying to hide an element 'GorillaSurfIn' after I click on it.
But also it should fire the 'setShouldGorillaSurfOut' to 'true'. The second part works, but after I added this function:
function hideGorillaSurfIn() {
let element = document.getElementById('gorilla-surf-in');
ReactDOM.findDOMNode(element).style.display =
this.state.isClicked ? 'grid' : 'none';
}
After I click, the code falls apart.
Once I click, the element should be hidden/removed till the next time the App restarts.
Here is the Code Sandbox Link for further explanation.
I am open to any solutions, but also explanations please, as I am still fresh in learning React.
I have changed your code a bit to make it work. You can make further changes according to your need. A few things that I would like to add: -
You should avoid using findDOMNode (in most cases refs can solve your problem) as there are certain drawbacks associated with findDOMNode, such as the react's documentation states "findDOMNode cannot be used with functional components".
I've used refs (forward ref in this case) to make it work.
GorillaSurfIn was called twice, so there were two Gorilla gifs on the screen with same IDs. Not sure if that was the intended behaviour but each element should have unique ID.
Check out the code sandbox.
My code is in React & is quite extensive, so i have provided a codesandbox link: My transactions section of my "under construction" app: https://codesandbox.io/s/agitated-sea-96048. A Chegg expert had given me the code that renders what you'll see above my transaction cards (his ModalEdit, for now, was placed inside of the TransactionsLayout component--this is just so you can visualize, somewhat, of what i'm looking for).
i like the way that the edit does what my question is asking. however, i do not know how to make it work within the framework of my app, because my TransactionCard.jsx file is a function component without state. and, the expert's code is with state.
i have tried putting his code as a separate component & calling to it with the button onClick (line#69 on the TransactionsCard.jsx file). however, this doesn't work because i can't figure out 'how' to convert his code to work within the framework of the transactions' files.