Rinat Rezyapov
Rinat Rezyapov

Reputation: 437

Bend/curve set of absolute positioned circles with JavaScript and CSS

I have a set of absolute positioned circles that I want to curve/bend. So far I managed to curve them based on their index but result is slightly different from what I expected. Central circles go up/down too far and thus effect of bending is abrupt.

function App() {
  const [parentHeight, setParentHeight] = React.useState(200);
  const [parentWidth, setParentWidth] = React.useState(300);

  const [curve, setCurve] = React.useState(0);

  const childDiameter = 40;

  const rows = [
    { position: 0, seats: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] },
    { position: 1, seats: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] },
    { position: 2, seats: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] },
    { position: 3, seats: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }
  ];

  const getCurve = (idx, length) => {
    return curve / (Math.abs(idx - (length - 1 - idx)) / 8);
  };
  
  return (
    <div>
      <div
        style={{
          position: "absolute",
          width: `${parentWidth}px`,
          height: `${parentHeight}px`,
          top: 100,
          left: 100,
          boxSizing: "border-box",
          display: "flex",
          flexWrap: "wrap"
        }}
      >
        {rows.map((row, rowIdx) => (
          <div>
            {row.seats.map((seat, idx) => (
              <div
                style={{
                  position: "absolute",
                  left: idx * (childDiameter + 10),
                  top:
                    rowIdx * (childDiameter + 10) +
                    getCurve(idx, row.seats.length),
                  backgroundColor: "grey",
                  border: "3px solid grey",
                  borderRadius: "50%",
                  width: childDiameter,
                  height: childDiameter
                }}
              >
                {idx}
              </div>
            ))}
          </div>
        ))}
      </div>
      <input
        id="typeinp"
        type="range"
        min="-200"
        max="200"
        value={curve}
        onChange={evt => setCurve(parseInt(evt.target.value))}
        step="1"
      />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

I know that I need to compensate somehow this shifting when I go to the center, but I can't figure out how. So how can I achive more smooth bending? Thank you.

Upvotes: 1

Views: 59

Answers (1)

Temani Afif
Temani Afif

Reputation: 272806

This is a pure math issue. You are using this formula curve / (Math.abs(idx - (length - 1 - idx)) / 8) which we can write in this case like this A / |2*x - 9| (considering length equal to 10 and A equal to curve/8)

If x is close to infinity then f(x) will be close to 0 And if 2*x - 9 is close to 0 then then f(x) will be close to infinity and we have this for the middle values (4 or 5).

Here is an illustration to better understand:

enter image description here

http://fooplot.com

You can clearly see how the values are bigger when using 4 or 5 compared to the other thus the issue. Basically you are creating the above curve with your elements.

You need to use another formula to have a better cuvre and avoid the infinity issue.

Below a better formula. I will not detail the math behind this choice but you can clearly notice that the polynom (X-5)²+X doesn't have any solution in ℝ thus will never be equal to 0 (no infinity for f(x)) and it will create the needed curve for this purpose.

enter image description here

function App() {
  const [parentHeight, setParentHeight] = React.useState(200);
  const [parentWidth, setParentWidth] = React.useState(300);

  const [curve, setCurve] = React.useState(0);

  const childDiameter = 40;

  const rows = [
    { position: 0, seats: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] },
    { position: 1, seats: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] },
    { position: 2, seats: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] },
    { position: 3, seats: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }
  ];

  const getCurve = (idx, length) => {
    return curve / ((idx - (length/2))*(idx - length/2) + idx);
  };
  
  return (
    <div>
      <div
        style={{
          position: "absolute",
          width: `${parentWidth}px`,
          height: `${parentHeight}px`,
          top: 100,
          left: 100,
          boxSizing: "border-box",
          display: "flex",
          flexWrap: "wrap"
        }}
      >
        {rows.map((row, rowIdx) => (
          <div>
            {row.seats.map((seat, idx) => (
              <div
                style={{
                  position: "absolute",
                  left: idx * (childDiameter + 10),
                  top:
                    rowIdx * (childDiameter + 10) +
                    getCurve(idx, row.seats.length),
                  backgroundColor: "grey",
                  border: "3px solid grey",
                  borderRadius: "50%",
                  width: childDiameter,
                  height: childDiameter
                }}
              >
                {idx}
              </div>
            ))}
          </div>
        ))}
      </div>
      <input
        id="typeinp"
        type="range"
        min="-200"
        max="200"
        value={curve}
        onChange={evt => setCurve(parseInt(evt.target.value))}
        step="1"
      />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 1

Related Questions