Tuesday, 30 May 2023

D3 animation morphing polygon shape per frame - chronological order

I want to create a d3.js visualization - that showcases different stages of a polygon shape - so having it animate/morph changing shape and scale.

Where the user click's on the little rectangles they essentially scroll through an array of data of "dimensions/coordinates" that showcase a "frame" of a shape (like a film frame)

so imagine - we have these shapes in an array --- a way of morphing/animating one to the other -- or/and using the rectangles to click and switch to that shape.

enter image description here enter image description here

So the data would be an array of shapes/positions

[
{"coordinateX": 20, "coordinateY": 30, "shape": [{"x":0.0, "y":25.0},
        {"x":8.5,"y":23.4}, {"x":13.0,"y":21.0}, {"x":19.0,"y":15.5}], "frame": 0, "date": 2023-05-23 10:00},
{"coordinateX": 19, "coordinateY": 29, "shape": [{"x":0.3, "y":23.0},
        {"x":8.5,"y":23.4}, {"x":33.0,"y":21.0}, {"x":19.0,"y":45.5}], "frame": 1, "date": 2023-05-24 11:00},
{"coordinateX": 19, "coordinateY": 30, "shape": [{"x":0.0, "y":25.0},
        {"x":5.5,"y":23.7}, {"x":11.0,"y":21.0}, {"x":12.0,"y":13.5}], "frame": 2, "date": 2023-05-25 11:00}
]

Dimensions and coordinates - scale and adjustment of shape per frame - chronological order

so morph the existing shape - into others..

here is some code samples to draw a static polygon

//example of drawing a polygon Proper format for drawing polygon data in D3 http://jsfiddle.net/4xXQT/

var vis = d3.select("body").append("svg")
         .attr("width", 1000)
         .attr("height", 667),

scaleX = d3.scale.linear()
        .domain([-30,30])
        .range([0,600]),

scaleY = d3.scale.linear()
        .domain([0,50])
        .range([500,0]),

poly = [{"x":0.0, "y":25.0},
        {"x":8.5,"y":23.4},
        {"x":13.0,"y":21.0},
        {"x":19.0,"y":15.5}];

vis.selectAll("polygon")
    .data([poly])
  .enter().append("polygon")
    .attr("points",function(d) { 
        return d.map(function(d) { return [scaleX(d.x),scaleY(d.y)].join(","); }).join(" ");})
    .attr("stroke","black")
    .attr("stroke-width",2);

with the data array -

http://jsfiddle.net/5k9z30vh/

I want to figure out a way of aniamting/morphing polygons.

how would I look at animating/morphing - through the polygons - https://www.youtube.com/watch?v=K1zHa1sAno0 Loop D3 animation of polygon vertex

var vis = d3.select("body").append("svg")
         .attr("width", 1000)
         .attr("height", 667),

scaleX = d3.scale.linear()
        .domain([-30,30])
        .range([0,600]),

scaleY = d3.scale.linear()
        .domain([0,50])
        .range([500,0]),

poly = [{"x":0.0, "y":25.0},
        {"x":8.5,"y":23.4},
        {"x":13.0,"y":21.0},
        {"x":19.0,"y":15.5}];
        
        var data = [{
        "coordinateX": 20,
        "coordinateY": 30,
        "shape": [{
                "x": 0.0,
                "y": 25.0
            },
            {
                "x": 8.5,
                "y": 23.4
            }, {
                "x": 13.0,
                "y": 21.0
            }, {
                "x": 19.0,
                "y": 15.5
            }
        ],
        "frame": 0,
        "date": "2023-05-23 10:00"
    },
    {
        "coordinateX": 19,
        "coordinateY": 29,
        "shape": [{
                "x": 0.3,
                "y": 23.0
            },
            {
                "x": 8.5,
                "y": 23.4
            }, {
                "x": 33.0,
                "y": 21.0
            }, {
                "x": 19.0,
                "y": 45.5
            }
        ],
        "frame": 1,
        "date": "2023-05-24 10:00"
    },
    {
        "coordinateX": 19,
        "coordinateY": 30,
        "shape": [{
                "x": 0.0,
                "y": 25.0
            },
            {
                "x": 5.5,
                "y": 23.7
            }, {
                "x": 11.0,
                "y": 21.0
            }, {
                "x": 12.0,
                "y": 13.5
            }
        ],
        "frame": 2,
        "date": "2023-05-25 10:00"
    }
]
        

