how to make Highcharts React redraw without changing data - javascript

I need highcharts to adapt its width to the parent's width. So if we change the parent's width without changing the data, the chart should resize.
But since the data is the same, it doesn't happen.
It seems to work if we resize the browser's screen, but that's not what we need.
import React, { useState } from 'react';
import { render } from 'react-dom';
import HighchartsReact from 'highcharts-react-official';
import Highcharts from 'highcharts';
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min);
}
const LineChart = () => {
const [width, setWidth] = useState(null);
const [chartOptions, setChartOptions] = useState({
xAxis: {
categories: ['A', 'B', 'C'],
},
series: [{ data: [1, 2, 3] }],
});
const handleWidth = () => {
const w = getRandomIntInclusive(100, 500);
setWidth(w);
};
return (
<div style={{ width }}>
<HighchartsReact highcharts={Highcharts} options={chartOptions} />
<button onClick={handleWidth}>change container width</button>
</div>
);
};
render(<LineChart />, document.getElementById('root'));
Here's a link to a "not working" example: https://stackblitz.com/edit/react-xhcmx4?file=index.js

From Highcharts API:
reflow([e])
Reflows the chart to its container. By default, the chart reflows automatically to its container following a window.resize event, as per
the chart.reflow option. However, there are no reliable events for div
resize, so if the container is resized without a window resize event,
this must be called explicitly.
As a solution, call reflow method after width change.
useEffect(() => {
const chart = chartComponent.current?.chart;
if (chart) chart.reflow(false);
}, [width]);
Live demo: https://stackblitz.com/edit/react-zydh8m?file=index.js
API Reference: https://api.highcharts.com/class-reference/Highcharts.Chart#reflow

If for any reason you can not use the .reflow solution from ppotaczek you can send a resize Event to the window object when resizing the container div.
setTimeout(() => window.dispatchEvent(new Event('resize')));
HighCharts listens to that event and will then repaint itself.
Stackblitz Live Demo

Related

Low quality Images on mobile devices

I am absolutely clueless regarding an issue i am having with blurry images on iOS.
I am working with react, and i am using #use-gesture library (use-gesture.netlify.app) to enable pinch zoom on some small images and videos. It all works like a charm on desktop, but on my mobile device images are always blurry-bad-quality when pinch-zoomed. Videos are not affected by that quality loss. I donĀ“t think it has anything todo with #use-gesture, since ive been using a lightbox component before that and had the same issue.
I've already read every single thread i could find regarding image scale on iOS but wasnt able to find a solution yet.
What i've gathered/tried so far:
Adding to index.html:
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1">
and adding this to the img tag:
resizeMode="contain"
aspectRatio="1.5"
height= "undefined"
width= "undefined"
Does it maybe have something to do with a difference in image rendering on mobile devices, to me it seems like images dimensions are defined onLoad in the size of my thumbnails and original image size/resolution is ignored? idk..
This is the code of a complete re-build of the issue i am having based on a new react project but i still get the same effect
import test from './imgtest.jpg';
import './App.css';
import React, { useRef, useEffect, useState } from 'react';
import { useSpring, animated } from "react-spring";
import { createUseGesture, dragAction, pinchAction } from '#use-gesture/react'
const useGesture = createUseGesture([dragAction, pinchAction])
function App() {
useEffect(() => {
const handler = e => e.preventDefault()
document.addEventListener('gesturestart', handler)
document.addEventListener('gesturechange', handler)
document.addEventListener('gestureend', handler)
return () => {
document.removeEventListener('gesturestart', handler)
document.removeEventListener('gesturechange', handler)
document.removeEventListener('gestureend', handler)
}
}, [])
const ref = React.useRef(null)
const [style, api] = useSpring(() => ({
x: 0,
y: 0,
scale: 1,
rotateZ: 0,
config: {
},
}))
useGesture(
{
onPinch: ({ origin: [ox, oy], first, active, movement: [ms], offset: [s, a], memo }) => {
if (first) {
const { width, height, x, y } = ref.current.getBoundingClientRect()
const tx = ox - (x + width / 2)
const ty = oy - (y + height / 2)
memo = [style.x.get(), style.y.get(), tx, ty]
}
const x = memo[0] - (ms - 1) * memo[2]
const y = memo[1] - (ms - 1) * memo[3]
api.start({ scale: s , offset: active ? x : 0, offset: active ? y : 0 })
return memo
},
},
{
target: ref,
pinch: { scaleBounds: { min: 1, max: 4 }, rubberband: true },
}
)
return (
<div className="App">
<animated.div ref={ref} style={style}>
<img
resizeMode="contain"
aspectRatio="1.5"
height= "undefined"
width= "undefined"
src={test}
className="App-logo"
/>
</animated.div>
</div>
);
}
export default App;
Please can somebody help me with this?
IOS devices have different DPIs than desktops. You need a bigger image to get a clear image.
also check out this link

How to measure the height of a child and pass it to a parent in React?

