7

The goal of this work is to understand and play with meaning of some object concept I've heard arround.

About the bounty

There is a lot of different way / approach to do this.

My tries are not really clean: for adding a 2st clock, with another timezone, I have to edit 3 different places. This is not well (see at bottom of the answer).

How could I do something more useful?

In the begining:

post-edit: the initial question was about choosing between jquery and mootools, now choice as been made; the goal is to improve this, with the use of mootools.

There is a little sample/demo i wrote to play with javascript and svg:

var cx  =128;
var cy  =128;
var slen=120;
var mlen=116;
var hlen= 80;
var selem;
var melem;
var helem;
function setvars() {
    selem=document.getElementById("seconds");
    melem=document.getElementById("minutes");
    helem=document.getElementById("hours");
    drawtime();
};
function drawtime() {
    var now=new Date();
    var nows=now.getTime()%60000;
    var nowm=now.getMinutes()*1.0+1.0*nows/60000;
    var nowh=now.getHours()*1.0+1.0*nowm/60;
    var sposx=cx + slen * Math.sin( nows / 30000 * Math.PI );
    var sposy=cy - slen * Math.cos( nows / 30000 * Math.PI );
    var mposx=cx + mlen * Math.sin( nowm / 30 * Math.PI );
    var mposy=cy - mlen * Math.cos( nowm / 30 * Math.PI );
    var hposx=cx + hlen * Math.sin( nowh / 6 * Math.PI );
    var hposy=cy - hlen * Math.cos( nowh / 6 * Math.PI );
    selem.setAttribute("x1",sposx);
    selem.setAttribute("y1",sposy);
    selem.setAttribute("x2",sposx);
    selem.setAttribute("y2",sposy);
    melem.setAttribute("x2",mposx);
    melem.setAttribute("y2",mposy);
    helem.setAttribute("x2",hposx);
    helem.setAttribute("y2",hposy);
    window.setTimeout(drawtime,80)
};
setvars();
#box1    { stroke: black; }
#minutes { stroke: #2266AA; }
#hours   { stroke: #3388CC; }
#seconds { stroke: #CCCC22; }
line,circle {
    opacity:0.65;
    fill:none;
    stroke-width:8;
    stroke-linecap:round;
    stroke-linejoin:round;
    marker:none;
    stroke-miterlimit:4;
    stroke-dasharray:none;
    stroke-opacity:1;
    visibility:visible;
    display:inline;
    overflow:visible;
    enable-background:accumulate
}
<svg xmlns="http://www.w3.org/2000/svg" id="svg2" width="100%"
     height="100%" viewBox="0 0 900 256" version="1.0">
    <title  id="title1">Clock</title>
    <circle id="box1"    cy="128" cx="128"  r="124" />
    <line   id="hours"   x1="128" y1="128" x2="128"  y2="48" />
    <line   id="minutes" x1="128" y1="128" x2="244" y2="128" />
    <line   id="seconds" x1="128"   y1="8" x2="128"   y2="8" />
</svg>

(Originaly posted at jsfiddle) as I'm not very experienced with javascript jquery and/or mootools, I would like to know if some simplier methods exists, maybe in writting this in a different manner.

how to do simple rotation about a fixed center, using jquery or mootools:

var hposx=cx + hlen * Math.sin( nowh / 6 * Math.PI );
var hposy=cy - hlen * Math.cos( nowh / 6 * Math.PI );
helem.setAttribute("x2",hposx);
helem.setAttribute("y2",hposy);

how to objectize this code? (if even it could be a good thing)...

All samples using object orientation, specific library, or else are welcome!

6
  • 3
    Your question is not very clear. Could you please elaborate on what objectize means? Commented Nov 3, 2012 at 10:47
  • Code looks fairly streamlined to me. Commented Nov 3, 2012 at 11:42
  • Rather than edit all meaning out of your original question, which also renders answers disconnected. Please post your solution below.
    – Sparky
    Commented Nov 4, 2012 at 13:49
  • instead of mootools, I suggest using d3.js to make a clock with moving hands Commented May 19, 2015 at 17:53
  • @DaveAlperovich I'm ok with that, my personal inlab try is now pure javascript again... But care, transform, rotate and translate exist in SVG and/or in CSS! Commented May 19, 2015 at 19:24

4 Answers 4

4

Your code is simple and straightforward. I don't think you should try using jQuery or MooTools if your task is simple without it.

For rotation I don't think there are built in tools in jQuery or MooTools, but there are matrix transformations you can use on svg objects, read: http://msdn.microsoft.com/en-us/library/ie/hh535760%28v=vs.85%29.aspx

Or check this question: SVG rotate transform Matrix .

As for making an object:

You can of course slice up your code to more functions, or you make an object that represents the current hours/minutes/secs

var clock = {
    time: {
        s: 0,
        m: 0,
        h: 0    
    },
    pos: {
        x: 128,
        y: 128
    },

   .... anything else you might want to add
};

You set its properties first in a set of functions

clock.setTime = function (date) {
    this.time.s = date.getTime()%60000;
    this.time.m = date.getMinutes()*1.0+1.0*nows/60000;
    this.time.h = date.getHours()*1.0+1.0*nowm/60;
};

And read them in an other set of functions:

clock.getMinPos = function () {
    var x = ...;// Sine is ok.
    var y = ...;// Cosine is ok.
    // I don't like matrices anyway.
    return [x, y];
};

Really just try to partition your code into functional tasks. One function should only do one thing.

2
  • How do you transform a clock path using matrix without the use of sines and cosines? Commented Nov 3, 2012 at 14:29
  • The 'rotate' transformation matrix has sine and cosine in it, so you can't. You need the cosine and sine of the angle you want to rotate something with. They are just convenient for rotating a bunch of points at the same time (like if you want to make fancy clock hands). For this simple application where you only need to move 1 point, it's probably the best to directly calculate every moving point. Commented Nov 3, 2012 at 17:11
