Draw an Array of different Images on an array of different xy-coordinates, in order

Viewed 203

I have a canvas:

<canvas id="canvas" width="500px" height="500px"></canvas>

I have an array of Ribbon Images being pulled from a selection of checkboxes on my page:

<th><input value="0001" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0002" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0003" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0004" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0005" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>


let RibbonsArray = Array.from(document.getElementsByClassName("Ribbons"));
let CheckedRibbons = RibbonsArray.filter(element => element.checked == true);

I have a few arrays of x/y coordinates:

var arr1 =[
[122, 235]
];
var arr2 =[
[69, 235],
[175, 235]
];
var arr3 =[
[16, 235],
[122, 235],
[228, 235]
];
var arr4 =[
[122, 220],
[16, 250],
[122, 250],
[228, 250]
];
var arr5 =[
[69, 220],
[175, 220],
[16, 250],
[122, 250],
[228, 250]
];

I have the following script:

function ClearLeft(){
    let clearLeft = canvas.height - 5;
    ctx.fillRect(2, 2, 345, clearLeft);
};

function ReDraw(){
    ClearLeft();
    //RIBBONS
    let CheckedRibbons = RibbonsArray.filter(element => element.checked == true);
        if (CheckedRibbons.length == arr1.length){
            CheckedRibbons.forEach(Ribbon => {
                let x = arr1[0][0];
                let y = arr1[0][1];
                let img = new Image();
                img.src = `Ribbons/${Ribbon.value}.png`
                img.addEventListener('load', () => {
                ctx.drawImage(img, x, y);
                });
            });
        } else if (CheckedRibbons.length == arr2.length){
        //When 2 Ribbons are checked -> Draw Ribbon 1 at arr2[0] which is [69, 235].
        //                           -> Draw Ribbon 2 at arr2[1] which is [175, 235].

        } else if (CheckedRibbons.length == arr3.length){
        //When 3 Ribbons are checked -> Draw Ribbon 1 at arr3[0] which is [16, 235].
        //                           -> Draw Ribbon 2 at arr3[1] which is [122, 235].
        //                           -> Draw Ribbon 3 at arr3[2] which is [228, 235].
        
        }
};//REDRAW ENDS

As you can see for the case where 2 or 3 Ribbons are selected, I'm trying to figure out a way to draw each Ribbon sitting in "CheckedRibbons"....in order....at each corresponding index of the array that matches in length (that's a mouthful eh, I hope I didn't mess that up). I have tried nested "forEach" in different configurations, I tried playing around with the indices. I just can't wrap my head around this.

1 Answers

There's a few ways to do it easier if you change up the data structure a bit. Upside is your code doesn't need to change or be repeated, it will all rely on the data changing which is a more maintainable approach in the long run.

Here's two different examples of what I mean, there are of course lots of ways to structure your data these are just two ways I chose for the answer. Full working code below each example (I used a test image).

First example uses an array, of arrays... of arrays. Not the most clear option representing the data just due to the fact you always have to access the initial data at the 0 index like so,

const renderGroups = [
  [
    [122, 235]
  ],
  [
    [69, 235],
    [175, 235]
  ],
  [
    [16, 235],
    [122, 235],
    [228, 235]
  ],
  [
    [122, 220],
    [16, 250],
    [122, 250],
    [228, 250]
  ],
  [
    [69, 220],
    [175, 220],
    [16, 250],
    [122, 250],
    [228, 250]
  ]
];

// access data group, since renderGroups is an array all data lives on the 0 index.
const renderGroup = renderGroups.filter(group => group.length === CheckedRibbons.length)[0]; 

Example of why this is the case, when we filter looking for the array with a length of 2 within resultsGroup

renderGroups.filter(el => el.length === 2);

this is what resultsGroup looks like.

[
  [
    [69, 235],
    [175, 235]
  ]
]

That's due to filter returning an array which means to access that very first group, we need to do Array[0] which then turns into

  [
    [69, 235],
    [175, 235]
  ]

Which then gives us our normal pairs we're expecting. So Array[0] for example is:

[69, 235]

Which is another array, so we need to do Array[0][0] to get 69, ect.