I want to calculate the height of a component and send it to its parent when the page is loaded and resized.
I'm using the below reusable Hook to successfully measure the height of the div inside the header component. But how do I send the height calculated from useDimensions in the child to its parent component as headHeight?
Measuring Hook
import { useState, useCallback, useEffect } from 'react';
function getDimensionObject(node) {
const rect = node.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
top: 'x' in rect ? rect.x : rect.top,
left: 'y' in rect ? rect.y : rect.left,
x: 'x' in rect ? rect.x : rect.left,
y: 'y' in rect ? rect.y : rect.top,
right: rect.right,
bottom: rect.bottom
};
}
export function useDimensions(data = null, liveMeasure = true) {
const [dimensions, setDimensions] = useState({});
const [node, setNode] = useState(null);
const ref = useCallback(node => {
setNode(node);
}, []);
useEffect(() => {
if (node) {
const measure = () =>
window.requestAnimationFrame(() =>
setDimensions(getDimensionObject(node))
);
measure();
if (liveMeasure) {
window.addEventListener('resize', measure);
window.addEventListener('scroll', measure);
return () => {
window.removeEventListener('resize', measure);
window.removeEventListener('scroll', measure);
};
}
}
}, [node, data]);
return [ref, dimensions, node];
}
Parent
export default function Main(props: { notifications: Notification[] }) {
const { notifications } = props;
const [headHeight, setHeadHeight] = useState(0)
const updateHeadHeight = () => {
setHeadHeight(headHeight)
}
return (
<main>
<Header updateParent={updateHeadHeight}/>
{headHeight}
</main>
)
}
Child
import { useDimensions } from '../../lib/design/measure';
import React, { useState, useLayoutEffect } from 'react';
export default function DefaultHeader(props, data) {
const [
ref,
{ height, width, top, left, x, y, right, bottom }
] = useDimensions(data);
;
return <>
<div ref={ref} className="header">
<h1>Hello!</h1>
</div>
</>
}
Personally, I would call the hook in the Main component and wrap the child component in a forwardRef (check docs here).
See full example here:
Main.tsx
export default function Main(props: { notifications: Notification[] }) {
const { notifications } = props;
const [ref, dimensions] = useDimensions()
return (
<main>
<Header ref={ref}/>
{JSON.stringify(dimensions)}
</main>
)
}
What's done here, we just pass the ref down the tree to the child component and we just show the dimensions (testing purposes).
DefaultHeader.tsx
import { forwardRef } from "react";
const DefaultHeader = forwardRef((_, ref) => {
return (
<>
<div ref={ref} className="header" >
<h1>Hello!</h1>
</div>
</>
);
});
export default DefaultHeader;
Here, we just attach the ref to the container that it has previously been (your example).
See full example on this CodeSandbox.
Let me know if you need more explanations on this.
You could just add a useEffect hook within the DefaultHeader component like this:
useEffect(() => props.updateParent(height), [props.updateParent, height])
This hook should run anytime it detects changes to the height variable or props.updateParent props. Just make sure you are declaring this hook after the useDimensions hook so it doesn't throw an undefined error.
You are seriously overcomplicating this by limiting yourself to the react way of doing things. You can simply use Event Bubbling to achieve what you want. All you need to do is dispatch a Custom Event event on the child element that will bubble up through each ancestor to the window. Then intercept this event in the parent element and react to it.
let m = document.querySelector('main');
let s = document.querySelector('section');
let b = document.querySelector('button');
b.addEventListener('click', event => {
s.style.height = '200px';
});
m.addEventListener('resize', event => {
console.log('new size:', event.detail);
});
new ResizeObserver(entries => {
for (let e of entries) {
s.dispatchEvent(
new CustomEvent('resize', {
detail: e.contentRect,
bubbles: true
})
);
}
}).observe(s);
main { background: green; padding: 1rem; margin-top: 1rem; }
section { background: orange; }
<button> Test </button>
<main>
<section></section>
</main>
Just implement the ResizeObserver block in the child component and the resize Event Listener in the parent component.
This method achieves the desired effect in a decoupled and standards-compliant way. The child element is merely announcing the change to it's state (in the DOM, not React state). Any ancestor element can then act on this behavior as needed or ignore it. The parent element can also receive this resize event from any descendant element regardless of how deeply nested it may be. It also does not care which descendant element has changed. If you have multiple descendants that could change, this will trigger for any one of them.
Additionally, the resize event here is NOT tied to the window. ANY changes to the child element's size will trigger the event listener on the parent.

"window.innerWidth < 768" in Next.js with Framer Motion

