JSON data and find returns undefined?

I’ve been following a video on YouTube where there is a tutorial on working with Points and Line Segments. After several days of building on this project suddenly a core section of my code fails and I have no idea why…

I’m easily 10 classes deep at this point and after tracing the data back from the error, it boils down to this code…

The error, specifically, is in bold below.

class Graph {
  constructor(points = [], segments = []) {
    this.points = points;
    this.segments = segments;
  }

  static load(info) {
    console.log(info)
    const points = info.points.map((i) => new Point(i.x, i.y));
    console.log('Points:', points);

    const segments = info.segments.map((i) => {
      **const p1 = points.find((p) => p.equals(i.p1));**
      const p2 = points.find((p) => p.equals(i.p2));

      console.log('i.p1:', i.p1, 'Found p1:', p1);
      console.log('i.p2:', i.p2, 'Found p2:', p2);

      return new Segment(p1, p2);
    });

    console.log('Segments:', segments);

    return new Graph(points, segments);
  }
... snipped ...

For some reason, the data from p1 is coming back as undefined and breaks the entire project.

This is the data that has been saved to localStorage using JSON.stringify

save() {
  localStorage.setItem('graph', JSON.stringify(graph));
}

Object
points: Array(2)
0: {x: 203, y: 413}
1: {x: 199, y: 202}

segments: Array(2)
0: Segment {p1: undefined, p2: Point}
1: Segment {p1: Point, p2: Point}

Does anyone have a suggestion? I’m lost as to why this is happening…

if p1 is undefined in this segment,

your code is telling it to find a point in points that matches undefined… which doesnt exist, because every item in points is defined.

Your problem appears to be whatever is filling the segments.

Thank you for your reply. I understand that the check for equality is failing because it is returning undefined. What I don’t understand is why the data that is pulled from localStorage is failing in the first place.

I have tried introducing a tolerance in the equality to see if perhaps floating point is introducing the error… but as it stands… p1 remains undefined…

equals(otherPoint, tolerance = 5) {
        const xDiff = Math.abs(this.x - otherPoint.x);
        const yDiff = Math.abs(this.y - otherPoint.y);

        console.log('Comparing:', this, otherPoint);
        console.log('xDiff:', xDiff, 'yDiff:', yDiff);

        return xDiff < tolerance && yDiff < tolerance;
    }
static load() {
        const data = JSON.parse(localStorage.getItem('graph'), Graph.reviver);
    
        if (!data || !Array.isArray(data.points) || !Array.isArray(data.segments)) {
            console.error('Invalid or missing data in localStorage.');
            return null; // Or handle the error in a way that fits your needs
        }
    
        const points = data.points.map((pointData) => new Point(pointData.x, pointData.y));
    
        const findPoint = (targetPoint) => {
            return points.find((p) => p.equals(targetPoint, 5)); // Adjust tolerance as needed
        };
    
        const segments = data.segments.map((segmentData) => {
            console.log('Segment data:', segmentData);
    
            // Log the entire points array
            console.log('All points:', points);
    
            // Attempt to find p1
            const p1 = findPoint(segmentData.p1);
    
            // Log the specific point that should be found
            console.log('Expected p1:', segmentData.p1);
            // Log the found p1
            console.log('Found p1:', p1);
    
            // Attempt to find p2
            const p2 = findPoint(segmentData.p2);
    
            // Log the specific point that should be found
            console.log('Expected p2:', segmentData.p2);
            // Log the found p2
            console.log('Found p2:', p2);
    
            if (p1 && p2) {
                return new Segment(p1, p2);
            } else {
                console.error('Invalid segment data:', segmentData);
                return null;
            }
        });
    
        // Filter out null segments if any (due to errors in data)
        const validSegments = segments.filter((segment) => segment !== null);
    
        console.log('Loaded points:', points);
        console.log('Loaded segments:', validSegments);
    
        return new Graph(points, validSegments);
    }

Is there another way to access the values of the Points other than map and find ?

At the moment, these changes now at least draw the first segment and fail on the second… I can create a new line but an error is thrown where point.draw() is not a function…

If it would be of greater assistance, I can provide the related classes… ?

Try this, because i’m trying to interpret where your problem is coming from specifically.