2
+350

I've created a D3 clock Object

Plunker

enter image description here


:

  • d3Clock Can be instantiated multiple times.

  • A constructor takes X, Y, Width, TimeZoneOffset Object, label

:

  <script>
    $(document).ready(function() {

      var tokyoClock = new d3Clock({
        clockCenterX: 140,
        clockCenterY: 150,
        clockWidth: 130,
        title: 'Tokyo',
        TZOffset: {
          hours: 13
        }
      });

:

  • Methods are also expose to manually set TZOffsets

:

      var iranClock = new d3Clock({
        clockCenterX: 355,
        clockCenterY: 360,
        clockWidth: 180,
        title: 'Iran'    
      });

      iranClock.Hours(8);
      iranClock.Minutes(30);

The d3Clock Enclosure:

var d3Clock = function(data) {
  var clockGroup, fields, formatHour, formatMinute, formatSecond, pi, render, scaleHours, scaleSecsMins, vis;

  width = data.clockWidth;
  height = data.clockWidth;
  offSetX = data.clockWidth / 2;
  offSetY = offSetX;
  var x = data.clockCenterX;
  var y = data.clockCenterY;

  var hourOffset = 0;
  var minOffset = 0;
  var secOffset = 0;

  title = data.title;

  var clockRadius = data.clockWidth * 0.45;
  formatSecond = d3.time.format("%S");
  formatMinute = d3.time.format("%M");
  formatHour = d3.time.format("%H");

  pi = Math.PI;
  scaleSecsMins = d3.scale.linear().domain([0, 59 + 59 / 60]).range([0, 2 * pi]);
  scaleHours = d3.scale.linear().domain([0, 11 + 59 / 60]).range([0, 2 * pi]);

  // create Parent Div and set x, y
  var iDiv = document.createElement('div');
  iDiv.setAttribute("style", 'background-color: transparent; height: ' + width + ' px; width: ' + width + 'px; position: absolute; top:' + y + 'px; left: ' + x + 'px; text-align: center; v-align:bottom;');
  iDiv.innerHTML = '<span style="font-weight: bold;">' + title + '</span>';
  document.getElementsByTagName('body')[0].appendChild(iDiv);
  vis = d3.select(iDiv).append("svg:svg").attr("width", width).attr("height", height);

  clockGroup = vis.append("svg:g").attr("transform", "translate(" + offSetX + "," + offSetY + ")");
  clockGroup.append("svg:circle").attr("r", clockRadius).attr("fill", "lightgrey").attr("class", "clock outercircle").attr("stroke", "black").attr("stroke-width", 5);
  clockGroup.append("svg:circle").attr("r", 4).attr("fill", "black").attr("class", "clock innercircle");


  // private set TZ Offset methods
  var setTZSeconds = function(sec) {
    if (sec !== undefined)
      secOffset = sec;
  }
  var setTZMins = function(min) {
    if (min !== undefined)
      minOffset = min;
  }
  var setTZHours = function(hr) {
    if (hr !== undefined)
      hourOffset = hr;
  }


  // exposed TimeZoneOffset
    this.Seconds = function(Sec) {
      setTZSeconds(Sec);
    }
    this.Minutes = function(Mins) {
      setTZMins(Mins);
    }
    this.Hours = function(Hours) {
      setTZHours(Hours);
    }


  if (data.TZOffset != undefined) {
    setTZHours(data.TZOffset.hours);
    setTZMins(data.TZOffset.mins);
    setTZSeconds(data.TZOffset.secs);
  }

  // Get time values and apply offsets
  fields = function() {
    var d, data, hour, minute, second;
    d = new Date();
    second = d.getSeconds() + secOffset;
    minute = d.getMinutes() + minOffset;
    hour = d.getHours() + hourOffset + minute / 60;
    return data = [{
      "unit": "seconds",
      "text": formatSecond(d),
      "numeric": second
    }, {
      "unit": "minutes",
      "text": formatMinute(d),
      "numeric": minute
    }, {
      "unit": "hours",
      "text": formatHour(d),
      "numeric": hour
    }];
  };


  render = function(data) {
    var hourArc, minuteArc, secondArc;
    clockGroup.selectAll(".clockhand").remove();
    secondArc = d3.svg.arc().innerRadius(0).outerRadius(clockRadius * .9).startAngle(function(d) {
      return scaleSecsMins(d.numeric);
    }).endAngle(function(d) {
      return scaleSecsMins(d.numeric);
    });
    minuteArc = d3.svg.arc().innerRadius(0).outerRadius(clockRadius * .85).startAngle(function(d) {
      return scaleSecsMins(d.numeric);
    }).endAngle(function(d) {
      return scaleSecsMins(d.numeric);
    });
    hourArc = d3.svg.arc().innerRadius(0).outerRadius(clockRadius * .7).startAngle(function(d) {
      return scaleHours(d.numeric % 12);
    }).endAngle(function(d) {
      return scaleHours(d.numeric % 12);
    });
    clockGroup.selectAll(".clockhand").data(data).enter().append("svg:path").attr("d", function(d) {
      if (d.unit === "seconds") {
        return secondArc(d);
      } else if (d.unit === "minutes") {
        return minuteArc(d);
      } else if (d.unit === "hours") {
        return hourArc(d);
      }
    }).attr("class", "clockhand").attr("stroke", "black").attr("style", function(d) {
      if (d.unit === "seconds") {
        return "stroke-width: 2; stroke: white"
      } else if (d.unit === "minutes") {
        return "stroke-width: 3;"
      } else if (d.unit === "hours") {
        return "stroke-width: 4;"
      }
    }).attr("fill", "none");
  };

  setInterval(function() {
    var data;
    data = fields();
    return render(data);
  }, 1000);
};

More methods can be exposed to allow customization

all functions with prefix this. are exposed public to be used on instantiated object. So:

this.Seconds = function(Sec) {
  setTZSeconds(Sec);
}
this.Minutes = function(Mins) {
  setTZMins(Mins);
}
this.Hours = function(Hours) {
  setTZHours(Hours);
}

are exposed access to the private setTZSeconds(), setTZMins(), setTZHours().

It's considered good OO Form for public methods to call private methods that do the work. The public methods can limit or filter the results (like I check not undefined in the private methods.

Public methods can be exposed to

  • change x, y so the clockObjects can be moved around the screen after instantiation
  • change size
  • change styles for clock hands, title location / styling
4
  • Nice, thanks, but I would appreciate clockTimeZone: option! Commented May 19, 2015 at 19:52
  • @F.Hauri, I have added a time Offset parameter. Check the plunker. Is this what you wanted? Commented May 19, 2015 at 20:23
  • Why did you not simply created a snippet instead of adding another dependency to external site? Commented May 22, 2015 at 2:56
  • @F.Hauri, I find the snippet to behave oddly at times Commented May 22, 2015 at 3:03
2

Edited from bounty: Go to bottom of this!

First answer

After reading first comments from pete (many thanks! you give me good ways of research) and SoonDead (thanks too for the first step of conversion), I've a little look around, jquery and mootools code and finally i've choose mootools to reduce my code, as mootools let me submit an array for object.get and a hash (associative array) for objet.set:

(function () {
    "use strict";
    var Point = function(x,y) {
         this.x=x;
         this.y=y;
    };
    var Path = function(center,length,both) {
        this.center = center;
        this.length = length;
        this.both   = both;
        this.end    = function(alpha) {
            var retx = 1.0*this.center.x +
                this.length*Math.sin(alpha);
            var rety = 1.0*this.center.y -
                this.length*Math.cos(alpha);
            if (typeof(this.both)!=='undefined')
              return { x1:retx, x2:retx, y1:rety, y2:rety }
            else return { x2:retx, y2:rety };
        };
    };
    var Hand = function(svgline,both) {
        this.elem = document.id(svgline);
        var p     = this.elem.get(['x1','y1','x2','y2']);
        this.path = new Path (
          new Point(p.x1,p.y1),Math.sqrt(
            Math.pow(1.0*p.x2-1.0*p.x1,2) +
            Math.pow(1.0*p.y2-1.0*p.y1,2)) ,both);
        this.setPos = function(angle) {
            this.elem.set(this.path.end(angle));
        };
    };
    var Clock = function(hour,minute,second,refresh) {
        this.hour    = new Hand(hour);
        this.minute  = new Hand(minute);
        this.second  = new Hand(second,true);
        this.refresh = refresh;
        this.setTime = function(timePos) {
            var self= this;
            var tps = 1.0*timePos.getTime() % 60000;
            var tpm = timePos.getMinutes()*1.0 +
                1.0* tps/60000;
            var tph = timePos.getHours()*1.0   + 1.0* tpm/60;
            this.second.setPos(tps / 30000 * Math.PI);
            this.minute.setPos(tpm / 30    * Math.PI);
            this.hour  .setPos(tph / 6     * Math.PI);
            setTimeout(function() {
              self.setTime(new Date())},this.refresh) };
    };
    var clock=new Clock('hours','minutes','seconds',120);
    clock.setTime(new Date());
}());
#box1    { stroke: black; fill:#ccc }
#minutes { stroke: #2288AA; }
#hours   { stroke: #3388CC; }
#seconds { stroke: #CCCC22; }
line,circle {
    opacity:0.65;
    fill:none;
    stroke-width:8;
    stroke-linecap:round;
    stroke-linejoin:round;
    marker:none;
    stroke-miterlimit:4;
    stroke-dasharray:none;
    stroke-opacity:1;
    visibility:visible;
    display:inline;
    overflow:visible;
    enable-background:accumulate
}
<script
  src="http://ajax.googleapis.com/ajax/libs/mootools/1.4.5/mootools-nocompat.js"
  ></script>
<svg xmlns="http://www.w3.org/2000/svg" id="svg2" width="100%"
     height="100%" viewBox="0 0 900 256" version="1.0">
    <title  id="title1">Clock</title>
    <circle id="box1"    cy="128" cx="128"  r="124" />
    <line   id="hours"   x1="128" y1="128" x2="128"  y2="48" />
    <line   id="minutes" x1="128" y1="128" x2="244" y2="128" />
    <line   id="seconds" x1="128" y1="128" x2="128"   y2="8" />
</svg>

Well, now my new code is 8 lines smaller and even alway readable.

This is a step, but objectization mean make it as an object... With the goals of

  1. Keep it reabable
  2. Keep it efficient (don't make useless operation or the same operation two time)
  3. Make it re-useable (as an object, keep all fixed variables out...)

One intermediary personal result,

But now the code look like:

(function () {
    "use strict";
    var Point = function(x,y) {
         this.x=x;
         this.y=y;
    };
    var Path = function(center,length,both) {
        this.center = center;
        this.length = length;
        this.both   = both;
        this.end    = function(alpha) {
            var retx=1.0*this.center.x+this.length*Math.sin(alpha);
            var rety=1.0*this.center.y-this.length*Math.cos(alpha);
            if (typeof(this.both)!=='undefined')
                 return { x1:retx, x2:retx, y1:rety, y2:rety }
            else return { x2:retx, y2:rety };
        };
    };
    var Hand = function(svgline,both) {
        this.elem   = document.id(svgline);
        var p=this.elem.get(['x1','y1','x2','y2']);
        this.path   = new Path ( new Point(p.x1,p.y1),
                                Math.sqrt(Math.pow(1.0*p.x2-1.0*p.x1,2)+
                                          Math.pow(1.0*p.y2-1.0*p.y1,2)),
                                 both);
        this.setPos = function(angle) {
            this.elem.set(this.path.end(angle));
        };
    };
    var Clock = function(hour,minute,second,refresh) {
        this.hour    = new Hand(hour);
        this.minute  = new Hand(minute);
        this.second  = new Hand(second,true);
        this.setTime = function(timePos) {
            var self= this;
            var tps = 1.0*timePos.getTime() % 60000;
            var tpm = timePos.getMinutes()*1.0 + 1.0* tps/60000;
            var tph = timePos.getHours()*1.0   + 1.0* tpm/60;
            this.second.setPos(tps / 30000 * Math.PI);
            this.minute.setPos(tpm / 30    * Math.PI);
            this.hour  .setPos(tph / 6     * Math.PI);
        };
    };
    var RefreshLoop = function(refresh) {
        var newdate=new Date();
        clock1.setTime(newdate);
        newdate=newdate.getTime()+newdate.getTimezoneOffset()*60000;
        clock2.setTime(new Date(newdate));
        clock3.setTime(new Date(newdate+20700000));
        clock4.setTime(new Date(newdate+28800000));
    };
    var clock1=new Clock('hours','minutes','seconds',120);
    var clock2=new Clock('hours2','minutes2','seconds2',120);
    var clock3=new Clock('hours3','minutes3','seconds3',120);
    var clock4=new Clock('hours4','minutes4','seconds4',120);
    RefreshLoop.periodical(500);
}());

Where all part stay small, my clock is a real reuseable object (now work 4 times).

The function setTime have to compute all hands together as each value hold a portion of other, but each operation have to be done one time only.

For my clock, a Path is defined by a fixed start point a fixed length and a variable direction, the Path.end is the computed end point for a specified direction

And a Hand is a given SVG line, with his original Path, and a optional flag speficying that on setting, both start point and end point have to be positionned at same values (zero length line, with round termination give a round point).

(Remark (bug?): As each elements are defined in the SVG and Path.length is computed from the distance between each end of a Path, the seconds path have to be drawn first with [x1,y1] in the center of the clock, ! = [x2,y2]! )

In the hope some want to correct/improve/discuss my objectization...

UPDATE2

I think, this is now the final version, where object are simple, mootools is used (maybe not too much, remarks welcomes) and my final clock could be used many time to display different timezome.

(function () {
    "use strict";
    var Point = function(x,y) {
         this.x=x;
         this.y=y;
    };
    var Path = function(center,length,both) {
        this.center = center;
        this.length = length;
        this.both   = both;
        this.end    = function(alpha) {
            var retx=1.0*this.center.x+this.length*Math.sin(alpha);
            var rety=1.0*this.center.y-this.length*Math.cos(alpha);
            if (typeof(this.both)!=='undefined')
                 return { x1:retx, x2:retx, y1:rety, y2:rety }
            else return { x2:retx, y2:rety };
        };
    };
    var Hand = function(svgline,both) {
        this.elem   = document.id(svgline);
        var p=this.elem.get(['x1','y1','x2','y2']);
        this.path   = new Path ( new Point(p.x1,p.y1),
                                Math.sqrt(Math.pow(1.0*p.x2-1.0*p.x1,2)+
                                          Math.pow(1.0*p.y2-1.0*p.y1,2)),
                                 both);
        this.setPos = function(angle) {
            this.elem.set(this.path.end(angle));
        };
    };
    var Clock = function(hour,minute,second,refresh) {
        this.hour    = new Hand(hour);
        this.minute  = new Hand(minute);
        this.second  = new Hand(second,true);
        this.setTime = function(timePos) {
            var self= this;
            var tps = 1.0*timePos.getTime() % 60000;
            var tpm = timePos.getMinutes()*1.0 + 1.0* tps/60000;
            var tph = timePos.getHours()*1.0 + 1.0* tpm/60;
            this.second.setPos(tps / 30000 * Math.PI);
            this.minute.setPos(tpm / 30    * Math.PI);
            this.hour  .setPos(tph / 6     * Math.PI);
        };
    };
    var RefreshLoop = function(refresh) {
        var newdate=new Date();
        clock1.setTime(newdate);
        newdate=newdate.getTime()+newdate.getTimezoneOffset()*60000;
        clock2.setTime(new Date(newdate));
        clock3.setTime(new Date(newdate+20700000));
    };
    var clock1=new Clock('hours','minutes','seconds',120);
    var clock2=new Clock('hours2','minutes2','seconds2',120);
    var clock3=new Clock('hours3','minutes3','seconds3',120);
    RefreshLoop.periodical(500);
}());
circle   { stroke: black; }
.startbg { stop-color: #eeeeee; }
.endbg   { stop-color: #777777; }
.box     { fill:url(#grad0); }
.box1    { fill:url(#grad1); }
.box2    { fill:url(#grad2); }
.box3    { fill:url(#grad3); }
.label   { stroke: #424242;fill:#eee;stroke-width:1; }
.minutes { stroke: #2288AA; }
.hours   { stroke: #3388CC; }
.seconds { stroke: #CCCC22; }
line,circle,rect {
    opacity:0.65;
    fill:none;
    stroke-width:8;
    stroke-linecap:round;
    stroke-linejoin:round;
    marker:none;
    stroke-miterlimit:4;
    stroke-dasharray:none;
    stroke-opacity:1;
    visibility:visible;
    display:inline;
    overflow:visible;
    enable-background:accumulate
}
text {
    font-size:15px;
    font-style:normal;
    font-variant:normal;
    font-weight:normal;
    font-stretch:normal;
    text-align:center;
    line-height:100%;
    writing-mode:lr-tb;
    text-anchor:middle;
    fill:#000000;fill-opacity:.7;
    stroke:none;
    font-family:Nimbus Sans L;
}
<script
  src="http://ajax.googleapis.com/ajax/libs/mootools/1.4.5/mootools-nocompat.js"
  ></script>
<svg xmlns="http://www.w3.org/2000/svg" id="svg2" width="100%"
    height="100%" viewBox="-1 -1 900 555" version="1.0"><defs>
    <linearGradient gradientUnits="userSpaceOnUse" id="g0"><stop
        class="startbg" /><stop class="endbg" offset="1" /></linearGradient>
    <linearGradient id="grad0" x1="-1"  y1="-1" x2="256" y2="277" xlink:href="#g0" />
    <linearGradient id="grad1" x1="256" y1="-1" x2="515" y2="277" xlink:href="#g0" />
    <linearGradient id="grad2" x1="512" y1="-1" x2="771" y2="277" xlink:href="#g0" />
    </defs>
    <circle class="box"   id="box1" cy="128" cx="128" r="124" />
    <line class="hours"   id="hours" x1="128" y1="128" x2="128" y2="48" />
    <line class="minutes" id="minutes" x1="128" y1="128" x2="244" y2="128" />
    <line class="seconds" id="seconds" x1="128" y1="128" x2="128" y2="8" />
    <rect class="label"   x="16"  y="256" width="224" height="20" />
    <text x="0" y="0" xml:space="preserve">
        <tspan x="128" y="271">Local time</tspan></text>

    <circle class="box1" id="box2" cy="128" cx="385" r="124" />
    <line class="hours" id="hours2" x1="385" y1="128" x2="385" y2="48" />
    <line class="minutes" id="minutes2" x1="385" y1="128" x2="501" y2="128" />
    <line class="seconds" id="seconds2" x1="385" y1="128" x2="385" y2="8" />
    <rect class="label" x="273" y="256" width="224" height="20" />
    <text x="0" y="0" xml:space="preserve">
        <tspan x="385" y="271">Universal Time Clock</tspan></text>

    <circle class="box2" id="box3" cy="128" cx="642" r="124" />
    <line class="hours" id="hours3" x1="642" y1="128" x2="642" y2="48" />
    <line class="minutes" id="minutes3" x1="642" y1="128" x2="758" y2="128" />
    <line class="seconds" id="seconds3" x1="642" y1="128" x2="642" y2="8" />
    <rect class="label" x="530"  y="256" width="224" height="20" />
    <text x="0" y="0" xml:space="preserve">
        <tspan x="642" y="271">Asia/Katmandu</tspan></text>
</svg>

New try completely rewritten

This use momentjs for intl infos, but no other lib.

"use strict";
var gv={ clockcount: 1,
         svg:'http://www.w3.org/2000/svg',
         xlnk:'http://www.w3.org/1999/xlink',
         tzlist:['Local'].concat(moment.tz.names()),
         vbox:document.getElementById('svg').getAttribute("viewBox").split(" ")
       };
function mousepos(event) {
    var minxy=innerWidth;
    if (minxy > innerHeight) minxy=innerHeight;
    return {
        x:((event.clientX-(innerWidth-minxy)/2)/minxy)*(gv.vbox[2]-gv.vbox[0]),
        y:((event.clientY-(innerHeight-minxy)/2)/minxy)*(gv.vbox[3]-gv.vbox[1])
    };
};

function myClock(cx,cy,r,tz) {
    var clock=this, elem;
    this.cx=128;
    if (typeof(cx)!=='undefined') this.cx=cx;
    this.cy=128;
    if (typeof(cy)!=='undefined') this.cy=cy;
    this.r=100;
    if (typeof(r)!=='undefined') this.r=r;
    this.tz=new Date().getTimezoneOffset();
    this.setTz=function(tz) {
        if (typeof(tz)!=='undefined') {
            this.label=tz;
            if (tz!=="Local") {
                var ndte=new Date();
                var tzoff=moment(ndte).tz(tz).format('HH mm ss').split(' ');
                var tznow=Math.floor(ndte/1000)%86400;
                this.tz=(tznow-(tzoff[0]*3600+tzoff[1]*60+1*tzoff[2]))/60;
            } else this.tz=new Date().getTimezoneOffset();
        } else this.label="Local";
    };
    this.setTz(tz);
    this.clkid=gv.clockcount++;
    this.floor=0;
    this.toggleFloor=function(e) { e.preventDefault();
                                   clock.floor=1-clock.floor; };
    this.toggleSecDraw=function(e) { e.preventDefault();
                                     clock.secdraw=1-clock.secdraw; };
    this.wheel=function(e) { e.preventDefault();
                             var sens=1;
                             if (typeof(e.detail)!=='undefined') {
                                 if ( 0 > e.detail ) { sens=-1; }
                             } else if ( 0 > e.wheelDelta ) { sens=-1; };
                             var cidx=gv.tzlist.indexOf(clock.label)*1+1*sens;
                             if (cidx < 0) cidx=gv.tzlist.length-1;
                             if (cidx >= gv.tzlist.length) cidx=0;
                             clock.setTz(gv.tzlist[cidx]);
                             clock.draw=0; };
    this.moused = function (evt) {
        evt.preventDefault(); var m=mousepos(evt);
        if ((clock.r/2 > Math.pow(Math.pow(Math.abs(clock.cx-m.x),2)+
                                  Math.pow(Math.abs(clock.cy-m.y),2),.5))) {
            clock.box.addEventListener("mousemove", clock.mousem, true);
        } else {
            clock.box.addEventListener("mousemove", clock.mouser, true);
        };
        clock.box.addEventListener("mouseup", clock.mouseu, true);
    };
    this.mouseu = function(evt) {
        evt.preventDefault(); clock.draw=0;
        clock.box.removeEventListener("mousemove", clock.mouser, true);
        clock.box.removeEventListener("mousemove", clock.mousem, true);
        clock.box.removeEventListener("mouseup", clock.mouseu, true);
    };
    this.mouser = function(evt) {
        evt.preventDefault(); clock.draw=0;
        var m=mousepos(evt);
        clock.r=1.25*Math.pow(Math.pow(Math.abs(clock.cx-m.x),2)+
                              Math.pow(Math.abs(clock.cy-m.y),2),.5);
    };
    this.mousem = function(evt) { evt.preventDefault(); clock.draw=0;
        var m=mousepos(evt); clock.cx=m.x; clock.cy=m.y; };
    this.drop = function(evt) { evt.preventDefault();clearInterval(clock.loop);
                                clock.box.remove(); };
    elem=document.createElementNS(gv.svg,'g');             
    elem.setAttribute('id','box'+this.clkid);
    document.getElementById('myClock').appendChild(elem);
    this.box=document.getElementById('box'+this.clkid);
    this.box.addEventListener("mousedown",     this.moused ,true);
    this.box.addEventListener("click",         this.toggleSecDraw,true);
    this.box.addEventListener("dblclick",      this.toggleFloor ,true);
    this.box.addEventListener('mousewheel',    this.wheel, true);
    this.box.addEventListener('DOMMouseScroll',this.wheel, true);
    this.box.addEventListener('contextmenu',   this.drop, true);
    
    elem=document.createElementNS(gv.svg,'circle');
    this.fill='fill: url(#g'+this.clkid+');'+
        'stroke: url(#gb'+this.clkid+');';
    elem.setAttribute('style',this.fill);
    elem.setAttribute('id','crc'+this.clkid);
    this.box.appendChild(elem);
    this.crc=document.getElementById('crc'+this.clkid);
    
    this.ticks=[];
    for (var i=0;i<60;i++) {
        elem=document.createElementNS(gv.svg,'line');
        elem.setAttribute('class','ticks');
        elem.setAttribute('id','t'+i+'c'+this.clkid);
        this.box.appendChild(elem);
        this.ticks.push(document.getElementById('t'+i+'c'+this.clkid));
    };
    
    elem=document.createElementNS(gv.svg,'rect');
    elem.setAttribute('class','label');
    elem.setAttribute('id','r'+this.clkid);
    this.box.appendChild(elem);
    this.rct=document.getElementById('r'+this.clkid);
    
    elem=document.createElementNS(gv.svg,'text');
    elem.setAttribute('id','x'+this.clkid);
    this.box.appendChild(elem);
    this.tbx=document.getElementById('x'+this.clkid);
    
    elem=document.createElementNS(gv.svg,'tspan');
    elem.setAttribute('id','t'+this.clkid);
    this.tbx.appendChild(elem);
    this.txt=document.getElementById('t'+this.clkid);

    elem=document.createElementNS(gv.svg,'line');
    elem.setAttribute('id','hr'+this.clkid);
    elem.setAttribute('class','hours');
    this.box.appendChild(elem);
    this.hhr=document.getElementById('hr'+this.clkid);

    elem=document.createElementNS(gv.svg,'line');
    elem.setAttribute('id','mn'+this.clkid);
    elem.setAttribute('class','minutes');
    this.box.appendChild(elem);
    this.hmn=document.getElementById('mn'+this.clkid);

    elem=document.createElementNS(gv.svg,'line'); 
    elem.setAttribute('id','sc'+this.clkid);
    elem.setAttribute('class','seconds');
    this.box.appendChild(elem);
    this.hsc=document.getElementById('sc'+this.clkid);
    
    elem=document.createElementNS(gv.svg,'linearGradient');
    elem.setAttribute('id','g'+this.clkid);
    elem.setAttributeNS(gv.xlnk,'xlink:href','#g0');
    document.getElementById('defs').appendChild(elem);
    this.deg=document.getElementById('g'+this.clkid);
    elem=document.createElementNS(gv.svg,'linearGradient');
    elem.setAttribute('id','gb'+this.clkid);
    elem.setAttributeNS(gv.xlnk,'xlink:href','#g0');
    document.getElementById('defs').appendChild(elem);
    this.dgb=document.getElementById('gb'+this.clkid);

    this.getTZ=function() { return this.tz; };
    this.setTZ=function(tz) { this.tz=tz; };
    this.draw=0;
    this.secdraw=1;
    this.adjust=function() {
        if (clock.draw!==1) {
            clock.crc.setAttribute('style','stroke-width:'+.03*clock.r+";"+
                                  clock.fill);
            clock.hhr.setAttribute('style','stroke-width:'+.11*clock.r);
            clock.hmn.setAttribute('style','stroke-width:'+.075*clock.r);
            clock.hsc.setAttribute('style','stroke-width:'+
                                  (clock.secdraw==1?.03:.09)*clock.r);
            clock.crc.setAttribute('cx',clock.cx);
            clock.crc.setAttribute('cy',clock.cy);
            clock.crc.setAttribute('r',clock.r);
            clock.rct.setAttribute('height',.2*clock.r);
            clock.rct.setAttribute('x',clock.cx-.9*clock.r);
            clock.rct.setAttribute('y',clock.cy*1+1.1*clock.r);
            clock.txt.innerHTML=clock.label;
            clock.txt.setAttribute('x',clock.cx);
            clock.txt.setAttribute('y',clock.cy*1+1.25*clock.r);
            clock.txt.setAttribute('style','font-size: '+(.15*clock.r)+"px;");
            var w=clock.label.length*.1*clock.r+20.0;
            clock.rct.setAttribute('x',clock.cx-w/2);
            clock.rct.setAttribute('width',w);
            for (var i=0;i<60;i++) {
                var x=clock.cx*1+.925*clock.r*Math.sin(i/30*Math.PI);
                var y=clock.cy*1+.925*clock.r*Math.cos(i/30*Math.PI);
                clock.ticks[i].setAttribute('x1',x);
                clock.ticks[i].setAttribute('y1',y);
                clock.ticks[i].setAttribute('x2',x);
                clock.ticks[i].setAttribute('y2',y);
                clock.ticks[i].setAttribute('style','stroke-width:'+
                                            (i%5==0?.04:.02)*clock.r);
            };
            clock.hsc.setAttribute('x1',clock.cx);
            clock.hsc.setAttribute('y1',clock.cy);
            clock.hmn.setAttribute('x1',clock.cx);
            clock.hmn.setAttribute('y1',clock.cy);
            clock.hhr.setAttribute('x1',clock.cx);
            clock.hhr.setAttribute('y1',clock.cy);
            clock.deg.setAttribute('x1',clock.cx-1.1*clock.r);
            clock.deg.setAttribute('y1',clock.cy-1.1*clock.r);
            clock.deg.setAttribute('x2',clock.cx+1.1*clock.r);
            clock.deg.setAttribute('y2',clock.cy+1.1*clock.r);
            clock.dgb.setAttribute('x1',clock.cx+1.1*clock.r);
            clock.dgb.setAttribute('y1',clock.cy+1.1*clock.r);
            clock.dgb.setAttribute('x2',clock.cx-1.1*clock.r);
            clock.dgb.setAttribute('y2',clock.cy-1.1*clock.r);
            clock.draw=1;
        };
        var now=new Date()/1000.0-this.tz*60;
        if (this.floor==1) now=Math.floor(now);
        var x=this.cx+(this.secdraw==1?.975:.925)*
            this.r*Math.sin((now % 60)/30*Math.PI);
        var y=this.cy-(this.secdraw==1?.975:.925)*
            this.r*Math.cos((now % 60)/30*Math.PI);
        this.hsc.setAttribute('x2',x);
        this.hsc.setAttribute('y2',y);
        if (this.secdraw==0) {
            this.hsc.setAttribute('x1',x);
            this.hsc.setAttribute('y1',y);
        }
        if (this.floor==1) now=Math.floor(now/60)         
        else now=now/60;
        x=this.cx+.9*this.r*Math.sin((now %60)/30*Math.PI);
        y=this.cy-.9*this.r*Math.cos((now %60)/30*Math.PI);
        this.hmn.setAttribute('x2',x);
        this.hmn.setAttribute('y2',y);
        if (this.floor==1) now=Math.floor(now/60)         
        else now=now/60;
        x=this.cx+.7*this.r*Math.sin((now % 12)/6*Math.PI);
        y=this.cy-.7*this.r*Math.cos((now % 12)/6*Math.PI);
        this.hhr.setAttribute('x2',x);
        this.hhr.setAttribute('y2',y);
    };
    this.animate = function() {        clock.adjust(); };
    this.loop=setInterval(this.animate,66);
    
};

document.getElementById('svg').addEventListener('dblclick', function(e){ if (e.
 target.id!=='svg')return;var m=mousepos(e);new myClock(m.x,m.y,80,'Local'); });

var clocks=['UTC','Local','Asia/Kolkata'];
for (var i=0;i<3;i++) { new myClock( 90+170*i,90,80,clocks[i]); };
circle   { stroke: black; }
.startbg { stop-color: #CCC; }
.endbg   { stop-color: #222; }
.label   { stroke: #424242;fill:#eee;stroke-width:1; }
.minutes { stroke: #2288AA; }
.hours   { stroke: #3388CC; }
.seconds { stroke: #CCCC22; }
.ticks   { stroke: black; }
line,circle,rect,point {
    opacity:0.65;
    stroke-linecap:round;
    stroke-linejoin:round;
    marker:none;
    stroke-miterlimit:4;
    stroke-dasharray:none;
    stroke-opacity:1;
    visibility:visible;
    display:inline;
    overflow:visible;
    enable-background:accumulate
}
text {
    font-style:normal;
    font-variant:normal;
    font-weight:normal;
    font-stretch:normal;
    text-align:center;
    line-height:100%;
    writing-mode:lr-tb;
    text-anchor:middle;
    fill:#000000;fill-opacity:.7;
    stroke:none;
    font-family:Nimbus Sans L;
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet type="text/css" href="myClock2.css" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600" id="svg"
    xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" >
   <defs id="defs">
     <linearGradient gradientUnits="userSpaceOnUse" id="g0"><stop
       class="startbg" /><stop class="endbg" offset="1" />
     </linearGradient></defs>
 <script type="text/ecmascript"
         xlink:href="http://momentjs.com/downloads/moment-with-locales.js" />
 <script type="text/ecmascript"
         xlink:href="http://momentjs.com/downloads/moment-timezone-with-data.js" />
 <title id="title">Clock object</title>
 <g id="myClock"></g>
 <script type="text/ecmascript" xlink:href="myClock2.js" />
 <script type="text/ecmascript">
</script>
</svg>

There is 3 object clock on a svg drawing, with some features:

Mouse commands:

  • when mouse over a clock

    • Click => toggle seconds: path or dot
    • Drag center => move clock
    • Drag border => resize clock
    • Double-click => toggle floor mode
    • Roll mouse wheel => change Timezone
    • Right click (contextmenu) => delete clock
  • when mouse over the background:

    • Double-click => Add a new clock

This is not perfect, as there are some bugs, mostly in mouse positionning with width="100%" height="100%", I think this is an effect of enclosed snippets, but that' not matter here.

You could find a full useable SVG picture on my site

4
1

UPDATE:

You could use jQuery or MooTools, but I don't see the point of doing so. jQuery and MooTools are libraries that make DOM Manipulation or AJAX much simpler and the primary purpose of what you are doing with SVG (in this case) does not do much DOM Manipulation and there's no AJAX involved.

That said, if you have your heart set on using one of them (and I'll use jQuery in this answer as I'm not well-versed with MooTools yet), I'd start by wrapping the function call in a $(document).ready() function instead of an IIFE.

$(document).ready(function {
    'use strict';
    ...
});

instead of:

(function () {
    'use strict';
    ...
}());

You could also store the element internally as a jQuery object:

this.element = $('#' + hand);

and then get/set the attributes with slightly less typing:

this.setPosition = function setPosition(now) {
    var x2 = this.element.attr('x2'),
        y2 = this.element.attr('y2');
    this.setInterval(now);
    x2 = this.center.x + (this.length * Math.sin(this.interval / this.frequency * Math.PI));
    y2 = this.center.y - (this.length * Math.cos(this.interval / this.frequency * Math.PI));
    this.element.attr('x2', x2);
    this.element.attr('y2', y2);
    if (this.isSeconds) {
        //special case
        this.element.attr('x1', x2);
        this.element.attr('y1', y2);
    }
};

Not much difference overall, and not worth either the extra overhead or the extra bandwidth (and certainly not worth both) for loading the library in this case.

If you had a more complex example, then it might be worth including either jQuery or MooTools to help out, but in this case I don't think either would add sufficient value to be worth it.

ORIGINAL:

I'm assuming by "objectize this code", you mean to make it more abstract. As it stands, the code you have:

  • does the job
  • is fairly compact
  • is reasonably readable

and is therefore unlikely to be made any smaller (or necessarily "better").

That said, you could abstract a "Hand" object like this:

(function () {
    'use strict';
    var Point = function Point(x, y) {
        this.x = x;
        this.y = y;
    };
    var Hand = function Hand(hand, center, length) {
        this.center = center;
        this.element = document.getElementById(hand);
        this.frequency = 0;
        this.isSeconds = (hand === 'seconds');
        this.hand = hand;
        this.length = length;
        this.interval = 0;
        this.parseMilliseconds = function parseMilliseconds(now) {
            return now.getTime() % 60000;
        };
        this.parseMinutes = function parseMinutes(now) {
            return now.getMinutes() + (this.parseMilliseconds(now) / 60000);
        };
        this.parseHours = function parseHours(now) {
            return now.getHours() + (this.parseMinutes(now) / 60);
        };
        this.setFrequency = function getFrequency() {
            switch (this.hand) {
            case 'hours':
                this.frequency = 6;
                break;
            case 'minutes':
                this.frequency = 30;
                break;
            case 'seconds':
                this.frequency = 30000;
                break;
            }
        };
        this.setInterval = function setInterval(now) {
            switch (this.hand) {
            case 'hours':
                this.interval = this.parseHours(now);
                break;
            case 'minutes':
                this.interval = this.parseMinutes(now);
                break;
            case 'seconds':
                this.interval = this.parseMilliseconds(now);
                break;
            }
        };
        this.setPosition = function setPosition(now) {
            var x2 = this.element.getAttribute('x2'),
                y2 = this.element.getAttribute('y2');
            this.setInterval(now);
            x2 = this.center.x + (this.length * Math.sin(this.interval / this.frequency * Math.PI));
            y2 = this.center.y - (this.length * Math.cos(this.interval / this.frequency * Math.PI));
            this.element.setAttribute('x2', x2);
            this.element.setAttribute('y2', y2);
            if (this.isSeconds) {
                //special case
                this.element.setAttribute('x1', x2);
                this.element.setAttribute('y1', y2);
            }
        };
        this.setFrequency();
    };
    var updateClock = function updateClock(hours, minutes, seconds) {
        var now = new Date();
        hours.setPosition(now);
        minutes.setPosition(now);
        seconds.setPosition(now);
        window.setTimeout(function () {
            updateClock(hours, minutes, seconds);
        }, 80);
    };
    var initClock = function initClock() {
        var center = new Point(128, 128),
            seconds = new Hand('seconds', center, 120),
            minutes = new Hand('minutes', center, 116),
            hours = new Hand('hours', center, 80);
        updateClock(hours, minutes, seconds);
    };
    initClock();
}());

In action here: http://jsfiddle.net/MbktF/19/

4
  • Well, very nice demonstration, thanks! But what's about the use of jQuery or MooTools? At all, this good work help me to understand what objectization could mead. Commented Nov 3, 2012 at 21:42
  • 1
    @F.Hauri: Updated answer to reflect lingering jQuery/MooTools question.
    – pete
    Commented Nov 4, 2012 at 4:18
  • Why did you get x2 and y2 before overwritting them? This is useless as x1 and x2 have to become real number!? At all it's a function call that take some time with no sense!? Commented Nov 4, 2012 at 8:47
  • 1
    @F.Hauri: When I wrote that, it was simply to remind me of what x2 and y2 represented. I realize that you're not using the old values at all, but it does not matter what those variables are initially set to. You could init them to 0, or leave them undefined, either of which would be more slightly more efficient because it's not doing a DOM lookup, however if efficiency is your goal then go with your original code. The whole point of OOP is to make large projects easier on the programmer, not the computer. In any case, two DOM lookups are not going to significantly impact performance.
    – pete
    Commented Nov 4, 2012 at 10:32

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