I would like to control when an animation (Framer Motion) can run using window.innerWidth, but in Next.js I get the following error message:
ReferenceError: window is not defined
This is a simplified version of my component named ValuesSection.jsx:
import React, { useEffect } from "react";
import { useInView } from "react-intersection-observer";
import { motion, useAnimation } from "framer-motion";
export default function ValuesSection() {
const controls = useAnimation();
const [ref, inView] = useInView();
const MobileView = {};
const isMobile = window.innerWidth < 768;
if (!isMobile) {
MobileView = {
visible: { y: 0, scale: 1 },
hidden: { y: 250, scale: 0 },
};
}
useEffect(() => {
if (inView) {
controls.start("visible");
}
}, [controls, inView]);
return (
<>
<motion.div
key={value.name}
ref={ref}
animate={controls}
initial="hidden"
variants={MobileView}
>Some content</motion.div>
</>
Can you help me understand what I'm doing wrong? And if you are able to provide me with a working example, it would be great and appreciated.
Best way of using screen size in next.js projects is that you can instead use a Hook from materiial ui that represents true or false when screen width is bigger or smaller than defined value and in my opinion it's better than the window because it has a lot of options that you can use here is what you should do step by step
first install material ui if you didn't already
// with npm
npm install #material-ui/core
// with yarn
yarn add #material-ui/core
then import and define it inside of your component
import { useMediaQuery } from "#material-ui/core";
export default function ValuesSection() {
const IsTabletOrPhone = useMediaQuery("(max-width:1024px)");
now if you screen size is bigger than 1024px it returns false, otherwise it returns true
so you can use it
if (!IsTabletOrPhone) {
MobileView = {
visible: { y: 0, scale: 1 },
hidden: { y: 250, scale: 0 },
};
}
Update:
Maybe it's because i assigned it with a uppercase letter, you can try changing the name to isTabletOrPhone with lowercase, if that didn't work try to change it to let instead
let isTabletOrPhone = useMediaQuery("(max-width:1024px)");
In Next.js pages are prerendered on the server. And on the server there's no window. You could just check if the window exists before to access it
const isMobile = global.window && window.innerWidth < 768;
Note that the user may rotate the phone or resize the window, so you should add (and clean up) a resize event handler into an useEffect

Dragging not working for react-use-gesture

For some reason, I just cannot get the most basic example of react-use-gesture to work. What I am trying to do is just have a square follow my mouse location when you drag it. I copy-pasted the example from their documentation multiple times (https://github.com/pmndrs/react-use-gesture) and still cannot get it to work. I just don't understand it anymore. I created a stackblitz to show you my code. What am I still doing wrong?
Stackblitz with code: https://stackblitz.com/edit/react-mg2u8p?file=src/Square.js
I will also include the most relevant code here:
import React from "react";
import { useSpring, animated } from "react-spring";
import { useDrag } from "react-use-gesture";
const Square = () => {
const [{ x, y }, set] = useSpring(() => ({ x: 0, y: 0 }));
const bind = useDrag(({ down, movement: [mx, my] }) => {
set({ x: down ? mx : 0, y: down ? my : 0 });
});
return (
<animated.div
{...bind()}
className="Square"
style={{ x, y, touchAction: "none" }}
/>
);
};
export default Square;
It's a version problem.
Your example uses code for react-spring version 9+, but the version of react-spring you're using in the example is 8.0.27.
The example the documentation gives for version 8 is this:
import { useSpring, animated } from 'react-spring'
import { useDrag } from 'react-use-gesture'
function PullRelease() {
const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] }))
// Set the drag hook and define component movement based on gesture data
const bind = useDrag(({ down, movement }) => {
set({ xy: down ? movement : [0, 0] })
})
// Bind it to a component
return (
<animated.div
{...bind()}
style={{
transform: xy.interpolate((x, y) => `translate3d(${x}px, ${y}px, 0)`),
}}
/>
)
}
So in your case you would only need to change PullRelease to Square and add className="Square" to the animated.div like you had it in your question.
For both the documentation on the v8 and v9 implementation of this using React UseGesture see this.
If you want to use the v9 version, you currently need to install react-spring#next according to the documentation (see previous link).

How to get numeric value from Reanmited Value?

I create a simple animation using react native reanimated but I can't access the numeric value of Reanimated Value
I using victory native pie chart, and I want to make a simple effect that pie angle goes from 0 to 360 but I've tried react-native animated API it works well with add listener but I want use reanimated for performance issue
the animation effect that I'm looking for that the chart starts from 0 to 360
run correctly with react-native Animated API:
const Chart = props => {
const { data, width, height } = props;
const endAngleAnimatedValue = new Value(0);
const [endAngle, setEndAngle] = useState(0);
const runTiming = Animated.timing(endAngleAnimatedValue, {
duration: 1000,
to: 360
});
useEffect(() => {
endAngleAnimatedValue.addListener(_endAngle => setEndAngle(_endAngle));
runTiming.start();
return () => {
endAngleAnimatedValue.removeAllListeners();
};
}, [endAngleAnimatedValue]);
return (
<VictoryPie
data={data}
width={width}
height={height}
padding={0}
startAngle={0}
endAngle={endAngle}
style={{
data: {
fill: ({ datum }) => datum.fill,
fillOpacity: 0.7
}
}}
/>
);
};
How I can achieve the desired output with reanimated?
I totally forgot about this question,
Here we can have two implementations if using react-native-reanimated v1:
1. using react-native-svg and some helpers from react-native-redash
2. using `const AnimatedPie = Animated.createAnimatedComponent(VictoryPie)` then passing `endAngleAnimatedValue` as a prop.
In reanimated v2:
You can use explicitly .value to animated sharedValues, derivedValues, but also you have to createAnimatedComponent

Categories

Resources