11
\$\begingroup\$

I've been doing some JavaScript for fun again. This time I tried my hand at drawing the Dragon curve - a neat fractal that consists of one line traveling across the plane, never crossing itself, and creating an interesting shape in the process.

There's several ways to go about generating the Dragon curve; for instance, one could apparently figure out the current turning direction as a function of the number of the turn. What I found easiest was one of the recursive approaches: For a dragon curve of "order" n, create two dragon curves of order n-1, then rotate one around the end point of the other. (A dragon curve of order 0 is just a line segment.)

I'm using three classes here (can you call them that in JS?): One for the Dragon itself, one for points, and one for "polylines", which are basically wrappers for an array of points.

Aside from the always-welcome general feedback, what I'd like to know is this:

  • Am I doing JavaScript OOP right? For my Markov text generator, I've made the main function act as a kind of constructor and then added methods by modifying the prototype object. This time my functions just directly return an object with the necessary attributes. I'm not sure how these two approaches differ, if at all.
  • Although the code seems reasonably fast for orders less than about 16, can you recommend any best practices on how to improve the performance?

function Point(x, y) {

    return {

        x: x,
        y: y,

        rotateAround: function(p) {
            var dx = p.x - this.x;
            var dy = p.y - this.y;

            this.x = p.x + dy;
            this.y = p.y - dx;
        }

    };

}

function Polyline(points) {

    return {

        points: points,

        getLastPoint: function() {
            return this.points[this.points.length - 1];
        },

        rotateAround: function(p) {
            for (var i = 0; i < this.points.length; i++) {
                this.points[i].rotateAround(p);
            }
        },

        removeLastPoint: function() {
            this.points.splice(-1, 1);
        },

        reverse: function() {
            this.points.reverse();
        },

        append: function(polyline) {
            this.points = this.points.concat(polyline.points);
        },

        draw: function(ctx) {
            ctx.beginPath();
            for (var i = 0; i < this.points.length - 1; i++) {
                ctx.lineTo(this.points[i + 1].x, this.points[i + 1].y);
            }
            ctx.stroke();
        }

    };
}

function DragonCurve(x, y, lineLength, order) {

    if (order === 0) {

        var points = [];
        points[0] = new Point(x, y);
        points[1] = new Point(x + lineLength, y);
        polyline = new Polyline(points);

    } else {

        var predecessor = new DragonCurve(x, y, lineLength, order - 1);
        polyline = clone(predecessor.polyline);

        var toAppend = clone(polyline);
        toAppend.rotateAround(polyline.getLastPoint());
        toAppend.removeLastPoint();     // the rotation point occurs in both polylines
        toAppend.reverse();             // ensure lines are drawn in the correct order
        polyline.append(toAppend);

    }

    return {

        polyline: polyline,

        draw: function(ctx) {
            ctx.moveTo(this.x, this.y);
            polyline.draw(ctx);
        }

    };

}

function clone(obj) { // from http://stackoverflow.com/a/122190/3972493

    if (obj === null || typeof(obj) !== 'object') {
        return obj;
    }

    var temp = obj.constructor();

    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            temp[key] = clone(obj[key]);
        }
    }

    return temp;

}

And here's how it might be used:

<canvas style="border: 1px solid gray;" id="my-canvas" width="800" height="600"></canvas>
<script type="text/javascript" src="DragonCurve.js"></script>
<script type="text/javascript">
    var ctx = document.getElementById('my-canvas').getContext('2d');
    var curve = new DragonCurve(185, 200, 2, 16);
    curve.draw(ctx);
</script>
\$\endgroup\$
7
\$\begingroup\$

First off, I'd like to say that your code is very neat and well-written for the most part.

Objects/Classes

I find that, in your classes, you return a unnamed object with all the desired fields and methods.

It would make more sense if, instead of returning an object, to set all the fields and methods in the class itself, and then return this. Advantages to this are:

  • The instances of your class won't be Object object - they will be Point object, which is more specific to the class. Also, this way, you can check whether an object is an instance of a certain class(you would be able to do this the way you are doing it as all instances would appear as Object)
  • It looks a lot more like a class, rather than just a function that returns an object.
  • You can custom-ly set whether a field/class will be public(this.) or private(var).
  • You can actually call the instances of your class, instances of your class. Right now, your "classes" are just returning instances of a general Object.

And, in fact, you don't even have to return this. Since no where in your code are you chaining methods, there is no point.

To sum that up, I re-wrote your Point class:

function Point(x, y) {
    this.x = x;
    this.y = y;

    this.rotateAround = function(p) {
        var dx = p.x - this.x;
        var dy = p.y - this.y

        this.x = p.x + dy;
        this.y = p.y - dx;
    }

    // no need for a return this here, classes already work that way in JavaScript
}

Yeah, it doesn't look that much different, it just follows practice in a better way.

Other than that, you code looks very Object Oriented - which is a good thing.

Speed

Like I said before, this code is very Object Oriented, so there isn't much to change in terms of speed and efficiency.

But if you want to, instead of storing instances of classes in variables, you can just use the instance of the class for what you need right there.

For example, in the first part of the first conditional of your DragonCurve class, you could change:

var points = [];
points[0] = new Point(x, y);
points[1] = new Point(x + lineLength, y);
polyline = new Polyline(points);

To:

polyline = new Polyline([new Point(x, y), new Point(x + lineLength, y)]);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.