let RibbonsArray = Array.from(document.getElementsByClassName("Ribbons"));
let CheckedRibbons = RibbonsArray.filter(element => element.checked == true);

const renderGroups = [
  [
    [122, 235]
  ],
  [
    [69, 235],
    [175, 235]
  ],
  [
    [16, 235],
    [122, 235],
    [228, 235]
  ],
  [
    [122, 220],
    [16, 250],
    [122, 250],
    [228, 250]
  ],
  [
    [69, 220],
    [175, 220],
    [16, 250],
    [122, 250],
    [228, 250]
  ]
];

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

function ClearLeft() {
  let clearLeft = canvas.height - 5;
  ctx.fillRect(2, 2, 345, clearLeft);
};

function ReDraw() {
  ClearLeft();
  //RIBBONS
  let CheckedRibbons = RibbonsArray.filter(element => element.checked == true);
  const renderGroup = renderGroups.filter(group => group.length === CheckedRibbons.length)[0];

  CheckedRibbons.forEach((Ribbon, idx) => {
    let x = renderGroup[idx][0];
    let y = renderGroup[idx][1];
    let img = new Image();
    img.src = 'https://i.picsum.photos/id/130/200/200.jpg?hmac=pMGv0FZ4yiuwOp40JbbSUg8DSKRdq2Rx70VXtqMrbjI';
    img.addEventListener('load', () => {
      ctx.drawImage(img, x, y);
    });
  });
}
// REDRAW ENDS
<canvas id="canvas" width="500px" height="500px"></canvas>
<th><input value="0001" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0002" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0003" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0004" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0005" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>

The second option is an object with named group properties which hold arrays, not as generic a little more manageable and readable however. The downside is accessing the property relies on precise naming which can lead to errors if you're missing an expected property, etc.

const renderGroups = {
  group1: [
    [122, 235]
  ],
  group2: [
    [69, 235],
    [175, 235]
  ],
  group3: [
    [16, 235],
    [122, 235],
    [228, 235]
  ],
  group4: [
    [122, 220],
    [16, 250],
    [122, 250],
    [228, 250]
  ],
  group5: [
    [69, 220],
    [175, 220],
    [16, 250],
    [122, 250],
    [228, 250]
  ]
};

// access data group
const renderGroup = renderGroups[`group${CheckedRibbons.length}`];

let RibbonsArray = Array.from(document.getElementsByClassName("Ribbons"));
let CheckedRibbons = RibbonsArray.filter(element => element.checked == true);

const renderGroups = {
  group1: [
    [122, 235]
  ],
  group2: [
    [69, 235],
    [175, 235]
  ],
  group3: [
    [16, 235],
    [122, 235],
    [228, 235]
  ],
  group4: [
    [122, 220],
    [16, 250],
    [122, 250],
    [228, 250]
  ],
  group5: [
    [69, 220],
    [175, 220],
    [16, 250],
    [122, 250],
    [228, 250]
  ]
};

const CheckedRibbons = RibbonsArray.filter(element => element.checked == true);
const renderGroup = renderGroups[`group${CheckedRibbons.length}`];

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

function ClearLeft() {
  let clearLeft = canvas.height - 5;
  ctx.fillRect(2, 2, 345, clearLeft);
};

function ReDraw() {
  ClearLeft();
  //RIBBONS
  const CheckedRibbons = RibbonsArray.filter(element => element.checked == true);
  const renderGroup = renderGroups[`group${CheckedRibbons.length}`];

  console.log(renderGroup)

  CheckedRibbons.forEach((Ribbon, idx) => {
    let x = renderGroup[idx][0];
    let y = renderGroup[idx][1];
    let img = new Image();
    img.src = 'https://i.picsum.photos/id/130/200/200.jpg?hmac=pMGv0FZ4yiuwOp40JbbSUg8DSKRdq2Rx70VXtqMrbjI';
    img.addEventListener('load', () => {
      ctx.drawImage(img, x, y);
    });
  });
}
// REDRAW ENDS
<canvas id="canvas" width="500px" height="500px"></canvas>
<th><input value="0001" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0002" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0003" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0004" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>
<th><input value="0005" class="Ribbons" type="checkbox" onchange="ReDraw()"></th>

Related