meyerweb.com

Skip to: site navigation/presentation
Skip to: Thoughts From Eric

Seeking JavaScript Help

Even though it turned out that there is no simple solution for the math problem I posted, I learned a fair amount from the fantastic responses—thanks, everyone!—and eventually came up with a solution that worked for me.  (I’d like to say it was one of the iterative approaches posted, but none of them worked for me.  In the end, I brute-forced it.)  I’m hoping for a different outcome with my next question, which is about JavaScript.

Consider the following structure, which is a much-edited-down version of part of the HYDEsim code:

function Detonation(name,lat,lon,yield) {
    var scale = Math.pow(yield,1/3);
    var gz = new GLatLng(lat,lon);
    this.name = name;
    this.lon = lon;
    this.lat = lat;
    this.gz = gz;
    this.yield = yield;
    this.overpressure = {
        p30 : 0.108 * scale,
        p15 : 0.153 * scale,
        p10 : 0.185 * scale,
         p5 : 0.281 * scale,
         p1 : 0.725 * scale
    };
    this.psi30 = {
        radius: 0.108 * scale,
        overlay : {
            points: makePoints(this.gz,0.108 * scale)
        }
    };
    this.psi15 = {
        radius: 0.153 * scale,
        overlay : {
            points: makePoints(this.gz, 0.153 * scale)
        }
    };
    this.therm20 = {
        radius: thermDist(20,this.yield,0.33,conditions.visibility),
        overlay : {
            points: makePoints(
                this.gz,
                thermDist(20,this.yield,0.33,conditions.visibility)
            )
    };
    // ...and so on...
}

There are two things I’ve tried and failed to do.  And tried, and tried, and tried; and failed, and failed, and failed.

  1. Eliminate the redundant calculations of radii.  Note how I define a radius property in each case?  And then have to not use it when I create the overlay?  It seems like there must be a way to just define the value once for each subsection and then use it as many times as needed within that context.  How?
  2. How do I make it so that all those properties and overlays and such automatically recalculate any time one of the “upper-level” terms changes?  As in, once I’ve created a new Detonation object det, how can I set things up so declaring det.yield = 250; will trigger recalculation of all the other pieces of the object?  At present, I’m just blowing away the existing det and creating a whole new one, which just seems clumsy and silly.  I have to believe there’s a better way.  I just can’t find it.

Please note: tossing off comments like “oh, just instantiate a mixin constructor with an extra closure” will be of no help at all, because I don’t understand what those terms mean.  Hell, I’m not even sure I used the words “object” and “property” correctly in my explanation above.  Similarly, pointing me to tutorials that use those terms liberally is unlikely to be of help, since the text will just confuse me.  Sample code (whether posted or in a tutorial) will help a great deal, because it will give me something to poke and prod and dissect.  That’s how I’ve always learned to program.  Actually, it’s how I’ve always learned anything.

As well, I’m absolutely willing to believe that there are much, much better ways to structure the object, but right now I really need to learn how those two things are accomplished in the context of what I already have.  Once I get familiar with those and finish up some other work, I can start thinking about more fundamental overhauls of the code (which needs it, but not now).

I really appreciate any concrete help you can give me.

Addendum: if you leave code in a comment, please just wrap it in a code element and use whatever indentation you like.  The indentation won’t show up when the post goes up, but I’ll go in after and wrap the code in a pre and then everything will be fine.  Sorry to those who’ve already gone to the effort of posting with indents or nbsp entities to try to preserve indentation!  As soon as I can dig up the right preference panel or plugin to allow pre in comments, I’ll do that, but for now I’ll manually edit in the needed pres as comments are added.  THanks, and again, apologies to those who posted before I made this clear!

21 Responses»

    • #1
    • Comment
    • Fri 16 Jan 2009
    • 0645
    Pete B wrote in to say...

    Looks like you might need to make use of the prototype object. Crude example:

    // Your constructor
    function Detonation (name,lat,lon,yield) {
      // Instance specific properties
    }
    
    // Prototype methods and properties inherited by all instances,
    Detonation.prototype = {
      defaultValue: 3.142,
      setYield: function () {
         this.yield = yield;
      }
    }
    
    var det = new Detonation(1, 2, 3, 4);
    det.setYield(21);
    
    • #2
    • Comment
    • Fri 16 Jan 2009
    • 0700
    Tom wrote in to say...

    As Pete B recommends, define a setter instead of a way to directly access the variable. setYeld should set the value for yeld and should also recompute things that depend on it (and if necessary assign those values using their own setters, but be careful not to fall into the trap of infinite recursive function calls) and announce the change to it’s children so they may recalculate whatever they need.

    • #3
    • Comment
    • Fri 16 Jan 2009
    • 0851
    tommey wrote in to say...

    Q/3.07FY=[(1/D^2)]+[1.4(D/V)e^-2(D/V)]/D^2

    I think you can solve this equation by integration..substitution method (been long time since calculus). Maybe even a double integration but don’t think its that complicated of an equation. I would say integration by substitution method. Sorry I forgot all that good calculus stuff. By the form of the equation looks like some sort of financial equation to calculate rate over time. Good Luck.

    • #4
    • Comment
    • Fri 16 Jan 2009
    • 0854
    Mike Duff wrote in to say...

    I would think what your doing for this.overpressure would work for radius, example:

    
    this.psi30 = {
            radius: this.overpressure.p30,
            overlay : {
                points: makePoints(this.gz, this.overpressure.p30)
            }
        };
    
    

    if that code populates the same results as the old way, then I would create something like:

    
    this.radCalcs = {
       psi30: 0.108 * scale,
       psi15: 0.153 * scale,
       therm20: thermDist(20,this.yield,0.33,conditions.visibility)
    }
    
    
    • #5
    • Comment
    • Fri 16 Jan 2009
    • 0905
    Paul Chaplin wrote in to say...

    Hi Eric

    From Pete B’s post (originally in a <blockquote>, but that played havoc with the formatting):

    // Prototype methods and properties inherited by all instances,
    Detonation.prototype = {
        defaultValue: 3.142,
        setYield: function () {
            this.yield = yield;
        }
    }
    var det = new Detonation(1, 2, 3, 4);
    det.setYield(21);

    …I believe (but might be wrong; my JS is rusty) that the above won’t quite work, as you still need to have a parameter, like so:

    Detonation.prototype = {
        setYield: function(yield_) {
            this.yield_ = yield_
    }
    }

    Also, note that yield is a reserved word in JavaScript 1.7, which probably won’t bite you now, but might later (it has to be programmatically enabled in Firefox 2.0+, but it’s worth bearing in mind). I suggest that you name it something like yield_ or det_yield instead to avoid any problems.

    I’m afraid that I don’t understand where/how you’re using the radius values, as your posted code shows them being defined, but — so far as I can see — never used. I’d need to know more about how they’re used to be able to suggest ways to refactor them (=reorganise/restructure, in coding jargon).

    Good luck, and I’ll check back to see how you go.

    • #6
    • Comment
    • Fri 16 Jan 2009
    • 0915
    Andrew wrote in to say...

    you can also specify a general setter for all “this.” variables like so:

    this.setVals = function(params){
        for(var p in params){
            this[p] = params[p]
        }
    }

    obviously then you can set any number of vars that you’d like, and add conditional code to be executed if a specific param is set (like yield).

    - Andrew

    • #7
    • Comment
    • Fri 16 Jan 2009
    • 0932
    Eddie Welker wrote in to say...

    This may not work… so you’ve got a template there for creating a Detonation object. But it’s important in what context you create that object, so this may not be right.

    1) If you’re essentially calculating the radius measurements when you’re creating the this.overpressure object, you should be able to refer specifically to the “this.overpressure.p30″. So in this.psi30, you can simply write “radius: this.overpressure.p30″. You can do the same 2 lines down, specifying “points: makePoints(this.gz, this.overpressure.p30)”. As you say, if you’ve referred to it once, you don’t need to recalculate.

    2) For this, you can’t really restructure your existing code to do it, you’ll need to change the structure of the Detonation obj. Here’s why…. As of the moment, when you create your object, you’re doing all of these calculations and wiring the results of the calculations into your Detonation object. To dynamically recalculate (like if you were to set a new yield), you’d have to create a new function that does all of the (needed) recalculations again. At this point, you’d then want to rewrite your code so when you create a new object, you also call this new function (so you don’t have 2 pieces of code that do the same thing, in multiple places.. it’s easier to maintain, etc.).

    However, a better solution (though you may not be able to… I’m only looking at an abbreviated version), would be to simply store the 4 or so parameters you pass to the Detonation object (name, lat, lon, yield). You then write functions that automatically create these values for you only when you ask for them. That way, you can set a new yield, and not waste any time recalculating then… you could wait to recalculate the values only when you need to.

    The ideal solution is probably somewhere inbetween…. where you store the 4 parameters, as well creating a function to re-calculate the radius measurements each time… since you use those so often, it’d be more efficient (as you said in #1) to calculate them just once.

    I know you said you’d like to look at code, and this is just a high-level overview, but let us know what kinda changes you can make, and we can try to get to code.

    • #8
    • Comment
    • Fri 16 Jan 2009
    • 1049
    Paul D. Waite wrote in to say...

    If this problem doesn”t get solved here, you could try posting it on Stack Overflow.

    • #9
    • Comment
    • Fri 16 Jan 2009
    • 1049
    bruce wrote in to say...

    Well, I’d instantiate a binding function, thereby avoiding the double recursion closure bug that you’ve got because you failed to globally initialise the object prototype.

    Or I’d hit it with a hammer and curse.

    • #10
    • Comment
    • Fri 16 Jan 2009
    • 1121
    Stefan Seiz wrote in to say...

    Well, I”d instantiate a binding function, thereby avoiding the double recursion closure bug that you”ve got because you failed to globally initialise the object prototype.

    I assume this is exactly the kind of comment Eric loves to hear (no offense Bruce i appreciate your comment nonetheless). I am in the same shoes as Eric and appreciate Eric’s calls for help very much. So much to learn here if you are able to understand the comments. Great!

    • #11
    • Comment
    • Fri 16 Jan 2009
    • 1136
    Michael Bester wrote in to say...

    Along the lines of what Eddie Welker suggested, instead of setting all the values when you instantiate the Detonation object, you calculate individual values as needed. This basically amounts to wrapping functions around everything. This code is untested, but should get you close:

    
    function Detonation(name,lat,lon,yield) {
    
    	this.name = name;
    	this.lon = lon;
    	this.lat = lat;
    	this.yield = yield;
    
    	this.getScale = function() {
    		return Math.pow(this.yield, 1/3);
    	};
    
    	this.getGZ = function() {
    		return new GLatLng(this.lat, this.lon);
    	};
    
    	this.overpressure = {
    		p30 : function() {
    			return 0.108 * this.getScale();
    		},
    		p15 : function() {
    			return 0.153 * this.getScale();
    		},
    		p10 : function() {
    			return 0.185 * this.getScale();
    		},
    		p5 : function() {
    			return 0.281 * this.getScale();
    		},
    		p1 : function() {
    			return 0.725 * this.getScale();
    		}
    	};
    
    	this.psi30 = {
    		radius: function() {
    			return this.overpressure.p30()
    		},
    		overlay : {
    			points: function() {
    				return makePoints(this.getGZ(), this.overpressure.p30());
    			}
    		}
    	};
    
    	this.psi15 = {
    		radius: function() {
    			return this.overpressure.p15()
    		},
    		overlay : {
    			points: function() {
    				return makePoints(this.getGZ(), this.overpressure.p15());
    			}
    		}
    	};
    
    	this.therm20 = {
    		radius: function() {
    			return thermDist(20, this.yield, 0.33, conditions.visibility),
    		},
    		overlay : {
    			points: function() {
    				return makePoints(
    					this.gz,
    					thermDist(20, this.yield, 0.33, conditions.visibility)
    				)
    			}
    		};
    		// ...and so on...
    	}
    
    

    This also takes care of issue #1 in that you set your radii calculations once and refer to them as needed.

    • #12
    • Comment
    • Fri 16 Jan 2009
    • 1136
    Kent Brewster wrote in to say...

    Right there with you, Eric. Any theoretical discussion of programming quickly turns into the last line of Dr. Seuss’s Fox In Socks for me. (From faulty memory: when the tweedle beetles battle in a bottle with their paddles, and the bottle’s on a poodle and the poodle’s eating noodles, THIS is what we call a muddle puddle tweedle beetle poodle noodle bottle paddle battle. Or something like that.)

    • #13
    • Comment
    • Fri 16 Jan 2009
    • 1139
    Rich Waters wrote in to say...

    Not really knowing the context of this code makes things a little tougher, but I would probably do something along these lines:

    
    function Detonation(name, lat, lon, yield) {
    	var gz = new GLatLng(lat, lon);
    	this.name = name;
    	this.lon = lon;
    	this.lat = lat;
    	this.gz = gz;
    	this.setYield(yield);
    }
    
    Detonation.prototype = {
    	pressures: {
    		p30: 0.108,
    		p15: 0.153,
    		p10: 0.185,
    		p5: 0.281,
    		p1: 0.725
    	},
    	setYield: function(yld) {
    		this.yield = yld;
    		this.scale = Math.pow(yld, 1 / 3);
    	},
    	overpressure: function(psi) {
    		return this.pressures[psi] * this.scale;
    	},
    	getPressure: function(pres) {
    		var p = this.overpressure(pres);
    		return {
    			radius: p,
    			overlay: {
    				points: makePoints(this.gz, p)
    			}
    		}
    	},
    	psi30: function() {
    		return this.getPressure('p30');
    	},
    	psi15: function() {
    		return this.getPressure('p15');
    	},
    	therm20: function() {
    		return {
    			radius: thermDist(20, this.yield, 0.33, conditions.visibility),
    			overlay: {
    				points: makePoints(this.gz, thermDist(20, this.yield, 0.33, conditions.visibility))
    			}
    		}
    	}
    }
    
    

    Sorry if the formatting didn’t come through, I tried a few tags and combinations and couldn’t get it to look right in the preview.

    • #14
    • Comment
    • Fri 16 Jan 2009
    • 1208
    Anthony Mills wrote in to say...

    Sorry for the underlines but it just does not format correctly otherwise. Argh!

    Anyway, the idea here is you use inner functions to calculate the main bits. When you want to access stuff, it’s like:

    var det = new Detonation("blah", 0, 0, 42);
    var radius = det.destructionAtPsi[30].radius;
    
    // iterate through overpressure values
    for (var overpressure in det.destructionAtPsi) {
        var overlay = det.destructionAtPsi[overpressure].overlay;
    }
    

    This way you only define your PSI values and other factors once.

    Anyway, the code is:

    function Detonation(name, lat, lon, yield) {
        this.name = name;
        this.lat = lat;
        this.lon = lon;
        this.gz = new GLatLng(lat, lon);
        this.yield = yield;
        recalculate();  // cheaper than doing setThis(); setThat(); all of which will call recalculate()
    }
    
    Detonation.prototype.setYield = function (yield) {
        this.yield = yield;
        recalculate();
    };
    
    Detonation.prototype.recalculate = function () {
    
        function makeDestruction() {
            var result = {};
            for (var i = 0; i < arguments.length; i++) {
                var radius = arguments[i].factor * scale;
                result[arguments[i].psi] = {
                    radius: radius,
                    overlay: { points: makePoints(this.gz, radius) }
                };
            }
            return result;
        }
    
        function makeTemperature() {
            var result = {};
            for (var i = 0; i < arguments.length; i++) {
                var radius = thermDist(arguments[i].celsius, this.yield, arguments[i].factor, conditions.visibility);
                result[arguments[i].celsius] = {
                    radius: radius,
                    overlay: { points: makePoints(this.gz, radius) }
                }
            }
            return result;
        }
    
        var scale = Math.pow(yield, 1/3);
        this.destructionAtPsi = makeDestruction.call(this,  // need ".call(this," to preserve "this" in inner function
            {psi: 30, factor: 0.108},
            {psi: 15, factor: 0.153},
            {psi: 10, factor: 0.185},
            {psi: 5, factor: 0.281},
            {psi: 1, factor: 0.725}
        );
        this.temperature = makeTemperature.call(this,
            {celsius: 20, factor: 0.33},
            ...
        );
    };
    
    • #15
    • Comment
    • Fri 16 Jan 2009
    • 1211
    Anthony Mills wrote in to say...

    Here’s a version of the code with underlines for indents:

    function Detonation(name, lat, lon, yield) {
    ____this.name = name;
    ____this.lat = lat;
    ____this.lon = lon;
    ____this.gz = new GLatLng(lat, lon);
    ____this.yield = yield;
    ____recalculate(); // cheaper than doing setThis(); setThat(); all of which will call recalculate()
    }
    Detonation.prototype.setYield = function (yield) {
    ____this.yield = yield;
    ____recalculate();
    };
    Detonation.prototype.recalculate = function () {
    ____function makeDestruction() {
    ________var result = {};
    ________for (var i = 0; i < arguments.length; i++) {
    ____________var radius = arguments[i].factor * scale;
    ____________result[arguments[i].psi] = {
    ________________radius: radius,
    ________________overlay: { points: makePoints(this.gz, radius) }
    ____________};
    ________}
    ________return result;
    ____}
    ____function makeTemperature() {
    ________var result = {};
    ________for (var i = 0; i < arguments.length; i++) {
    ____________var radius = thermDist(arguments[i].celsius, this.yield, arguments[i].factor, conditions.visibility);
    ____________result[arguments[i].celsius] = {
    ________________radius: radius,
    ________________overlay: { points: makePoints(this.gz, radius) }
    ____________}
    ________}
    ________return result;
    ____}
    ____var scale = Math.pow(yield, 1/3);
    ____this.destructionAtPsi = makeDestruction.call(this, // need ".call(this," to preserve "this" in inner function
    ________{psi: 30, factor: 0.108},
    ________{psi: 15, factor: 0.153},
    ________{psi: 10, factor: 0.185},
    ________{psi: 5, factor: 0.281},
    ________{psi: 1, factor: 0.725}
    ____);
    ____this.temperature = makeTemperature.call(this,
    ________{celsius: 20, factor: 0.33},
    ________...
    ____);
    };

    Can we please get a markup engine that DOESN’T SCREW UP CODE IN CODE TAGS AAAAAAAAAAAARGH.

    Sorry, just had to vent.

    • #16
    • Comment
    • Fri 16 Jan 2009
    • 1221
    VanillaMozilla wrote in to say...

    “Even though it turned out that there is no simple solution for the math problem I posted….”

    But there is a simple solution: the value of D that satisfies the equation. What can be simpler than that?

    Maybe a few comments at this point will make such problems easier in the future.

    There is a simple numerical solution but no ALGEBRAIC solution. And your brute-force method is exactly how such equations are solved, that is to say, by numerical approximation. Now there are some mathematical tricks to improve the efficiency of arriving at a solution. Two simple methods come to mind.

    1. g(D) = Q-f(D). At what value of D does g(D) = 0? Trial and error. (Extra points if you can efficiently estimate the next guess. The pros use the slope g’(D) to improve their aim. And if you can’t calculate the slope algebraically, you can estimate it numerically.)

    2. D approximately = f(D). At what point does D=f(D)? This is a helpful trick that you can use sometimes. If you know the approximate range of values of the various parameters, you may be able to rewrite the equation in a way such that f(D) does not depend much on D. Then you can iterate and get the value in a hurry.

    In your previous example, if D/V << 1 and both are positive, then as a first approximation maybe you can ignore the exponential. Then D^2=3.07FY(1+1.4ED/V)/Q, where E is that nasty exponential. You can solve that for D algebraically to get your approximation. Make a first guess, find the value of E, plug all the numbers into the equation to get your next guess. Plug that value back into E to get the next guess. Repeat until satisfied. If D/V is not sufficiently small, it might not work, as you will find out very quickly.

    In both cases, you have to watch out for the special cases Q=0 and V=0. (The function is undefined at those values!) If you are iterating and encounter those values (or cross those values), then obviously there is trouble.

    • #17
    • Comment
    • Fri 16 Jan 2009
    • 1259
    Anthony Mills wrote in to say...

    Oops, I mean this.recalculate(), of course. Not just recalculate().

    • #18
    • Comment
    • Fri 16 Jan 2009
    • 1719
    Adam van den Hoven wrote in to say...

    Here’s my kick at the can. I’ve only rewritten what it given in the example, I assume that more can be added later.:

    window.Detination = (function(){
      function makeOverPressure(scale){
        var OP_MULT = {
          30 : 0.108,
          15 : 0.153,
          10 : 0.185,
          5 : 0.281,
          1 : 0.725,
        }, res = {};
        for( mult in OP_MULT ){
          res[mult] = OP_MULT[mult] * scale;
        }
        return res;
      }
      function makeThermalDistances( yield, visibility ){
        var TD_MULT = {
          20 : 0.33
        }, res = {};
        for( mult in TD_MULT ){
          res[mult] = thermDist( mult, yield, TD_MULT[mult], visibility);
        }
        return res;
      }
      function makeOverlays(distance, center) {
        var res = {};
        for( dist in distance ){
          res[dist] = {
            radius: distance[dist],
            overlay{
              points: makePoints(center, distance[dist])
            }
          }
        }
        return res;
      }
      return function (name,lat,lon,yield){
        var scale = Math.pow(yield,1/3),
        gz = new GLatLng(lat,lon),
        op = makeOverPressure(scale),
        td = makeThermalDistances( yield, conditions.visibility);
        return {
          psi: makeDistances( op ),
          thermal: makeDistances( td )
        }
      }
    }){};
    
    

    Gaak! nbsp to get the indenting.

    First of all, Detination uses a variation on Crockfords power constructor so don’t call it with new (but it should work regardless). Second the object it creates has two members, psi and therm. These each have a number of numerically identified members each of which contain the radius and points. You would do the following:

    
    foo = Detenation( 'example', 50, 120, 200 );
    foo.psi[10].radius;
    foo.psi[10].overlay.points;
    foo.therm[20].overlay.points;
    
    

    I’ll leave it up to the reader to add the other useful bits in (like the name, yield, etc).

    This has a number of advantages. First of all the global scope is kept pristine. Second, those things that are fixed (the different PSI levels, for example) and the functions that use them are only created once. Third its easy to add levels, simply add members to the OP_MULT and TD_MULT constants.

    • #19
    • Comment
    • Fri 16 Jan 2009
    • 2110
    Eric Meyer wrote in to say...

    Wow! You all are 125% awesome, with a side order of fantastic. When I finally decide to give HYDEsim a complete overhaul, I know who to e-mail. Thank you!

    (Also, I think pre is allowed in comments even if it isn’t listed as accepted. Someone try it and we’ll see. If not, sorry! I’ll get it on the list.)

    • #20
    • Comment
    • Fri 16 Jan 2009
    • 2316
    JFSIII wrote in to say...

    Piggy-backing on comments and code from Micheal Bester and Eddie Welker:

    Detonation1 makes small changes internal to Detonation, but the code which uses it (det.whatever) should remain unchanged.

    However, it only addresses your first of your requests:

    
    function Detonation1(name,lat,lon,yld)
    {
      var pow = (1/3), scale = Math.pow(yld,pow);
    
      // arbitrary values so I could test
      var conditions = { visibility: 1.0 };
      function makePoints (a,b)    { return [a,b]; }
      function thermDist  (a,b,c,d){ return (a*b*c*d); }
      function GLatLng    (lat,lon){ return [lat,lon]; }
    
      this.name = name;
      this.lon  = lon;
      this.lat  = lat;
      this.yld  = yld;
      this.gz   = new GLatLng(lat,lon);
    
      this.overpressure = {
        p30 : 0.108 * scale,
        p15 : 0.153 * scale,
        p10 : 0.185 * scale,
        p5 : 0.281 * scale,
        p1 : 0.725 * scale
      };
      this.psi30 = {
        radius: this.overpressure.p30,
        overlay : {
          points: makePoints(this.gz, pow)
        }
      };
      this.psi15 = {
        radius: this.overpressure.p15,
        overlay : {
          points: makePoints(this.gz, pow)
        }
      };
      // split this up so you don't do double thermDist calcs
      this.therm20 = {
        radius: thermDist(20,this.yld,pow,conditions.visibility)
      };
      this.therm20.overlay = {
          points: makePoints(this.gz, this.therm20.radius)
      };
      // ...and so on...
    }
    
    

    Detonation2 causes somes changes externally (ie, det.yield = 20 becomes det.yield(20) and similar)

    However, you do get 1) automatic recalculation and 2) only calculated when needed.

    
    function Detonation2(name,lat,lon,yld)
    {                  // D (this) is what gets returned as your instance
      var D   = this,  // D.some_name functions/properties available to instance
          pow = (1/3), // just a guess. saw 1/3 and 0.33 in a few places
          __s = {};   // data structure for "private" state info
    
      // arbitrary values so I could test
      var conditions = { visibility: 1.0 };
      function makePoints (a,b)    { return [a,b]; }
      function thermDist  (a,b,c,d){ return (a*b*c*d); }
      function GLatLng    (lat,lon){ return [lat,lon]; }
    
      // functions set values if passed arguments, else return current value
      // det.yield = 20 becomes det.yld(20)
      // and det.name = 'boom' becomes det.name('boom')
      D.name  = function(n){ return __get_set('name', n); };
      D.lat   = function(l){ return __get_set('lat', l); };
      D.lon   = function(l){ return __get_set('lon', l); };
      D.yld   = function(y){ return __get_set('yld', y); };
      // D.gz = function(){ return __get_set(GLatLng(D.lat, D.lon)); }; // once
      D.gz    = function(){ return new GLatLng(D.lat, D.lon); };        // always new
      D.scale = function(){ return Math.pow(D.yld(), pow); };
    
      // base getter/setter function for D.name() and other convenience functions
      function __get_set(key,val){
        if (val){ __s[key] = val; } // set to val, if we get one
        return __s[key];            // return current (old or new) value of key
      };
    
      // set values based on constructor arguments
      D.name(name);
      D.lat(lat);
      D.lon(lon);
      D.yld(yld);
    
      D.overpressure = {
        p30: function(){ return 0.108 * D.scale(); },
        p15: function(){ return 0.153 * D.scale(); },
        p10: function(){ return 0.185 * D.scale(); },
        p5:  function(){ return 0.281 * D.scale(); },
        p1:  function(){ return 0.725 * D.scale(); }
      };
    
      D.psi30 = {
        radius: function(){ return D.overpressure.p30(); },
        overlay: {
          points: function(){
    	return makePoints(D.gz(), D.overpressure.p30());
          }
        }
      };
    
      D.psi15 = {
        radius: function(){ return D.overpressure.p15(); },
        overlay: {
          points: function(){
    	return makePoints(D.gz(), D.overpressure.p15());
          }
        }
      };
    
      // split D.therm20 assignment so you don't run thermDist() twice
      D.therm20 = {
        radius: function(){
          return thermDist(20, D.yld(), pow, conditions.visibility);
        }
      };
    
      D.therm20.overlay = {
        points: function(){
          return makePoints(D.gz(), D.therm20.radius()); // thermDist() results
        }
      };
    
      // ...and so on...
    
      return D; // D.properties are available to instance (result of new Detonation)
    }
    
    

    You said you wanted something to poke at, so I went a bit heavy on the comments.

    Just reading the example from Adam van den Hoven and it’s along the lines of what I was going to suggest. Even after my refactoring, it’s clear there are still patterns in the properties and function names (and their calculations) that could surely be reduced to a more abstract or basic formula.

    Anyway, thanks for the mental exercise and, of course, your CSS and standards work.

    P.S. “pre” tag doesn’t work. Sorry, but I just can’t go back and replace spaces w/ nbsb;

    • #21
    • Comment
    • Sun 18 Jan 2009
    • 1415
    Lachlan Hunt wrote in to say...

    Here’s an alternative using the getters and setters syntax supported by Firefox, Opera and Safari. If support for IE is needed, than this won’t be suitable. (IE8 has added support for getters and setters recently, but unfortunately, it seems the syntax defined by ECMAScript 3.1 and implemented by IE is not entirely compatible with that used by the others.)

    There are various improvements that could be made to optimise this, but it’s difficult to know what can be changed without known what it’s for or how it’s meant to be used. I’m not even sure some of the changes I made to this version are suitable for what you need. But I’m sure you can use this to get you started anyway.

    function Detonation(name, lat, lon, yieldValue) {
        var self = this; // Keep a reference to this object availale
        var gz = new GLatLng(lat, lon);
    
        // Use _yeild because yeild is a reserved keyword in JavaScript 1.7
        var scale, _yield;
        
        // Use a setter function to recalculate the scale when yield is set
        this.__defineSetter__("yield", function(value) {
    	    _yield = value;
            scale = Math.pow(_yield, 1/3);
    	} );
    
        this.__defineGetter__("yield", function() {
            return _yield;
        } );
        this.yield = yieldValue;
    
        this.name = name;
        
        // Use getters here so that these values can't be changed externally
        // If you need them to be settable externally, use __defineSetter__ to
        // define setter functions for lat and lon that recalculate gz when set
        this.__defineGetter__("lat", function() { return lat; };
        this.__defineGetter__("lon", function() { return lon; };
        this.__defineGetter__("gz", function() { return gz; };
        
        // Use getters for each of these so the value is recalculated
        // based on the current scale value
        this.overpressure = {
            get p30() { return 0.108 * scale; },
            get p15() { return 0.153 * scale; },
            get p10() { return 0.185 * scale; },
            get  p5() { return 0.281 * scale; },
            get  p1() { return 0.725 * scale; }
        };
    
        // Similarly, use getters for these properties so their values are calculated
        // based on the current values when needed
        this.psi30 = {
            get radius() { return self.overpressure.p30 },
            overlay: {
                get points() { return makePoints(self.gz, self.overpressure.p30) }
            }
        };
        this.psi15 = {
            get radius() { return self.overpressure.p15 },
            overlay: {
                get points() { return makePoints(self.gz, self.overpressure.p15) }
            }
        };
        this.therm20 = {
            get radius() { thermDist(20, self.yield , 0.33, conditions.visibility) },
            overlay: {
                get points() { return makePoints(self.gz, self.psi15.radius * scale) }
            }
        };
        // ...and so on...
    }
    

Leave a Comment

Line and paragraph breaks automatic, e-mail address required but never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>



Remember to encode character entities if you're posting markup examples! Management reserves the right to edit or remove any comment—especially those that are abusive, irrelevant to the topic at hand, or made by anonymous posters—although honestly, most edits are a matter of fixing mangled markup. Thus the note about encoding your entities. If you're satisfied with what you've written, then go ahead...


January 2009
SMTWTFS
December February
 123
45678910
11121314151617
18192021222324
25262728293031

Sidestep

Feeds

Extras