vis.selectAll("polygon")
    .data([data[0].shape])
  .enter().append("polygon")
    .attr("points",function(d) { 
        return d.map(function(d) { return [scaleX(d.x),scaleY(d.y)].join(","); }).join(" ");})
    .attr("stroke","black")
    .attr("stroke-width",2);

hand drawing a polygon https://jsfiddle.net/7pwaLfth/

https://lucidar.me/en/d3.js/part-06-basic-shapes/ https://gist.github.com/RiseupDev/b07f7ccc1c499efc24e9

/// 30th May 2023 update

I have managed to create this demo that shows the concept working - the polygon morphs into the next -- this would be useful to showcase a "wound" healing from day 1 to day x -- essentially it would shrink into eventual non-existence..

I would like to fix/add these features/issues

-- there is a bug if you click on autoplay too much like event bubbling - if there is a way to avoid a malfunction if the user clicks too quickly

  • I'd like to add different speeds/fps options -- be able to watch a frame each second 1fps, or 2 frames a second 2fp ---- to try and make it more fluent watching a wound heal in timelapse almost --- fix the overall structure of the visualization -- create a sketlon - g placeholders -- fix the ability to scale the visualization cleanly
  • the polygon holder has no transformation to really align things properly -- the polygons are really big - I dont know how to just make them smaller - and if I reduce the canvas size then polygons become clipped --I dont know how to clean up the attachment of the little rectangle bar controller - it just seemed created/appended in a clunky manner

jsfiddle http://jsfiddle.net/5kqLftzy/

code

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>D3 Drawing</title>
  <script src="https://cdn.jsdelivr.net/d3js/3.5.9/d3.min.js"></script>
</head>
<body>
  <h3>Draw a polygon 2</h3>

    <div>
      <button onclick="beginAutoPlay()">Start Autoplay</button>
      <button onclick="stopAutoPlay()">Stop Autoplay</button>
    </div>  

  <script>


let polygons = [
  {
    "id": "1",
    "plot": "469,59.5625,246,256.5625,348,499.5625,527,482.5625,653,457.5625,693,406.5625,732,371.5625,743,260.5625,690,257.5625,650,172.5625,622,143.5625,589,98.5625,544,90.5625,511,55.5625"
  }, 
  {
    "id": "2",
    "plot": "462,79.5625,258,258.5625,361,470.5625,527,482.5625,653,457.5625,693,406.5625,718,363.5625,705,284.5625,684,241.5625,650,172.5625,622,143.5625,587,108.5625,544,90.5625,513,95.5625"
  },
  {
    "id": "3",
    "plot": "410,173.5625,324,259.5625,361,470.5625,527,435.5625,604,379.5625,672,374.5625,718,363.5625,692,281.5625,662,231.5625,650,172.5625,622,143.5625,582,132.5625,544,90.5625,513,95.5625"
  },
  {
    "id": "4",
    "plot": "422,186.5625,359,267.5625,374,430.5625,527,435.5625,604,379.5625,672,374.5625,691,325.5625,692,281.5625,662,231.5625,650,172.5625,622,143.5625,582,132.5625,543,111.5625,503,124.5625"
  },
  {
    "id": "5",
    "plot": "486,200.5625,359,267.5625,394,393.5625,527,435.5625,604,379.5625,617,332.5625,634,292.5625,624,252.5625,602,216.5625,606,192.5625,596,171.5625,585,148.5625,573,175.5625,525,175.5625"
  },
  {
    "id": "6",
    "plot": "486,200.5625,440,264.5625,419,370.5625,527,435.5625,604,379.5625,580,359.5625,589,330.5625,607,306.5625,610,277.5625,604,252.5625,588,246.5625,577,238.5625,549,220.5625,514,219.5625"
  },
  {
    "id": "7",
    "plot": "485,272.5625,469,294.5625,456,359.5625,491,396.5625,604,379.5625,580,359.5625,589,330.5625,607,306.5625,610,277.5625,604,252.5625,588,246.5625,571,265.5625,548,270.5625,521,277.5625"
  },
  {
    "id": "8",
    "plot": "499,297.5625,483,310.5625,488,333.5625,492,356.5625,534,366.5625,566,349.5625,589,330.5625,607,306.5625,610,277.5625,593,273.5625,578,275.5625,571,265.5625,548,270.5625,521,277.5625"
  }
]


