Hello everyone we are building an app for a company. My task is to create an image crop feature for profile pictures. I am using the react-easy-crop library. I have implemented nearly all the features the only missing thing is zooming in to a certain pixel. Our company doesn't want users to be able to zoom in less than 600 X 600 pixels. What I meany by that is when users zoom in the image size can't be lower than 600 x 600 pixels. here is my code structure (Be aware this not the full code I have reduced it for complexity issues)
import React, { useEffect, useRef, useState, FC, useCallback, SetStateAction } from 'react';
import Cropper from 'react-easy-crop';
import Resizer from 'react-image-file-resizer';
import { Text, Button, DragAndDrop } from '#shared/components';
import { Modal } from '#shared/components/Modal/lazy';
import { getOrientation } from 'get-orientation';
import { COLOR_NAMES } from '#src/settings/colors.constants';
import { Icon } from '#src/shared/components/icon';
import { Centered } from '#src/shared/components/layout/centered';
import { useDispatch } from 'react-redux';
import { Flex } from 'rebass';
import { Dispatch } from 'redux';
import { ZoomAndApplyContainer, CropContainer } from '#app/shared/components/crop-image/styled';
import { FileValue } from '#shared/components/upload-single-document/interfaces';
import { UploadSingleImageProps } from '#app/shared/components/upload-single-image/interfaces';
import { CoverPicEditComponent, ImageUploadContainer, PicEditComponent } from '#app/shared/components/upload-single-image/styled';
import { MODAL_NAMES, MODAL_TYPES } from '#app/shared/store/constants/modal.constants';
import { ShowModalAC, HideModalAC } from '#app/shared/store/actions/modal.actions';
import { NumberOfBytesInOneMB, TOASTER_APPEARANCES } from '#app/shared/constants';
import { SetToastsActionCreator } from '#app/shared/store/actions/toast.actions';
import { validateFileType } from '#app/utils/validations';
import { PRIMARY } from '../button/button.constants';
import { FormikFieldErrorMessage } from '../formik-field/styled';
const readFile: any = (file: any) =>
new Promise((resolve: any) => {
const reader: any = new FileReader();
reader.addEventListener('load', () => resolve(reader.result), false);
reader.readAsDataURL(file);
});
const createImage: any = (url: any) =>
new Promise((resolve: any, reject: any) => {
const image: any = new Image();
image.addEventListener('load', () => resolve(image));
image.addEventListener('error', (error: any) => reject(error));
image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
image.src = url;
});
const getRadianAngle: any = (degreeValue: any) => (degreeValue * Math.PI) / 180;
const ORIENTATION_TO_ANGLE: any = {
3: 180,
6: 90,
8: -90,
};
/**
* This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
* #param {File} image - Image File url
* #param {Object} pixelCrop - pixelCrop Object provided by react-easy-crop
* #param {number} rotation - optional rotation parameter
*/
const getCroppedImg: any = async (imageSrc: any, pixelCrop: any, rotation: any = 0) => {
const image: any = await createImage(imageSrc);
const canvas: any = document.createElement('canvas');
const ctx: any = canvas.getContext('2d');
const maxSize: any = Math.max(image.width, image.height);
const safeArea: any = 2 * ((maxSize / 2) * Math.sqrt(2));
// set each dimensions to double largest dimension to allow for a safe area for the
// image to rotate in without being clipped by canvas context
canvas.width = safeArea;
canvas.height = safeArea;
// translate canvas context to a central location on image to allow rotating around the center.
ctx.translate(safeArea / 2, safeArea / 2);
ctx.rotate(getRadianAngle(rotation));
ctx.translate(-safeArea / 2, -safeArea / 2);
// draw rotated image and store data.
ctx.drawImage(image, safeArea / 2 - image.width * 0.5, safeArea / 2 - image.height * 0.5);
const data: any = ctx.getImageData(0, 0, safeArea, safeArea);
// set canvas width to final desired crop size - this will clear existing context
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
// paste generated rotate image with correct offsets for x,y crop values.
ctx.putImageData(data, Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x), Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y));
// As Base64 string
// return canvas.toDataURL('image/jpeg');
// As a blob
return new Promise((resolve: any) => {
canvas.toBlob((file: any) => {
resolve(file);
}, 'image/jpeg');
});
};
const getRotatedImage: any = async (imageSrc: any, rotation: number = 0) => {
const image: any = await createImage(imageSrc);
const canvas: any = document.createElement('canvas');
const ctx: any = canvas.getContext('2d');
const orientationChanged: boolean = rotation === 90 || rotation === -90 || rotation === 270 || rotation === -270;
if (orientationChanged) {
canvas.width = image.height;
canvas.height = image.width;
} else {
canvas.width = image.width;
canvas.height = image.height;
}
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate((rotation * Math.PI) / 180);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
return new Promise((resolve: any) => {
canvas.toBlob((file: any) => {
resolve(URL.createObjectURL(file));
}, 'image/jpeg');
});
};
export const UploadSingleImage: FC<UploadSingleImageProps> = ({
setFieldValue,
setFieldTouched,
name,
extensionName,
width = '600',
height = '600',
errorMessage,
isDisabled,
fileId,
extension,
title,
validationRules,
isCoverPhoto,
customContainerCss,
onChange,
editIconName = 'edit',
onUploaded,
}: UploadSingleImageProps) => {
const [value, setValue]: [FileValue, React.Dispatch<SetStateAction<FileValue>>] = useState(null);
const [imgSrc, setImgSrc]: any = useState(null);
const [maxZoom, setMaxZoom]: any = useState(1);
const [rotation, setRotation]: any = useState(0);
const [crop, setCrop]: any = useState({ x: 0, y: 0 });
const [imageSendingFail, setImageSendingFail]: [boolean, React.Dispatch<SetStateAction<boolean>>] = useState(true);
const [zoom, setZoom]: any = useState(1);
const [croppedAreaPixels, setCroppedAreaPixels]: any = useState(null);
const showCroppedImage: any = useCallback(async () => {
try {
const cropedImage: any = await getCroppedImg(imgSrc, croppedAreaPixels, rotation);
Resizer.imageFileResizer(
cropedImage,
600,
600,
'JPEG',
100,
0,
(file: any) => {
onChange(file, setValue);
dispatch(HideModalAC(MODAL_NAMES.IMAGE_CROP_MODAL));
setImgSrc(null);
},
'blob'
);
} catch (e) {
console.error(e);
}
}, [imgSrc, croppedAreaPixels, rotation]);
const imageInput: React.MutableRefObject<HTMLInputElement> = useRef();
const dispatch: Dispatch = useDispatch();
const toast: any = useCallback((toasts: any) => dispatch(SetToastsActionCreator(toasts)), [dispatch]);
const onCropComplete: any = useCallback((croppedArea: any, croppedAreaPixel: any) => {
setCroppedAreaPixels(croppedAreaPixel);
}, []);
const handleFileDrop: any = (e: any) => {
const files: any = e.dataTransfer.files;
if (files && files.length === 1) {
validateImage(files[0]);
}
};
const onFileChange: any = async (e: any) => {
if (e.target.files && e.target.files.length === 1) {
const file: any = e.target.files[0];
validateImage(file);
}
};
const onClick: any = (e: any) => {
setZoom(1);
e.target.value = '';
};
const validateImage: (file: File) => void = async (file: any) => {
setImageSendingFail(false);
// const imageDataUrl: any = await readFile(file);
// setImgSrc(imageDataUrl);
if (setFieldTouched) {
setFieldTouched(name);
}
if (validateFileType(toast, validationRules?.fileTypes, file)) {
let imageDataUrl: any = await readFile(file);
const img: any = createImage(imageDataUrl);
if (!validationRules || validateImg(toast, validationRules, img, file)) {
const orientation: any = await getOrientation(file);
const rotationPortion: any = ORIENTATION_TO_ANGLE[orientation];
if (rotation) {
imageDataUrl = await getRotatedImage(imageDataUrl, rotationPortion);
}
setImgSrc(imageDataUrl);
dispatch(ShowModalAC(MODAL_NAMES.IMAGE_CROP_MODAL));
} else {
imageInput.current.value = '';
setImageSendingFail(true);
}
}
};
useEffect(() => {
if (fileId && extension) {
setValue({ fileId, extension });
}
}, [fileId, extension]);
useEffect(() => {
if (setFieldValue) {
setFieldValue(name, value?.fileId);
setFieldValue(extensionName, value?.extension);
}
if (onUploaded && value?.fileId) {
onUploaded(name, value);
}
}, [value?.fileId, value?.extension]);
return (
<Flex justifyContent={'center'} alignItems={'center'} flexDirection={'column'} css={{ height: '100%' }} name={name}>
{imgSrc && (
<Modal bodyCss={{ padding: 0 }} bodyHeight='90%' width={'70%'} height={'85%'} borderRadius={'4px'} modalId={MODAL_NAMES.IMAGE_CROP_MODAL} headingText={'Resmi Düzenle'} type={MODAL_TYPES.NORMAL}>
<CropContainer>
<div style={{ width: '80%', height: '70%', position: 'relative' }}>
<Cropper
image={imgSrc}
crop={crop}
zoom={zoom}
rotation={rotation}
aspect={1 / 1}
onCropChange={setCrop}
onCropComplete={onCropComplete}
onZoomChange={setZoom}
restrictPosition={false}
onRotationChange={setRotation}
minZoom={0.5}
maxZoom={maxZoom}
onMediaLoaded={(imageSize: any) => {
if (imageSize.naturalWidth > 600) {
setMaxZoom(600 / Math.max(imageSize.height, imageSize.width));
} else {
setMaxZoom(1);
}
console.log(imageSize);
}}
/>
</div>
<ZoomAndApplyContainer>
<input type='range' value={zoom} min={0.5} max={maxZoom} step={0.05} onChange={(e: any) => setZoom(e.target.value)} />
<input type='range' value={rotation} min={0} max={360} step={10} onChange={(e: any) => setRotation(e.target.value)} />
<Button button={PRIMARY} onClick={showCroppedImage}>
Upload
</Button>
</ZoomAndApplyContainer>
</CropContainer>
</Modal>
)}
<DragAndDrop handleDrop={handleFileDrop}>
<ImageUploadContainer customContainerCss={customContainerCss} url={fileId && extension && `${fileId}${extension}`} width={width} height={height} errorMessage={errorMessage}>
{value?.fileId && value?.extension && isCoverPhoto && !isDisabled && (
<CoverPicEditComponent>
<label htmlFor={name}>
<Icon margin={'auto'} name={'upload-white'} />
<Text color={COLOR_NAMES.REMAX_WHITE} customWeight={1}>
Yeni kapak fotoğrafı yükle
</Text>
</label>
</CoverPicEditComponent>
)}
{value?.fileId && value?.extension && !isCoverPhoto && !isDisabled && (
<PicEditComponent className='edit-icon-container' htmlFor={name}>
<Icon name={editIconName} />
</PicEditComponent>
)}
{!(value?.fileId && value?.extension) && (
<Centered css={{ flexDirection: 'column' }}>
<Text customSize={[2, 3, 3]} lineHeight={1} customWeight={1} color={COLOR_NAMES.REMAX_TEXT_GREY_7f7f7f} css={{ textAlign: 'center', width: '145px', paddingBottom: '12px' }}>
{title}
</Text>
<label htmlFor={name}>
<Text customSize={[2, 3, 3]} customWeight={1} color={COLOR_NAMES.REMAX_BLUE_SELECTED_1451EF} textDecoration={'underline'}>
Dosya Seç
</Text>
</label>
</Centered>
)}
<input id={name} ref={imageInput} name={name} type='file' onChange={onFileChange} onClick={onClick} />
</ImageUploadContainer>
</DragAndDrop>
{/* Eğer resim yok ve upload işlemi fail olduysa hata mesajı basılsın */}
{!value?.fileId && imageSendingFail && errorMessage && <FormikFieldErrorMessage elipsis={true}>{errorMessage}</FormikFieldErrorMessage>}
</Flex>
);
};
<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>
here is my solution for the problem: this callback inside the cropper
onMediaLoaded={(imageSize: any) => {
if (imageSize.naturalWidth > 600) {
setMaxZoom(600 / Math.max(imageSize.height, imageSize.width));
} else {
setMaxZoom(1);
}
}}
<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>
but this code doesn't ensure that I have a max zoom restriction that produces 600 x 600 in the end. (It works for some images but some others not. I would really appreciate any suggestion thanks in advance.
const onCropComplete: any = useCallback((croppedArea: any, croppedAreaPixel: any) => {
if(croppedAreaPixel.width < requiredWidth || croppedAreaPixel.height< requiredHeight){
/** do something here to disallow zoom
* this is a workaround
*/
}
setCroppedAreaPixels(croppedAreaPixel);
}, []);
Related
I have been trying to create a little animation on canvas using React.js.
I expect the red block inside the canvas to go up and drop back down when I hit my spacebar by decreasing the velocity so that it will execute the if statement I have got and pull it back down, but so far it's not working. I can see the velocity value did change and it did execute the if statement when I console.log, it doesn't seem like showing on the animation.
const { useRef, useEffect, useState } = React;
const Canvas = () => {
const { innerWidth: innerwidth, innerHeight: innerheight } = window;
const contextRef = useRef()
const [position, setPosition] = useState({ x: 100, y: 100 })
const [size, setSize] = useState({ width: 30, height: 30 })
const [velocity, setVelocity] = useState({ x: 0, y: 0 })
const gravity = 4.5
const draw = (context) => {
context.fillStyle = 'red'
context.fillRect(position.x, position.y, size.width, size.height);
};
const update = (context, canvas) => {
draw(context)
setPosition({ x:position.x, y:position.y += velocity.y })
if (position.y + size.height +velocity.y <= canvas.height) {
setVelocity({ x:velocity.x, y: velocity.y += gravity })
} else {
setVelocity({ x:velocity.x, y: velocity.y = 0 })
}
}
const animate = (context, width, height, canvas) => {
requestAnimationFrame(() => {
animate(context, width, height, canvas )
})
context.clearRect(0, 0, width, height)
update(context, canvas)
}
useEffect(() => {
const canvas = contextRef.current;
const context = canvas.getContext("2d");
canvas.width = innerwidth - 2
canvas.height = innerheight - 2
animate(context, canvas.width, canvas.height, canvas)
}, []);
const handleKeydown = (e) => {
switch(e.keyCode) {
case 37:
console.log(velocity)
return "left";
case 39:
return "right";
case 32:
setVelocity({ x:velocity.x, y:velocity.y -= 50 });
console.log(velocity)
break
default:
console.log("keyCode is " + e.keyCode)
return 'default';
}
}
return (
<canvas ref={contextRef} tabIndex={-1} onKeyDown={(e) => {
handleKeydown(e)
}} />
);
};
const App = () => <Canvas />;
ReactDOM
.createRoot(document.getElementById("root"))
.render(<App />);
html, body { width: 100%; height: 100%; margin: 0; }
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
What can I try next?
You should wrap some of your function in a useCallback so that they are not redefined on each render.
Also, I would use key instead of keyCode.
const { useCallback, useEffect, useState, useRef } = React;
const gravity = 4.5;
const Canvas = () => {
const { innerWidth: innerwidth, innerHeight: innerheight } = window;
const canvasRef = useRef();
const [position, setPosition] = useState({ x: 100, y: 100 });
const [size, setSize] = useState({ width: 30, height: 30 });
const [velocity, setVelocity] = useState({ x: 0, y: 0 });
const draw = useCallback((context) => {
context.fillStyle = 'red';
context.fillRect(position.x, position.y, size.width, size.height);
}, [position, size]);
const update = useCallback((context, canvas) => {
draw(context)
setPosition({ x: position.x, y: position.y += velocity.y })
if (position.y + size.height + velocity.y <= canvas.height) {
setVelocity({ x: velocity.x, y: velocity.y += gravity })
} else {
setVelocity({ x:velocity.x, y: velocity.y = 0 })
}
}, [position, size, velocity]);
const animate = (context, width, height, canvas) => {
requestAnimationFrame(() => {
animate(context, width, height, canvas);
});
context.clearRect(0, 0, width, height);
update(context, canvas);
};
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
canvas.width= innerwidth - 10;
canvas.height= innerheight - 10;
animate(context, canvas.width, canvas.height, canvas)
}, []);
const handleKeyDown = useCallback(({ key }) => {
switch (key) {
case 'ArrowLeft':
break;
case 'ArrowRight':
break;
case ' ': // Spacebar
setVelocity({ x: velocity.x, y: velocity.y -= 50 });
console.log(velocity)
break
default:
console.log(`Unknown key: ${key}`);
}
}, []);
return (
<canvas
ref={canvasRef}
tabIndex={-1}
onKeyDown={handleKeyDown}
/>
);
};
const App = () => <Canvas />;
ReactDOM
.createRoot(document.getElementById("root"))
.render(<App />);
html, body, #root { width: 100%; height: 100%; margin: 0; }
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
I am making an app using react-native-expo.
We tried to recognize objects in conjunction with the camera with the trained model.
In order to use the existing Python code in react-native-expo, I tried to port the code to fit js.
But it wasn't as easy as I thought. please tell me how to solve it.
python
while True:
# READ IMAGE
success, imgOrignal = cap.read()
img = np.asarray(imgOrignal)
img = cv2.resize(img, (32, 32))
img = preprocessing(img)
cv2.imshow("Processed Image", img)
img = img.reshape(1, 32, 32, 1)
cv2.putText(imgOrignal, "CLASS: " , (20, 35), font, 0.75, (0, 0, 255), 2, cv2.LINE_AA)
cv2.putText(imgOrignal, "PROBABILITY: ", (20, 75), font, 0.75, (0, 0, 255), 2, cv2.LINE_AA)
predictions = model.predict(img)
classIndex = np.argmax(predictions[0])
probabilityValue =np.amax(predictions)
if probabilityValue > threshold:
cv2.putText(imgOrignal,str(classIndex)+" "+str(getCalssName(classIndex)), (120, 35), font, 0.75, (0, 0, 255), 2, cv2.LINE_AA)
cv2.putText(imgOrignal, str(round(probabilityValue*100,2) )+"%", (180, 75), font, 0.75, (0, 0, 255), 2, cv2.LINE_AA)
cv2.imshow("Result", imgOrignal)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
react-native-expo
/* eslint-disable react-hooks/exhaustive-deps */
import React, { createRef, useCallback, useEffect, useState } from "react";
import { Platform, SafeAreaView, StyleSheet } from "react-native";
import * as tf from "#tensorflow/tfjs";
import {
bundleResourceIO,
cameraWithTensors,
} from "#tensorflow/tfjs-react-native";
import { Camera, CameraType } from "expo-camera";
const CAMERA_SIZE = { height: 640, width: 480 };
const App = () => {
const [signDetector, setSignDetector] = useState<tf.LayersModel>();
const TensorCamera = cameraWithTensors(Camera);
const tensorCameraRef = createRef<any>();
const textureDims =
Platform.OS === "ios"
? { height: 1920, width: 1080 }
: { height: 1200, width: 1600 };
useEffect(() => {
loadModel();
}, []);
const loadModel = useCallback(async () => {
const isAllowedCamera = fn_requestPermisison();
if (!isAllowedCamera) return;
console.log("[+] Application started");
//Wait for tensorflow module to be ready
await tf.ready();
console.log("[+] Loading custom mask detection model");
//Replce model.json and group1-shard.bin with your own custom model
const modelJson = await require("./assets/AIDatas/model.json");
const modelWeight = await require("./assets/AIDatas/group1-shard1of1.bin");
const newSignDetector = await tf.loadLayersModel(
bundleResourceIO(modelJson, modelWeight)
);
console.log("[+] Loading pre-trained face detection model");
console.log("summary", signDetector?.summary());
setSignDetector(newSignDetector);
console.log("[+] Model Loaded");
}, []);
const fn_estimateBlazeFace = async (tensorImage: any) => {
if (signDetector) {
const predictions: any = await signDetector.predict(tensorImage, {
batchSize: 3,
});
console.log(`[+] [${predictions}]`);
}
return true;
};
const fn_onReadyTensorCamera = async (
images: IterableIterator<tf.Tensor3D>
) => {
const loop = async () => {
if (signDetector !== undefined) {
const nextImageTensor = tf.image.resizeBilinear(
images.next().value,
[32, 32],
true
);
const expandedImageTensor = tf.expandDims(nextImageTensor, 0);
console.log("expandedImageTensor", expandedImageTensor);
await fn_estimateBlazeFace(expandedImageTensor)
.then((isEstimate) => {
console.log("result", isEstimate);
})
.catch((error) => {
console.log(error);
});
setTimeout(() => {
requestAnimationFrame(loop);
}, 20000);
}
};
loop();
};
const fn_requestPermisison = async () => {
const { status } = await Camera.requestCameraPermissionsAsync();
if (status !== "granted") {
console.log("The camera's permission request was not approved.");
return false;
}
return true;
};
return (
<SafeAreaView style={styles.container}>
<TensorCamera
ref={tensorCameraRef}
style={styles.camera}
type={CameraType.back}
cameraTextureHeight={textureDims.height}
cameraTextureWidth={textureDims.width}
resizeHeight={CAMERA_SIZE.height}
resizeWidth={CAMERA_SIZE.width}
resizeDepth={3}
autorender={true}
useCustomShadersToResize={false}
onReady={fn_onReadyTensorCamera}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
camera: {
position: "absolute",
width: CAMERA_SIZE.width,
height: CAMERA_SIZE.height,
},
});
export default App;
;
I am trying to create a custom range slider, and to get the value of the translationX and pass it as a prop and/or into my redux, however this has seemed impossible, as it does not update, even when I use useState.
Has any one experienced this issue in the past.
Below is my current code
const CustomSlider = ({ onPressOut, testData }) => {
const translateX = useSharedValue(SCREEN_WIDTH / 2);
const context = useSharedValue({ x: SCREEN_WIDTH / 2 });
console.log(testData);
const handleGesture = Gesture.Pan()
.onStart(() => {
context.value = { x: translateX.value };
})
.onUpdate(event => {
translateX.value = event.translationX + context.value.x;
if (translateX.value < SCREEN_WIDTH / 2) {
translateX.value = Math.max(translateX.value, MIN_TRANSLATE_X);
} else {
translateX.value = Math.min(translateX.value, MAX_TRANSLATE_X);
}
testData(translateX.value);
});
const rBottomSheetStyle = useAnimatedStyle(() => {
return {
left: translateX.value,
};
});
return (
<View style={styles.container}>
<View style={styles.negativeLineStyle} />
<View style={styles.positiveLineStyle} />
<GestureDetector gesture={handleGesture}>
<AnimatedTouchableOpacity
activeOpacity={1}
onPressOut={onPressOut}
style={[rBottomSheetStyle, styles.rangeSliderContainer]}>
<LinearGradient
colors={[SECONDARY, PRIMARY]}
start={[1, 0.9]}
end={[1, 0]}
style={styles.rangeSliderStyle}
/>
</AnimatedTouchableOpacity>
</GestureDetector>
</View>
);
};
I have a grid with data, and when I click on a draggable icon I want to take every selected element and store it inside an array. Currently, my code works, but for some reason currently, not all selected elements are saved in the array, some get stored others not in a random way. Why is this occurring and how can I push each item once its selected in the array? Here is my code:
import * as React from 'react';
import { ReorderContext } from './main';
import { useDraggable, useDroppable } from '#progress/kendo-react-common';
export const DraggableRow = (props) => {
const [dropped, setDropped] = React.useState(false);
const [dragged, setDragged] = React.useState(false);
const [direction, setDirection] = React.useState(null);
const [selectedItems] = React.useState([]);
const [initial, setInitial] = React.useState({
x: 0,
y: 0,
});
const { dragStart, reorder } = React.useContext(ReorderContext);
const element = React.useRef(null);
const handlePress = (event) => {
if (event.ctrlKey == true) {
element.current.style.color = 'red';
selectedItems.push(props.dataItem);
console.log(selectedItems);
}
setInitial({
x: event.clientX - event.offsetX,
y: event.clientY - event.offsetY,
});
};
const handleDragStart = (event) => {
if (
!event.originalEvent.target ||
!event.originalEvent.target.dataset.dragHandle
) {
return;
}
setDragged(true);
dragStart(props.dataItem);
};
const handleDrag = (event) => {
if (!element.current || !dragged) {
return;
}
element.current.style.transform = `translateY(${
event.clientY - initial.y + event.scrollY
}px)`;
};
const handleDragEnd = () => {
setDragged(false);
setDropped(false);
setInitial({
x: 0,
y: 0,
});
};
const handleRelease = () => {
if (!element.current) {
return;
}
element.current.style.transform = null;
};
const handleDragEnter = () => {
setDropped(true);
setDirection(null);
};
const handleDragOver = (event) => {
if (!element.current) {
return;
}
const rect = element.current.getBoundingClientRect();
setDirection(
rect.top + rect.height / 2 <= event.pageY ? 'after' : 'before'
);
};
const handleDragLeave = () => {
setDropped(false);
setDirection(null);
};
const handleDrop = () => {
reorder(props.dataItem, direction);
setDropped(false);
setDirection(null);
};
useDraggable(
element,
{
onPress: handlePress,
onDragStart: handleDragStart,
onDrag: handleDrag,
onDragEnd: handleDragEnd,
onRelease: handleRelease,
},
{
autoScroll: dragged,
}
);
useDroppable(element, {
onDragEnter: handleDragEnter,
onDragOver: handleDragOver,
onDragLeave: handleDragLeave,
onDrop: handleDrop,
});
return (
<React.Fragment>
{dropped && direction === 'before' && (
<tr
style={{
outlineStyle: 'solid',
outlineWidth: 1,
outlineColor: 'red',
}}
/>
)}
<tr
{...props.elementProps}
ref={element}
style={{
backgroundColor: '#fff',
userSelect: 'none',
pointerEvents: dragged ? 'none' : undefined,
opacity: dragged ? '0.8' : undefined,
}}
/>
{dropped && direction === 'after' && (
<tr
style={{
outlineStyle: 'solid',
outlineWidth: 1,
outlineColor: 'red',
}}
/>
)}
</React.Fragment>
);
};
and here is a runnable example:
https://stackblitz.com/edit/react-cv2hnu-euptwm?file=app/draggable-row.jsx
I am doing the d3 azimuthal equal-area projection in react-native, i used this example for this. its working fine but im updating rotate values using panGestureHandler this is also working but it's not smooth and it's take time to update map.
this the repo of this.
this is the code where i update rotate values:
const countryPaths = useMemo(() => {
const clipAngle = 150;
const projection = d3
.geoAzimuthalEqualArea()
// .rotate([0, -90])
.rotate([rotateX, rotateY])
.fitSize([mapExtent, mapExtent], {
type: 'FeatureCollection',
features: COUNTRIES,
})
.clipAngle(clipAngle)
.translate([dimensions.width / 2, mapExtent / 2]);
const geoPath = d3.geoPath().projection(projection);
const windowPaths = COUNTRIES.map(geoPath);
return windowPaths;
}, [dimensions, rotateX, rotateY]);
here is my complete code
App.js
import React, {useState, useMemo, useEffect, useRef} from 'react';
import {
StyleSheet,
View,
Dimensions,
Animated,
PanResponder,
Text,
SafeAreaView,
} from 'react-native';
import Map from './components/Map';
import COLORS from './constants/Colors';
import movingAverage from './functions/movingAverage';
import * as d3 from 'd3';
import covidData_raw from './assets/data/who_data.json';
export default function App(props) {
const dimensions = Dimensions.get('window');
const [stat, setStat] = useState('avg_confirmed');
const [date, setDate] = useState('2020-04-24');
//Data Manipulation
const covidData = useMemo(() => {
const countriesAsArray = Object.keys(covidData_raw).map((key) => ({
name: key,
data: covidData_raw[key],
}));
const windowSize = 7;
const countriesWithAvg = countriesAsArray.map((country) => ({
name: country.name,
data: [...movingAverage(country.data, windowSize)],
}));
const onlyCountriesWithData = countriesWithAvg.filter(
(country) => country.data.findIndex((d, _) => d[stat] >= 10) != -1,
);
return onlyCountriesWithData;
}, []);
const maxY = useMemo(() => {
return d3.max(covidData, (country) => d3.max(country.data, (d) => d[stat]));
}, [stat]);
const colorize = useMemo(() => {
const colorScale = d3
.scaleSequentialSymlog(d3.interpolateReds)
.domain([0, maxY]);
return colorScale;
});
return (
<SafeAreaView>
<View>
<Map
dimensions={dimensions}
data={covidData}
date={date}
colorize={colorize}
stat={stat}
/>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.primary,
alignItems: 'center',
justifyContent: 'center',
},
rotateView: {
width: 300,
height: 300,
backgroundColor: 'black',
shadowOpacity: 0.2,
},
});
map.js
import React, {useMemo, useState, useEffect} from 'react';
import {StyleSheet, View, Animated, PanResponder} from 'react-native';
//LIBRARIES
import Svg, {G, Path, Circle} from 'react-native-svg';
import * as d3 from 'd3';
import {
PanGestureHandler,
PinchGestureHandler,
State,
} from 'react-native-gesture-handler';
//CONSTANTS
import {COUNTRIES} from '../constants/CountryShapes';
import COLORS from '../constants/Colors';
//COMPONENTS
import Button from './Button';
const Map = (props) => {
const [countryList, setCountryList] = useState([]);
const [translateX, setTranslateX] = useState(0);
const [translateY, setTranslateY] = useState(0);
const [lastTranslateX, setLastTranslateX] = useState(0);
const [lastTranslateY, setLastTranslateY] = useState(0);
const [buttonOpacity, _] = useState(new Animated.Value(0));
const [scale, setScale] = useState(1);
const [prevScale, setPrevScale] = useState(1);
const [lastScaleOffset, setLastScaleOffset] = useState(0);
const [rotateX, setrotateX] = useState();
const [rotateY, setrotateY] = useState();
const {dimensions, data, date, colorize, stat} = props;
//Gesture Handlers
const panStateHandler = (event) => {
if (event.nativeEvent.oldState === State.UNDETERMINED) {
setLastTranslateX(translateX);
setLastTranslateY(translateY);
}
if (event.nativeEvent.oldState === State.ACTIVE) {
Animated.timing(buttonOpacity, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
}
};
const panGestureHandler = (event) => {
console.log('event', event.nativeEvent);
setrotateX(event.nativeEvent.x);
setrotateX(event.nativeEvent.y);
setTranslateX(-event.nativeEvent.translationX / scale + lastTranslateX);
setTranslateY(-event.nativeEvent.translationY / scale + lastTranslateY);
};
const pinchStateHandler = (event) => {
if (event.nativeEvent.oldState === State.UNDETERMINED) {
setLastScaleOffset(-1 + scale);
}
if (event.nativeEvent.oldState === State.ACTIVE) {
Animated.timing(buttonOpacity, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
}
};
const pinchGestureHandler = (event) => {
if (
event.nativeEvent.scale + lastScaleOffset >= 1 &&
event.nativeEvent.scale + lastScaleOffset <= 5
) {
setPrevScale(scale);
setScale(event.nativeEvent.scale + lastScaleOffset);
setTranslateX(
translateX -
(event.nativeEvent.focalX / scale -
event.nativeEvent.focalX / prevScale),
);
setTranslateY(
translateY -
(event.nativeEvent.focalY / scale -
event.nativeEvent.focalY / prevScale),
);
}
};
//Initialize Map Transforms
const initializeMap = () => {
setTranslateX(0);
setTranslateY(0);
setScale(1);
setPrevScale(1);
setLastScaleOffset(0);
Animated.timing(buttonOpacity, {
toValue: 0,
duration: 1000,
useNativeDriver: true,
}).start();
};
//Create Map Paths
const mapExtent = useMemo(() => {
return dimensions.width > dimensions.height / 2
? dimensions.height / 2
: dimensions.width;
}, [dimensions]);
const countryPaths = useMemo(() => {
const clipAngle = 150;
const projection = d3
.geoAzimuthalEqualArea()
// .rotate([0, -90])
.rotate([rotateX, rotateY])
.fitSize([mapExtent, mapExtent], {
type: 'FeatureCollection',
features: COUNTRIES,
})
.clipAngle(clipAngle)
.translate([dimensions.width / 2, mapExtent / 2]);
const geoPath = d3.geoPath().projection(projection);
const windowPaths = COUNTRIES.map(geoPath);
return windowPaths;
}, [dimensions, rotateX, rotateY]);
useEffect(() => {
setCountryList(
countryPaths.map((path, i) => {
const curCountry = COUNTRIES[i].properties.name;
const isCountryNameInData = data.some(
(country) => country.name === curCountry,
);
const curCountryData = isCountryNameInData
? data.find((country) => country.name === curCountry)['data']
: null;
const isDataAvailable = isCountryNameInData
? curCountryData.some((data) => data.date === date)
: false;
const dateIndex = isDataAvailable
? curCountryData.findIndex((x) => x.date === date)
: null;
return (
<Path
key={COUNTRIES[i].properties.name}
d={path}
stroke={COLORS.greyLight}
strokeOpacity={0.3}
strokeWidth={0.6}
fill={
isDataAvailable
? colorize(curCountryData[dateIndex][stat])
: COLORS.greyLight
}
opacity={isDataAvailable ? 1 : 0.4}
/>
);
}),
);
}, [rotateX, rotateY]);
return (
<View>
<PanGestureHandler
onGestureEvent={(e) => panGestureHandler(e)}
onHandlerStateChange={(e) => panStateHandler(e)}>
<PinchGestureHandler
onGestureEvent={(e) => pinchGestureHandler(e)}
onHandlerStateChange={(e) => pinchStateHandler(e)}>
<Svg
width={dimensions.width}
height={dimensions.height / 2}
style={styles.svg}>
<G
// transform={`scale(${scale}) translate(${-translateX},${-translateY})`}
>
<Circle
cx={dimensions.width / 2}
cy={mapExtent / 2}
r={mapExtent / 2}
fill={COLORS.lightPrimary}
/>
{countryList.map((x) => x)}
</G>
</Svg>
</PinchGestureHandler>
</PanGestureHandler>
</View>
);
};
const styles = StyleSheet.create({
svg: {},
rotateView: {
width: 100,
height: 400,
backgroundColor: 'black',
shadowOffset: {height: 1, width: 1},
shadowOpacity: 0.2,
},
});
export default Map;
how i fixed this issue is :
I cange countries json to countries-110m.json';
delete the rotateX, rotateY and replace by translateX translateY
new rotate code is: .rotate([-translateX, translateY])
if any doubts please check my complete source code from Github