79140712

Date: 2024-10-30 10:46:08
Score: 0.5
Natty:
Report link

I tried similar multiple circle selection.

import React, { useEffect, useState } from 'react';
import {
  View,
  StyleSheet,
  Text,
  Animated,
  TouchableOpacity,
  Dimensions,
} from 'react-native';

const SCREEN_WIDTH = Dimensions.get('window').width;
const SCREEN_HEIGHT = Dimensions.get('window').height;

type Circle = {
  id: number;
  radius: number;
  label: string;
  position: { x: number; y: number };
  animatedRadius: Animated.Value;
  animatedPosition: Animated.ValueXY;
  color: string;
  textColor: string;
  isSelected: boolean;
};

const calculateRadius = (text: string) => {
  // Basic formula to calculate radius based on text length (can be adjusted)
  return Math.max(40, text.length * 8);
};

const App = () => {
  const [circles, setCircles] = useState<Circle[]>([]);

  const handleCollision = (updatedCircles: Circle[]) => {
    const padding = 10;
    for (let i = 0; i < updatedCircles.length; i++) {
      for (let j = i + 1; j < updatedCircles.length; j++) {
        const distX = updatedCircles[j].position.x - updatedCircles[i].position.x;
        const distY = updatedCircles[j].position.y - updatedCircles[i].position.y;
        const distance = Math.sqrt(distX * distX + distY * distY);
        const minDist = updatedCircles[i].radius + updatedCircles[j].radius + padding;

        if (distance < minDist) {
          const angle = Math.atan2(distY, distX);
          const overlap = minDist - distance;
          updatedCircles[j].position.x += overlap * Math.cos(angle);
          updatedCircles[j].position.y += overlap * Math.sin(angle);

          // Apply animation
          Animated.spring(updatedCircles[j].animatedPosition, {
            toValue: { x: updatedCircles[j].position.x, y: updatedCircles[j].position.y },
            useNativeDriver: false,
          }).start();
        }
      }
    }
  };

  useEffect(() => {
    const createCircles = () => {
      const randomPositions = () => ({
        x: Math.random() * SCREEN_WIDTH * 0.8 + SCREEN_WIDTH * 0.1,
        y: Math.random() * SCREEN_HEIGHT * 0.6 + SCREEN_HEIGHT * 0.2,
      });

      const labels = [
        'Raf Simons', 'Maison Margiela', 'Versace', 'Fendi', 'Prada', 'Burberry',
        'Hilfiger', 'Stussy', 'Reebok', 'The North Face', 'Visvim'
      ];

      const newCircles: Circle[] = labels.map((label, index) => {
        const radius = calculateRadius(label); // Calculate radius based on text length
        return {
          id: index,
          label,
          radius,
          position: randomPositions(),
          animatedRadius: new Animated.Value(0), // Initial radius for loading animation
          animatedPosition: new Animated.ValueXY({ x: SCREEN_WIDTH / 2, y: SCREEN_HEIGHT / 2 }), // Start at center for animation
          color: 'white',
          textColor: 'black',
          isSelected: false,
        };
      });

      setCircles(newCircles);

      // Animate circles on load
      newCircles.forEach((circle, index) => {
        setTimeout(() => {
          Animated.spring(circle.animatedRadius, {
            toValue: circle.radius,
            useNativeDriver: false,
          }).start();

          Animated.timing(circle.animatedPosition, {
            toValue: { x: circle.position.x, y: circle.position.y },
            duration: 800,
            useNativeDriver: false,
          }).start();
        }, index * 100); // Stagger animations for each circle
      });

      // Handle collisions
      setTimeout(() => {
        handleCollision(newCircles);
      }, 1000);
    };

    createCircles();
  }, []);

  // Handle circle press (toggle selection and animate)
  const handleCirclePress = (circleId: number) => {
    setCircles((prevCircles) =>
      prevCircles.map((circle) => {
        if (circle.id === circleId) {
          const isSelected = !circle.isSelected;

          // Console the selected circle text
          if (isSelected) {
            console.log(`Selected circle: ${circle.label}`);
          }

          // Animate size on selection
          Animated.timing(circle.animatedRadius, {
            toValue: isSelected ? circle.radius * 1.2 : circle.radius,
            duration: 300,
            useNativeDriver: false,
          }).start();

          return {
            ...circle,
            color: isSelected ? 'black' : 'white',
            textColor: isSelected ? 'white' : 'black',
            isSelected,
          };
        }
        return circle;
      })
    );
  };

  return (
    <View style={styles.container}>
      {/* Header */}
      <View style={styles.header}>
        <Text style={styles.headerText}>Choose three or more favorites</Text>
      </View>

      {/* Circles */}
      <View style={styles.circleContainer}>
        {circles.map((circle) => (
          <Animated.View
            key={circle.id}
            style={[
              styles.circle,
              {
                width: circle.animatedRadius,
                height: circle.animatedRadius,
                borderRadius: circle.animatedRadius,
                backgroundColor: circle.color,
                transform: circle.animatedPosition.getTranslateTransform(),
              },
            ]}
          >
            <TouchableOpacity
              onPress={() => handleCirclePress(circle.id)}
              style={styles.circleTouchable}
            >
              <Text style={[styles.circleText, { color: circle.textColor }]}>
                {circle.label}
              </Text>
            </TouchableOpacity>
          </Animated.View>
        ))}
      </View>

      {/* Footer */}
      <View style={styles.footer}>
        <TouchableOpacity style={styles.button}>
          <Text style={styles.buttonText}>Load More</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
    alignItems: 'center',
  },
  header: {
    paddingTop: 50,
    paddingBottom: 20,
  },
  headerText: {
    fontSize: 18,
    fontWeight: 'bold',
    color: 'black',
  },
  circleContainer: {
    flex: 1,
    width: '100%',
    position: 'relative',
  },
  circle: {
    position: 'absolute',
    justifyContent: 'center',
    alignItems: 'center',
    borderWidth: 2,
    borderColor: 'black',
  },
  circleTouchable: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  circleText: {
    fontSize: 12,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  footer: {
    paddingBottom: 20,
  },
  button: {
    backgroundColor: 'black',
    paddingHorizontal: 20,
    paddingVertical: 10,
    borderRadius: 20,
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
});

export default App;

enter image description here

And another logic as below

import React, { useEffect, useState, useRef } from 'react';
import {
  View,
  StyleSheet,
  Text,
  Animated,
  TouchableOpacity,
  Dimensions,
} from 'react-native';

const SCREEN_WIDTH = Dimensions.get('window').width;
const SCREEN_HEIGHT = Dimensions.get('window').height;

const defaultOptions = {
  size: 80, // Reduce the size for better mobile adaptation
  minSize: 20,
  gutter: 16,
  numCols: 3, // Limit the number of columns for mobile
  yRadius: 200,
  xRadius: 200,
};

// Define Circle type
interface Circle {
  id: number;
  label: string;
  radius: number;
  animatedRadius: Animated.Value;
  animatedPosition: Animated.ValueXY;
  color: string;
  textColor: string;
  isSelected: boolean;
  position: {
    x: number;
    y: number;
  };
}

const App = () => {
  const [circles, setCircles] = useState<Circle[]>([]);
  const scrollableRef = useRef(null);

  useEffect(() => {
    const labels = [
      'Raf Simons', 'Maison Margiela', 'Versace', 'Fendi', 'Prada', 
      'Burberry', 'Hilfiger', 'Stussy', 'Reebok', 'The North Face', 
      'Visvim',
    ];

    const createCircles = () => {
      const newCircles: Circle[] = [];

      labels.forEach((label, index) => {
        const position = calculatePosition(index, labels.length);
        const animatedRadius = new Animated.Value(0);
        const animatedPosition = new Animated.ValueXY({ x: position.x, y: position.y });

        newCircles.push({
          id: index,
          label,
          radius: defaultOptions.size,
          animatedRadius,
          animatedPosition,
          color: 'white',
          textColor: 'black',
          isSelected: false,
          position,
        });
      });

      setCircles(newCircles);

      // Animate circles on load
      newCircles.forEach((circle, index) => {
        setTimeout(() => {
          Animated.spring(circle.animatedRadius, {
            toValue: circle.radius,
            useNativeDriver: false,
          }).start();

          Animated.timing(circle.animatedPosition, {
            toValue: { x: circle.position.x, y: circle.position.y },
            duration: 800,
            useNativeDriver: false,
          }).start();
        }, index * 100); // Stagger animations for each circle
      });
    };

    createCircles();
  }, []);

  const calculatePosition = (index: number, total: number) => {
    const numCols = Math.min(defaultOptions.numCols, total);
    const row = Math.floor(index / numCols);
    const col = index % numCols;

    // Calculate the size and gutter based on screen width
    const circleSize = defaultOptions.size;
    const gutter = defaultOptions.gutter;

    const xOffset = (circleSize + gutter) * col + (SCREEN_WIDTH - (numCols * circleSize + (numCols - 1) * gutter)) / 2;
    const yOffset = (circleSize + gutter) * row;

    return {
      x: xOffset,
      y: yOffset,
    };
  };

  const handleCirclePress = (circleId: number) => {
    setCircles((prevCircles) =>
      prevCircles.map((circle) => {
        if (circle.id === circleId) {
          const isSelected = !circle.isSelected;

          if (isSelected) {
            console.log(`Selected circle: ${circle.label}`);
          }

          Animated.timing(circle.animatedRadius, {
            toValue: isSelected ? circle.radius * 1.2 : circle.radius,
            duration: 300,
            useNativeDriver: false,
          }).start();

          return {
            ...circle,
            color: isSelected ? 'black' : 'white',
            textColor: isSelected ? 'white' : 'black',
            isSelected,
          };
        }
        return circle;
      })
    );
  };

  return (
    <View style={styles.container}>
      {/* Header */}
      <View style={styles.header}>
        <Text style={styles.headerText}>Choose three or more favorites</Text>
      </View>

      {/* Circles */}
      <View style={styles.circleContainer}>
        {circles.map((circle) => (
          <Animated.View
            key={circle.id}
            style={[
              styles.circle,
              {
                width: circle.animatedRadius,
                height: circle.animatedRadius,
                borderRadius: circle.animatedRadius,
                backgroundColor: circle.color,
                position: 'absolute', // Make position absolute for correct placement
                left: circle.animatedPosition.x, // Use animated position for x
                top: circle.animatedPosition.y, // Use animated position for y
              },
            ]}
          >
            <TouchableOpacity
              onPress={() => handleCirclePress(circle.id)}
              style={styles.circleTouchable}
            >
              <Text style={[styles.circleText, { color: circle.textColor }]}>
                {circle.label}
              </Text>
            </TouchableOpacity>
          </Animated.View>
        ))}
      </View>

      {/* Footer */}
      <View style={styles.footer}>
        <TouchableOpacity style={styles.button}>
          <Text style={styles.buttonText}>Load More</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
    alignItems: 'center',
  },
  header: {
    paddingTop: 50,
    paddingBottom: 20,
  },
  headerText: {
    fontSize: 18,
    fontWeight: 'bold',
    color: 'black',
  },
  circleContainer: {
    flex: 1,
    width: '100%',
    position: 'relative',
  },
  circle: {
    justifyContent: 'center',
    alignItems: 'center',
    borderWidth: 2,
    borderColor: 'black',
  },
  circleTouchable: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  circleText: {
    fontSize: 12,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  footer: {
    paddingBottom: 20,
  },
  button: {
    backgroundColor: 'black',
    paddingHorizontal: 20,
    paddingVertical: 10,
    borderRadius: 20,
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
});

export default App;

enter image description here

Reasons:
  • Probably link only (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Sankar R