let width = 1000;
let height = 600;


let polyMaxY = 300



var vis = d3.select("body").append("svg")
  .attr("width", width)
  .attr("height", height),

  scaleX = d3.scale.linear()
  .domain([-30, 30])
  .range([0, 600]),

  scaleY = d3.scale.linear()
  .domain([0, 50])
  .range([polyMaxY, 0])




let polyStage = vis.append("g")
  .attr("class", "polyStage")
  .attr("transform", "0 0")

let thePolygon = polyStage.append("polygon")
  .attr("id", "mainPoly")
  .attr("stroke", "black")
  .attr("stroke-width", 2)
  .attr("points", polygons[0])



let paddingBottom = 50



let chooserX = 100
let chooserY = polyMaxY + paddingBottom

let buttonWidth = 7
let buttonHeight = 50
let buttonRightPadding = 3

let selectedColor = "aqua"
let rectColor = "black"

let currentPoly = 0

let animationDuration = 2000
let goRightDelay = 1000
let autoplay = true;



let framePicker = vis.append("g")
  .attr("class", "framePicker")


  let buttonGroups = framePicker.selectAll("g")
    .data(polygons)
    .enter().append("g")
    .attr("transform", buttonGroupTranslate)

    buttonGroups.append("rect")
      .attr("id", (d, i) => "chooserRect" + i)
      .attr("width", buttonWidth)
      .attr("height", buttonHeight)
      .style("fill", rectColor)

    buttonGroups.on("click", navigateTo)

    // Listen to left and right arrow
    d3.select("body").on("keydown", function() {
      let keyCode = d3.event.keyCode
      d3.event.stopPropagation()
      if (keyCode == 37) { //  37 is left arrow
        goLeft()
      }
      if (keyCode == 39) {
        goRight()
      }
    })





    function goLeft() {
      let nextIndex = currentPoly - 1
      if (nextIndex < 0) {
        nextIndex = polygons.length - 1
      }
      navigateTo("", nextIndex)
    }

    function goRight() {
      let nextIndex = currentPoly + 1
      if (nextIndex >= polygons.length) {
        nextIndex = 0
      }
      navigateTo("", nextIndex)
    }


    function buttonGroupTranslate(d, i) {
      let groupX = chooserX + (i * (buttonWidth + buttonRightPadding))
      return "translate(" + groupX + "," + chooserY + ")"
    }

    function navigateTo(d, i) {
      buttonGroups.selectAll("rect").style("fill", rectColor) // reset all colors
      d3.select("#chooserRect" + i).style("fill", selectedColor) // set color for the current one
      d3.select("#mainPoly").transition()
        .duration(animationDuration)
        .attr("points", polygons[i]["plot"])
      currentPoly = i
    }

    let timerCallBack = function() {
      return function() {
        if (autoplay) {
          goRight()
          d3.timer(timerCallBack(), animationDuration + goRightDelay) // Set up a new timer
        }
        return true; // Cancel the current timer
      }
    }

    function beginAutoPlay() {
      autoplay = true
      d3.timer(timerCallBack(), animationDuration + goRightDelay)
    }

    function stopAutoPlay() {
      autoplay = false
    }



    navigateTo("", currentPoly)

  </script>
</body>
</html>


from D3 animation morphing polygon shape per frame - chronological order

No comments:

Post a Comment