 const segments = info.segments.map((i) => {
    console.log(`Trying to find: ${i.p1.x}, ${i.p1.y} in points: `+JSON.stringify(points));

Throw the last line of that console output before the error in here. (If this line BECOMES the error, there’s a problem in your info, meaning whatever’s load’ing your info has bad data).

Trying to find: 316, 414 in points: [{“x”:203,“y”:413},{“x”:199,“y”:202}]
Trying to find: 203, 413 in points: [{“x”:203,“y”:413},{“x”:199,“y”:202}]
Loaded points: (2) [Point, Point]
Loaded segments: (2) [undefined, undefined]

const graphString = localStorage.getItem('graph');
const graphInfo = graphString ? JSON.parse(graphString) : null;
const graph = graphInfo ? Graph.load(graphInfo) : new Graph();

const world = new World(/*graph,*/ 100, 10);

With the suggestion you made, I had to comment out passing graph as it prevented the code from executing.

The data simply represents and L shape. 4 points with 2 corresponding line segments.

Well this point does not exist in your set of points. So yes, it will fail to find anything. Either you’ve somehow told it to search for something that doesnt exist, or your set of points is incomplete. So… onward to figure out which of those directions has misfired?

Maybe it would be easier to visualize the project? https://codepen.io/ifaus/pen/VwRYeWQ

This is fairly complex, and I’m not entirely sure how to modify the data that is stored in localStorage so that I can correct the bad data… ?

Hint: You can execute code in the console :wink:

Get the data from localstorage, copy it to a text editor, make the necessary changes, and set the data back into localStorage. Then reload the page.

I didn’t know you could do that. :sweat_smile:

How do I format it so it does not report undefined?

localStorage.setItem(‘graph’, JSON.stringify(points:[{x:200,y:400},{x:300,y:200}],segments:[{p1:{x:200,y:400},p2:{x:300,y:200}}]}))

I am trying to create a single segment with 2 points to correct the data.

Well I just changed the data…

const dataToSave = {
  points: [{ x: 200, y: 400 }, { x: 300, y: 200 }],
  segments: [{ p1: { x: 200, y: 400 }, p2: { x: 300, y: 200 } }]
};

// Save to localStorage
localStorage.setItem('graph', JSON.stringify(dataToSave));

Now the console reports

Trying to find: 200, 400 in points: [{"x":200,"y":400},{"x":300,"y":200}]
graph.js:39 Loaded points: (2) [Point, Point]
graph.js:40 Loaded segments: [undefined]

and no segments are displayed. ?

You’re missing the matching { for the red } (it should be before points)

Did you un-comment all the previous code? Where is the 'found p1" and such?

My apologies. The segment now renders correctly from localStorage.

Segment data: {p1: {…}, p2: {…}}
graph.js:37 All points: (2) [Point, Point]
graph.js:43 Expected p1: {x: 200, y: 400}
graph.js:45 Found p1: Point {x: 200, y: 400}
graph.js:51 Expected p2: {x: 300, y: 200}
graph.js:53 Found p2: Point {x: 300, y: 200}
graph.js:66 Loaded points: (2) [Point, Point]
graph.js:67 Loaded segments: [Segment]
graph.js:156 Uncaught TypeError: point.draw is not a function

However, if I select a point and attempt to draw a new Segment… I get a new error for this.p2.equals is not a function

Thank you very kindly for your assistance with this.

Where is your new error occuring? We’re in a new section of code now.

According the console report…

In Graph

    draw(ctx) {
        for (const segment of this.segments) {
            if (segment instanceof Segment) {
                segment.draw(ctx);
            }
        }

        for (const point of this.points) {
            point.draw(ctx);
        }
    }

In Point

equals(otherPoint, tolerance = 5) {
        const xDiff = Math.abs(this.x - otherPoint.x);
        const yDiff = Math.abs(this.y - otherPoint.y);

        return xDiff < tolerance && yDiff < tolerance;
    }

In GraphEditor

display() {
        this.graph.draw(this.ctx);

        if (this.hovered) {
            this.hovered.draw(this.ctx, { fill: true });
        }

        if (this.selected) {
            const intent = this.hovered ? this.hovered : this.mouse;
            new Segment(this.selected, intent).draw(this.ctx, { dash: [3,3] });
            this.selected.draw(this.ctx, { outline: true });
        }
    }

I’m… going to guess from the error message being “this.p2.equals”, your error is occuring in Segment’s draw function…
A Point would not have a “p2”, the only thing that should have a p2 is a segment.

You’re absolutely correct. That was an oversight on my behalf.

class Segment {
    constructor(p1, p2) {
        this.p1 = p1;
        this.p2 = p2;
    }

    equals(segment) {
        return this.includes(segment.p1) && this.includes(segment.p2);
    }

    includes(point) {
        return this.p1.equals(point) || this.p2.equals(point);
    }

    draw(ctx, { width = 2, color = 'black', dash=[] } = {}) {
        ctx.beginPath();
        ctx.lineWidth = width;
        ctx.strokeStyle = color;
        ctx.setLineDash(dash);
        ctx.moveTo(this.p1.x, this.p1.y);
        ctx.lineTo(this.p2.x, this.p2.y);
        ctx.stroke();
        ctx.setLineDash([]);
    }
}

So something is calling includes, which… unless you’ve got something else calling it, would be equals… so something called a Segment object’s equals.

If i had to guess… it’s bacwards from here:

What’s the definition of ctx’s beginPath code? what is ctx?

I suspect the stack trace is currently…
Segment.includes()
Segment.equals()
(Unknown Type) ctx.beginPath()
Segment.draw()
Graph.draw()
GraphEditor.display()

Essentially this is what happens from here forward.

ctx is the canvas.getContext(‘2d’) being passed as a paramater.

A segment is created. A polygon is created using the segment as a skeleton. A wrapper, coded as an envelope, is wrapped around the segment.

class Polygon {
    constructor(points){
        this.points = points;
        this.segments = [];
        for (let i=1; i <= points.length; i++) {
            this.segments.push(
                new Segment(points[i-1], points[i % points.length]));
        }
    }
... snipped....
drawSegments(ctx) {
        for (const seg of this.segments) {
            seg.draw(ctx, { color: getRandomColor(), width: 5 });
        }
    }

    draw(ctx, { stroke = 'blue', lineWidth = 2, fill = 'rgba(0,0,255,.2)' } = {}) {
        ctx.beginPath();
        ctx.fillStyle = fill;
        ctx.strokeStyle = stroke;
        ctx.lineWidth = lineWidth;
        ctx.moveTo(this.points[0].x, this.points[0].y);
        for (let i=1; i < this.points.length; i++) {
            ctx.lineTo(this.points[i].x, this.points[i].y);
        }
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
    }
class Envelope {
    constructor (skeleton, width, roundness = 1) {
        this.skeleton = skeleton;
        this.poly = this.#generatePolygon(width, roundness);
    }

    #generatePolygon(width, roundness) {
        const { p1, p2 } = this.skeleton;
... snipped ...
        return new Polygon(points);
    }

    draw(ctx) {
        this.poly.draw(ctx);
        this.poly.drawSegments(ctx);
    }
}

So it’s a little unclear to me which of those calls the draw a Segment is the one triggering the error. I can only assume it is the base Segment draw() and not the others down the line…