From 337f2e3158694e89ca9ca46f173b21f70f090a32 Mon Sep 17 00:00:00 2001 From: EndiUN Date: Sun, 30 Nov 2025 21:17:50 +0100 Subject: [PATCH 1/8] Distributed logic of ValueTool and RangeTool to separate .jsx files --- app/(Minitool_one)/tools/RangeTool.jsx | 338 +++++++++++++++++++++++++ app/(Minitool_one)/tools/ValueTool.jsx | 168 ++++++++++++ 2 files changed, 506 insertions(+) create mode 100644 app/(Minitool_one)/tools/RangeTool.jsx create mode 100644 app/(Minitool_one)/tools/ValueTool.jsx diff --git a/app/(Minitool_one)/tools/RangeTool.jsx b/app/(Minitool_one)/tools/RangeTool.jsx new file mode 100644 index 0000000..fc2e63c --- /dev/null +++ b/app/(Minitool_one)/tools/RangeTool.jsx @@ -0,0 +1,338 @@ +import React, { useCallback } from "react"; +import { Platform } from "react-native"; +import { Gesture, GestureDetector } from "react-native-gesture-handler"; +import Animated, { + useSharedValue, + useAnimatedProps, + useAnimatedStyle, + useAnimatedReaction, + runOnJS, + clamp, + withTiming, +} from "react-native-reanimated"; +import { Rect, Line, G } from "react-native-svg"; + +const AnimatedG = Animated.createAnimatedComponent(G); +const AnimatedRect = Animated.createAnimatedComponent(Rect); +const AnimatedLine = Animated.createAnimatedComponent(Line); + +/** + * RangeTool Hook + * Encapsulates all range tool logic including gestures, animations, and state management + * Returns an object with animated props and handlers for integration into the main chart + */ +const useRangeTool = ({ + isActive, + onActiveChange, + onRangeChange, + onCountChange, + chartWidth, + chartHeight, + maxLifespan = 130, + initialStartValue = 102, + initialEndValue = 126, + rangeHandleSize = 15, + rangeToolColor = "#0000FF", + displayedData = [], +}) => { + // --- Range Tool Gesture Logic --- + const initialRangeStartX = (initialStartValue / maxLifespan) * chartWidth; + const initialRangeEndX = (initialEndValue / maxLifespan) * chartWidth; + const rangeStartX = useSharedValue(initialRangeStartX); + const rangeEndX = useSharedValue(initialRangeEndX); + const rangeContext = useSharedValue({ start: 0, end: 0 }); + + const movePanGesture = Gesture.Pan() + .onStart(() => { + rangeContext.value = { start: rangeStartX.value, end: rangeEndX.value }; + }) + .onUpdate((event) => { + const rangeWidth = rangeContext.value.end - rangeContext.value.start; + const newStart = clamp( + rangeContext.value.start + event.translationX, + 0, + chartWidth - rangeWidth + ); + rangeStartX.value = newStart; + rangeEndX.value = newStart + rangeWidth; + }); + + const leftHandlePanGesture = Gesture.Pan() + .onStart(() => { + rangeContext.value = { start: rangeStartX.value, end: rangeEndX.value }; + }) + .onUpdate((event) => { + rangeStartX.value = clamp( + rangeContext.value.start + event.translationX, + 0, + rangeEndX.value - rangeHandleSize + ); + }); + + const rightHandlePanGesture = Gesture.Pan() + .onStart(() => { + rangeContext.value = { start: rangeStartX.value, end: rangeEndX.value }; + }) + .onUpdate((event) => { + rangeEndX.value = clamp( + rangeContext.value.end + event.translationX, + rangeStartX.value + rangeHandleSize, + chartWidth + ); + }); + + // --- Animated Props for range tool elements --- + const animatedRangeRectProps = useAnimatedProps(() => ({ + x: rangeStartX.value, + width: rangeEndX.value - rangeStartX.value, + })); + + const animatedRangeLeftLineProps = useAnimatedProps(() => ({ + x1: rangeStartX.value, + x2: rangeStartX.value, + })); + + const animatedRangeRightLineProps = useAnimatedProps(() => ({ + x1: rangeEndX.value, + x2: rangeEndX.value, + })); + + const animatedLeftHandleProps = useAnimatedProps(() => ({ + x: rangeStartX.value - rangeHandleSize / 2, + })); + + const animatedRightHandleProps = useAnimatedProps(() => ({ + x: rangeEndX.value - rangeHandleSize / 2, + })); + + const animatedMoveHandleProps = useAnimatedProps(() => ({ + x: rangeStartX.value, + width: Math.abs(rangeEndX.value - rangeStartX.value), + })); + + // --- Animation for label --- + const animatedRangeLabelStyle = useAnimatedStyle(() => ({ + transform: [{ translateX: (rangeStartX.value + rangeEndX.value) / 2 }], + opacity: withTiming(isActive ? 1 : 0), + })); + + // --- Animation for tool visibility --- + const rangeToolContainerAnimatedProps = useAnimatedProps(() => { + return { opacity: withTiming(isActive ? 1 : 0) }; + }); + + // --- Function to handle range updates and calculate count --- + useAnimatedReaction( + () => ({ start: rangeStartX.value, end: rangeEndX.value }), + (currentRange, previousRange) => { + if ( + currentRange.start !== previousRange?.start || + currentRange.end !== previousRange?.end + ) { + const minLifespanValue = + (currentRange.start / chartWidth) * maxLifespan; + const maxLifespanValue = (currentRange.end / chartWidth) * maxLifespan; + const count = displayedData.filter( + (item) => + item.visible && + item.lifespan >= minLifespanValue && + item.lifespan <= maxLifespanValue + ).length; + runOnJS(onCountChange)(count); + if (onRangeChange) { + runOnJS(onRangeChange)({ + start: minLifespanValue, + end: maxLifespanValue, + }); + } + } + }, + [chartWidth, maxLifespan, displayedData] + ); + + const handleToggle = useCallback( + (newValue) => { + onActiveChange(newValue); + }, + [onActiveChange] + ); + + // --- Render component --- + const renderRangeTool = () => ( + + + + + + {/* --- Rectangles - gesture handlers --- */} + {/* {Platform.OS === "web" && isActive ? ( */} + <> + { + e.stopPropagation(); + const startX = e.nativeEvent.pageX; + const initialStart = rangeStartX.value; + + const handleMouseMove = (moveEvent) => { + const deltaX = moveEvent.pageX - startX; + rangeStartX.value = clamp( + initialStart + deltaX, + 0, + rangeEndX.value - rangeHandleSize + ); + }; + + const handleMouseUp = () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }} + /> + { + e.stopPropagation(); + const startX = e.nativeEvent.pageX; + const initialEnd = rangeEndX.value; + + const handleMouseMove = (moveEvent) => { + const deltaX = moveEvent.pageX - startX; + rangeEndX.value = clamp( + initialEnd + deltaX, + rangeStartX.value + rangeHandleSize, + chartWidth + ); + }; + + const handleMouseUp = () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }} + /> + { + e.stopPropagation(); + const startX = e.nativeEvent.pageX; + const initialStart = rangeStartX.value; + const initialEnd = rangeEndX.value; + const rangeWidth = initialEnd - initialStart; + + const handleMouseMove = (moveEvent) => { + const deltaX = moveEvent.pageX - startX; + const newStart = clamp( + initialStart + deltaX, + 0, + chartWidth - rangeWidth + ); + rangeStartX.value = newStart; + rangeEndX.value = newStart + rangeWidth; + }; + + const handleMouseUp = () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }} + /> + + {/* ) : ( + <> + + + + + + + + + + + )} */} + + ); + + return { + // Rendered component + renderRangeTool, + + // Shared values and gestures (exposed if needed for advanced usage) + rangeStartX, + rangeEndX, + movePanGesture, + leftHandlePanGesture, + rightHandlePanGesture, + + // Animated props (exposed if needed) + animatedRangeRectProps, + animatedRangeLeftLineProps, + animatedRangeRightLineProps, + animatedLeftHandleProps, + animatedRightHandleProps, + animatedMoveHandleProps, + animatedRangeLabelStyle, + rangeToolContainerAnimatedProps, + + // Handlers + handleToggle, + }; +}; + +export default useRangeTool; diff --git a/app/(Minitool_one)/tools/ValueTool.jsx b/app/(Minitool_one)/tools/ValueTool.jsx new file mode 100644 index 0000000..02abcf3 --- /dev/null +++ b/app/(Minitool_one)/tools/ValueTool.jsx @@ -0,0 +1,168 @@ +import React, { useCallback } from "react"; +import { Platform } from "react-native"; +import { Gesture, GestureDetector } from "react-native-gesture-handler"; +import Animated, { + useSharedValue, + useAnimatedProps, + useAnimatedStyle, + useAnimatedReaction, + runOnJS, + clamp, + withTiming, +} from "react-native-reanimated"; +import { Rect, Line, G } from "react-native-svg"; + +const AnimatedG = Animated.createAnimatedComponent(G); +const AnimatedRect = Animated.createAnimatedComponent(Rect); +const AnimatedLine = Animated.createAnimatedComponent(Line); + +/** + * ValueTool Hook + * Encapsulates all value tool logic and returns the rendered component + * Returns an object with the rendered component and control functions + */ +const useValueTool = ({ + isActive, + onActiveChange, + onValueChange, + chartWidth, + chartHeight, + maxLifespan = 130, + toolValue = 80.0, + toolColor = "red", +}) => { + // --- Value Tool Gesture Logic --- + const initialTranslateX = (toolValue / maxLifespan) * chartWidth; + const translateX = useSharedValue(initialTranslateX); + const context = useSharedValue({ x: 0 }); + + // --- Sync translateX with toolValue when it changes externally --- + useAnimatedReaction( + () => toolValue, + (currentToolValue) => { + const newTranslateX = (currentToolValue / maxLifespan) * chartWidth; + translateX.value = newTranslateX; + }, + [maxLifespan, chartWidth] + ); + + const panGesture = Gesture.Pan() + .onStart(() => { + context.value = { x: translateX.value }; + }) + .onUpdate((event) => { + translateX.value = clamp( + event.translationX + context.value.x, + 0, + chartWidth + ); + }); + + // --- Animated Props for line and handle --- + const animatedToolProps = useAnimatedProps(() => ({ + x: translateX.value - 7.5, + })); + + const animatedValueLineProps = useAnimatedProps(() => ({ + x1: translateX.value, + x2: translateX.value, + })); + + // --- Animation for label --- + const animatedLabelStyle = useAnimatedStyle(() => ({ + transform: [{ translateX: translateX.value }], + opacity: withTiming(isActive ? 1 : 0), + })); + + // --- Animation for tool visibility --- + const valueToolContainerAnimatedProps = useAnimatedProps(() => { + return { opacity: withTiming(isActive ? 1 : 0) }; + }); + + // --- Function to handle value updates --- + useAnimatedReaction( + () => translateX.value, + (currentValue) => { + const newValue = (currentValue / chartWidth) * maxLifespan; + runOnJS(onValueChange)(newValue); + }, + [chartWidth, maxLifespan] + ); + + const handleToggle = useCallback( + (newValue) => { + onActiveChange(newValue); + }, + [onActiveChange] + ); + + // --- Render component --- + const renderValueTool = () => ( + + + {/* {Platform.OS === "web" && isActive ? ( */} + { + e.stopPropagation(); + const startX = e.nativeEvent.pageX; + const initialTranslate = translateX.value; + + const handleMouseMove = (moveEvent) => { + const deltaX = moveEvent.pageX - startX; + translateX.value = clamp(initialTranslate + deltaX, 0, chartWidth); + }; + + const handleMouseUp = () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }} + /> + {/* ) : ( + + + + )} */} + + ); + + return { + // Rendered component + renderValueTool, + + // Shared values and gestures (exposed if needed for advanced usage) + translateX, + panGesture, + + // Animated props (exposed if needed) + animatedToolProps, + animatedValueLineProps, + animatedLabelStyle, + valueToolContainerAnimatedProps, + + // Handlers + handleToggle, + }; +}; + +export default useValueTool; From 106111e133d5040696a39ad203d5ae2c407f71cf Mon Sep 17 00:00:00 2001 From: EndiUN Date: Sun, 30 Nov 2025 21:20:47 +0100 Subject: [PATCH 2/8] Distributed logic of SortBySize, SortByColour into one separate .jsx file. Add new functionality: HideGreenBar,HidePurpleBars and ShowOnlyDots. --- app/(Minitool_one)/controls/ChartControls.jsx | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 app/(Minitool_one)/controls/ChartControls.jsx diff --git a/app/(Minitool_one)/controls/ChartControls.jsx b/app/(Minitool_one)/controls/ChartControls.jsx new file mode 100644 index 0000000..8c3d1a8 --- /dev/null +++ b/app/(Minitool_one)/controls/ChartControls.jsx @@ -0,0 +1,152 @@ +import React, { useState, useCallback } from "react"; +import { View, Text, Switch, Button, StyleSheet } from "react-native"; + +/** + * ChartControls Hook + * Encapsulates all chart control logic including sorting, filtering, and tool toggles + * Returns state setters and a renderControls() function for rendering + */ +const useChartControls = () => { + // --- Control state --- + const [isSortedBySize, setIsSortedBySize] = useState(false); + const [isSortedByColor, setIsSortedByColor] = useState(false); + const [hideGreenBars, setHideGreenBars] = useState(false); + const [hidePurpleBars, setHidePurpleBars] = useState(false); + const [showDotsOnly, setShowDotsOnly] = useState(false); + const [valueToolActive, setValueToolActive] = useState(false); + const [rangeToolActive, setRangeToolActive] = useState(false); + + // --- Handler functions --- + const handleSortBySize = useCallback((isActive) => { + setIsSortedBySize(isActive); + // When sorting by size, turn off color sort + if (isActive) { + setIsSortedByColor(false); + } + }, []); + + const handleSortByColor = useCallback((isActive) => { + setIsSortedByColor(isActive); + // When sorting by color, turn off size sort + if (isActive) { + setIsSortedBySize(false); + } + }, []); + + const handleValueTool = useCallback((isActive) => { + setValueToolActive(isActive); + }, []); + + const handleRangeTool = useCallback((isActive) => { + setRangeToolActive(isActive); + }, []); + + const handleHideGreenBars = useCallback((isActive) => { + setHideGreenBars(isActive); + }, []); + + const handleHidePurpleBars = useCallback((isActive) => { + setHidePurpleBars(isActive); + }, []); + + const handleShowDotsOnly = useCallback((isActive) => { + setShowDotsOnly(isActive); + }, []); + + // --- Render component with all controls --- + const renderControls = useCallback( + () => ( + + {/* --- Tool toggles --- */} + + Value tool + + Range tool + + + + {/* --- Visibility filters --- */} + + Hide Green Bars + + Hide Purple Bars + + Show Dots Only + + + + {/* --- Sorting controllers --- */} + +