Ticket #1170: 1170-p123.darcspatch

File 1170-p123.darcspatch, 367.7 KB (added by warner, at 2010-09-01T04:16:25Z)

patchbundle with all three patches, ready to land upon passing review

Line 
1Tue Aug 31 18:35:58 PDT 2010  "Brian Warner <warner@lothar.com>"
2  * Share: drop received data after each block finishes. Quick fix for the #1170 spans.py complexity bug.
3
4Tue Aug 31 18:37:02 PDT 2010  "Brian Warner <warner@lothar.com>"
5  * SegmentFetcher: use new diversity-seeking share-selection algorithm, and
6  deliver all shares at once instead of feeding them out one-at-a-time.
7 
8  Also fix distribution of real-number-of-segments information: now all
9  CommonShares (not just the ones used for the first segment) get a
10  correctly-sized hashtree. Previously, the late ones might not, which would
11  make them crash and get dropped (causing the download to fail if the initial
12  set were insufficient, perhaps because one of their servers went away).
13 
14  Update tests, add some TODO notes, improve variable names and comments.
15  Improve logging: add logparents, set more appropriate levels.
16 
17
18Tue Aug 31 20:48:17 PDT 2010  "Brian Warner <warner@lothar.com>"
19  * Add Protovis.js-based download-status timeline visualization. Still kinda
20  rough, but illuminating.
21 
22  Also add dl-status test for /download-%s/event_json, remove
23  /download-%s?t=json
24 
25
26New patches:
27
28[Share: drop received data after each block finishes. Quick fix for the #1170 spans.py complexity bug.
29"Brian Warner <warner@lothar.com>"**20100901013558] {
30hunk ./src/allmydata/immutable/downloader/share.py 534
31             for o in observers:
32                 # goes to SegmentFetcher._block_request_activity
33                 o.notify(state=COMPLETE, block=block)
34+            # now clear our received data, to dodge the #1170 spans.py
35+            # complexity bug
36+            self._received = DataSpans()
37         except (BadHashError, NotEnoughHashesError), e:
38             # rats, we have a corrupt block. Notify our clients that they
39             # need to look elsewhere, and advise the server. Unlike
40hunk ./src/allmydata/test/test_immutable.py 55
41         def _after_download(unused=None):
42             after_download_reads = self._count_reads()
43             #print before_download_reads, after_download_reads
44-            self.failIf(after_download_reads-before_download_reads > 27,
45+            self.failIf(after_download_reads-before_download_reads > 36,
46                         (after_download_reads, before_download_reads))
47         d.addCallback(self._download_and_check_plaintext)
48         d.addCallback(_after_download)
49hunk ./src/allmydata/test/test_immutable.py 73
50         def _after_download(unused=None):
51             after_download_reads = self._count_reads()
52             #print before_download_reads, after_download_reads
53-            self.failIf(after_download_reads-before_download_reads > 27, (after_download_reads, before_download_reads))
54+            self.failIf(after_download_reads-before_download_reads > 37, (after_download_reads, before_download_reads))
55         d.addCallback(self._download_and_check_plaintext)
56         d.addCallback(_after_download)
57         return d
58}
59[SegmentFetcher: use new diversity-seeking share-selection algorithm, and
60"Brian Warner <warner@lothar.com>"**20100901013702
61 deliver all shares at once instead of feeding them out one-at-a-time.
62 
63 Also fix distribution of real-number-of-segments information: now all
64 CommonShares (not just the ones used for the first segment) get a
65 correctly-sized hashtree. Previously, the late ones might not, which would
66 make them crash and get dropped (causing the download to fail if the initial
67 set were insufficient, perhaps because one of their servers went away).
68 
69 Update tests, add some TODO notes, improve variable names and comments.
70 Improve logging: add logparents, set more appropriate levels.
71 
72] {
73hunk ./src/allmydata/immutable/downloader/fetcher.py 7
74 from allmydata.interfaces import NotEnoughSharesError, NoSharesError
75 from allmydata.util import log
76 from allmydata.util.dictutil import DictOfSets
77-from common import AVAILABLE, PENDING, OVERDUE, COMPLETE, CORRUPT, DEAD, \
78-     BADSEGNUM, BadSegmentNumberError
79+from common import OVERDUE, COMPLETE, CORRUPT, DEAD, BADSEGNUM, \
80+     BadSegmentNumberError
81 
82 class SegmentFetcher:
83     """I am responsible for acquiring blocks for a single segment. I will use
84hunk ./src/allmydata/immutable/downloader/fetcher.py 25
85     will shut down and do no further work. My parent can also call my stop()
86     method to have me shut down early."""
87 
88-    def __init__(self, node, segnum, k):
89+    def __init__(self, node, segnum, k, logparent):
90         self._node = node # _Node
91         self.segnum = segnum
92         self._k = k
93hunk ./src/allmydata/immutable/downloader/fetcher.py 29
94-        self._shares = {} # maps non-dead Share instance to a state, one of
95-                          # (AVAILABLE, PENDING, OVERDUE, COMPLETE, CORRUPT).
96-                          # State transition map is:
97-                          #  AVAILABLE -(send-read)-> PENDING
98-                          #  PENDING -(timer)-> OVERDUE
99-                          #  PENDING -(rx)-> COMPLETE, CORRUPT, DEAD, BADSEGNUM
100-                          #  OVERDUE -(rx)-> COMPLETE, CORRUPT, DEAD, BADSEGNUM
101-                          # If a share becomes DEAD, it is removed from the
102-                          # dict. If it becomes BADSEGNUM, the whole fetch is
103-                          # terminated.
104+        self._shares = [] # unused Share instances, sorted by "goodness"
105+                          # (RTT), then shnum. This is populated when DYHB
106+                          # responses arrive, or (for later segments) at
107+                          # startup. We remove shares from it when we call
108+                          # sh.get_block() on them.
109+        self._shares_from_server = DictOfSets() # maps serverid to set of
110+                                                # Shares on that server for
111+                                                # which we have outstanding
112+                                                # get_block() calls.
113+        self._max_shares_per_server = 1 # how many Shares we're allowed to
114+                                        # pull from each server. This starts
115+                                        # at 1 and grows if we don't have
116+                                        # sufficient diversity.
117+        self._active_share_map = {} # maps shnum to outstanding (and not
118+                                    # OVERDUE) Share that provides it.
119+        self._overdue_share_map = DictOfSets() # shares in the OVERDUE state
120+        self._lp = logparent
121         self._share_observers = {} # maps Share to EventStreamObserver for
122                                    # active ones
123hunk ./src/allmydata/immutable/downloader/fetcher.py 48
124-        self._shnums = DictOfSets() # maps shnum to the shares that provide it
125         self._blocks = {} # maps shnum to validated block data
126         self._no_more_shares = False
127hunk ./src/allmydata/immutable/downloader/fetcher.py 50
128-        self._bad_segnum = False
129         self._last_failure = None
130         self._running = True
131 
132hunk ./src/allmydata/immutable/downloader/fetcher.py 55
133     def stop(self):
134         log.msg("SegmentFetcher(%s).stop" % self._node._si_prefix,
135-                level=log.NOISY, umid="LWyqpg")
136+                level=log.NOISY, parent=self._lp, umid="LWyqpg")
137         self._cancel_all_requests()
138         self._running = False
139hunk ./src/allmydata/immutable/downloader/fetcher.py 58
140-        self._shares.clear() # let GC work # ??? XXX
141+        # help GC ??? XXX
142+        del self._shares, self._shares_from_server, self._active_share_map
143+        del self._share_observers
144 
145 
146     # called by our parent _Node
147hunk ./src/allmydata/immutable/downloader/fetcher.py 69
148         # called when ShareFinder locates a new share, and when a non-initial
149         # segment fetch is started and we already know about shares from the
150         # previous segment
151-        for s in shares:
152-            self._shares[s] = AVAILABLE
153-            self._shnums.add(s._shnum, s)
154+        self._shares.extend(shares)
155+        self._shares.sort(key=lambda s: (s._dyhb_rtt, s._shnum) )
156         eventually(self.loop)
157 
158     def no_more_shares(self):
159hunk ./src/allmydata/immutable/downloader/fetcher.py 80
160 
161     # internal methods
162 
163-    def _count_shnums(self, *states):
164-        """shnums for which at least one state is in the following list"""
165-        shnums = []
166-        for shnum,shares in self._shnums.iteritems():
167-            matches = [s for s in shares if self._shares.get(s) in states]
168-            if matches:
169-                shnums.append(shnum)
170-        return len(shnums)
171-
172     def loop(self):
173         try:
174             # if any exception occurs here, kill the download
175hunk ./src/allmydata/immutable/downloader/fetcher.py 92
176         k = self._k
177         if not self._running:
178             return
179-        if self._bad_segnum:
180+        numsegs, authoritative = self._node.get_num_segments()
181+        if authoritative and self.segnum >= numsegs:
182             # oops, we were asking for a segment number beyond the end of the
183             # file. This is an error.
184             self.stop()
185hunk ./src/allmydata/immutable/downloader/fetcher.py 103
186             self._node.fetch_failed(self, f)
187             return
188 
189+        #print "LOOP", self._blocks.keys(), "active:", self._active_share_map, "overdue:", self._overdue_share_map, "unused:", self._shares
190+        # Should we sent out more requests?
191+        while len(set(self._blocks.keys())
192+                  | set(self._active_share_map.keys())
193+                  ) < k:
194+            # we don't have data or active requests for enough shares. Are
195+            # there any unused shares we can start using?
196+            (sent_something, want_more_diversity) = self._find_and_use_share()
197+            if sent_something:
198+                # great. loop back around in case we need to send more.
199+                continue
200+            if want_more_diversity:
201+                # we could have sent something if we'd been allowed to pull
202+                # more shares per server. Increase the limit and try again.
203+                self._max_shares_per_server += 1
204+                log.msg("SegmentFetcher(%s) increasing diversity limit to %d"
205+                        % (self._node._si_prefix, self._max_shares_per_server),
206+                        level=log.NOISY, umid="xY2pBA")
207+                # Also ask for more shares, in the hopes of achieving better
208+                # diversity for the next segment.
209+                self._ask_for_more_shares()
210+                continue
211+            # we need more shares than the ones in self._shares to make
212+            # progress
213+            self._ask_for_more_shares()
214+            if self._no_more_shares:
215+                # But there are no more shares to be had. If we're going to
216+                # succeed, it will be with the shares we've already seen.
217+                # Will they be enough?
218+                if len(set(self._blocks.keys())
219+                       | set(self._active_share_map.keys())
220+                       | set(self._overdue_share_map.keys())
221+                       ) < k:
222+                    # nope. bail.
223+                    self._no_shares_error() # this calls self.stop()
224+                    return
225+                # our outstanding or overdue requests may yet work.
226+            # more shares may be coming. Wait until then.
227+            return
228+
229         # are we done?
230hunk ./src/allmydata/immutable/downloader/fetcher.py 144
231-        if self._count_shnums(COMPLETE) >= k:
232+        if len(set(self._blocks.keys())) >= k:
233             # yay!
234             self.stop()
235             self._node.process_blocks(self.segnum, self._blocks)
236hunk ./src/allmydata/immutable/downloader/fetcher.py 150
237             return
238 
239-        # we may have exhausted everything
240-        if (self._no_more_shares and
241-            self._count_shnums(AVAILABLE, PENDING, OVERDUE, COMPLETE) < k):
242-            # no more new shares are coming, and the remaining hopeful shares
243-            # aren't going to be enough. boo!
244+    def _no_shares_error(self):
245+        if not (self._shares or self._active_share_map or
246+                self._overdue_share_map or self._blocks):
247+            format = ("no shares (need %(k)d)."
248+                      " Last failure: %(last_failure)s")
249+            args = { "k": self._k,
250+                     "last_failure": self._last_failure }
251+            error = NoSharesError
252+        else:
253+            format = ("ran out of shares: complete=%(complete)s"
254+                      " pending=%(pending)s overdue=%(overdue)s"
255+                      " unused=%(unused)s need %(k)d."
256+                      " Last failure: %(last_failure)s")
257+            def join(shnums): return ",".join(["sh%d" % shnum
258+                                               for shnum in sorted(shnums)])
259+            pending_s = ",".join([str(sh)
260+                                  for sh in self._active_share_map.values()])
261+            overdue = set()
262+            for shares in self._overdue_share_map.values():
263+                overdue |= shares
264+            overdue_s = ",".join([str(sh) for sh in overdue])
265+            args = {"complete": join(self._blocks.keys()),
266+                    "pending": pending_s,
267+                    "overdue": overdue_s,
268+                    # 'unused' should be zero
269+                    "unused": ",".join([str(sh) for sh in self._shares]),
270+                    "k": self._k,
271+                    "last_failure": self._last_failure,
272+                    }
273+            error = NotEnoughSharesError
274+        log.msg(format=format,
275+                level=log.UNUSUAL, parent=self._lp, umid="1DsnTg",
276+                **args)
277+        e = error(format % args)
278+        f = Failure(e)
279+        self.stop()
280+        self._node.fetch_failed(self, f)
281 
282hunk ./src/allmydata/immutable/downloader/fetcher.py 188
283-            log.msg("share states: %r" % (self._shares,),
284-                    level=log.NOISY, umid="0ThykQ")
285-            if self._count_shnums(AVAILABLE, PENDING, OVERDUE, COMPLETE) == 0:
286-                format = ("no shares (need %(k)d)."
287-                          " Last failure: %(last_failure)s")
288-                args = { "k": k,
289-                         "last_failure": self._last_failure }
290-                error = NoSharesError
291-            else:
292-                format = ("ran out of shares: %(complete)d complete,"
293-                          " %(pending)d pending, %(overdue)d overdue,"
294-                          " %(unused)d unused, need %(k)d."
295-                          " Last failure: %(last_failure)s")
296-                args = {"complete": self._count_shnums(COMPLETE),
297-                        "pending": self._count_shnums(PENDING),
298-                        "overdue": self._count_shnums(OVERDUE),
299-                        # 'unused' should be zero
300-                        "unused": self._count_shnums(AVAILABLE),
301-                        "k": k,
302-                        "last_failure": self._last_failure,
303-                        }
304-                error = NotEnoughSharesError
305-            log.msg(format=format, level=log.UNUSUAL, umid="1DsnTg", **args)
306-            e = error(format % args)
307-            f = Failure(e)
308-            self.stop()
309-            self._node.fetch_failed(self, f)
310-            return
311+    def _find_and_use_share(self):
312+        sent_something = False
313+        want_more_diversity = False
314+        for sh in self._shares: # find one good share to fetch
315+            shnum = sh._shnum ; serverid = sh._peerid
316+            if shnum in self._blocks:
317+                continue # don't request data we already have
318+            if shnum in self._active_share_map:
319+                # note: OVERDUE shares are removed from _active_share_map
320+                # and added to _overdue_share_map instead.
321+                continue # don't send redundant requests
322+            sfs = self._shares_from_server
323+            if len(sfs.get(serverid,set())) >= self._max_shares_per_server:
324+                # don't pull too much from a single server
325+                want_more_diversity = True
326+                continue
327+            # ok, we can use this share
328+            self._shares.remove(sh)
329+            self._active_share_map[shnum] = sh
330+            self._shares_from_server.add(serverid, sh)
331+            self._start_share(sh, shnum)
332+            sent_something = True
333+            break
334+        return (sent_something, want_more_diversity)
335 
336hunk ./src/allmydata/immutable/downloader/fetcher.py 213
337-        # nope, not done. Are we "block-hungry" (i.e. do we want to send out
338-        # more read requests, or do we think we have enough in flight
339-        # already?)
340-        while self._count_shnums(PENDING, COMPLETE) < k:
341-            # we're hungry.. are there any unused shares?
342-            sent = self._send_new_request()
343-            if not sent:
344-                break
345+    def _start_share(self, share, shnum):
346+        self._share_observers[share] = o = share.get_block(self.segnum)
347+        o.subscribe(self._block_request_activity, share=share, shnum=shnum)
348 
349hunk ./src/allmydata/immutable/downloader/fetcher.py 217
350-        # ok, now are we "share-hungry" (i.e. do we have enough known shares
351-        # to make us happy, or should we ask the ShareFinder to get us more?)
352-        if self._count_shnums(AVAILABLE, PENDING, COMPLETE) < k:
353-            # we're hungry for more shares
354+    def _ask_for_more_shares(self):
355+        if not self._no_more_shares:
356             self._node.want_more_shares()
357hunk ./src/allmydata/immutable/downloader/fetcher.py 220
358-            # that will trigger the ShareFinder to keep looking
359-
360-    def _find_one(self, shares, state):
361-        # TODO could choose fastest, or avoid servers already in use
362-        for s in shares:
363-            if self._shares[s] == state:
364-                return s
365-        # can never get here, caller has assert in case of code bug
366-
367-    def _send_new_request(self):
368-        # TODO: this is probably O(k^2), and we're called from a range(k)
369-        # loop, so O(k^3)
370-
371-        # this first loop prefers sh0, then sh1, sh2, etc
372-        for shnum,shares in sorted(self._shnums.iteritems()):
373-            states = [self._shares[s] for s in shares]
374-            if COMPLETE in states or PENDING in states:
375-                # don't send redundant requests
376-                continue
377-            if AVAILABLE not in states:
378-                # no candidates for this shnum, move on
379-                continue
380-            # here's a candidate. Send a request.
381-            s = self._find_one(shares, AVAILABLE)
382-            assert s
383-            self._shares[s] = PENDING
384-            self._share_observers[s] = o = s.get_block(self.segnum)
385-            o.subscribe(self._block_request_activity, share=s, shnum=shnum)
386-            # TODO: build up a list of candidates, then walk through the
387-            # list, sending requests to the most desireable servers,
388-            # re-checking our block-hunger each time. For non-initial segment
389-            # fetches, this would let us stick with faster servers.
390-            return True
391-        # nothing was sent: don't call us again until you have more shares to
392-        # work with, or one of the existing shares has been declared OVERDUE
393-        return False
394+            # that will trigger the ShareFinder to keep looking, and call our
395+            # add_shares() or no_more_shares() later.
396 
397     def _cancel_all_requests(self):
398         for o in self._share_observers.values():
399hunk ./src/allmydata/immutable/downloader/fetcher.py 235
400         log.msg("SegmentFetcher(%s)._block_request_activity:"
401                 " Share(sh%d-on-%s) -> %s" %
402                 (self._node._si_prefix, shnum, share._peerid_s, state),
403-                level=log.NOISY, umid="vilNWA")
404-        # COMPLETE, CORRUPT, DEAD, BADSEGNUM are terminal.
405+                level=log.NOISY, parent=self._lp, umid="vilNWA")
406+        # COMPLETE, CORRUPT, DEAD, BADSEGNUM are terminal. Remove the share
407+        # from all our tracking lists.
408         if state in (COMPLETE, CORRUPT, DEAD, BADSEGNUM):
409             self._share_observers.pop(share, None)
410hunk ./src/allmydata/immutable/downloader/fetcher.py 240
411+            self._shares_from_server.discard(shnum, share)
412+            if self._active_share_map.get(shnum) is share:
413+                del self._active_share_map[shnum]
414+            self._overdue_share_map.discard(shnum, share)
415+
416         if state is COMPLETE:
417hunk ./src/allmydata/immutable/downloader/fetcher.py 246
418-            # 'block' is fully validated
419-            self._shares[share] = COMPLETE
420+            # 'block' is fully validated and complete
421             self._blocks[shnum] = block
422hunk ./src/allmydata/immutable/downloader/fetcher.py 248
423-        elif state is OVERDUE:
424-            self._shares[share] = OVERDUE
425+
426+        if state is OVERDUE:
427+            # no longer active, but still might complete
428+            del self._active_share_map[shnum]
429+            self._overdue_share_map.add(shnum, share)
430             # OVERDUE is not terminal: it will eventually transition to
431             # COMPLETE, CORRUPT, or DEAD.
432hunk ./src/allmydata/immutable/downloader/fetcher.py 255
433-        elif state is CORRUPT:
434-            self._shares[share] = CORRUPT
435-        elif state is DEAD:
436-            del self._shares[share]
437-            self._shnums[shnum].remove(share)
438-            self._last_failure = f
439-        elif state is BADSEGNUM:
440-            self._shares[share] = BADSEGNUM # ???
441-            self._bad_segnum = True
442-        eventually(self.loop)
443 
444hunk ./src/allmydata/immutable/downloader/fetcher.py 256
445+        if state is DEAD:
446+            self._last_failure = f
447+        if state is BADSEGNUM:
448+            # our main loop will ask the DownloadNode each time for the
449+            # number of segments, so we'll deal with this in the top of
450+            # _do_loop
451+            pass
452 
453hunk ./src/allmydata/immutable/downloader/fetcher.py 264
454+        eventually(self.loop)
455hunk ./src/allmydata/immutable/downloader/finder.py 38
456         self._storage_broker = storage_broker
457         self.share_consumer = self.node = node
458         self.max_outstanding_requests = max_outstanding_requests
459-
460         self._hungry = False
461 
462         self._commonshares = {} # shnum to CommonShare instance
463hunk ./src/allmydata/immutable/downloader/finder.py 41
464-        self.undelivered_shares = []
465         self.pending_requests = set()
466         self.overdue_requests = set() # subset of pending_requests
467         self.overdue_timers = {}
468hunk ./src/allmydata/immutable/downloader/finder.py 53
469                            si=self._si_prefix,
470                            level=log.NOISY, parent=logparent, umid="2xjj2A")
471 
472+    def update_num_segments(self):
473+        (numsegs, authoritative) = self.node.get_num_segments()
474+        assert authoritative
475+        for cs in self._commonshares.values():
476+            cs.set_authoritative_num_segments(numsegs)
477+
478     def start_finding_servers(self):
479         # don't get servers until somebody uses us: creating the
480         # ImmutableFileNode should not cause work to happen yet. Test case is
481hunk ./src/allmydata/immutable/downloader/finder.py 90
482 
483     # internal methods
484     def loop(self):
485-        undelivered_s = ",".join(["sh%d@%s" %
486-                                  (s._shnum, idlib.shortnodeid_b2a(s._peerid))
487-                                  for s in self.undelivered_shares])
488         pending_s = ",".join([idlib.shortnodeid_b2a(rt.peerid)
489                               for rt in self.pending_requests]) # sort?
490         self.log(format="ShareFinder loop: running=%(running)s"
491hunk ./src/allmydata/immutable/downloader/finder.py 93
492-                 " hungry=%(hungry)s, undelivered=%(undelivered)s,"
493-                 " pending=%(pending)s",
494-                 running=self.running, hungry=self._hungry,
495-                 undelivered=undelivered_s, pending=pending_s,
496+                 " hungry=%(hungry)s, pending=%(pending)s",
497+                 running=self.running, hungry=self._hungry, pending=pending_s,
498                  level=log.NOISY, umid="kRtS4Q")
499         if not self.running:
500             return
501hunk ./src/allmydata/immutable/downloader/finder.py 100
502         if not self._hungry:
503             return
504-        if self.undelivered_shares:
505-            sh = self.undelivered_shares.pop(0)
506-            # they will call hungry() again if they want more
507-            self._hungry = False
508-            self.log(format="delivering Share(shnum=%(shnum)d, server=%(peerid)s)",
509-                     shnum=sh._shnum, peerid=sh._peerid_s,
510-                     level=log.NOISY, umid="2n1qQw")
511-            eventually(self.share_consumer.got_shares, [sh])
512-            return
513 
514         non_overdue = self.pending_requests - self.overdue_requests
515         if len(non_overdue) >= self.max_outstanding_requests:
516hunk ./src/allmydata/immutable/downloader/finder.py 139
517         lp = self.log(format="sending DYHB to [%(peerid)s]",
518                       peerid=idlib.shortnodeid_b2a(peerid),
519                       level=log.NOISY, umid="Io7pyg")
520-        d_ev = self._download_status.add_dyhb_sent(peerid, now())
521+        time_sent = now()
522+        d_ev = self._download_status.add_dyhb_sent(peerid, time_sent)
523         # TODO: get the timer from a Server object, it knows best
524         self.overdue_timers[req] = reactor.callLater(self.OVERDUE_TIMEOUT,
525                                                      self.overdue, req)
526hunk ./src/allmydata/immutable/downloader/finder.py 147
527         d = rref.callRemote("get_buckets", self._storage_index)
528         d.addBoth(incidentally, self._request_retired, req)
529         d.addCallbacks(self._got_response, self._got_error,
530-                       callbackArgs=(rref.version, peerid, req, d_ev, lp),
531+                       callbackArgs=(rref.version, peerid, req, d_ev,
532+                                     time_sent, lp),
533                        errbackArgs=(peerid, req, d_ev, lp))
534         d.addErrback(log.err, format="error in send_request",
535                      level=log.WEIRD, parent=lp, umid="rpdV0w")
536hunk ./src/allmydata/immutable/downloader/finder.py 167
537         self.overdue_requests.add(req)
538         eventually(self.loop)
539 
540-    def _got_response(self, buckets, server_version, peerid, req, d_ev, lp):
541+    def _got_response(self, buckets, server_version, peerid, req, d_ev,
542+                      time_sent, lp):
543         shnums = sorted([shnum for shnum in buckets])
544hunk ./src/allmydata/immutable/downloader/finder.py 170
545-        d_ev.finished(shnums, now())
546-        if buckets:
547-            shnums_s = ",".join([str(shnum) for shnum in shnums])
548-            self.log(format="got shnums [%(shnums)s] from [%(peerid)s]",
549-                     shnums=shnums_s, peerid=idlib.shortnodeid_b2a(peerid),
550-                     level=log.NOISY, parent=lp, umid="0fcEZw")
551-        else:
552+        time_received = now()
553+        d_ev.finished(shnums, time_received)
554+        dyhb_rtt = time_received - time_sent
555+        if not buckets:
556             self.log(format="no shares from [%(peerid)s]",
557                      peerid=idlib.shortnodeid_b2a(peerid),
558                      level=log.NOISY, parent=lp, umid="U7d4JA")
559hunk ./src/allmydata/immutable/downloader/finder.py 177
560-        if self.node.num_segments is None:
561-            best_numsegs = self.node.guessed_num_segments
562-        else:
563-            best_numsegs = self.node.num_segments
564+            return
565+        shnums_s = ",".join([str(shnum) for shnum in shnums])
566+        self.log(format="got shnums [%(shnums)s] from [%(peerid)s]",
567+                 shnums=shnums_s, peerid=idlib.shortnodeid_b2a(peerid),
568+                 level=log.NOISY, parent=lp, umid="0fcEZw")
569+        shares = []
570         for shnum, bucket in buckets.iteritems():
571hunk ./src/allmydata/immutable/downloader/finder.py 184
572-            self._create_share(best_numsegs, shnum, bucket, server_version,
573-                               peerid)
574+            s = self._create_share(shnum, bucket, server_version, peerid,
575+                                   dyhb_rtt)
576+            shares.append(s)
577+        self._deliver_shares(shares)
578 
579hunk ./src/allmydata/immutable/downloader/finder.py 189
580-    def _create_share(self, best_numsegs, shnum, bucket, server_version,
581-                      peerid):
582+    def _create_share(self, shnum, bucket, server_version, peerid, dyhb_rtt):
583         if shnum in self._commonshares:
584             cs = self._commonshares[shnum]
585         else:
586hunk ./src/allmydata/immutable/downloader/finder.py 193
587-            cs = CommonShare(best_numsegs, self._si_prefix, shnum,
588+            numsegs, authoritative = self.node.get_num_segments()
589+            cs = CommonShare(numsegs, self._si_prefix, shnum,
590                              self._node_logparent)
591hunk ./src/allmydata/immutable/downloader/finder.py 196
592+            if authoritative:
593+                cs.set_authoritative_num_segments(numsegs)
594             # Share._get_satisfaction is responsible for updating
595             # CommonShare.set_numsegs after we know the UEB. Alternatives:
596             #  1: d = self.node.get_num_segments()
597hunk ./src/allmydata/immutable/downloader/finder.py 213
598             #     Yuck.
599             self._commonshares[shnum] = cs
600         s = Share(bucket, server_version, self.verifycap, cs, self.node,
601-                  self._download_status, peerid, shnum,
602+                  self._download_status, peerid, shnum, dyhb_rtt,
603                   self._node_logparent)
604hunk ./src/allmydata/immutable/downloader/finder.py 215
605-        self.undelivered_shares.append(s)
606+        return s
607+
608+    def _deliver_shares(self, shares):
609+        # they will call hungry() again if they want more
610+        self._hungry = False
611+        shares_s = ",".join([str(sh) for sh in shares])
612+        self.log(format="delivering shares: %s" % shares_s,
613+                 level=log.NOISY, umid="2n1qQw")
614+        eventually(self.share_consumer.got_shares, shares)
615 
616     def _got_error(self, f, peerid, req, d_ev, lp):
617         d_ev.finished("error", now())
618hunk ./src/allmydata/immutable/downloader/node.py 75
619         # things to track callers that want data
620 
621         # _segment_requests can have duplicates
622-        self._segment_requests = [] # (segnum, d, cancel_handle)
623+        self._segment_requests = [] # (segnum, d, cancel_handle, logparent)
624         self._active_segment = None # a SegmentFetcher, with .segnum
625 
626         self._segsize_observers = observer.OneShotObserverList()
627hunk ./src/allmydata/immutable/downloader/node.py 84
628         # for each read() call. Segmentation and get_segment() messages are
629         # associated with the read() call, everything else is tied to the
630         # _Node's log entry.
631-        lp = log.msg(format="Immutable _Node(%(si)s) created: size=%(size)d,"
632+        lp = log.msg(format="Immutable.DownloadNode(%(si)s) created:"
633+                     " size=%(size)d,"
634                      " guessed_segsize=%(guessed_segsize)d,"
635                      " guessed_numsegs=%(guessed_numsegs)d",
636                      si=self._si_prefix, size=verifycap.size,
637hunk ./src/allmydata/immutable/downloader/node.py 107
638         # as with CommonShare, our ciphertext_hash_tree is a stub until we
639         # get the real num_segments
640         self.ciphertext_hash_tree = IncompleteHashTree(self.guessed_num_segments)
641+        self.ciphertext_hash_tree_leaves = self.guessed_num_segments
642 
643     def __repr__(self):
644hunk ./src/allmydata/immutable/downloader/node.py 110
645-        return "Imm_Node(%s)" % (self._si_prefix,)
646+        return "ImmutableDownloadNode(%s)" % (self._si_prefix,)
647 
648     def stop(self):
649         # called by the Terminator at shutdown, mostly for tests
650hunk ./src/allmydata/immutable/downloader/node.py 180
651         The Deferred can also errback with other fatal problems, such as
652         NotEnoughSharesError, NoSharesError, or BadCiphertextHashError.
653         """
654-        log.msg(format="imm Node(%(si)s).get_segment(%(segnum)d)",
655-                si=base32.b2a(self._verifycap.storage_index)[:8],
656-                segnum=segnum,
657-                level=log.OPERATIONAL, parent=logparent, umid="UKFjDQ")
658+        lp = log.msg(format="imm Node(%(si)s).get_segment(%(segnum)d)",
659+                     si=base32.b2a(self._verifycap.storage_index)[:8],
660+                     segnum=segnum,
661+                     level=log.OPERATIONAL, parent=logparent, umid="UKFjDQ")
662         self._download_status.add_segment_request(segnum, now())
663         d = defer.Deferred()
664         c = Cancel(self._cancel_request)
665hunk ./src/allmydata/immutable/downloader/node.py 187
666-        self._segment_requests.append( (segnum, d, c) )
667+        self._segment_requests.append( (segnum, d, c, lp) )
668         self._start_new_segment()
669         return (d, c)
670 
671hunk ./src/allmydata/immutable/downloader/node.py 213
672         if self._active_segment is None and self._segment_requests:
673             segnum = self._segment_requests[0][0]
674             k = self._verifycap.needed_shares
675+            lp = self._segment_requests[0][3]
676             log.msg(format="%(node)s._start_new_segment: segnum=%(segnum)d",
677                     node=repr(self), segnum=segnum,
678hunk ./src/allmydata/immutable/downloader/node.py 216
679-                    level=log.NOISY, umid="wAlnHQ")
680-            self._active_segment = fetcher = SegmentFetcher(self, segnum, k)
681+                    level=log.NOISY, parent=lp, umid="wAlnHQ")
682+            self._active_segment = fetcher = SegmentFetcher(self, segnum, k, lp)
683             active_shares = [s for s in self._shares if s.is_alive()]
684             fetcher.add_shares(active_shares) # this triggers the loop
685 
686hunk ./src/allmydata/immutable/downloader/node.py 240
687         h = hashutil.uri_extension_hash(UEB_s)
688         if h != self._verifycap.uri_extension_hash:
689             raise BadHashError
690-        UEB_dict = uri.unpack_extension(UEB_s)
691-        self._parse_and_store_UEB(UEB_dict) # sets self._stuff
692+        self._parse_and_store_UEB(UEB_s) # sets self._stuff
693         # TODO: a malformed (but authentic) UEB could throw an assertion in
694         # _parse_and_store_UEB, and we should abandon the download.
695         self.have_UEB = True
696hunk ./src/allmydata/immutable/downloader/node.py 245
697 
698-    def _parse_and_store_UEB(self, d):
699+        # inform the ShareFinder about our correct number of segments. This
700+        # will update the block-hash-trees in all existing CommonShare
701+        # instances, and will populate new ones with the correct value.
702+        self._sharefinder.update_num_segments()
703+
704+    def _parse_and_store_UEB(self, UEB_s):
705         # Note: the UEB contains needed_shares and total_shares. These are
706         # redundant and inferior (the filecap contains the authoritative
707         # values). However, because it is possible to encode the same file in
708hunk ./src/allmydata/immutable/downloader/node.py 262
709 
710         # therefore, we ignore d['total_shares'] and d['needed_shares'].
711 
712+        d = uri.unpack_extension(UEB_s)
713+
714         log.msg(format="UEB=%(ueb)s, vcap=%(vcap)s",
715hunk ./src/allmydata/immutable/downloader/node.py 265
716-                ueb=repr(d), vcap=self._verifycap.to_string(),
717+                ueb=repr(uri.unpack_extension_readable(UEB_s)),
718+                vcap=self._verifycap.to_string(),
719                 level=log.NOISY, parent=self._lp, umid="cVqZnA")
720 
721         k, N = self._verifycap.needed_shares, self._verifycap.total_shares
722hunk ./src/allmydata/immutable/downloader/node.py 305
723         # shares of file B. self.ciphertext_hash_tree was a guess before:
724         # this is where we create it for real.
725         self.ciphertext_hash_tree = IncompleteHashTree(self.num_segments)
726+        self.ciphertext_hash_tree_leaves = self.num_segments
727         self.ciphertext_hash_tree.set_hashes({0: d['crypttext_root_hash']})
728 
729         self.share_hash_tree.set_hashes({0: d['share_root_hash']})
730hunk ./src/allmydata/immutable/downloader/node.py 358
731                                    % (hashnum, len(self.share_hash_tree)))
732         self.share_hash_tree.set_hashes(share_hashes)
733 
734+    def get_desired_ciphertext_hashes(self, segnum):
735+        if segnum < self.ciphertext_hash_tree_leaves:
736+            return self.ciphertext_hash_tree.needed_hashes(segnum,
737+                                                           include_leaf=True)
738+        return []
739     def get_needed_ciphertext_hashes(self, segnum):
740         cht = self.ciphertext_hash_tree
741         return cht.needed_hashes(segnum, include_leaf=True)
742hunk ./src/allmydata/immutable/downloader/node.py 366
743+
744     def process_ciphertext_hashes(self, hashes):
745         assert self.num_segments is not None
746         # this may raise BadHashError or NotEnoughHashesError
747hunk ./src/allmydata/immutable/downloader/node.py 477
748     def _extract_requests(self, segnum):
749         """Remove matching requests and return their (d,c) tuples so that the
750         caller can retire them."""
751-        retire = [(d,c) for (segnum0, d, c) in self._segment_requests
752+        retire = [(d,c) for (segnum0, d, c, lp) in self._segment_requests
753                   if segnum0 == segnum]
754         self._segment_requests = [t for t in self._segment_requests
755                                   if t[0] != segnum]
756hunk ./src/allmydata/immutable/downloader/node.py 486
757     def _cancel_request(self, c):
758         self._segment_requests = [t for t in self._segment_requests
759                                   if t[2] != c]
760-        segnums = [segnum for (segnum,d,c) in self._segment_requests]
761+        segnums = [segnum for (segnum,d,c,lp) in self._segment_requests]
762         # self._active_segment might be None in rare circumstances, so make
763         # sure we tolerate it
764         if self._active_segment and self._active_segment.segnum not in segnums:
765hunk ./src/allmydata/immutable/downloader/node.py 493
766             self._active_segment.stop()
767             self._active_segment = None
768             self._start_new_segment()
769+
770+    # called by ShareFinder to choose hashtree sizes in CommonShares, and by
771+    # SegmentFetcher to tell if it is still fetching a valid segnum.
772+    def get_num_segments(self):
773+        # returns (best_num_segments, authoritative)
774+        if self.num_segments is None:
775+            return (self.guessed_num_segments, False)
776+        return (self.num_segments, True)
777hunk ./src/allmydata/immutable/downloader/share.py 36
778     # servers. A different backend would use a different class.
779 
780     def __init__(self, rref, server_version, verifycap, commonshare, node,
781-                 download_status, peerid, shnum, logparent):
782+                 download_status, peerid, shnum, dyhb_rtt, logparent):
783         self._rref = rref
784         self._server_version = server_version
785         self._node = node # holds share_hash_tree and UEB
786hunk ./src/allmydata/immutable/downloader/share.py 54
787         self._storage_index = verifycap.storage_index
788         self._si_prefix = base32.b2a(verifycap.storage_index)[:8]
789         self._shnum = shnum
790+        self._dyhb_rtt = dyhb_rtt
791         # self._alive becomes False upon fatal corruption or server error
792         self._alive = True
793         self._lp = log.msg(format="%(share)s created", share=repr(self),
794hunk ./src/allmydata/immutable/downloader/share.py 282
795             if not self._satisfy_UEB():
796                 # can't check any hashes without the UEB
797                 return False
798+            # the call to _satisfy_UEB() will immediately set the
799+            # authoritative num_segments in all our CommonShares. If we
800+            # guessed wrong, we might stil be working on a bogus segnum
801+            # (beyond the real range). We catch this and signal BADSEGNUM
802+            # before invoking any further code that touches hashtrees.
803         self.actual_segment_size = self._node.segment_size # might be updated
804         assert self.actual_segment_size is not None
805 
806hunk ./src/allmydata/immutable/downloader/share.py 290
807-        # knowing the UEB means knowing num_segments. Despite the redundancy,
808-        # this is the best place to set this. CommonShare.set_numsegs will
809-        # ignore duplicate calls.
810+        # knowing the UEB means knowing num_segments
811         assert self._node.num_segments is not None
812hunk ./src/allmydata/immutable/downloader/share.py 292
813-        cs = self._commonshare
814-        cs.set_numsegs(self._node.num_segments)
815 
816         segnum, observers = self._active_segnum_and_observers()
817         # if segnum is None, we don't really need to do anything (we have no
818hunk ./src/allmydata/immutable/downloader/share.py 309
819                 # can't check block_hash_tree without a root
820                 return False
821 
822-        if cs.need_block_hash_root():
823+        if self._commonshare.need_block_hash_root():
824             block_hash_root = self._node.share_hash_tree.get_leaf(self._shnum)
825hunk ./src/allmydata/immutable/downloader/share.py 311
826-            cs.set_block_hash_root(block_hash_root)
827+            self._commonshare.set_block_hash_root(block_hash_root)
828 
829         if segnum is None:
830             return False # we don't want any particular segment right now
831hunk ./src/allmydata/immutable/downloader/share.py 365
832                                   ] ):
833             offsets[field] = fields[i]
834         self.actual_offsets = offsets
835-        log.msg("actual offsets: data=%d, plaintext_hash_tree=%d, crypttext_hash_tree=%d, block_hashes=%d, share_hashes=%d, uri_extension=%d" % tuple(fields))
836+        log.msg("actual offsets: data=%d, plaintext_hash_tree=%d, crypttext_hash_tree=%d, block_hashes=%d, share_hashes=%d, uri_extension=%d" % tuple(fields),
837+                level=log.NOISY, parent=self._lp, umid="jedQcw")
838         self._received.remove(0, 4) # don't need this anymore
839 
840         # validate the offsets a bit
841hunk ./src/allmydata/immutable/downloader/share.py 523
842         block = self._received.pop(blockstart, blocklen)
843         if not block:
844             log.msg("no data for block %s (want [%d:+%d])" % (repr(self),
845-                                                              blockstart, blocklen))
846+                                                              blockstart, blocklen),
847+                    level=log.NOISY, parent=self._lp, umid="aK0RFw")
848             return False
849         log.msg(format="%(share)s._satisfy_data_block [%(start)d:+%(length)d]",
850                 share=repr(self), start=blockstart, length=blocklen,
851hunk ./src/allmydata/immutable/downloader/share.py 596
852         if self.actual_offsets or self._overrun_ok:
853             if not self._node.have_UEB:
854                 self._desire_UEB(desire, o)
855-            # They might ask for a segment that doesn't look right.
856-            # _satisfy() will catch+reject bad segnums once we know the UEB
857-            # (and therefore segsize and numsegs), so we'll only fail this
858-            # test if we're still guessing. We want to avoid asking the
859-            # hashtrees for needed_hashes() for bad segnums. So don't enter
860-            # _desire_hashes or _desire_data unless the segnum looks
861-            # reasonable.
862-            if segnum < r["num_segments"]:
863-                # XXX somehow we're getting here for sh5. we don't yet know
864-                # the actual_segment_size, we're still working off the guess.
865-                # the ciphertext_hash_tree has been corrected, but the
866-                # commonshare._block_hash_tree is still in the guessed state.
867-                self._desire_share_hashes(desire, o)
868-                if segnum is not None:
869-                    self._desire_block_hashes(desire, o, segnum)
870-                    self._desire_data(desire, o, r, segnum, segsize)
871-            else:
872-                log.msg("_desire: segnum(%d) looks wrong (numsegs=%d)"
873-                        % (segnum, r["num_segments"]),
874-                        level=log.UNUSUAL, parent=self._lp, umid="tuYRQQ")
875+            self._desire_share_hashes(desire, o)
876+            if segnum is not None:
877+                # They might be asking for a segment number that is beyond
878+                # what we guess the file contains, but _desire_block_hashes
879+                # and _desire_data will tolerate that.
880+                self._desire_block_hashes(desire, o, segnum)
881+                self._desire_data(desire, o, r, segnum, segsize)
882 
883         log.msg("end _desire: want_it=%s need_it=%s gotta=%s"
884hunk ./src/allmydata/immutable/downloader/share.py 605
885-                % (want_it.dump(), need_it.dump(), gotta_gotta_have_it.dump()))
886+                % (want_it.dump(), need_it.dump(), gotta_gotta_have_it.dump()),
887+                level=log.NOISY, parent=self._lp, umid="IG7CgA")
888         if self.actual_offsets:
889             return (want_it, need_it+gotta_gotta_have_it)
890         else:
891hunk ./src/allmydata/immutable/downloader/share.py 676
892         (want_it, need_it, gotta_gotta_have_it) = desire
893 
894         # block hash chain
895-        for hashnum in self._commonshare.get_needed_block_hashes(segnum):
896+        for hashnum in self._commonshare.get_desired_block_hashes(segnum):
897             need_it.add(o["block_hashes"]+hashnum*HASH_SIZE, HASH_SIZE)
898 
899         # ciphertext hash chain
900hunk ./src/allmydata/immutable/downloader/share.py 680
901-        for hashnum in self._node.get_needed_ciphertext_hashes(segnum):
902+        for hashnum in self._node.get_desired_ciphertext_hashes(segnum):
903             need_it.add(o["crypttext_hash_tree"]+hashnum*HASH_SIZE, HASH_SIZE)
904 
905     def _desire_data(self, desire, o, r, segnum, segsize):
906hunk ./src/allmydata/immutable/downloader/share.py 684
907+        if segnum > r["num_segments"]:
908+            # they're asking for a segment that's beyond what we think is the
909+            # end of the file. We won't get here if we've already learned the
910+            # real UEB: _get_satisfaction() will notice the out-of-bounds and
911+            # terminate the loop. So we must still be guessing, which means
912+            # that they might be correct in asking for such a large segnum.
913+            # But if they're right, then our segsize/segnum guess is
914+            # certainly wrong, which means we don't know what data blocks to
915+            # ask for yet. So don't bother adding anything. When the UEB
916+            # comes back and we learn the correct segsize/segnums, we'll
917+            # either reject the request or have enough information to proceed
918+            # normally. This costs one roundtrip.
919+            log.msg("_desire_data: segnum(%d) looks wrong (numsegs=%d)"
920+                    % (segnum, r["num_segments"]),
921+                    level=log.UNUSUAL, parent=self._lp, umid="tuYRQQ")
922+            return
923         (want_it, need_it, gotta_gotta_have_it) = desire
924         tail = (segnum == r["num_segments"]-1)
925         datastart = o["data"]
926hunk ./src/allmydata/immutable/downloader/share.py 814
927 
928 
929 class CommonShare:
930+    # TODO: defer creation of the hashtree until somebody uses us. There will
931+    # be a lot of unused shares, and we shouldn't spend the memory on a large
932+    # hashtree unless necessary.
933     """I hold data that is common across all instances of a single share,
934     like sh2 on both servers A and B. This is just the block hash tree.
935     """
936hunk ./src/allmydata/immutable/downloader/share.py 820
937-    def __init__(self, guessed_numsegs, si_prefix, shnum, logparent):
938+    def __init__(self, best_numsegs, si_prefix, shnum, logparent):
939         self.si_prefix = si_prefix
940         self.shnum = shnum
941hunk ./src/allmydata/immutable/downloader/share.py 823
942+
943         # in the beginning, before we have the real UEB, we can only guess at
944         # the number of segments. But we want to ask for block hashes early.
945         # So if we're asked for which block hashes are needed before we know
946hunk ./src/allmydata/immutable/downloader/share.py 828
947         # numsegs for sure, we return a guess.
948-        self._block_hash_tree = IncompleteHashTree(guessed_numsegs)
949-        self._know_numsegs = False
950+        self._block_hash_tree = IncompleteHashTree(best_numsegs)
951+        self._block_hash_tree_is_authoritative = False
952+        self._block_hash_tree_leaves = best_numsegs
953         self._logparent = logparent
954 
955hunk ./src/allmydata/immutable/downloader/share.py 833
956-    def set_numsegs(self, numsegs):
957-        if self._know_numsegs:
958-            return
959-        self._block_hash_tree = IncompleteHashTree(numsegs)
960-        self._know_numsegs = True
961+    def __repr__(self):
962+        return "CommonShare(%s-sh%d)" % (self.si_prefix, self.shnum)
963+
964+    def set_authoritative_num_segments(self, numsegs):
965+        if self._block_hash_tree_leaves != numsegs:
966+            self._block_hash_tree = IncompleteHashTree(numsegs)
967+            self._block_hash_tree_leaves = numsegs
968+        self._block_hash_tree_is_authoritative = True
969 
970     def need_block_hash_root(self):
971         return bool(not self._block_hash_tree[0])
972hunk ./src/allmydata/immutable/downloader/share.py 846
973 
974     def set_block_hash_root(self, roothash):
975-        assert self._know_numsegs
976+        assert self._block_hash_tree_is_authoritative
977         self._block_hash_tree.set_hashes({0: roothash})
978 
979hunk ./src/allmydata/immutable/downloader/share.py 849
980+    def get_desired_block_hashes(self, segnum):
981+        if segnum < self._block_hash_tree_leaves:
982+            return self._block_hash_tree.needed_hashes(segnum,
983+                                                       include_leaf=True)
984+
985+        # the segnum might be out-of-bounds. Originally it was due to a race
986+        # between the receipt of the UEB on one share (from which we learn
987+        # the correct number of segments, update all hash trees to the right
988+        # size, and queue a BADSEGNUM to the SegmentFetcher) and the delivery
989+        # of a new Share to the SegmentFetcher while that BADSEGNUM was
990+        # queued (which sends out requests to the stale segnum, now larger
991+        # than the hash tree). I fixed that (by making SegmentFetcher.loop
992+        # check for a bad segnum at the start of each pass, instead of using
993+        # the queued BADSEGNUM or a flag it sets), but just in case this
994+        # still happens, I'm leaving the < in place. If it gets hit, there's
995+        # a potential lost-progress problem, but I'm pretty sure that it will
996+        # get cleared up on the following turn.
997+        return []
998+
999     def get_needed_block_hashes(self, segnum):
1000hunk ./src/allmydata/immutable/downloader/share.py 869
1001+        assert self._block_hash_tree_is_authoritative
1002         # XXX: include_leaf=True needs thought: how did the old downloader do
1003         # it? I think it grabbed *all* block hashes and set them all at once.
1004         # Since we want to fetch less data, we either need to fetch the leaf
1005hunk ./src/allmydata/immutable/downloader/share.py 879
1006         return self._block_hash_tree.needed_hashes(segnum, include_leaf=True)
1007 
1008     def process_block_hashes(self, block_hashes):
1009-        assert self._know_numsegs
1010+        assert self._block_hash_tree_is_authoritative
1011         # this may raise BadHashError or NotEnoughHashesError
1012         self._block_hash_tree.set_hashes(block_hashes)
1013 
1014hunk ./src/allmydata/immutable/downloader/share.py 884
1015     def check_block(self, segnum, block):
1016-        assert self._know_numsegs
1017+        assert self._block_hash_tree_is_authoritative
1018         h = hashutil.block_hash(block)
1019         # this may raise BadHashError or NotEnoughHashesError
1020         self._block_hash_tree.set_hashes(leaves={segnum: h})
1021hunk ./src/allmydata/immutable/downloader/share.py 888
1022+
1023+# TODO: maybe stop using EventStreamObserver: instead, use a Deferred and an
1024+# auxilliary OVERDUE callback. Just make sure to get all the messages in the
1025+# right order and on the right turns.
1026+
1027+# TODO: we're asking for too much data. We probably don't need
1028+# include_leaf=True in the block hash tree or ciphertext hash tree.
1029+
1030+# TODO: we ask for ciphertext hash tree nodes from all shares (whenever
1031+# _desire is called while we're missing those nodes), but we only consume it
1032+# from the first response, leaving the rest of the data sitting in _received.
1033+# This was ameliorated by clearing self._received after each block is
1034+# complete.
1035hunk ./src/allmydata/test/test_cli.py 2306
1036         # the download is abandoned as soon as it's clear that we won't get
1037         # enough shares. The one remaining share might be in either the
1038         # COMPLETE or the PENDING state.
1039-        in_complete_msg = "ran out of shares: 1 complete, 0 pending, 0 overdue, 0 unused, need 3"
1040-        in_pending_msg = "ran out of shares: 0 complete, 1 pending, 0 overdue, 0 unused, need 3"
1041+        in_complete_msg = "ran out of shares: complete=sh0 pending= overdue= unused= need 3"
1042+        in_pending_msg = "ran out of shares: complete= pending=Share(sh0-on-fob7v) overdue= unused= need 3"
1043 
1044         d.addCallback(lambda ign: self.do_cli("get", self.uri_1share))
1045         def _check1((rc, out, err)):
1046hunk ./src/allmydata/test/test_download.py 18
1047 from allmydata.test.common import ShouldFailMixin
1048 from allmydata.interfaces import NotEnoughSharesError, NoSharesError
1049 from allmydata.immutable.downloader.common import BadSegmentNumberError, \
1050-     BadCiphertextHashError, DownloadStopped
1051+     BadCiphertextHashError, DownloadStopped, COMPLETE, OVERDUE, DEAD
1052 from allmydata.immutable.downloader.status import DownloadStatus
1053hunk ./src/allmydata/test/test_download.py 20
1054+from allmydata.immutable.downloader.fetcher import SegmentFetcher
1055 from allmydata.codec import CRSDecoder
1056 from foolscap.eventual import fireEventually, flushEventualQueue
1057 
1058hunk ./src/allmydata/test/test_download.py 299
1059             # shares
1060             servers = []
1061             shares = sorted([s._shnum for s in self.n._cnode._node._shares])
1062-            self.failUnlessEqual(shares, [0,1,2])
1063+            self.failUnlessEqual(shares, [0,1,2,3])
1064             # break the RIBucketReader references
1065             for s in self.n._cnode._node._shares:
1066                 s._rref.broken = True
1067hunk ./src/allmydata/test/test_download.py 322
1068             self.failUnlessEqual("".join(c.chunks), plaintext)
1069             shares = sorted([s._shnum for s in self.n._cnode._node._shares])
1070             # we should now be using more shares than we were before
1071-            self.failIfEqual(shares, [0,1,2])
1072+            self.failIfEqual(shares, [0,1,2,3])
1073         d.addCallback(_check_failover)
1074         return d
1075 
1076hunk ./src/allmydata/test/test_download.py 543
1077             def _con1_should_not_succeed(res):
1078                 self.fail("the first read should not have succeeded")
1079             def _con1_failed(f):
1080-                self.failUnless(f.check(NotEnoughSharesError))
1081+                self.failUnless(f.check(NoSharesError))
1082                 con2.producer.stopProducing()
1083                 return d2
1084             d.addCallbacks(_con1_should_not_succeed, _con1_failed)
1085hunk ./src/allmydata/test/test_download.py 587
1086             def _con1_should_not_succeed(res):
1087                 self.fail("the first read should not have succeeded")
1088             def _con1_failed(f):
1089-                self.failUnless(f.check(NotEnoughSharesError))
1090+                self.failUnless(f.check(NoSharesError))
1091                 # we *don't* cancel the second one here: this exercises a
1092                 # lost-progress bug from #1154. We just wait for it to
1093                 # succeed.
1094hunk ./src/allmydata/test/test_download.py 1125
1095                 # All these tests result in a failed download.
1096                 d.addCallback(self._corrupt_flip_all, imm_uri, i)
1097                 d.addCallback(lambda ign:
1098-                              self.shouldFail(NotEnoughSharesError, which,
1099+                              self.shouldFail(NoSharesError, which,
1100                                               substring,
1101                                               _download, imm_uri))
1102                 d.addCallback(lambda ign: self.restore_all_shares(self.shares))
1103hunk ./src/allmydata/test/test_download.py 1261
1104         e2.update(1000, 2.0, 2.0)
1105         e2.finished(now+5)
1106         self.failUnlessEqual(ds.get_progress(), 1.0)
1107+
1108+class MyShare:
1109+    def __init__(self, shnum, peerid, rtt):
1110+        self._shnum = shnum
1111+        self._peerid = peerid
1112+        self._peerid_s = peerid
1113+        self._dyhb_rtt = rtt
1114+    def __repr__(self):
1115+        return "sh%d-on-%s" % (self._shnum, self._peerid)
1116+
1117+class MySegmentFetcher(SegmentFetcher):
1118+    def __init__(self, *args, **kwargs):
1119+        SegmentFetcher.__init__(self, *args, **kwargs)
1120+        self._test_start_shares = []
1121+    def _start_share(self, share, shnum):
1122+        self._test_start_shares.append(share)
1123+
1124+class FakeNode:
1125+    def __init__(self):
1126+        self.want_more = 0
1127+        self.failed = None
1128+        self.processed = None
1129+        self._si_prefix = "si_prefix"
1130+    def want_more_shares(self):
1131+        self.want_more += 1
1132+    def fetch_failed(self, fetcher, f):
1133+        self.failed = f
1134+    def process_blocks(self, segnum, blocks):
1135+        self.processed = (segnum, blocks)
1136+    def get_num_segments(self):
1137+        return 1, True
1138+
1139+class Selection(unittest.TestCase):
1140+    def test_no_shares(self):
1141+        node = FakeNode()
1142+        sf = SegmentFetcher(node, 0, 3, None)
1143+        sf.add_shares([])
1144+        d = flushEventualQueue()
1145+        def _check1(ign):
1146+            self.failUnlessEqual(node.want_more, 1)
1147+            self.failUnlessEqual(node.failed, None)
1148+            sf.no_more_shares()
1149+            return flushEventualQueue()
1150+        d.addCallback(_check1)
1151+        def _check2(ign):
1152+            self.failUnless(node.failed)
1153+            self.failUnless(node.failed.check(NoSharesError))
1154+        d.addCallback(_check2)
1155+        return d
1156+
1157+    def test_only_one_share(self):
1158+        node = FakeNode()
1159+        sf = MySegmentFetcher(node, 0, 3, None)
1160+        shares = [MyShare(0, "peer-A", 0.0)]
1161+        sf.add_shares(shares)
1162+        d = flushEventualQueue()
1163+        def _check1(ign):
1164+            self.failUnlessEqual(node.want_more, 1)
1165+            self.failUnlessEqual(node.failed, None)
1166+            sf.no_more_shares()
1167+            return flushEventualQueue()
1168+        d.addCallback(_check1)
1169+        def _check2(ign):
1170+            self.failUnless(node.failed)
1171+            self.failUnless(node.failed.check(NotEnoughSharesError))
1172+            self.failUnlessIn("complete= pending=sh0-on-peer-A overdue= unused=",
1173+                              str(node.failed))
1174+        d.addCallback(_check2)
1175+        return d
1176+
1177+    def test_good_diversity_early(self):
1178+        node = FakeNode()
1179+        sf = MySegmentFetcher(node, 0, 3, None)
1180+        shares = [MyShare(i, "peer-%d" % i, i) for i in range(10)]
1181+        sf.add_shares(shares)
1182+        d = flushEventualQueue()
1183+        def _check1(ign):
1184+            self.failUnlessEqual(node.want_more, 0)
1185+            self.failUnlessEqual(sf._test_start_shares, shares[:3])
1186+            for sh in sf._test_start_shares:
1187+                sf._block_request_activity(sh, sh._shnum, COMPLETE,
1188+                                           "block-%d" % sh._shnum)
1189+            return flushEventualQueue()
1190+        d.addCallback(_check1)
1191+        def _check2(ign):
1192+            self.failIfEqual(node.processed, None)
1193+            self.failUnlessEqual(node.processed, (0, {0: "block-0",
1194+                                                      1: "block-1",
1195+                                                      2: "block-2"}) )
1196+        d.addCallback(_check2)
1197+        return d
1198+
1199+    def test_good_diversity_late(self):
1200+        node = FakeNode()
1201+        sf = MySegmentFetcher(node, 0, 3, None)
1202+        shares = [MyShare(i, "peer-%d" % i, i) for i in range(10)]
1203+        sf.add_shares([])
1204+        d = flushEventualQueue()
1205+        def _check1(ign):
1206+            self.failUnlessEqual(node.want_more, 1)
1207+            sf.add_shares(shares)
1208+            return flushEventualQueue()
1209+        d.addCallback(_check1)
1210+        def _check2(ign):
1211+            self.failUnlessEqual(sf._test_start_shares, shares[:3])
1212+            for sh in sf._test_start_shares:
1213+                sf._block_request_activity(sh, sh._shnum, COMPLETE,
1214+                                           "block-%d" % sh._shnum)
1215+            return flushEventualQueue()
1216+        d.addCallback(_check2)
1217+        def _check3(ign):
1218+            self.failIfEqual(node.processed, None)
1219+            self.failUnlessEqual(node.processed, (0, {0: "block-0",
1220+                                                      1: "block-1",
1221+                                                      2: "block-2"}) )
1222+        d.addCallback(_check3)
1223+        return d
1224+
1225+    def test_avoid_bad_diversity_late(self):
1226+        node = FakeNode()
1227+        sf = MySegmentFetcher(node, 0, 3, None)
1228+        # we could satisfy the read entirely from the first server, but we'd
1229+        # prefer not to. Instead, we expect to only pull one share from the
1230+        # first server
1231+        shares = [MyShare(0, "peer-A", 0.0),
1232+                  MyShare(1, "peer-A", 0.0),
1233+                  MyShare(2, "peer-A", 0.0),
1234+                  MyShare(3, "peer-B", 1.0),
1235+                  MyShare(4, "peer-C", 2.0),
1236+                  ]
1237+        sf.add_shares([])
1238+        d = flushEventualQueue()
1239+        def _check1(ign):
1240+            self.failUnlessEqual(node.want_more, 1)
1241+            sf.add_shares(shares)
1242+            return flushEventualQueue()
1243+        d.addCallback(_check1)
1244+        def _check2(ign):
1245+            self.failUnlessEqual(sf._test_start_shares,
1246+                                 [shares[0], shares[3], shares[4]])
1247+            for sh in sf._test_start_shares:
1248+                sf._block_request_activity(sh, sh._shnum, COMPLETE,
1249+                                           "block-%d" % sh._shnum)
1250+            return flushEventualQueue()
1251+        d.addCallback(_check2)
1252+        def _check3(ign):
1253+            self.failIfEqual(node.processed, None)
1254+            self.failUnlessEqual(node.processed, (0, {0: "block-0",
1255+                                                      3: "block-3",
1256+                                                      4: "block-4"}) )
1257+        d.addCallback(_check3)
1258+        return d
1259+
1260+    def test_suffer_bad_diversity_late(self):
1261+        node = FakeNode()
1262+        sf = MySegmentFetcher(node, 0, 3, None)
1263+        # we satisfy the read entirely from the first server because we don't
1264+        # have any other choice.
1265+        shares = [MyShare(0, "peer-A", 0.0),
1266+                  MyShare(1, "peer-A", 0.0),
1267+                  MyShare(2, "peer-A", 0.0),
1268+                  MyShare(3, "peer-A", 0.0),
1269+                  MyShare(4, "peer-A", 0.0),
1270+                  ]
1271+        sf.add_shares([])
1272+        d = flushEventualQueue()
1273+        def _check1(ign):
1274+            self.failUnlessEqual(node.want_more, 1)
1275+            sf.add_shares(shares)
1276+            return flushEventualQueue()
1277+        d.addCallback(_check1)
1278+        def _check2(ign):
1279+            self.failUnlessEqual(node.want_more, 3)
1280+            self.failUnlessEqual(sf._test_start_shares,
1281+                                 [shares[0], shares[1], shares[2]])
1282+            for sh in sf._test_start_shares:
1283+                sf._block_request_activity(sh, sh._shnum, COMPLETE,
1284+                                           "block-%d" % sh._shnum)
1285+            return flushEventualQueue()
1286+        d.addCallback(_check2)
1287+        def _check3(ign):
1288+            self.failIfEqual(node.processed, None)
1289+            self.failUnlessEqual(node.processed, (0, {0: "block-0",
1290+                                                      1: "block-1",
1291+                                                      2: "block-2"}) )
1292+        d.addCallback(_check3)
1293+        return d
1294+
1295+    def test_suffer_bad_diversity_early(self):
1296+        node = FakeNode()
1297+        sf = MySegmentFetcher(node, 0, 3, None)
1298+        # we satisfy the read entirely from the first server because we don't
1299+        # have any other choice.
1300+        shares = [MyShare(0, "peer-A", 0.0),
1301+                  MyShare(1, "peer-A", 0.0),
1302+                  MyShare(2, "peer-A", 0.0),
1303+                  MyShare(3, "peer-A", 0.0),
1304+                  MyShare(4, "peer-A", 0.0),
1305+                  ]
1306+        sf.add_shares(shares)
1307+        d = flushEventualQueue()
1308+        def _check1(ign):
1309+            self.failUnlessEqual(node.want_more, 2)
1310+            self.failUnlessEqual(sf._test_start_shares,
1311+                                 [shares[0], shares[1], shares[2]])
1312+            for sh in sf._test_start_shares:
1313+                sf._block_request_activity(sh, sh._shnum, COMPLETE,
1314+                                           "block-%d" % sh._shnum)
1315+            return flushEventualQueue()
1316+        d.addCallback(_check1)
1317+        def _check2(ign):
1318+            self.failIfEqual(node.processed, None)
1319+            self.failUnlessEqual(node.processed, (0, {0: "block-0",
1320+                                                      1: "block-1",
1321+                                                      2: "block-2"}) )
1322+        d.addCallback(_check2)
1323+        return d
1324+
1325+    def test_overdue(self):
1326+        node = FakeNode()
1327+        sf = MySegmentFetcher(node, 0, 3, None)
1328+        shares = [MyShare(i, "peer-%d" % i, i) for i in range(10)]
1329+        sf.add_shares(shares)
1330+        d = flushEventualQueue()
1331+        def _check1(ign):
1332+            self.failUnlessEqual(node.want_more, 0)
1333+            self.failUnlessEqual(sf._test_start_shares, shares[:3])
1334+            for sh in sf._test_start_shares:
1335+                sf._block_request_activity(sh, sh._shnum, OVERDUE)
1336+            return flushEventualQueue()
1337+        d.addCallback(_check1)
1338+        def _check2(ign):
1339+            self.failUnlessEqual(sf._test_start_shares, shares[:6])
1340+            for sh in sf._test_start_shares[3:]:
1341+                sf._block_request_activity(sh, sh._shnum, COMPLETE,
1342+                                           "block-%d" % sh._shnum)
1343+            return flushEventualQueue()
1344+        d.addCallback(_check2)
1345+        def _check3(ign):
1346+            self.failIfEqual(node.processed, None)
1347+            self.failUnlessEqual(node.processed, (0, {3: "block-3",
1348+                                                      4: "block-4",
1349+                                                      5: "block-5"}) )
1350+        d.addCallback(_check3)
1351+        return d
1352+
1353+    def test_overdue_fails(self):
1354+        node = FakeNode()
1355+        sf = MySegmentFetcher(node, 0, 3, None)
1356+        shares = [MyShare(i, "peer-%d" % i, i) for i in range(6)]
1357+        sf.add_shares(shares)
1358+        sf.no_more_shares()
1359+        d = flushEventualQueue()
1360+        def _check1(ign):
1361+            self.failUnlessEqual(node.want_more, 0)
1362+            self.failUnlessEqual(sf._test_start_shares, shares[:3])
1363+            for sh in sf._test_start_shares:
1364+                sf._block_request_activity(sh, sh._shnum, OVERDUE)
1365+            return flushEventualQueue()
1366+        d.addCallback(_check1)
1367+        def _check2(ign):
1368+            self.failUnlessEqual(sf._test_start_shares, shares[:6])
1369+            for sh in sf._test_start_shares[3:]:
1370+                sf._block_request_activity(sh, sh._shnum, DEAD)
1371+            return flushEventualQueue()
1372+        d.addCallback(_check2)
1373+        def _check3(ign):
1374+            # we're still waiting
1375+            self.failUnlessEqual(node.processed, None)
1376+            self.failUnlessEqual(node.failed, None)
1377+            # now complete one of the overdue ones, and kill one of the other
1378+            # ones, leaving one hanging. This should trigger a failure, since
1379+            # we cannot succeed.
1380+            live = sf._test_start_shares[0]
1381+            die = sf._test_start_shares[1]
1382+            sf._block_request_activity(live, live._shnum, COMPLETE, "block")
1383+            sf._block_request_activity(die, die._shnum, DEAD)
1384+            return flushEventualQueue()
1385+        d.addCallback(_check3)
1386+        def _check4(ign):
1387+            self.failUnless(node.failed)
1388+            self.failUnless(node.failed.check(NotEnoughSharesError))
1389+            self.failUnlessIn("complete=sh0 pending= overdue=sh2-on-peer-2 unused=",
1390+                              str(node.failed))
1391+        d.addCallback(_check4)
1392+        return d
1393+
1394+    def test_avoid_redundancy(self):
1395+        node = FakeNode()
1396+        sf = MySegmentFetcher(node, 0, 3, None)
1397+        # we could satisfy the read entirely from the first server, but we'd
1398+        # prefer not to. Instead, we expect to only pull one share from the
1399+        # first server
1400+        shares = [MyShare(0, "peer-A", 0.0),
1401+                  MyShare(1, "peer-B", 1.0),
1402+                  MyShare(0, "peer-C", 2.0), # this will be skipped
1403+                  MyShare(1, "peer-D", 3.0),
1404+                  MyShare(2, "peer-E", 4.0),
1405+                  ]
1406+        sf.add_shares(shares[:3])
1407+        d = flushEventualQueue()
1408+        def _check1(ign):
1409+            self.failUnlessEqual(node.want_more, 1)
1410+            self.failUnlessEqual(sf._test_start_shares,
1411+                                 [shares[0], shares[1]])
1412+            # allow sh1 to retire
1413+            sf._block_request_activity(shares[1], 1, COMPLETE, "block-1")
1414+            return flushEventualQueue()
1415+        d.addCallback(_check1)
1416+        def _check2(ign):
1417+            # and then feed in the remaining shares
1418+            sf.add_shares(shares[3:])
1419+            sf.no_more_shares()
1420+            return flushEventualQueue()
1421+        d.addCallback(_check2)
1422+        def _check3(ign):
1423+            self.failUnlessEqual(sf._test_start_shares,
1424+                                 [shares[0], shares[1], shares[4]])
1425+            sf._block_request_activity(shares[0], 0, COMPLETE, "block-0")
1426+            sf._block_request_activity(shares[4], 2, COMPLETE, "block-2")
1427+            return flushEventualQueue()
1428+        d.addCallback(_check3)
1429+        def _check4(ign):
1430+            self.failIfEqual(node.processed, None)
1431+            self.failUnlessEqual(node.processed, (0, {0: "block-0",
1432+                                                      1: "block-1",
1433+                                                      2: "block-2"}) )
1434+        d.addCallback(_check4)
1435+        return d
1436hunk ./src/allmydata/test/test_immutable.py 55
1437         def _after_download(unused=None):
1438             after_download_reads = self._count_reads()
1439             #print before_download_reads, after_download_reads
1440-            self.failIf(after_download_reads-before_download_reads > 36,
1441+            self.failIf(after_download_reads-before_download_reads > 41,
1442                         (after_download_reads, before_download_reads))
1443         d.addCallback(self._download_and_check_plaintext)
1444         d.addCallback(_after_download)
1445hunk ./src/allmydata/test/test_web.py 4262
1446         def _check_one_share(body):
1447             self.failIf("<html>" in body, body)
1448             body = " ".join(body.strip().split())
1449-            msg = ("NotEnoughSharesError: This indicates that some "
1450-                   "servers were unavailable, or that shares have been "
1451-                   "lost to server departure, hard drive failure, or disk "
1452-                   "corruption. You should perform a filecheck on "
1453-                   "this object to learn more. The full error message is:"
1454-                   " ran out of shares: %d complete, %d pending, 0 overdue,"
1455-                   " 0 unused, need 3. Last failure: None")
1456-            msg1 = msg % (1, 0)
1457-            msg2 = msg % (0, 1)
1458+            msgbase = ("NotEnoughSharesError: This indicates that some "
1459+                       "servers were unavailable, or that shares have been "
1460+                       "lost to server departure, hard drive failure, or disk "
1461+                       "corruption. You should perform a filecheck on "
1462+                       "this object to learn more. The full error message is:"
1463+                       )
1464+            msg1 = msgbase + (" ran out of shares:"
1465+                              " complete=sh0"
1466+                              " pending="
1467+                              " overdue= unused= need 3. Last failure: None")
1468+            msg2 = msgbase + (" ran out of shares:"
1469+                              " complete="
1470+                              " pending=Share(sh0-on-xgru5)"
1471+                              " overdue= unused= need 3. Last failure: None")
1472             self.failUnless(body == msg1 or body == msg2, body)
1473         d.addCallback(_check_one_share)
1474 
1475}
1476[Add Protovis.js-based download-status timeline visualization. Still kinda
1477"Brian Warner <warner@lothar.com>"**20100901034817
1478 rough, but illuminating.
1479 
1480 Also add dl-status test for /download-%s/event_json, remove
1481 /download-%s?t=json
1482 
1483] {
1484hunk ./src/allmydata/immutable/downloader/finder.py 140
1485                       peerid=idlib.shortnodeid_b2a(peerid),
1486                       level=log.NOISY, umid="Io7pyg")
1487         time_sent = now()
1488-        d_ev = self._download_status.add_dyhb_sent(peerid, time_sent)
1489+        d_ev = self._download_status.add_dyhb_request(peerid, time_sent)
1490         # TODO: get the timer from a Server object, it knows best
1491         self.overdue_timers[req] = reactor.callLater(self.OVERDUE_TIMEOUT,
1492                                                      self.overdue, req)
1493hunk ./src/allmydata/immutable/downloader/finder.py 226
1494         eventually(self.share_consumer.got_shares, shares)
1495 
1496     def _got_error(self, f, peerid, req, d_ev, lp):
1497-        d_ev.finished("error", now())
1498+        d_ev.error(now())
1499         self.log(format="got error from [%(peerid)s]",
1500                  peerid=idlib.shortnodeid_b2a(peerid), failure=f,
1501                  level=log.UNUSUAL, parent=lp, umid="zUKdCw")
1502hunk ./src/allmydata/immutable/downloader/node.py 75
1503         # things to track callers that want data
1504 
1505         # _segment_requests can have duplicates
1506-        self._segment_requests = [] # (segnum, d, cancel_handle, logparent)
1507+        self._segment_requests = [] # (segnum, d, cancel_handle, seg_ev, lp)
1508         self._active_segment = None # a SegmentFetcher, with .segnum
1509 
1510         self._segsize_observers = observer.OneShotObserverList()
1511hunk ./src/allmydata/immutable/downloader/node.py 122
1512     # things called by outside callers, via CiphertextFileNode. get_segment()
1513     # may also be called by Segmentation.
1514 
1515-    def read(self, consumer, offset=0, size=None, read_ev=None):
1516+    def read(self, consumer, offset, size, read_ev):
1517         """I am the main entry point, from which FileNode.read() can get
1518         data. I feed the consumer with the desired range of ciphertext. I
1519         return a Deferred that fires (with the consumer) when the read is
1520hunk ./src/allmydata/immutable/downloader/node.py 129
1521         finished.
1522 
1523         Note that there is no notion of a 'file pointer': each call to read()
1524-        uses an independent offset= value."""
1525-        # for concurrent operations: each gets its own Segmentation manager
1526-        if size is None:
1527-            size = self._verifycap.size
1528-        # clip size so offset+size does not go past EOF
1529-        size = min(size, self._verifycap.size-offset)
1530-        if read_ev is None:
1531-            read_ev = self._download_status.add_read_event(offset, size, now())
1532+        uses an independent offset= value.
1533+        """
1534+        assert size is not None
1535+        assert read_ev is not None
1536 
1537         lp = log.msg(format="imm Node(%(si)s).read(%(offset)d, %(size)d)",
1538                      si=base32.b2a(self._verifycap.storage_index)[:8],
1539hunk ./src/allmydata/immutable/downloader/node.py 142
1540             sp = self._history.stats_provider
1541             sp.count("downloader.files_downloaded", 1) # really read() calls
1542             sp.count("downloader.bytes_downloaded", size)
1543+        # for concurrent operations, each read() gets its own Segmentation
1544+        # manager
1545         s = Segmentation(self, offset, size, consumer, read_ev, lp)
1546hunk ./src/allmydata/immutable/downloader/node.py 145
1547+
1548         # this raises an interesting question: what segments to fetch? if
1549         # offset=0, always fetch the first segment, and then allow
1550         # Segmentation to be responsible for pulling the subsequent ones if
1551hunk ./src/allmydata/immutable/downloader/node.py 183
1552                      si=base32.b2a(self._verifycap.storage_index)[:8],
1553                      segnum=segnum,
1554                      level=log.OPERATIONAL, parent=logparent, umid="UKFjDQ")
1555-        self._download_status.add_segment_request(segnum, now())
1556+        seg_ev = self._download_status.add_segment_request(segnum, now())
1557         d = defer.Deferred()
1558         c = Cancel(self._cancel_request)
1559hunk ./src/allmydata/immutable/downloader/node.py 186
1560-        self._segment_requests.append( (segnum, d, c, lp) )
1561+        self._segment_requests.append( (segnum, d, c, seg_ev, lp) )
1562         self._start_new_segment()
1563         return (d, c)
1564 
1565hunk ./src/allmydata/immutable/downloader/node.py 210
1566 
1567     def _start_new_segment(self):
1568         if self._active_segment is None and self._segment_requests:
1569-            segnum = self._segment_requests[0][0]
1570+            (segnum, d, c, seg_ev, lp) = self._segment_requests[0]
1571             k = self._verifycap.needed_shares
1572hunk ./src/allmydata/immutable/downloader/node.py 212
1573-            lp = self._segment_requests[0][3]
1574             log.msg(format="%(node)s._start_new_segment: segnum=%(segnum)d",
1575                     node=repr(self), segnum=segnum,
1576                     level=log.NOISY, parent=lp, umid="wAlnHQ")
1577hunk ./src/allmydata/immutable/downloader/node.py 216
1578             self._active_segment = fetcher = SegmentFetcher(self, segnum, k, lp)
1579+            seg_ev.activate(now())
1580             active_shares = [s for s in self._shares if s.is_alive()]
1581             fetcher.add_shares(active_shares) # this triggers the loop
1582 
1583hunk ./src/allmydata/immutable/downloader/node.py 380
1584     def fetch_failed(self, sf, f):
1585         assert sf is self._active_segment
1586         # deliver error upwards
1587-        for (d,c) in self._extract_requests(sf.segnum):
1588+        for (d,c,seg_ev) in self._extract_requests(sf.segnum):
1589+            seg_ev.error(now())
1590             eventually(self._deliver, d, c, f)
1591         self._active_segment = None
1592         self._start_new_segment()
1593hunk ./src/allmydata/immutable/downloader/node.py 390
1594         d = defer.maybeDeferred(self._decode_blocks, segnum, blocks)
1595         d.addCallback(self._check_ciphertext_hash, segnum)
1596         def _deliver(result):
1597-            ds = self._download_status
1598-            if isinstance(result, Failure):
1599-                ds.add_segment_error(segnum, now())
1600-            else:
1601-                (offset, segment, decodetime) = result
1602-                ds.add_segment_delivery(segnum, now(),
1603-                                        offset, len(segment), decodetime)
1604             log.msg(format="delivering segment(%(segnum)d)",
1605                     segnum=segnum,
1606                     level=log.OPERATIONAL, parent=self._lp,
1607hunk ./src/allmydata/immutable/downloader/node.py 394
1608                     umid="j60Ojg")
1609-            for (d,c) in self._extract_requests(segnum):
1610-                eventually(self._deliver, d, c, result)
1611+            when = now()
1612+            if isinstance(result, Failure):
1613+                # this catches failures in decode or ciphertext hash
1614+                for (d,c,seg_ev) in self._extract_requests(segnum):
1615+                    seg_ev.error(when)
1616+                    eventually(self._deliver, d, c, result)
1617+            else:
1618+                (offset, segment, decodetime) = result
1619+                for (d,c,seg_ev) in self._extract_requests(segnum):
1620+                    # when we have two requests for the same segment, the
1621+                    # second one will not be "activated" before the data is
1622+                    # delivered, so to allow the status-reporting code to see
1623+                    # consistent behavior, we activate them all now. The
1624+                    # SegmentEvent will ignore duplicate activate() calls.
1625+                    # Note that this will result in an infinite "receive
1626+                    # speed" for the second request.
1627+                    seg_ev.activate(when)
1628+                    seg_ev.deliver(when, offset, len(segment), decodetime)
1629+                    eventually(self._deliver, d, c, result)
1630             self._active_segment = None
1631             self._start_new_segment()
1632         d.addBoth(_deliver)
1633hunk ./src/allmydata/immutable/downloader/node.py 416
1634-        d.addErrback(lambda f:
1635-                     log.err("unhandled error during process_blocks",
1636-                             failure=f, level=log.WEIRD,
1637-                             parent=self._lp, umid="MkEsCg"))
1638+        d.addErrback(log.err, "unhandled error during process_blocks",
1639+                     level=log.WEIRD, parent=self._lp, umid="MkEsCg")
1640 
1641     def _decode_blocks(self, segnum, blocks):
1642         tail = (segnum == self.num_segments-1)
1643hunk ./src/allmydata/immutable/downloader/node.py 485
1644     def _extract_requests(self, segnum):
1645         """Remove matching requests and return their (d,c) tuples so that the
1646         caller can retire them."""
1647-        retire = [(d,c) for (segnum0, d, c, lp) in self._segment_requests
1648+        retire = [(d,c,seg_ev)
1649+                  for (segnum0,d,c,seg_ev,lp) in self._segment_requests
1650                   if segnum0 == segnum]
1651         self._segment_requests = [t for t in self._segment_requests
1652                                   if t[0] != segnum]
1653hunk ./src/allmydata/immutable/downloader/node.py 495
1654     def _cancel_request(self, c):
1655         self._segment_requests = [t for t in self._segment_requests
1656                                   if t[2] != c]
1657-        segnums = [segnum for (segnum,d,c,lp) in self._segment_requests]
1658+        segnums = [segnum for (segnum,d,c,seg_ev,lp) in self._segment_requests]
1659         # self._active_segment might be None in rare circumstances, so make
1660         # sure we tolerate it
1661         if self._active_segment and self._active_segment.segnum not in segnums:
1662hunk ./src/allmydata/immutable/downloader/segmentation.py 126
1663         # the consumer might call our .pauseProducing() inside that write()
1664         # call, setting self._hungry=False
1665         self._read_ev.update(len(desired_data), 0, 0)
1666+        # note: filenode.DecryptingConsumer is responsible for calling
1667+        # _read_ev.update with how much decrypt_time was consumed
1668         self._maybe_fetch_next()
1669 
1670     def _retry_bad_segment(self, f):
1671hunk ./src/allmydata/immutable/downloader/share.py 730
1672                          share=repr(self),
1673                          start=start, length=length,
1674                          level=log.NOISY, parent=self._lp, umid="sgVAyA")
1675-            req_ev = ds.add_request_sent(self._peerid, self._shnum,
1676-                                         start, length, now())
1677+            block_ev = ds.add_block_request(self._peerid, self._shnum,
1678+                                            start, length, now())
1679             d = self._send_request(start, length)
1680hunk ./src/allmydata/immutable/downloader/share.py 733
1681-            d.addCallback(self._got_data, start, length, req_ev, lp)
1682-            d.addErrback(self._got_error, start, length, req_ev, lp)
1683+            d.addCallback(self._got_data, start, length, block_ev, lp)
1684+            d.addErrback(self._got_error, start, length, block_ev, lp)
1685             d.addCallback(self._trigger_loop)
1686             d.addErrback(lambda f:
1687                          log.err(format="unhandled error during send_request",
1688hunk ./src/allmydata/immutable/downloader/share.py 744
1689     def _send_request(self, start, length):
1690         return self._rref.callRemote("read", start, length)
1691 
1692-    def _got_data(self, data, start, length, req_ev, lp):
1693-        req_ev.finished(len(data), now())
1694+    def _got_data(self, data, start, length, block_ev, lp):
1695+        block_ev.finished(len(data), now())
1696         if not self._alive:
1697             return
1698         log.msg(format="%(share)s._got_data [%(start)d:+%(length)d] -> %(datalen)d",
1699hunk ./src/allmydata/immutable/downloader/share.py 787
1700         # the wanted/needed span is only "wanted" for the first pass. Once
1701         # the offset table arrives, it's all "needed".
1702 
1703-    def _got_error(self, f, start, length, req_ev, lp):
1704-        req_ev.finished("error", now())
1705+    def _got_error(self, f, start, length, block_ev, lp):
1706+        block_ev.error(now())
1707         log.msg(format="error requesting %(start)d+%(length)d"
1708                 " from %(server)s for si %(si)s",
1709                 start=start, length=length,
1710hunk ./src/allmydata/immutable/downloader/status.py 6
1711 from zope.interface import implements
1712 from allmydata.interfaces import IDownloadStatus
1713 
1714-class RequestEvent:
1715-    def __init__(self, download_status, tag):
1716-        self._download_status = download_status
1717-        self._tag = tag
1718-    def finished(self, received, when):
1719-        self._download_status.add_request_finished(self._tag, received, when)
1720+class ReadEvent:
1721+    def __init__(self, ev, ds):
1722+        self._ev = ev
1723+        self._ds = ds
1724+    def update(self, bytes, decrypttime, pausetime):
1725+        self._ev["bytes_returned"] += bytes
1726+        self._ev["decrypt_time"] += decrypttime
1727+        self._ev["paused_time"] += pausetime
1728+    def finished(self, finishtime):
1729+        self._ev["finish_time"] = finishtime
1730+        self._ds.update_last_timestamp(finishtime)
1731+
1732+class SegmentEvent:
1733+    def __init__(self, ev, ds):
1734+        self._ev = ev
1735+        self._ds = ds
1736+    def activate(self, when):
1737+        if self._ev["active_time"] is None:
1738+            self._ev["active_time"] = when
1739+    def deliver(self, when, start, length, decodetime):
1740+        assert self._ev["active_time"] is not None
1741+        self._ev["finish_time"] = when
1742+        self._ev["success"] = True
1743+        self._ev["decode_time"] = decodetime
1744+        self._ev["segment_start"] = start
1745+        self._ev["segment_length"] = length
1746+        self._ds.update_last_timestamp(when)
1747+    def error(self, when):
1748+        self._ev["finish_time"] = when
1749+        self._ev["success"] = False
1750+        self._ds.update_last_timestamp(when)
1751 
1752 class DYHBEvent:
1753hunk ./src/allmydata/immutable/downloader/status.py 39
1754-    def __init__(self, download_status, tag):
1755-        self._download_status = download_status
1756-        self._tag = tag
1757+    def __init__(self, ev, ds):
1758+        self._ev = ev
1759+        self._ds = ds
1760+    def error(self, when):
1761+        self._ev["finish_time"] = when
1762+        self._ev["success"] = False
1763+        self._ds.update_last_timestamp(when)
1764     def finished(self, shnums, when):
1765hunk ./src/allmydata/immutable/downloader/status.py 47
1766-        self._download_status.add_dyhb_finished(self._tag, shnums, when)
1767+        self._ev["finish_time"] = when
1768+        self._ev["success"] = True
1769+        self._ev["response_shnums"] = shnums
1770+        self._ds.update_last_timestamp(when)
1771+
1772+class BlockRequestEvent:
1773+    def __init__(self, ev, ds):
1774+        self._ev = ev
1775+        self._ds = ds
1776+    def finished(self, received, when):
1777+        self._ev["finish_time"] = when
1778+        self._ev["success"] = True
1779+        self._ev["response_length"] = received
1780+        self._ds.update_last_timestamp(when)
1781+    def error(self, when):
1782+        self._ev["finish_time"] = when
1783+        self._ev["success"] = False
1784+        self._ds.update_last_timestamp(when)
1785 
1786hunk ./src/allmydata/immutable/downloader/status.py 66
1787-class ReadEvent:
1788-    def __init__(self, download_status, tag):
1789-        self._download_status = download_status
1790-        self._tag = tag
1791-    def update(self, bytes, decrypttime, pausetime):
1792-        self._download_status.update_read_event(self._tag, bytes,
1793-                                                decrypttime, pausetime)
1794-    def finished(self, finishtime):
1795-        self._download_status.finish_read_event(self._tag, finishtime)
1796 
1797 class DownloadStatus:
1798     # There is one DownloadStatus for each CiphertextFileNode. The status
1799hunk ./src/allmydata/immutable/downloader/status.py 78
1800         self.size = size
1801         self.counter = self.statusid_counter.next()
1802         self.helper = False
1803-        self.started = None
1804-        # self.dyhb_requests tracks "do you have a share" requests and
1805-        # responses. It maps serverid to a tuple of:
1806-        #  send time
1807-        #  tuple of response shnums (None if response hasn't arrived, "error")
1808-        #  response time (None if response hasn't arrived yet)
1809-        self.dyhb_requests = {}
1810 
1811hunk ./src/allmydata/immutable/downloader/status.py 79
1812-        # self.requests tracks share-data requests and responses. It maps
1813-        # serverid to a tuple of:
1814-        #  shnum,
1815-        #  start,length,  (of data requested)
1816-        #  send time
1817-        #  response length (None if reponse hasn't arrived yet, or "error")
1818-        #  response time (None if response hasn't arrived)
1819-        self.requests = {}
1820+        self.first_timestamp = None
1821+        self.last_timestamp = None
1822 
1823hunk ./src/allmydata/immutable/downloader/status.py 82
1824-        # self.segment_events tracks segment requests and delivery. It is a
1825-        # list of:
1826-        #  type ("request", "delivery", "error")
1827-        #  segment number
1828-        #  event time
1829-        #  segment start (file offset of first byte, None except in "delivery")
1830-        #  segment length (only in "delivery")
1831-        #  time spent in decode (only in "delivery")
1832-        self.segment_events = []
1833+        # all four of these _events lists are sorted by start_time, because
1834+        # they are strictly append-only (some elements are later mutated in
1835+        # place, but none are removed or inserted in the middle).
1836 
1837hunk ./src/allmydata/immutable/downloader/status.py 86
1838-        # self.read_events tracks read() requests. It is a list of:
1839+        # self.read_events tracks read() requests. It is a list of dicts,
1840+        # each with the following keys:
1841         #  start,length  (of data requested)
1842hunk ./src/allmydata/immutable/downloader/status.py 89
1843-        #  request time
1844-        #  finish time (None until finished)
1845-        #  bytes returned (starts at 0, grows as segments are delivered)
1846-        #  time spent in decrypt (None for ciphertext-only reads)
1847-        #  time spent paused
1848+        #  start_time
1849+        #  finish_time (None until finished)
1850+        #  bytes_returned (starts at 0, grows as segments are delivered)
1851+        #  decrypt_time (time spent in decrypt, None for ciphertext-only reads)
1852+        #  paused_time (time spent paused by client via pauseProducing)
1853         self.read_events = []
1854 
1855hunk ./src/allmydata/immutable/downloader/status.py 96
1856-        self.known_shares = [] # (serverid, shnum)
1857-        self.problems = []
1858+        # self.segment_events tracks segment requests and their resolution.
1859+        # It is a list of dicts:
1860+        #  segment_number
1861+        #  start_time
1862+        #  active_time (None until work has begun)
1863+        #  decode_time (time spent in decode, None until delievered)
1864+        #  finish_time (None until resolved)
1865+        #  success (None until resolved, then boolean)
1866+        #  segment_start (file offset of first byte, None until delivered)
1867+        #  segment_length (None until delivered)
1868+        self.segment_events = []
1869 
1870hunk ./src/allmydata/immutable/downloader/status.py 108
1871+        # self.dyhb_requests tracks "do you have a share" requests and
1872+        # responses. It is a list of dicts:
1873+        #  serverid (binary)
1874+        #  start_time
1875+        #  success (None until resolved, then boolean)
1876+        #  response_shnums (tuple, None until successful)
1877+        #  finish_time (None until resolved)
1878+        self.dyhb_requests = []
1879 
1880hunk ./src/allmydata/immutable/downloader/status.py 117
1881-    def add_dyhb_sent(self, serverid, when):
1882-        r = (when, None, None)
1883-        if serverid not in self.dyhb_requests:
1884-            self.dyhb_requests[serverid] = []
1885-        self.dyhb_requests[serverid].append(r)
1886-        tag = (serverid, len(self.dyhb_requests[serverid])-1)
1887-        return DYHBEvent(self, tag)
1888+        # self.block_requests tracks share-data requests and responses. It is
1889+        # a list of dicts:
1890+        #  serverid (binary),
1891+        #  shnum,
1892+        #  start,length,  (of data requested)
1893+        #  start_time
1894+        #  finish_time (None until resolved)
1895+        #  success (None until resolved, then bool)
1896+        #  response_length (None until success)
1897+        self.block_requests = []
1898 
1899hunk ./src/allmydata/immutable/downloader/status.py 128
1900-    def add_dyhb_finished(self, tag, shnums, when):
1901-        # received="error" on error, else tuple(shnums)
1902-        (serverid, index) = tag
1903-        r = self.dyhb_requests[serverid][index]
1904-        (sent, _, _) = r
1905-        r = (sent, shnums, when)
1906-        self.dyhb_requests[serverid][index] = r
1907+        self.known_shares = [] # (serverid, shnum)
1908+        self.problems = []
1909 
1910hunk ./src/allmydata/immutable/downloader/status.py 131
1911-    def add_request_sent(self, serverid, shnum, start, length, when):
1912-        r = (shnum, start, length, when, None, None)
1913-        if serverid not in self.requests:
1914-            self.requests[serverid] = []
1915-        self.requests[serverid].append(r)
1916-        tag = (serverid, len(self.requests[serverid])-1)
1917-        return RequestEvent(self, tag)
1918 
1919hunk ./src/allmydata/immutable/downloader/status.py 132
1920-    def add_request_finished(self, tag, received, when):
1921-        # received="error" on error, else len(data)
1922-        (serverid, index) = tag
1923-        r = self.requests[serverid][index]
1924-        (shnum, start, length, sent, _, _) = r
1925-        r = (shnum, start, length, sent, received, when)
1926-        self.requests[serverid][index] = r
1927+    def add_read_event(self, start, length, when):
1928+        if self.first_timestamp is None:
1929+            self.first_timestamp = when
1930+        r = { "start": start,
1931+              "length": length,
1932+              "start_time": when,
1933+              "finish_time": None,
1934+              "bytes_returned": 0,
1935+              "decrypt_time": 0,
1936+              "paused_time": 0,
1937+              }
1938+        self.read_events.append(r)
1939+        return ReadEvent(r, self)
1940 
1941     def add_segment_request(self, segnum, when):
1942hunk ./src/allmydata/immutable/downloader/status.py 147
1943-        if self.started is None:
1944-            self.started = when
1945-        r = ("request", segnum, when, None, None, None)
1946-        self.segment_events.append(r)
1947-    def add_segment_delivery(self, segnum, when, start, length, decodetime):
1948-        r = ("delivery", segnum, when, start, length, decodetime)
1949-        self.segment_events.append(r)
1950-    def add_segment_error(self, segnum, when):
1951-        r = ("error", segnum, when, None, None, None)
1952+        if self.first_timestamp is None:
1953+            self.first_timestamp = when
1954+        r = { "segment_number": segnum,
1955+              "start_time": when,
1956+              "active_time": None,
1957+              "finish_time": None,
1958+              "success": None,
1959+              "decode_time": None,
1960+              "segment_start": None,
1961+              "segment_length": None,
1962+              }
1963         self.segment_events.append(r)
1964hunk ./src/allmydata/immutable/downloader/status.py 159
1965+        return SegmentEvent(r, self)
1966 
1967hunk ./src/allmydata/immutable/downloader/status.py 161
1968-    def add_read_event(self, start, length, when):
1969-        if self.started is None:
1970-            self.started = when
1971-        r = (start, length, when, None, 0, 0, 0)
1972-        self.read_events.append(r)
1973-        tag = len(self.read_events)-1
1974-        return ReadEvent(self, tag)
1975-    def update_read_event(self, tag, bytes_d, decrypt_d, paused_d):
1976-        r = self.read_events[tag]
1977-        (start, length, requesttime, finishtime, bytes, decrypt, paused) = r
1978-        bytes += bytes_d
1979-        decrypt += decrypt_d
1980-        paused += paused_d
1981-        r = (start, length, requesttime, finishtime, bytes, decrypt, paused)
1982-        self.read_events[tag] = r
1983-    def finish_read_event(self, tag, finishtime):
1984-        r = self.read_events[tag]
1985-        (start, length, requesttime, _, bytes, decrypt, paused) = r
1986-        r = (start, length, requesttime, finishtime, bytes, decrypt, paused)
1987-        self.read_events[tag] = r
1988+    def add_dyhb_request(self, serverid, when):
1989+        r = { "serverid": serverid,
1990+              "start_time": when,
1991+              "success": None,
1992+              "response_shnums": None,
1993+              "finish_time": None,
1994+              }
1995+        self.dyhb_requests.append(r)
1996+        return DYHBEvent(r, self)
1997+
1998+    def add_block_request(self, serverid, shnum, start, length, when):
1999+        r = { "serverid": serverid,
2000+              "shnum": shnum,
2001+              "start": start,
2002+              "length": length,
2003+              "start_time": when,
2004+              "finish_time": None,
2005+              "success": None,
2006+              "response_length": None,
2007+              }
2008+        self.block_requests.append(r)
2009+        return BlockRequestEvent(r, self)
2010+
2011+    def update_last_timestamp(self, when):
2012+        if self.last_timestamp is None or when > self.last_timestamp:
2013+            self.last_timestamp = when
2014 
2015     def add_known_share(self, serverid, shnum):
2016         self.known_shares.append( (serverid, shnum) )
2017hunk ./src/allmydata/immutable/downloader/status.py 205
2018         # mention all outstanding segment requests
2019         outstanding = set()
2020         errorful = set()
2021-        for s_ev in self.segment_events:
2022-            (etype, segnum, when, segstart, seglen, decodetime) = s_ev
2023-            if etype == "request":
2024-                outstanding.add(segnum)
2025-            elif etype == "delivery":
2026-                outstanding.remove(segnum)
2027-            else: # "error"
2028-                outstanding.remove(segnum)
2029-                errorful.add(segnum)
2030+        outstanding = set([s_ev["segment_number"]
2031+                           for s_ev in self.segment_events
2032+                           if s_ev["finish_time"] is None])
2033+        errorful = set([s_ev["segment_number"]
2034+                        for s_ev in self.segment_events
2035+                        if s_ev["success"] is False])
2036         def join(segnums):
2037             if len(segnums) == 1:
2038                 return "segment %s" % list(segnums)[0]
2039hunk ./src/allmydata/immutable/downloader/status.py 233
2040             return 0.0
2041         total_outstanding, total_received = 0, 0
2042         for r_ev in self.read_events:
2043-            (start, length, ign1, finishtime, bytes, ign2, ign3) = r_ev
2044-            if finishtime is None:
2045-                total_outstanding += length
2046-                total_received += bytes
2047+            if r_ev["finish_time"] is None:
2048+                total_outstanding += r_ev["length"]
2049+                total_received += r_ev["bytes_returned"]
2050             # else ignore completed requests
2051         if not total_outstanding:
2052             return 1.0
2053hunk ./src/allmydata/immutable/downloader/status.py 246
2054     def get_active(self):
2055         return False # TODO
2056     def get_started(self):
2057-        return self.started
2058+        return self.first_timestamp
2059     def get_results(self):
2060         return None # TODO
2061hunk ./src/allmydata/immutable/filenode.py 58
2062         return a Deferred that fires (with the consumer) when the read is
2063         finished."""
2064         self._maybe_create_download_node()
2065-        actual_size = size
2066-        if actual_size is None:
2067-            actual_size = self._verifycap.size - offset
2068-        read_ev = self._download_status.add_read_event(offset, actual_size,
2069-                                                       now())
2070+        if size is None:
2071+            size = self._verifycap.size
2072+        # clip size so offset+size does not go past EOF
2073+        size = min(size, self._verifycap.size-offset)
2074+        read_ev = self._download_status.add_read_event(offset, size, now())
2075         if IDownloadStatusHandlingConsumer.providedBy(consumer):
2076             consumer.set_download_status_read_event(read_ev)
2077         return self._node.read(consumer, offset, size, read_ev)
2078hunk ./src/allmydata/immutable/filenode.py 180
2079 
2080     def __init__(self, consumer, readkey, offset):
2081         self._consumer = consumer
2082-        self._read_event = None
2083+        self._read_ev = None
2084         # TODO: pycryptopp CTR-mode needs random-access operations: I want
2085         # either a=AES(readkey, offset) or better yet both of:
2086         #  a=AES(readkey, offset=0)
2087hunk ./src/allmydata/immutable/filenode.py 193
2088         self._decryptor.process("\x00"*offset_small)
2089 
2090     def set_download_status_read_event(self, read_ev):
2091-        self._read_event = read_ev
2092+        self._read_ev = read_ev
2093 
2094     def registerProducer(self, producer, streaming):
2095         # this passes through, so the real consumer can flow-control the real
2096hunk ./src/allmydata/immutable/filenode.py 206
2097     def write(self, ciphertext):
2098         started = now()
2099         plaintext = self._decryptor.process(ciphertext)
2100-        if self._read_event:
2101+        if self._read_ev:
2102             elapsed = now() - started
2103hunk ./src/allmydata/immutable/filenode.py 208
2104-            self._read_event.update(0, elapsed, 0)
2105+            self._read_ev.update(0, elapsed, 0)
2106         self._consumer.write(plaintext)
2107 
2108 class ImmutableFileNode:
2109hunk ./src/allmydata/test/test_download.py 1225
2110         now = 12345.1
2111         ds = DownloadStatus("si-1", 123)
2112         self.failUnlessEqual(ds.get_status(), "idle")
2113-        ds.add_segment_request(0, now)
2114+        ev0 = ds.add_segment_request(0, now)
2115         self.failUnlessEqual(ds.get_status(), "fetching segment 0")
2116hunk ./src/allmydata/test/test_download.py 1227
2117-        ds.add_segment_delivery(0, now+1, 0, 1000, 2.0)
2118+        ev0.activate(now+0.5)
2119+        ev0.deliver(now+1, 0, 1000, 2.0)
2120         self.failUnlessEqual(ds.get_status(), "idle")
2121hunk ./src/allmydata/test/test_download.py 1230
2122-        ds.add_segment_request(2, now+2)
2123-        ds.add_segment_request(1, now+2)
2124+        ev2 = ds.add_segment_request(2, now+2)
2125+        ev1 = ds.add_segment_request(1, now+2)
2126         self.failUnlessEqual(ds.get_status(), "fetching segments 1,2")
2127hunk ./src/allmydata/test/test_download.py 1233
2128-        ds.add_segment_error(1, now+3)
2129+        ev1.error(now+3)
2130         self.failUnlessEqual(ds.get_status(),
2131                              "fetching segment 2; errors on segment 1")
2132hunk ./src/allmydata/test/test_download.py 1236
2133+        del ev2 # hush pyflakes
2134 
2135     def test_progress(self):
2136         now = 12345.1
2137hunk ./src/allmydata/test/test_web.py 22
2138 from allmydata.unknown import UnknownNode
2139 from allmydata.web import status, common
2140 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
2141-from allmydata.util import fileutil, base32
2142+from allmydata.util import fileutil, base32, hashutil
2143 from allmydata.util.consumer import download_to_data
2144 from allmydata.util.netstring import split_netstring
2145 from allmydata.util.encodingutil import to_str
2146hunk ./src/allmydata/test/test_web.py 81
2147     ds = DownloadStatus("storage_index", 1234)
2148     now = time.time()
2149 
2150-    ds.add_segment_request(0, now)
2151-    # segnum, when, start,len, decodetime
2152-    ds.add_segment_delivery(0, now+1, 0, 100, 0.5)
2153-    ds.add_segment_request(1, now+2)
2154-    ds.add_segment_error(1, now+3)
2155+    serverid_a = hashutil.tagged_hash("foo", "serverid_a")[:20]
2156+    serverid_b = hashutil.tagged_hash("foo", "serverid_b")[:20]
2157+    storage_index = hashutil.storage_index_hash("SI")
2158+    e0 = ds.add_segment_request(0, now)
2159+    e0.activate(now+0.5)
2160+    e0.deliver(now+1, 0, 100, 0.5) # when, start,len, decodetime
2161+    e1 = ds.add_segment_request(1, now+2)
2162+    e1.error(now+3)
2163     # two outstanding requests
2164hunk ./src/allmydata/test/test_web.py 90
2165-    ds.add_segment_request(2, now+4)
2166-    ds.add_segment_request(3, now+5)
2167+    e2 = ds.add_segment_request(2, now+4)
2168+    e3 = ds.add_segment_request(3, now+5)
2169+    del e2,e3 # hush pyflakes
2170 
2171hunk ./src/allmydata/test/test_web.py 94
2172-    # simulate a segment which gets delivered faster than a system clock tick (ticket #1166)
2173-    ds.add_segment_request(4, now)
2174-    ds.add_segment_delivery(4, now, 0, 140, 0.5)
2175+    # simulate a segment which gets delivered faster than a system clock tick
2176+    # (ticket #1166)
2177+    e = ds.add_segment_request(4, now)
2178+    e.activate(now)
2179+    e.deliver(now, 0, 140, 0.5)
2180 
2181hunk ./src/allmydata/test/test_web.py 100
2182-    e = ds.add_dyhb_sent("serverid_a", now)
2183+    e = ds.add_dyhb_request(serverid_a, now)
2184     e.finished([1,2], now+1)
2185hunk ./src/allmydata/test/test_web.py 102
2186-    e = ds.add_dyhb_sent("serverid_b", now+2) # left unfinished
2187+    e = ds.add_dyhb_request(serverid_b, now+2) # left unfinished
2188 
2189     e = ds.add_read_event(0, 120, now)
2190     e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
2191hunk ./src/allmydata/test/test_web.py 109
2192     e.finished(now+1)
2193     e = ds.add_read_event(120, 30, now+2) # left unfinished
2194 
2195-    e = ds.add_request_sent("serverid_a", 1, 100, 20, now)
2196+    e = ds.add_block_request(serverid_a, 1, 100, 20, now)
2197     e.finished(20, now+1)
2198hunk ./src/allmydata/test/test_web.py 111
2199-    e = ds.add_request_sent("serverid_a", 1, 120, 30, now+1) # left unfinished
2200+    e = ds.add_block_request(serverid_a, 1, 120, 30, now+1) # left unfinished
2201 
2202     # make sure that add_read_event() can come first too
2203hunk ./src/allmydata/test/test_web.py 114
2204-    ds1 = DownloadStatus("storage_index", 1234)
2205+    ds1 = DownloadStatus(storage_index, 1234)
2206     e = ds1.add_read_event(0, 120, now)
2207     e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
2208     e.finished(now+1)
2209hunk ./src/allmydata/test/test_web.py 563
2210         def _check_dl(res):
2211             self.failUnless("File Download Status" in res, res)
2212         d.addCallback(_check_dl)
2213-        d.addCallback(lambda res: self.GET("/status/down-%d?t=json" % dl_num))
2214+        d.addCallback(lambda res: self.GET("/status/down-%d/event_json" % dl_num))
2215         def _check_dl_json(res):
2216             data = simplejson.loads(res)
2217             self.failUnless(isinstance(data, dict))
2218hunk ./src/allmydata/test/test_web.py 567
2219+            # this data comes from build_one_ds() above
2220+            self.failUnlessEqual(set(data["serverids"].values()),
2221+                                 set(["phwr", "cmpu"]))
2222+            self.failUnlessEqual(len(data["segment"]), 5)
2223+            self.failUnlessEqual(len(data["read"]), 2)
2224         d.addCallback(_check_dl_json)
2225         d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
2226         def _check_ul(res):
2227addfile ./src/allmydata/web/download-status-timeline.xhtml
2228hunk ./src/allmydata/web/download-status-timeline.xhtml 1
2229+<html xmlns:n="http://nevow.com/ns/nevow/0.1">
2230+  <head>
2231+    <title>AllMyData - Tahoe - File Download Status Timeline</title>
2232+    <link href="/tahoe_css" rel="stylesheet" type="text/css"/>
2233+    <link href="/webform_css" rel="stylesheet" type="text/css"/>
2234+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
2235+    <script type="text/javascript" src="/jquery.js"></script>
2236+    <script type="text/javascript" src="/protovis-r3.2.js"></script>
2237+    <script type="text/javascript" src="/download_status_timeline.js"></script>
2238+  </head>
2239+  <body>
2240+
2241+<h1>File Download Status</h1>
2242+
2243+<ul>
2244+  <li>Started: <span n:render="started"/></li>
2245+  <li>Storage Index: <span n:render="si"/></li>
2246+  <li>Helper?: <span n:render="helper"/></li>
2247+  <li>Total Size: <span n:render="total_size"/></li>
2248+  <li>Progress: <span n:render="progress"/></li>
2249+  <li>Status: <span n:render="status"/></li>
2250+</ul>
2251+
2252+
2253+<div style="">
2254+  <div id="overview" style="float:right;width:166px;height:100px; border: 1px solid #ddd">overview</div>
2255+  <div id="timeline" style="width:600px;border: 1px solid #aaa">Timeline</div>
2256+</div>
2257+
2258+<div>Return to the <a href="/">Welcome Page</a></div>
2259+
2260+  </body>
2261+</html>
2262hunk ./src/allmydata/web/download-status.xhtml 19
2263   <li>Total Size: <span n:render="total_size"/></li>
2264   <li>Progress: <span n:render="progress"/></li>
2265   <li>Status: <span n:render="status"/></li>
2266+  <li><span n:render="timeline_link"/></li>
2267 </ul>
2268 
2269 <div n:render="events"></div>
2270addfile ./src/allmydata/web/download_status_timeline.js
2271hunk ./src/allmydata/web/download_status_timeline.js 1
2272+
2273+$(function() {
2274+
2275+      function onDataReceived(data) {
2276+          var bounds = { min: data.bounds.min,
2277+                         max: data.bounds.max
2278+                       };
2279+          //bounds.max = data.dyhb[data.dyhb.length-1].finish_time;
2280+          var duration = bounds.max - bounds.min;
2281+          var WIDTH = 600;
2282+          var vis = new pv.Panel().canvas("timeline").margin(30);
2283+
2284+          var dyhb_top = 0;
2285+          var read_top = dyhb_top + 30*data.dyhb[data.dyhb.length-1].row+60;
2286+          var segment_top = read_top + 30*data.read[data.read.length-1].row+60;
2287+          var block_top = segment_top + 30*data.segment[data.segment.length-1].row+60;
2288+          var block_row_to_y = {};
2289+          var row_y=0;
2290+          for (var group=0; group < data.block_rownums.length; group++) {
2291+              for (var row=0; row < data.block_rownums[group]; row++) {
2292+                  block_row_to_y[group+"-"+row] = row_y;
2293+                  row_y += 10;
2294+              }
2295+              row_y += 5;
2296+          }
2297+
2298+          var height = block_top + row_y;
2299+          var kx = bounds.min;
2300+          var ky = 1;
2301+          var x = pv.Scale.linear(bounds.min, bounds.max).range(0, WIDTH-40);
2302+          var relx = pv.Scale.linear(0, duration).range(0, WIDTH-40);
2303+          //var y = pv.Scale.linear(-ky,ky).range(0, height);
2304+          //x.nice(); relx.nice();
2305+
2306+          /* add the invisible panel now, at the bottom of the stack, so that
2307+          it won't steal mouseover events and prevent tooltips from
2308+          working. */
2309+          vis.add(pv.Panel)
2310+              .events("all")
2311+              .event("mousedown", pv.Behavior.pan())
2312+              .event("mousewheel", pv.Behavior.zoom())
2313+              .event("pan", transform)
2314+              .event("zoom", transform)
2315+          ;
2316+
2317+          vis.anchor("top").top(-20).add(pv.Label).text("DYHB Requests");
2318+
2319+          vis.add(pv.Bar)
2320+              .data(data.dyhb)
2321+              .height(20)
2322+              .top(function (d) {return 30*d.row;})
2323+              .left(function(d){return x(d.start_time);})
2324+              .width(function(d){return x(d.finish_time)-x(d.start_time);})
2325+              .title(function(d){return "shnums: "+d.response_shnums;})
2326+              .fillStyle(function(d){return data.server_info[d.serverid].color;})
2327+              .strokeStyle("black").lineWidth(1);
2328+
2329+          vis.add(pv.Rule)
2330+              .data(data.dyhb)
2331+              .top(function(d){return 30*d.row + 20/2;})
2332+              .left(0).width(0)
2333+              .strokeStyle("#888")
2334+              .anchor("left").add(pv.Label)
2335+              .text(function(d){return d.serverid.slice(0,4);});
2336+
2337+          /* we use a function for data=relx.ticks() here instead of
2338+           simply .data(relx.ticks()) so that it will be recalculated when
2339+           the scales change (by pan/zoom) */
2340+          var xaxis = vis.add(pv.Rule)
2341+              .data(function() {return relx.ticks();})
2342+              .strokeStyle("#ccc")
2343+              .left(relx)
2344+              .anchor("bottom").add(pv.Label)
2345+              .text(function(d){return relx.tickFormat(d)+"s";});
2346+
2347+          var read = vis.add(pv.Panel).top(read_top);
2348+          read.anchor("top").top(-20).add(pv.Label).text("read() requests");
2349+
2350+          read.add(pv.Bar)
2351+              .data(data.read)
2352+              .height(20)
2353+              .top(function (d) {return 30*d.row;})
2354+              .left(function(d){return x(d.start_time);})
2355+              .width(function(d){return x(d.finish_time)-x(d.start_time);})
2356+              .title(function(d){return "read(start="+d.start+", len="+d.length+") -> "+d.bytes_returned+" bytes";})
2357+              .fillStyle("red")
2358+              .strokeStyle("black").lineWidth(1);
2359+
2360+          var segment = vis.add(pv.Panel).top(segment_top);
2361+          segment.anchor("top").top(-20).add(pv.Label).text("segment() requests");
2362+
2363+          segment.add(pv.Bar)
2364+              .data(data.segment)
2365+              .height(20)
2366+              .top(function (d) {return 30*d.row;})
2367+              .left(function(d){return x(d.start_time);})
2368+              .width(function(d){return x(d.finish_time)-x(d.start_time);})
2369+              .title(function(d){return "seg"+d.segment_number+" ["+d.segment_start+":+"+d.segment_length+"] (took "+(d.finish_time-d.start_time)+")";})
2370+              .fillStyle(function(d){if (d.success) return "#c0ffc0";
2371+                                    else return "#ffc0c0";})
2372+              .strokeStyle("black").lineWidth(1);
2373+
2374+          var block = vis.add(pv.Panel).top(block_top);
2375+          block.anchor("top").top(-20).add(pv.Label).text("block() requests");
2376+
2377+          var shnum_colors = pv.Colors.category10();
2378+          block.add(pv.Bar)
2379+              .data(data.block)
2380+              .height(10)
2381+              .top(function (d) {return block_row_to_y[d.row[0]+"-"+d.row[1]];})
2382+              .left(function(d){return x(d.start_time);})
2383+              .width(function(d){return x(d.finish_time)-x(d.start_time);})
2384+              .title(function(d){return "sh"+d.shnum+"-on-"+d.serverid.slice(0,4)+" ["+d.start+":+"+d.length+"] -> "+d.response_length;})
2385+              .fillStyle(function(d){return data.server_info[d.serverid].color;})
2386+              .strokeStyle(function(d){return shnum_colors(d.shnum).color;})
2387+              .lineWidth(function(d)
2388+                         {if (d.response_length > 100) return 3;
2389+                         else return 1;
2390+                          })
2391+          ;
2392+
2393+
2394+          vis.height(height);
2395+
2396+          function transform() {
2397+              var t0= this.transform();
2398+              var t = this.transform().invert();
2399+              // when t.x=0 and t.k=1.0, left should be bounds.min
2400+              x.domain(bounds.min + (t.x/WIDTH)*duration,
2401+                       bounds.min + t.k*duration + (t.x/WIDTH)*duration);
2402+              relx.domain(0 + t.x/WIDTH*duration,
2403+                          t.k*duration + (t.x/WIDTH)*duration);
2404+              vis.render();
2405+          }
2406+
2407+          vis.render();
2408+      }
2409+
2410+      $.ajax({url: "event_json",
2411+              method: 'GET',
2412+              dataType: 'json',
2413+              success: onDataReceived });
2414+});
2415+
2416addfile ./src/allmydata/web/jquery.js
2417hunk ./src/allmydata/web/jquery.js 1
2418+/*!
2419+ * jQuery JavaScript Library v1.3.2
2420+ * http://jquery.com/
2421+ *
2422+ * Copyright (c) 2009 John Resig
2423+ * Dual licensed under the MIT and GPL licenses.
2424+ * http://docs.jquery.com/License
2425+ *
2426+ * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
2427+ * Revision: 6246
2428+ */
2429+(function(){
2430+
2431+var
2432+       // Will speed up references to window, and allows munging its name.
2433+       window = this,
2434+       // Will speed up references to undefined, and allows munging its name.
2435+       undefined,
2436+       // Map over jQuery in case of overwrite
2437+       _jQuery = window.jQuery,
2438+       // Map over the $ in case of overwrite
2439+       _$ = window.$,
2440+
2441+       jQuery = window.jQuery = window.$ = function( selector, context ) {
2442+               // The jQuery object is actually just the init constructor 'enhanced'
2443+               return new jQuery.fn.init( selector, context );
2444+       },
2445+
2446+       // A simple way to check for HTML strings or ID strings
2447+       // (both of which we optimize for)
2448+       quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
2449+       // Is it a simple selector
2450+       isSimple = /^.[^:#\[\.,]*$/;
2451+
2452+jQuery.fn = jQuery.prototype = {
2453+       init: function( selector, context ) {
2454+               // Make sure that a selection was provided
2455+               selector = selector || document;
2456+
2457+               // Handle $(DOMElement)
2458+               if ( selector.nodeType ) {
2459+                       this[0] = selector;
2460+                       this.length = 1;
2461+                       this.context = selector;
2462+                       return this;
2463+               }
2464+               // Handle HTML strings
2465+               if ( typeof selector === "string" ) {
2466+                       // Are we dealing with HTML string or an ID?
2467+                       var match = quickExpr.exec( selector );
2468+
2469+                       // Verify a match, and that no context was specified for #id
2470+                       if ( match && (match[1] || !context) ) {
2471+
2472+                               // HANDLE: $(html) -> $(array)
2473+                               if ( match[1] )
2474+                                       selector = jQuery.clean( [ match[1] ], context );
2475+
2476+                               // HANDLE: $("#id")
2477+                               else {
2478+                                       var elem = document.getElementById( match[3] );
2479+
2480+                                       // Handle the case where IE and Opera return items
2481+                                       // by name instead of ID
2482+                                       if ( elem && elem.id != match[3] )
2483+                                               return jQuery().find( selector );
2484+
2485+                                       // Otherwise, we inject the element directly into the jQuery object
2486+                                       var ret = jQuery( elem || [] );
2487+                                       ret.context = document;
2488+                                       ret.selector = selector;
2489+                                       return ret;
2490+                               }
2491+
2492+                       // HANDLE: $(expr, [context])
2493+                       // (which is just equivalent to: $(content).find(expr)
2494+                       } else
2495+                               return jQuery( context ).find( selector );
2496+
2497+               // HANDLE: $(function)
2498+               // Shortcut for document ready
2499+               } else if ( jQuery.isFunction( selector ) )
2500+                       return jQuery( document ).ready( selector );
2501+
2502+               // Make sure that old selector state is passed along
2503+               if ( selector.selector && selector.context ) {
2504+                       this.selector = selector.selector;
2505+                       this.context = selector.context;
2506+               }
2507+
2508+               return this.setArray(jQuery.isArray( selector ) ?
2509+                       selector :
2510+                       jQuery.makeArray(selector));
2511+       },
2512+
2513+       // Start with an empty selector
2514+       selector: "",
2515+
2516+       // The current version of jQuery being used
2517+       jquery: "1.3.2",
2518+
2519+       // The number of elements contained in the matched element set
2520+       size: function() {
2521+               return this.length;
2522+       },
2523+
2524+       // Get the Nth element in the matched element set OR
2525+       // Get the whole matched element set as a clean array
2526+       get: function( num ) {
2527+               return num === undefined ?
2528+
2529+                       // Return a 'clean' array
2530+                       Array.prototype.slice.call( this ) :
2531+
2532+                       // Return just the object
2533+                       this[ num ];
2534+       },
2535+
2536+       // Take an array of elements and push it onto the stack
2537+       // (returning the new matched element set)
2538+       pushStack: function( elems, name, selector ) {
2539+               // Build a new jQuery matched element set
2540+               var ret = jQuery( elems );
2541+
2542+               // Add the old object onto the stack (as a reference)
2543+               ret.prevObject = this;
2544+
2545+               ret.context = this.context;
2546+
2547+               if ( name === "find" )
2548+                       ret.selector = this.selector + (this.selector ? " " : "") + selector;
2549+               else if ( name )
2550+                       ret.selector = this.selector + "." + name + "(" + selector + ")";
2551+
2552+               // Return the newly-formed element set
2553+               return ret;
2554+       },
2555+
2556+       // Force the current matched set of elements to become
2557+       // the specified array of elements (destroying the stack in the process)
2558+       // You should use pushStack() in order to do this, but maintain the stack
2559+       setArray: function( elems ) {
2560+               // Resetting the length to 0, then using the native Array push
2561+               // is a super-fast way to populate an object with array-like properties
2562+               this.length = 0;
2563+               Array.prototype.push.apply( this, elems );
2564+
2565+               return this;
2566+       },
2567+
2568+       // Execute a callback for every element in the matched set.
2569+       // (You can seed the arguments with an array of args, but this is
2570+       // only used internally.)
2571+       each: function( callback, args ) {
2572+               return jQuery.each( this, callback, args );
2573+       },
2574+
2575+       // Determine the position of an element within
2576+       // the matched set of elements
2577+       index: function( elem ) {
2578+               // Locate the position of the desired element
2579+               return jQuery.inArray(
2580+                       // If it receives a jQuery object, the first element is used
2581+                       elem && elem.jquery ? elem[0] : elem
2582+               , this );
2583+       },
2584+
2585+       attr: function( name, value, type ) {
2586+               var options = name;
2587+
2588+               // Look for the case where we're accessing a style value
2589+               if ( typeof name === "string" )
2590+                       if ( value === undefined )
2591+                               return this[0] && jQuery[ type || "attr" ]( this[0], name );
2592+
2593+                       else {
2594+                               options = {};
2595+                               options[ name ] = value;
2596+                       }
2597+
2598+               // Check to see if we're setting style values
2599+               return this.each(function(i){
2600+                       // Set all the styles
2601+                       for ( name in options )
2602+                               jQuery.attr(
2603+                                       type ?
2604+                                               this.style :
2605+                                               this,
2606+                                       name, jQuery.prop( this, options[ name ], type, i, name )
2607+                               );
2608+               });
2609+       },
2610+
2611+       css: function( key, value ) {
2612+               // ignore negative width and height values
2613+               if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
2614+                       value = undefined;
2615+               return this.attr( key, value, "curCSS" );
2616+       },
2617+
2618+       text: function( text ) {
2619+               if ( typeof text !== "object" && text != null )
2620+                       return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
2621+
2622+               var ret = "";
2623+
2624+               jQuery.each( text || this, function(){
2625+                       jQuery.each( this.childNodes, function(){
2626+                               if ( this.nodeType != 8 )
2627+                                       ret += this.nodeType != 1 ?
2628+                                               this.nodeValue :
2629+                                               jQuery.fn.text( [ this ] );
2630+                       });
2631+               });
2632+
2633+               return ret;
2634+       },
2635+
2636+       wrapAll: function( html ) {
2637+               if ( this[0] ) {
2638+                       // The elements to wrap the target around
2639+                       var wrap = jQuery( html, this[0].ownerDocument ).clone();
2640+
2641+                       if ( this[0].parentNode )
2642+                               wrap.insertBefore( this[0] );
2643+
2644+                       wrap.map(function(){
2645+                               var elem = this;
2646+
2647+                               while ( elem.firstChild )
2648+                                       elem = elem.firstChild;
2649+
2650+                               return elem;
2651+                       }).append(this);
2652+               }
2653+
2654+               return this;
2655+       },
2656+
2657+       wrapInner: function( html ) {
2658+               return this.each(function(){
2659+                       jQuery( this ).contents().wrapAll( html );
2660+               });
2661+       },
2662+
2663+       wrap: function( html ) {
2664+               return this.each(function(){
2665+                       jQuery( this ).wrapAll( html );
2666+               });
2667+       },
2668+
2669+       append: function() {
2670+               return this.domManip(arguments, true, function(elem){
2671+                       if (this.nodeType == 1)
2672+                               this.appendChild( elem );
2673+               });
2674+       },
2675+
2676+       prepend: function() {
2677+               return this.domManip(arguments, true, function(elem){
2678+                       if (this.nodeType == 1)
2679+                               this.insertBefore( elem, this.firstChild );
2680+               });
2681+       },
2682+
2683+       before: function() {
2684+               return this.domManip(arguments, false, function(elem){
2685+                       this.parentNode.insertBefore( elem, this );
2686+               });
2687+       },
2688+
2689+       after: function() {
2690+               return this.domManip(arguments, false, function(elem){
2691+                       this.parentNode.insertBefore( elem, this.nextSibling );
2692+               });
2693+       },
2694+
2695+       end: function() {
2696+               return this.prevObject || jQuery( [] );
2697+       },
2698+
2699+       // For internal use only.
2700+       // Behaves like an Array's method, not like a jQuery method.
2701+       push: [].push,
2702+       sort: [].sort,
2703+       splice: [].splice,
2704+
2705+       find: function( selector ) {
2706+               if ( this.length === 1 ) {
2707+                       var ret = this.pushStack( [], "find", selector );
2708+                       ret.length = 0;
2709+                       jQuery.find( selector, this[0], ret );
2710+                       return ret;
2711+               } else {
2712+                       return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){
2713+                               return jQuery.find( selector, elem );
2714+                       })), "find", selector );
2715+               }
2716+       },
2717+
2718+       clone: function( events ) {
2719+               // Do the clone
2720+               var ret = this.map(function(){
2721+                       if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
2722+                               // IE copies events bound via attachEvent when
2723+                               // using cloneNode. Calling detachEvent on the
2724+                               // clone will also remove the events from the orignal
2725+                               // In order to get around this, we use innerHTML.
2726+                               // Unfortunately, this means some modifications to
2727+                               // attributes in IE that are actually only stored
2728+                               // as properties will not be copied (such as the
2729+                               // the name attribute on an input).
2730+                               var html = this.outerHTML;
2731+                               if ( !html ) {
2732+                                       var div = this.ownerDocument.createElement("div");
2733+                                       div.appendChild( this.cloneNode(true) );
2734+                                       html = div.innerHTML;
2735+                               }
2736+
2737+                               return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
2738+                       } else
2739+                               return this.cloneNode(true);
2740+               });
2741+
2742+               // Copy the events from the original to the clone
2743+               if ( events === true ) {
2744+                       var orig = this.find("*").andSelf(), i = 0;
2745+
2746+                       ret.find("*").andSelf().each(function(){
2747+                               if ( this.nodeName !== orig[i].nodeName )
2748+                                       return;
2749+
2750+                               var events = jQuery.data( orig[i], "events" );
2751+
2752+                               for ( var type in events ) {
2753+                                       for ( var handler in events[ type ] ) {
2754+                                               jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
2755+                                       }
2756+                               }
2757+
2758+                               i++;
2759+                       });
2760+               }
2761+
2762+               // Return the cloned set
2763+               return ret;
2764+       },
2765+
2766+       filter: function( selector ) {
2767+               return this.pushStack(
2768+                       jQuery.isFunction( selector ) &&
2769+                       jQuery.grep(this, function(elem, i){
2770+                               return selector.call( elem, i );
2771+                       }) ||
2772+
2773+                       jQuery.multiFilter( selector, jQuery.grep(this, function(elem){
2774+                               return elem.nodeType === 1;
2775+                       }) ), "filter", selector );
2776+       },
2777+
2778+       closest: function( selector ) {
2779+               var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
2780+                       closer = 0;
2781+
2782+               return this.map(function(){
2783+                       var cur = this;
2784+                       while ( cur && cur.ownerDocument ) {
2785+                               if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
2786+                                       jQuery.data(cur, "closest", closer);
2787+                                       return cur;
2788+                               }
2789+                               cur = cur.parentNode;
2790+                               closer++;
2791+                       }
2792+               });
2793+       },
2794+
2795+       not: function( selector ) {
2796+               if ( typeof selector === "string" )
2797+                       // test special case where just one selector is passed in
2798+                       if ( isSimple.test( selector ) )
2799+                               return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector );
2800+                       else
2801+                               selector = jQuery.multiFilter( selector, this );
2802+
2803+               var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
2804+               return this.filter(function() {
2805+                       return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
2806+               });
2807+       },
2808+
2809+       add: function( selector ) {
2810+               return this.pushStack( jQuery.unique( jQuery.merge(
2811+                       this.get(),
2812+                       typeof selector === "string" ?
2813+                               jQuery( selector ) :
2814+                               jQuery.makeArray( selector )
2815+               )));
2816+       },
2817+
2818+       is: function( selector ) {
2819+               return !!selector && jQuery.multiFilter( selector, this ).length > 0;
2820+       },
2821+
2822+       hasClass: function( selector ) {
2823+               return !!selector && this.is( "." + selector );
2824+       },
2825+
2826+       val: function( value ) {
2827+               if ( value === undefined ) {                   
2828+                       var elem = this[0];
2829+
2830+                       if ( elem ) {
2831+                               if( jQuery.nodeName( elem, 'option' ) )
2832+                                       return (elem.attributes.value || {}).specified ? elem.value : elem.text;
2833+                               
2834+                               // We need to handle select boxes special
2835+                               if ( jQuery.nodeName( elem, "select" ) ) {
2836+                                       var index = elem.selectedIndex,
2837+                                               values = [],
2838+                                               options = elem.options,
2839+                                               one = elem.type == "select-one";
2840+
2841+                                       // Nothing was selected
2842+                                       if ( index < 0 )
2843+                                               return null;
2844+
2845+                                       // Loop through all the selected options
2846+                                       for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
2847+                                               var option = options[ i ];
2848+
2849+                                               if ( option.selected ) {
2850+                                                       // Get the specifc value for the option
2851+                                                       value = jQuery(option).val();
2852+
2853+                                                       // We don't need an array for one selects
2854+                                                       if ( one )
2855+                                                               return value;
2856+
2857+                                                       // Multi-Selects return an array
2858+                                                       values.push( value );
2859+                                               }
2860+                                       }
2861+
2862+                                       return values;                         
2863+                               }
2864+
2865+                               // Everything else, we just grab the value
2866+                               return (elem.value || "").replace(/\r/g, "");
2867+
2868+                       }
2869+
2870+                       return undefined;
2871+               }
2872+
2873+               if ( typeof value === "number" )
2874+                       value += '';
2875+
2876+               return this.each(function(){
2877+                       if ( this.nodeType != 1 )
2878+                               return;
2879+
2880+                       if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) )
2881+                               this.checked = (jQuery.inArray(this.value, value) >= 0 ||
2882+                                       jQuery.inArray(this.name, value) >= 0);
2883+
2884+                       else if ( jQuery.nodeName( this, "select" ) ) {
2885+                               var values = jQuery.makeArray(value);
2886+
2887+                               jQuery( "option", this ).each(function(){
2888+                                       this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
2889+                                               jQuery.inArray( this.text, values ) >= 0);
2890+                               });
2891+
2892+                               if ( !values.length )
2893+                                       this.selectedIndex = -1;
2894+
2895+                       } else
2896+                               this.value = value;
2897+               });
2898+       },
2899+
2900+       html: function( value ) {
2901+               return value === undefined ?
2902+                       (this[0] ?
2903+                               this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
2904+                               null) :
2905+                       this.empty().append( value );
2906+       },
2907+
2908+       replaceWith: function( value ) {
2909+               return this.after( value ).remove();
2910+       },
2911+
2912+       eq: function( i ) {
2913+               return this.slice( i, +i + 1 );
2914+       },
2915+
2916+       slice: function() {
2917+               return this.pushStack( Array.prototype.slice.apply( this, arguments ),
2918+                       "slice", Array.prototype.slice.call(arguments).join(",") );
2919+       },
2920+
2921+       map: function( callback ) {
2922+               return this.pushStack( jQuery.map(this, function(elem, i){
2923+                       return callback.call( elem, i, elem );
2924+               }));
2925+       },
2926+
2927+       andSelf: function() {
2928+               return this.add( this.prevObject );
2929+       },
2930+
2931+       domManip: function( args, table, callback ) {
2932+               if ( this[0] ) {
2933+                       var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
2934+                               scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
2935+                               first = fragment.firstChild;
2936+
2937+                       if ( first )
2938+                               for ( var i = 0, l = this.length; i < l; i++ )
2939+                                       callback.call( root(this[i], first), this.length > 1 || i > 0 ?
2940+                                                       fragment.cloneNode(true) : fragment );
2941+               
2942+                       if ( scripts )
2943+                               jQuery.each( scripts, evalScript );
2944+               }
2945+
2946+               return this;
2947+               
2948+               function root( elem, cur ) {
2949+                       return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
2950+                               (elem.getElementsByTagName("tbody")[0] ||
2951+                               elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
2952+                               elem;
2953+               }
2954+       }
2955+};
2956+
2957+// Give the init function the jQuery prototype for later instantiation
2958+jQuery.fn.init.prototype = jQuery.fn;
2959+
2960+function evalScript( i, elem ) {
2961+       if ( elem.src )
2962+               jQuery.ajax({
2963+                       url: elem.src,
2964+                       async: false,
2965+                       dataType: "script"
2966+               });
2967+
2968+       else
2969+               jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
2970+
2971+       if ( elem.parentNode )
2972+               elem.parentNode.removeChild( elem );
2973+}
2974+
2975+function now(){
2976+       return +new Date;
2977+}
2978+
2979+jQuery.extend = jQuery.fn.extend = function() {
2980+       // copy reference to target object
2981+       var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
2982+
2983+       // Handle a deep copy situation
2984+       if ( typeof target === "boolean" ) {
2985+               deep = target;
2986+               target = arguments[1] || {};
2987+               // skip the boolean and the target
2988+               i = 2;
2989+       }
2990+
2991+       // Handle case when target is a string or something (possible in deep copy)
2992+       if ( typeof target !== "object" && !jQuery.isFunction(target) )
2993+               target = {};
2994+
2995+       // extend jQuery itself if only one argument is passed
2996+       if ( length == i ) {
2997+               target = this;
2998+               --i;
2999+       }
3000+
3001+       for ( ; i < length; i++ )
3002+               // Only deal with non-null/undefined values
3003+               if ( (options = arguments[ i ]) != null )
3004+                       // Extend the base object
3005+                       for ( var name in options ) {
3006+                               var src = target[ name ], copy = options[ name ];
3007+
3008+                               // Prevent never-ending loop
3009+                               if ( target === copy )
3010+                                       continue;
3011+
3012+                               // Recurse if we're merging object values
3013+                               if ( deep && copy && typeof copy === "object" && !copy.nodeType )
3014+                                       target[ name ] = jQuery.extend( deep,
3015+                                               // Never move original objects, clone them
3016+                                               src || ( copy.length != null ? [ ] : { } )
3017+                                       , copy );
3018+
3019+                               // Don't bring in undefined values
3020+                               else if ( copy !== undefined )
3021+                                       target[ name ] = copy;
3022+
3023+                       }
3024+
3025+       // Return the modified object
3026+       return target;
3027+};
3028+
3029+// exclude the following css properties to add px
3030+var    exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
3031+       // cache defaultView
3032+       defaultView = document.defaultView || {},
3033+       toString = Object.prototype.toString;
3034+
3035+jQuery.extend({
3036+       noConflict: function( deep ) {
3037+               window.$ = _$;
3038+
3039+               if ( deep )
3040+                       window.jQuery = _jQuery;
3041+
3042+               return jQuery;
3043+       },
3044+
3045+       // See test/unit/core.js for details concerning isFunction.
3046+       // Since version 1.3, DOM methods and functions like alert
3047+       // aren't supported. They return false on IE (#2968).
3048+       isFunction: function( obj ) {
3049+               return toString.call(obj) === "[object Function]";
3050+       },
3051+
3052+       isArray: function( obj ) {
3053+               return toString.call(obj) === "[object Array]";
3054+       },
3055+
3056+       // check if an element is in a (or is an) XML document
3057+       isXMLDoc: function( elem ) {
3058+               return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
3059+                       !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
3060+       },
3061+
3062+       // Evalulates a script in a global context
3063+       globalEval: function( data ) {
3064+               if ( data && /\S/.test(data) ) {
3065+                       // Inspired by code by Andrea Giammarchi
3066+                       // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
3067+                       var head = document.getElementsByTagName("head")[0] || document.documentElement,
3068+                               script = document.createElement("script");
3069+
3070+                       script.type = "text/javascript";
3071+                       if ( jQuery.support.scriptEval )
3072+                               script.appendChild( document.createTextNode( data ) );
3073+                       else
3074+                               script.text = data;
3075+
3076+                       // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
3077+                       // This arises when a base node is used (#2709).
3078+                       head.insertBefore( script, head.firstChild );
3079+                       head.removeChild( script );
3080+               }
3081+       },
3082+
3083+       nodeName: function( elem, name ) {
3084+               return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
3085+       },
3086+
3087+       // args is for internal usage only
3088+       each: function( object, callback, args ) {
3089+               var name, i = 0, length = object.length;
3090+
3091+               if ( args ) {
3092+                       if ( length === undefined ) {
3093+                               for ( name in object )
3094+                                       if ( callback.apply( object[ name ], args ) === false )
3095+                                               break;
3096+                       } else
3097+                               for ( ; i < length; )
3098+                                       if ( callback.apply( object[ i++ ], args ) === false )
3099+                                               break;
3100+
3101+               // A special, fast, case for the most common use of each
3102+               } else {
3103+                       if ( length === undefined ) {
3104+                               for ( name in object )
3105+                                       if ( callback.call( object[ name ], name, object[ name ] ) === false )
3106+                                               break;
3107+                       } else
3108+                               for ( var value = object[0];
3109+                                       i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
3110+               }
3111+
3112+               return object;
3113+       },
3114+
3115+       prop: function( elem, value, type, i, name ) {
3116+               // Handle executable functions
3117+               if ( jQuery.isFunction( value ) )
3118+                       value = value.call( elem, i );
3119+
3120+               // Handle passing in a number to a CSS property
3121+               return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ?
3122+                       value + "px" :
3123+                       value;
3124+       },
3125+
3126+       className: {
3127+               // internal only, use addClass("class")
3128+               add: function( elem, classNames ) {
3129+                       jQuery.each((classNames || "").split(/\s+/), function(i, className){
3130+                               if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
3131+                                       elem.className += (elem.className ? " " : "") + className;
3132+                       });
3133+               },
3134+
3135+               // internal only, use removeClass("class")
3136+               remove: function( elem, classNames ) {
3137+                       if (elem.nodeType == 1)
3138+                               elem.className = classNames !== undefined ?
3139+                                       jQuery.grep(elem.className.split(/\s+/), function(className){
3140+                                               return !jQuery.className.has( classNames, className );
3141+                                       }).join(" ") :
3142+                                       "";
3143+               },
3144+
3145+               // internal only, use hasClass("class")
3146+               has: function( elem, className ) {
3147+                       return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
3148+               }
3149+       },
3150+
3151+       // A method for quickly swapping in/out CSS properties to get correct calculations
3152+       swap: function( elem, options, callback ) {
3153+               var old = {};
3154+               // Remember the old values, and insert the new ones
3155+               for ( var name in options ) {
3156+                       old[ name ] = elem.style[ name ];
3157+                       elem.style[ name ] = options[ name ];
3158+               }
3159+
3160+               callback.call( elem );
3161+
3162+               // Revert the old values
3163+               for ( var name in options )
3164+                       elem.style[ name ] = old[ name ];
3165+       },
3166+
3167+       css: function( elem, name, force, extra ) {
3168+               if ( name == "width" || name == "height" ) {
3169+                       var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
3170+
3171+                       function getWH() {
3172+                               val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
3173+
3174+                               if ( extra === "border" )
3175+                                       return;
3176+
3177+                               jQuery.each( which, function() {
3178+                                       if ( !extra )
3179+                                               val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
3180+                                       if ( extra === "margin" )
3181+                                               val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
3182+                                       else
3183+                                               val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
3184+                               });
3185+                       }
3186+
3187+                       if ( elem.offsetWidth !== 0 )
3188+                               getWH();
3189+                       else
3190+                               jQuery.swap( elem, props, getWH );
3191+
3192+                       return Math.max(0, Math.round(val));
3193+               }
3194+
3195+               return jQuery.curCSS( elem, name, force );
3196+       },
3197+
3198+       curCSS: function( elem, name, force ) {
3199+               var ret, style = elem.style;
3200+
3201+               // We need to handle opacity special in IE
3202+               if ( name == "opacity" && !jQuery.support.opacity ) {
3203+                       ret = jQuery.attr( style, "opacity" );
3204+
3205+                       return ret == "" ?
3206+                               "1" :
3207+                               ret;
3208+               }
3209+
3210+               // Make sure we're using the right name for getting the float value
3211+               if ( name.match( /float/i ) )
3212+                       name = styleFloat;
3213+
3214+               if ( !force && style && style[ name ] )
3215+                       ret = style[ name ];
3216+
3217+               else if ( defaultView.getComputedStyle ) {
3218+
3219+                       // Only "float" is needed here
3220+                       if ( name.match( /float/i ) )
3221+                               name = "float";
3222+
3223+                       name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
3224+
3225+                       var computedStyle = defaultView.getComputedStyle( elem, null );
3226+
3227+                       if ( computedStyle )
3228+                               ret = computedStyle.getPropertyValue( name );
3229+
3230+                       // We should always get a number back from opacity
3231+                       if ( name == "opacity" && ret == "" )
3232+                               ret = "1";
3233+
3234+               } else if ( elem.currentStyle ) {
3235+                       var camelCase = name.replace(/\-(\w)/g, function(all, letter){
3236+                               return letter.toUpperCase();
3237+                       });
3238+
3239+                       ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
3240+
3241+                       // From the awesome hack by Dean Edwards
3242+                       // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
3243+
3244+                       // If we're not dealing with a regular pixel number
3245+                       // but a number that has a weird ending, we need to convert it to pixels
3246+                       if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
3247+                               // Remember the original values
3248+                               var left = style.left, rsLeft = elem.runtimeStyle.left;
3249+
3250+                               // Put in the new values to get a computed value out
3251+                               elem.runtimeStyle.left = elem.currentStyle.left;
3252+                               style.left = ret || 0;
3253+                               ret = style.pixelLeft + "px";
3254+
3255+                               // Revert the changed values
3256+                               style.left = left;
3257+                               elem.runtimeStyle.left = rsLeft;
3258+                       }
3259+               }
3260+
3261+               return ret;
3262+       },
3263+
3264+       clean: function( elems, context, fragment ) {
3265+               context = context || document;
3266+
3267+               // !context.createElement fails in IE with an error but returns typeof 'object'
3268+               if ( typeof context.createElement === "undefined" )
3269+                       context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
3270+
3271+               // If a single string is passed in and it's a single tag
3272+               // just do a createElement and skip the rest
3273+               if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
3274+                       var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
3275+                       if ( match )
3276+                               return [ context.createElement( match[1] ) ];
3277+               }
3278+
3279+               var ret = [], scripts = [], div = context.createElement("div");
3280+
3281+               jQuery.each(elems, function(i, elem){
3282+                       if ( typeof elem === "number" )
3283+                               elem += '';
3284+
3285+                       if ( !elem )
3286+                               return;
3287+
3288+                       // Convert html string into DOM nodes
3289+                       if ( typeof elem === "string" ) {
3290+                               // Fix "XHTML"-style tags in all browsers
3291+                               elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
3292+                                       return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
3293+                                               all :
3294+                                               front + "></" + tag + ">";
3295+                               });
3296+
3297+                               // Trim whitespace, otherwise indexOf won't work as expected
3298+                               var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();
3299+
3300+                               var wrap =
3301+                                       // option or optgroup
3302+                                       !tags.indexOf("<opt") &&
3303+                                       [ 1, "<select multiple='multiple'>", "</select>" ] ||
3304+
3305+                                       !tags.indexOf("<leg") &&
3306+                                       [ 1, "<fieldset>", "</fieldset>" ] ||
3307+
3308+                                       tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
3309+                                       [ 1, "<table>", "</table>" ] ||
3310+
3311+                                       !tags.indexOf("<tr") &&
3312+                                       [ 2, "<table><tbody>", "</tbody></table>" ] ||
3313+
3314+                                       // <thead> matched above
3315+                                       (!tags.indexOf("<td") || !tags.indexOf("<th")) &&
3316+                                       [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
3317+
3318+                                       !tags.indexOf("<col") &&
3319+                                       [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
3320+
3321+                                       // IE can't serialize <link> and <script> tags normally
3322+                                       !jQuery.support.htmlSerialize &&
3323+                                       [ 1, "div<div>", "</div>" ] ||
3324+
3325+                                       [ 0, "", "" ];
3326+
3327+                               // Go to html and back, then peel off extra wrappers
3328+                               div.innerHTML = wrap[1] + elem + wrap[2];
3329+
3330+                               // Move to the right depth
3331+                               while ( wrap[0]-- )
3332+                                       div = div.lastChild;
3333+
3334+                               // Remove IE's autoinserted <tbody> from table fragments
3335+                               if ( !jQuery.support.tbody ) {
3336+
3337+                                       // String was a <table>, *may* have spurious <tbody>
3338+                                       var hasBody = /<tbody/i.test(elem),
3339+                                               tbody = !tags.indexOf("<table") && !hasBody ?
3340+                                                       div.firstChild && div.firstChild.childNodes :
3341+
3342+                                               // String was a bare <thead> or <tfoot>
3343+                                               wrap[1] == "<table>" && !hasBody ?
3344+                                                       div.childNodes :
3345+                                                       [];
3346+
3347+                                       for ( var j = tbody.length - 1; j >= 0 ; --j )
3348+                                               if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
3349+                                                       tbody[ j ].parentNode.removeChild( tbody[ j ] );
3350+
3351+                                       }
3352+
3353+                               // IE completely kills leading whitespace when innerHTML is used
3354+                               if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
3355+                                       div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
3356+                               
3357+                               elem = jQuery.makeArray( div.childNodes );
3358+                       }
3359+
3360+                       if ( elem.nodeType )
3361+                               ret.push( elem );
3362+                       else
3363+                               ret = jQuery.merge( ret, elem );
3364+
3365+               });
3366+
3367+               if ( fragment ) {
3368+                       for ( var i = 0; ret[i]; i++ ) {
3369+                               if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
3370+                                       scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
3371+                               } else {
3372+                                       if ( ret[i].nodeType === 1 )
3373+                                               ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
3374+                                       fragment.appendChild( ret[i] );
3375+                               }
3376+                       }
3377+                       
3378+                       return scripts;
3379+               }
3380+
3381+               return ret;
3382+       },
3383+
3384+       attr: function( elem, name, value ) {
3385+               // don't set attributes on text and comment nodes
3386+               if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
3387+                       return undefined;
3388+
3389+               var notxml = !jQuery.isXMLDoc( elem ),
3390+                       // Whether we are setting (or getting)
3391+                       set = value !== undefined;
3392+
3393+               // Try to normalize/fix the name
3394+               name = notxml && jQuery.props[ name ] || name;
3395+
3396+               // Only do all the following if this is a node (faster for style)
3397+               // IE elem.getAttribute passes even for style
3398+               if ( elem.tagName ) {
3399+
3400+                       // These attributes require special treatment
3401+                       var special = /href|src|style/.test( name );
3402+
3403+                       // Safari mis-reports the default selected property of a hidden option
3404+                       // Accessing the parent's selectedIndex property fixes it
3405+                       if ( name == "selected" && elem.parentNode )
3406+                               elem.parentNode.selectedIndex;
3407+
3408+                       // If applicable, access the attribute via the DOM 0 way
3409+                       if ( name in elem && notxml && !special ) {
3410+                               if ( set ){
3411+                                       // We can't allow the type property to be changed (since it causes problems in IE)
3412+                                       if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
3413+                                               throw "type property can't be changed";
3414+
3415+                                       elem[ name ] = value;
3416+                               }
3417+
3418+                               // browsers index elements by id/name on forms, give priority to attributes.
3419+                               if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
3420+                                       return elem.getAttributeNode( name ).nodeValue;
3421+
3422+                               // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
3423+                               // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
3424+                               if ( name == "tabIndex" ) {
3425+                                       var attributeNode = elem.getAttributeNode( "tabIndex" );
3426+                                       return attributeNode && attributeNode.specified
3427+                                               ? attributeNode.value
3428+                                               : elem.nodeName.match(/(button|input|object|select|textarea)/i)
3429+                                                       ? 0
3430+                                                       : elem.nodeName.match(/^(a|area)$/i) && elem.href
3431+                                                               ? 0
3432+                                                               : undefined;
3433+                               }
3434+
3435+                               return elem[ name ];
3436+                       }
3437+
3438+                       if ( !jQuery.support.style && notxml &&  name == "style" )
3439+                               return jQuery.attr( elem.style, "cssText", value );
3440+
3441+                       if ( set )
3442+                               // convert the value to a string (all browsers do this but IE) see #1070
3443+                               elem.setAttribute( name, "" + value );
3444+
3445+                       var attr = !jQuery.support.hrefNormalized && notxml && special
3446+                                       // Some attributes require a special call on IE
3447+                                       ? elem.getAttribute( name, 2 )
3448+                                       : elem.getAttribute( name );
3449+
3450+                       // Non-existent attributes return null, we normalize to undefined
3451+                       return attr === null ? undefined : attr;
3452+               }
3453+
3454+               // elem is actually elem.style ... set the style
3455+
3456+               // IE uses filters for opacity
3457+               if ( !jQuery.support.opacity && name == "opacity" ) {
3458+                       if ( set ) {
3459+                               // IE has trouble with opacity if it does not have layout
3460+                               // Force it by setting the zoom level
3461+                               elem.zoom = 1;
3462+
3463+                               // Set the alpha filter to set the opacity
3464+                               elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
3465+                                       (parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
3466+                       }
3467+
3468+                       return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
3469+                               (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
3470+                               "";
3471+               }
3472+
3473+               name = name.replace(/-([a-z])/ig, function(all, letter){
3474+                       return letter.toUpperCase();
3475+               });
3476+
3477+               if ( set )
3478+                       elem[ name ] = value;
3479+
3480+               return elem[ name ];
3481+       },
3482+
3483+       trim: function( text ) {
3484+               return (text || "").replace( /^\s+|\s+$/g, "" );
3485+       },
3486+
3487+       makeArray: function( array ) {
3488+               var ret = [];
3489+
3490+               if( array != null ){
3491+                       var i = array.length;
3492+                       // The window, strings (and functions) also have 'length'
3493+                       if( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval )
3494+                               ret[0] = array;
3495+                       else
3496+                               while( i )
3497+                                       ret[--i] = array[i];
3498+               }
3499+
3500+               return ret;
3501+       },
3502+
3503+       inArray: function( elem, array ) {
3504+               for ( var i = 0, length = array.length; i < length; i++ )
3505+               // Use === because on IE, window == document
3506+                       if ( array[ i ] === elem )
3507+                               return i;
3508+
3509+               return -1;
3510+       },
3511+
3512+       merge: function( first, second ) {
3513+               // We have to loop this way because IE & Opera overwrite the length
3514+               // expando of getElementsByTagName
3515+               var i = 0, elem, pos = first.length;
3516+               // Also, we need to make sure that the correct elements are being returned
3517+               // (IE returns comment nodes in a '*' query)
3518+               if ( !jQuery.support.getAll ) {
3519+                       while ( (elem = second[ i++ ]) != null )
3520+                               if ( elem.nodeType != 8 )
3521+                                       first[ pos++ ] = elem;
3522+
3523+               } else
3524+                       while ( (elem = second[ i++ ]) != null )
3525+                               first[ pos++ ] = elem;
3526+
3527+               return first;
3528+       },
3529+
3530+       unique: function( array ) {
3531+               var ret = [], done = {};
3532+
3533+               try {
3534+
3535+                       for ( var i = 0, length = array.length; i < length; i++ ) {
3536+                               var id = jQuery.data( array[ i ] );
3537+
3538+                               if ( !done[ id ] ) {
3539+                                       done[ id ] = true;
3540+                                       ret.push( array[ i ] );
3541+                               }
3542+                       }
3543+
3544+               } catch( e ) {
3545+                       ret = array;
3546+               }
3547+
3548+               return ret;
3549+       },
3550+
3551+       grep: function( elems, callback, inv ) {
3552+               var ret = [];
3553+
3554+               // Go through the array, only saving the items
3555+               // that pass the validator function
3556+               for ( var i = 0, length = elems.length; i < length; i++ )
3557+                       if ( !inv != !callback( elems[ i ], i ) )
3558+                               ret.push( elems[ i ] );
3559+
3560+               return ret;
3561+       },
3562+
3563+       map: function( elems, callback ) {
3564+               var ret = [];
3565+
3566+               // Go through the array, translating each of the items to their
3567+               // new value (or values).
3568+               for ( var i = 0, length = elems.length; i < length; i++ ) {
3569+                       var value = callback( elems[ i ], i );
3570+
3571+                       if ( value != null )
3572+                               ret[ ret.length ] = value;
3573+               }
3574+
3575+               return ret.concat.apply( [], ret );
3576+       }
3577+});
3578+
3579+// Use of jQuery.browser is deprecated.
3580+// It's included for backwards compatibility and plugins,
3581+// although they should work to migrate away.
3582+
3583+var userAgent = navigator.userAgent.toLowerCase();
3584+
3585+// Figure out what browser is being used
3586+jQuery.browser = {
3587+       version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
3588+       safari: /webkit/.test( userAgent ),
3589+       opera: /opera/.test( userAgent ),
3590+       msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
3591+       mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
3592+};
3593+
3594+jQuery.each({
3595+       parent: function(elem){return elem.parentNode;},
3596+       parents: function(elem){return jQuery.dir(elem,"parentNode");},
3597+       next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
3598+       prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
3599+       nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
3600+       prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
3601+       siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
3602+       children: function(elem){return jQuery.sibling(elem.firstChild);},
3603+       contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
3604+}, function(name, fn){
3605+       jQuery.fn[ name ] = function( selector ) {
3606+               var ret = jQuery.map( this, fn );
3607+
3608+               if ( selector && typeof selector == "string" )
3609+                       ret = jQuery.multiFilter( selector, ret );
3610+
3611+               return this.pushStack( jQuery.unique( ret ), name, selector );
3612+       };
3613+});
3614+
3615+jQuery.each({
3616+       appendTo: "append",
3617+       prependTo: "prepend",
3618+       insertBefore: "before",
3619+       insertAfter: "after",
3620+       replaceAll: "replaceWith"
3621+}, function(name, original){
3622+       jQuery.fn[ name ] = function( selector ) {
3623+               var ret = [], insert = jQuery( selector );
3624+
3625+               for ( var i = 0, l = insert.length; i < l; i++ ) {
3626+                       var elems = (i > 0 ? this.clone(true) : this).get();
3627+                       jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
3628+                       ret = ret.concat( elems );
3629+               }
3630+
3631+               return this.pushStack( ret, name, selector );
3632+       };
3633+});
3634+
3635+jQuery.each({
3636+       removeAttr: function( name ) {
3637+               jQuery.attr( this, name, "" );
3638+               if (this.nodeType == 1)
3639+                       this.removeAttribute( name );
3640+       },
3641+
3642+       addClass: function( classNames ) {
3643+               jQuery.className.add( this, classNames );
3644+       },
3645+
3646+       removeClass: function( classNames ) {
3647+               jQuery.className.remove( this, classNames );
3648+       },
3649+
3650+       toggleClass: function( classNames, state ) {
3651+               if( typeof state !== "boolean" )
3652+                       state = !jQuery.className.has( this, classNames );
3653+               jQuery.className[ state ? "add" : "remove" ]( this, classNames );
3654+       },
3655+
3656+       remove: function( selector ) {
3657+               if ( !selector || jQuery.filter( selector, [ this ] ).length ) {
3658+                       // Prevent memory leaks
3659+                       jQuery( "*", this ).add([this]).each(function(){
3660+                               jQuery.event.remove(this);
3661+                               jQuery.removeData(this);
3662+                       });
3663+                       if (this.parentNode)
3664+                               this.parentNode.removeChild( this );
3665+               }
3666+       },
3667+
3668+       empty: function() {
3669+               // Remove element nodes and prevent memory leaks
3670+               jQuery(this).children().remove();
3671+
3672+               // Remove any remaining nodes
3673+               while ( this.firstChild )
3674+                       this.removeChild( this.firstChild );
3675+       }
3676+}, function(name, fn){
3677+       jQuery.fn[ name ] = function(){
3678+               return this.each( fn, arguments );
3679+       };
3680+});
3681+
3682+// Helper function used by the dimensions and offset modules
3683+function num(elem, prop) {
3684+       return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
3685+}
3686+var expando = "jQuery" + now(), uuid = 0, windowData = {};
3687+
3688+jQuery.extend({
3689+       cache: {},
3690+
3691+       data: function( elem, name, data ) {
3692+               elem = elem == window ?
3693+                       windowData :
3694+                       elem;
3695+
3696+               var id = elem[ expando ];
3697+
3698+               // Compute a unique ID for the element
3699+               if ( !id )
3700+                       id = elem[ expando ] = ++uuid;
3701+
3702+               // Only generate the data cache if we're
3703+               // trying to access or manipulate it
3704+               if ( name && !jQuery.cache[ id ] )
3705+                       jQuery.cache[ id ] = {};
3706+
3707+               // Prevent overriding the named cache with undefined values
3708+               if ( data !== undefined )
3709+                       jQuery.cache[ id ][ name ] = data;
3710+
3711+               // Return the named cache data, or the ID for the element
3712+               return name ?
3713+                       jQuery.cache[ id ][ name ] :
3714+                       id;
3715+       },
3716+
3717+       removeData: function( elem, name ) {
3718+               elem = elem == window ?
3719+                       windowData :
3720+                       elem;
3721+
3722+               var id = elem[ expando ];
3723+
3724+               // If we want to remove a specific section of the element's data
3725+               if ( name ) {
3726+                       if ( jQuery.cache[ id ] ) {
3727+                               // Remove the section of cache data
3728+                               delete jQuery.cache[ id ][ name ];
3729+
3730+                               // If we've removed all the data, remove the element's cache
3731+                               name = "";
3732+
3733+                               for ( name in jQuery.cache[ id ] )
3734+                                       break;
3735+
3736+                               if ( !name )
3737+                                       jQuery.removeData( elem );
3738+                       }
3739+
3740+               // Otherwise, we want to remove all of the element's data
3741+               } else {
3742+                       // Clean up the element expando
3743+                       try {
3744+                               delete elem[ expando ];
3745+                       } catch(e){
3746+                               // IE has trouble directly removing the expando
3747+                               // but it's ok with using removeAttribute
3748+                               if ( elem.removeAttribute )
3749+                                       elem.removeAttribute( expando );
3750+                       }
3751+
3752+                       // Completely remove the data cache
3753+                       delete jQuery.cache[ id ];
3754+               }
3755+       },
3756+       queue: function( elem, type, data ) {
3757+               if ( elem ){
3758+       
3759+                       type = (type || "fx") + "queue";
3760+       
3761+                       var q = jQuery.data( elem, type );
3762+       
3763+                       if ( !q || jQuery.isArray(data) )
3764+                               q = jQuery.data( elem, type, jQuery.makeArray(data) );
3765+                       else if( data )
3766+                               q.push( data );
3767+       
3768+               }
3769+               return q;
3770+       },
3771+
3772+       dequeue: function( elem, type ){
3773+               var queue = jQuery.queue( elem, type ),
3774+                       fn = queue.shift();
3775+               
3776+               if( !type || type === "fx" )
3777+                       fn = queue[0];
3778+                       
3779+               if( fn !== undefined )
3780+                       fn.call(elem);
3781+       }
3782+});
3783+
3784+jQuery.fn.extend({
3785+       data: function( key, value ){
3786+               var parts = key.split(".");
3787+               parts[1] = parts[1] ? "." + parts[1] : "";
3788+
3789+               if ( value === undefined ) {
3790+                       var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
3791+
3792+                       if ( data === undefined && this.length )
3793+                               data = jQuery.data( this[0], key );
3794+
3795+                       return data === undefined && parts[1] ?
3796+                               this.data( parts[0] ) :
3797+                               data;
3798+               } else
3799+                       return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
3800+                               jQuery.data( this, key, value );
3801+                       });
3802+       },
3803+
3804+       removeData: function( key ){
3805+               return this.each(function(){
3806+                       jQuery.removeData( this, key );
3807+               });
3808+       },
3809+       queue: function(type, data){
3810+               if ( typeof type !== "string" ) {
3811+                       data = type;
3812+                       type = "fx";
3813+               }
3814+
3815+               if ( data === undefined )
3816+                       return jQuery.queue( this[0], type );
3817+
3818+               return this.each(function(){
3819+                       var queue = jQuery.queue( this, type, data );
3820+                       
3821+                        if( type == "fx" && queue.length == 1 )
3822+                               queue[0].call(this);
3823+               });
3824+       },
3825+       dequeue: function(type){
3826+               return this.each(function(){
3827+                       jQuery.dequeue( this, type );
3828+               });
3829+       }
3830+});/*!
3831+ * Sizzle CSS Selector Engine - v0.9.3
3832+ *  Copyright 2009, The Dojo Foundation
3833+ *  Released under the MIT, BSD, and GPL Licenses.
3834+ *  More information: http://sizzlejs.com/
3835+ */
3836+(function(){
3837+
3838+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
3839+       done = 0,
3840+       toString = Object.prototype.toString;
3841+
3842+var Sizzle = function(selector, context, results, seed) {
3843+       results = results || [];
3844+       context = context || document;
3845+
3846+       if ( context.nodeType !== 1 && context.nodeType !== 9 )
3847+               return [];
3848+       
3849+       if ( !selector || typeof selector !== "string" ) {
3850+               return results;
3851+       }
3852+
3853+       var parts = [], m, set, checkSet, check, mode, extra, prune = true;
3854+       
3855+       // Reset the position of the chunker regexp (start from head)
3856+       chunker.lastIndex = 0;
3857+       
3858+       while ( (m = chunker.exec(selector)) !== null ) {
3859+               parts.push( m[1] );
3860+               
3861+               if ( m[2] ) {
3862+                       extra = RegExp.rightContext;
3863+                       break;
3864+               }
3865+       }
3866+
3867+       if ( parts.length > 1 && origPOS.exec( selector ) ) {
3868+               if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
3869+                       set = posProcess( parts[0] + parts[1], context );
3870+               } else {
3871+                       set = Expr.relative[ parts[0] ] ?
3872+                               [ context ] :
3873+                               Sizzle( parts.shift(), context );
3874+
3875+                       while ( parts.length ) {
3876+                               selector = parts.shift();
3877+
3878+                               if ( Expr.relative[ selector ] )
3879+                                       selector += parts.shift();
3880+
3881+                               set = posProcess( selector, set );
3882+                       }
3883+               }
3884+       } else {
3885+               var ret = seed ?
3886+                       { expr: parts.pop(), set: makeArray(seed) } :
3887+                       Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
3888+               set = Sizzle.filter( ret.expr, ret.set );
3889+
3890+               if ( parts.length > 0 ) {
3891+                       checkSet = makeArray(set);
3892+               } else {
3893+                       prune = false;
3894+               }
3895+
3896+               while ( parts.length ) {
3897+                       var cur = parts.pop(), pop = cur;
3898+
3899+                       if ( !Expr.relative[ cur ] ) {
3900+                               cur = "";
3901+                       } else {
3902+                               pop = parts.pop();
3903+                       }
3904+
3905+                       if ( pop == null ) {
3906+                               pop = context;
3907+                       }
3908+
3909+                       Expr.relative[ cur ]( checkSet, pop, isXML(context) );
3910+               }
3911+       }
3912+
3913+       if ( !checkSet ) {
3914+               checkSet = set;
3915+       }
3916+
3917+       if ( !checkSet ) {
3918+               throw "Syntax error, unrecognized expression: " + (cur || selector);
3919+       }
3920+
3921+       if ( toString.call(checkSet) === "[object Array]" ) {
3922+               if ( !prune ) {
3923+                       results.push.apply( results, checkSet );
3924+               } else if ( context.nodeType === 1 ) {
3925+                       for ( var i = 0; checkSet[i] != null; i++ ) {
3926+                               if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
3927+                                       results.push( set[i] );
3928+                               }
3929+                       }
3930+               } else {
3931+                       for ( var i = 0; checkSet[i] != null; i++ ) {
3932+                               if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
3933+                                       results.push( set[i] );
3934+                               }
3935+                       }
3936+               }
3937+       } else {
3938+               makeArray( checkSet, results );
3939+       }
3940+
3941+       if ( extra ) {
3942+               Sizzle( extra, context, results, seed );
3943+
3944+               if ( sortOrder ) {
3945+                       hasDuplicate = false;
3946+                       results.sort(sortOrder);
3947+
3948+                       if ( hasDuplicate ) {
3949+                               for ( var i = 1; i < results.length; i++ ) {
3950+                                       if ( results[i] === results[i-1] ) {
3951+                                               results.splice(i--, 1);
3952+                                       }
3953+                               }
3954+                       }
3955+               }
3956+       }
3957+
3958+       return results;
3959+};
3960+
3961+Sizzle.matches = function(expr, set){
3962+       return Sizzle(expr, null, null, set);
3963+};
3964+
3965+Sizzle.find = function(expr, context, isXML){
3966+       var set, match;
3967+
3968+       if ( !expr ) {
3969+               return [];
3970+       }
3971+
3972+       for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
3973+               var type = Expr.order[i], match;
3974+               
3975+               if ( (match = Expr.match[ type ].exec( expr )) ) {
3976+                       var left = RegExp.leftContext;
3977+
3978+                       if ( left.substr( left.length - 1 ) !== "\\" ) {
3979+                               match[1] = (match[1] || "").replace(/\\/g, "");
3980+                               set = Expr.find[ type ]( match, context, isXML );
3981+                               if ( set != null ) {
3982+                                       expr = expr.replace( Expr.match[ type ], "" );
3983+                                       break;
3984+                               }
3985+                       }
3986+               }
3987+       }
3988+
3989+       if ( !set ) {
3990+               set = context.getElementsByTagName("*");
3991+       }
3992+
3993+       return {set: set, expr: expr};
3994+};
3995+
3996+Sizzle.filter = function(expr, set, inplace, not){
3997+       var old = expr, result = [], curLoop = set, match, anyFound,
3998+               isXMLFilter = set && set[0] && isXML(set[0]);
3999+
4000+       while ( expr && set.length ) {
4001+               for ( var type in Expr.filter ) {
4002+                       if ( (match = Expr.match[ type ].exec( expr )) != null ) {
4003+                               var filter = Expr.filter[ type ], found, item;
4004+                               anyFound = false;
4005+
4006+                               if ( curLoop == result ) {
4007+                                       result = [];
4008+                               }
4009+
4010+                               if ( Expr.preFilter[ type ] ) {
4011+                                       match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
4012+
4013+                                       if ( !match ) {
4014+                                               anyFound = found = true;
4015+                                       } else if ( match === true ) {
4016+                                               continue;
4017+                                       }
4018+                               }
4019+
4020+                               if ( match ) {
4021+                                       for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
4022+                                               if ( item ) {
4023+                                                       found = filter( item, match, i, curLoop );
4024+                                                       var pass = not ^ !!found;
4025+
4026+                                                       if ( inplace && found != null ) {
4027+                                                               if ( pass ) {
4028+                                                                       anyFound = true;
4029+                                                               } else {
4030+                                                                       curLoop[i] = false;
4031+                                                               }
4032+                                                       } else if ( pass ) {
4033+                                                               result.push( item );
4034+                                                               anyFound = true;
4035+                                                       }
4036+                                               }
4037+                                       }
4038+                               }
4039+
4040+                               if ( found !== undefined ) {
4041+                                       if ( !inplace ) {
4042+                                               curLoop = result;
4043+                                       }
4044+
4045+                                       expr = expr.replace( Expr.match[ type ], "" );
4046+
4047+                                       if ( !anyFound ) {
4048+                                               return [];
4049+                                       }
4050+
4051+                                       break;
4052+                               }
4053+                       }
4054+               }
4055+
4056+               // Improper expression
4057+               if ( expr == old ) {
4058+                       if ( anyFound == null ) {
4059+                               throw "Syntax error, unrecognized expression: " + expr;
4060+                       } else {
4061+                               break;
4062+                       }
4063+               }
4064+
4065+               old = expr;
4066+       }
4067+
4068+       return curLoop;
4069+};
4070+
4071+var Expr = Sizzle.selectors = {
4072+       order: [ "ID", "NAME", "TAG" ],
4073+       match: {
4074+               ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
4075+               CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
4076+               NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
4077+               ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
4078+               TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
4079+               CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
4080+               POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
4081+               PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
4082+       },
4083+       attrMap: {
4084+               "class": "className",
4085+               "for": "htmlFor"
4086+       },
4087+       attrHandle: {
4088+               href: function(elem){
4089+                       return elem.getAttribute("href");
4090+               }
4091+       },
4092+       relative: {
4093+               "+": function(checkSet, part, isXML){
4094+                       var isPartStr = typeof part === "string",
4095+                               isTag = isPartStr && !/\W/.test(part),
4096+                               isPartStrNotTag = isPartStr && !isTag;
4097+
4098+                       if ( isTag && !isXML ) {
4099+                               part = part.toUpperCase();
4100+                       }
4101+
4102+                       for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
4103+                               if ( (elem = checkSet[i]) ) {
4104+                                       while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
4105+
4106+                                       checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
4107+                                               elem || false :
4108+                                               elem === part;
4109+                               }
4110+                       }
4111+
4112+                       if ( isPartStrNotTag ) {
4113+                               Sizzle.filter( part, checkSet, true );
4114+                       }
4115+               },
4116+               ">": function(checkSet, part, isXML){
4117+                       var isPartStr = typeof part === "string";
4118+
4119+                       if ( isPartStr && !/\W/.test(part) ) {
4120+                               part = isXML ? part : part.toUpperCase();
4121+
4122+                               for ( var i = 0, l = checkSet.length; i < l; i++ ) {
4123+                                       var elem = checkSet[i];
4124+                                       if ( elem ) {
4125+                                               var parent = elem.parentNode;
4126+                                               checkSet[i] = parent.nodeName === part ? parent : false;
4127+                                       }
4128+                               }
4129+                       } else {
4130+                               for ( var i = 0, l = checkSet.length; i < l; i++ ) {
4131+                                       var elem = checkSet[i];
4132+                                       if ( elem ) {
4133+                                               checkSet[i] = isPartStr ?
4134+                                                       elem.parentNode :
4135+                                                       elem.parentNode === part;
4136+                                       }
4137+                               }
4138+
4139+                               if ( isPartStr ) {
4140+                                       Sizzle.filter( part, checkSet, true );
4141+                               }
4142+                       }
4143+               },
4144+               "": function(checkSet, part, isXML){
4145+                       var doneName = done++, checkFn = dirCheck;
4146+
4147+                       if ( !part.match(/\W/) ) {
4148+                               var nodeCheck = part = isXML ? part : part.toUpperCase();
4149+                               checkFn = dirNodeCheck;
4150+                       }
4151+
4152+                       checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
4153+               },
4154+               "~": function(checkSet, part, isXML){
4155+                       var doneName = done++, checkFn = dirCheck;
4156+
4157+                       if ( typeof part === "string" && !part.match(/\W/) ) {
4158+                               var nodeCheck = part = isXML ? part : part.toUpperCase();
4159+                               checkFn = dirNodeCheck;
4160+                       }
4161+
4162+                       checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
4163+               }
4164+       },
4165+       find: {
4166+               ID: function(match, context, isXML){
4167+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
4168+                               var m = context.getElementById(match[1]);
4169+                               return m ? [m] : [];
4170+                       }
4171+               },
4172+               NAME: function(match, context, isXML){
4173+                       if ( typeof context.getElementsByName !== "undefined" ) {
4174+                               var ret = [], results = context.getElementsByName(match[1]);
4175+
4176+                               for ( var i = 0, l = results.length; i < l; i++ ) {
4177+                                       if ( results[i].getAttribute("name") === match[1] ) {
4178+                                               ret.push( results[i] );
4179+                                       }
4180+                               }
4181+
4182+                               return ret.length === 0 ? null : ret;
4183+                       }
4184+               },
4185+               TAG: function(match, context){
4186+                       return context.getElementsByTagName(match[1]);
4187+               }
4188+       },
4189+       preFilter: {
4190+               CLASS: function(match, curLoop, inplace, result, not, isXML){
4191+                       match = " " + match[1].replace(/\\/g, "") + " ";
4192+
4193+                       if ( isXML ) {
4194+                               return match;
4195+                       }
4196+
4197+                       for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
4198+                               if ( elem ) {
4199+                                       if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
4200+                                               if ( !inplace )
4201+                                                       result.push( elem );
4202+                                       } else if ( inplace ) {
4203+                                               curLoop[i] = false;
4204+                                       }
4205+                               }
4206+                       }
4207+
4208+                       return false;
4209+               },
4210+               ID: function(match){
4211+                       return match[1].replace(/\\/g, "");
4212+               },
4213+               TAG: function(match, curLoop){
4214+                       for ( var i = 0; curLoop[i] === false; i++ ){}
4215+                       return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
4216+               },
4217+               CHILD: function(match){
4218+                       if ( match[1] == "nth" ) {
4219+                               // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
4220+                               var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
4221+                                       match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
4222+                                       !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
4223+
4224+                               // calculate the numbers (first)n+(last) including if they are negative
4225+                               match[2] = (test[1] + (test[2] || 1)) - 0;
4226+                               match[3] = test[3] - 0;
4227+                       }
4228+
4229+                       // TODO: Move to normal caching system
4230+                       match[0] = done++;
4231+
4232+                       return match;
4233+               },
4234+               ATTR: function(match, curLoop, inplace, result, not, isXML){
4235+                       var name = match[1].replace(/\\/g, "");
4236+                       
4237+                       if ( !isXML && Expr.attrMap[name] ) {
4238+                               match[1] = Expr.attrMap[name];
4239+                       }
4240+
4241+                       if ( match[2] === "~=" ) {
4242+                               match[4] = " " + match[4] + " ";
4243+                       }
4244+
4245+                       return match;
4246+               },
4247+               PSEUDO: function(match, curLoop, inplace, result, not){
4248+                       if ( match[1] === "not" ) {
4249+                               // If we're dealing with a complex expression, or a simple one
4250+                               if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
4251+                                       match[3] = Sizzle(match[3], null, null, curLoop);
4252+                               } else {
4253+                                       var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
4254+                                       if ( !inplace ) {
4255+                                               result.push.apply( result, ret );
4256+                                       }
4257+                                       return false;
4258+                               }
4259+                       } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
4260+                               return true;
4261+                       }
4262+                       
4263+                       return match;
4264+               },
4265+               POS: function(match){
4266+                       match.unshift( true );
4267+                       return match;
4268+               }
4269+       },
4270+       filters: {
4271+               enabled: function(elem){
4272+                       return elem.disabled === false && elem.type !== "hidden";
4273+               },
4274+               disabled: function(elem){
4275+                       return elem.disabled === true;
4276+               },
4277+               checked: function(elem){
4278+                       return elem.checked === true;
4279+               },
4280+               selected: function(elem){
4281+                       // Accessing this property makes selected-by-default
4282+                       // options in Safari work properly
4283+                       elem.parentNode.selectedIndex;
4284+                       return elem.selected === true;
4285+               },
4286+               parent: function(elem){
4287+                       return !!elem.firstChild;
4288+               },
4289+               empty: function(elem){
4290+                       return !elem.firstChild;
4291+               },
4292+               has: function(elem, i, match){
4293+                       return !!Sizzle( match[3], elem ).length;
4294+               },
4295+               header: function(elem){
4296+                       return /h\d/i.test( elem.nodeName );
4297+               },
4298+               text: function(elem){
4299+                       return "text" === elem.type;
4300+               },
4301+               radio: function(elem){
4302+                       return "radio" === elem.type;
4303+               },
4304+               checkbox: function(elem){
4305+                       return "checkbox" === elem.type;
4306+               },
4307+               file: function(elem){
4308+                       return "file" === elem.type;
4309+               },
4310+               password: function(elem){
4311+                       return "password" === elem.type;
4312+               },
4313+               submit: function(elem){
4314+                       return "submit" === elem.type;
4315+               },
4316+               image: function(elem){
4317+                       return "image" === elem.type;
4318+               },
4319+               reset: function(elem){
4320+                       return "reset" === elem.type;
4321+               },
4322+               button: function(elem){
4323+                       return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
4324+               },
4325+               input: function(elem){
4326+                       return /input|select|textarea|button/i.test(elem.nodeName);
4327+               }
4328+       },
4329+       setFilters: {
4330+               first: function(elem, i){
4331+                       return i === 0;
4332+               },
4333+               last: function(elem, i, match, array){
4334+                       return i === array.length - 1;
4335+               },
4336+               even: function(elem, i){
4337+                       return i % 2 === 0;
4338+               },
4339+               odd: function(elem, i){
4340+                       return i % 2 === 1;
4341+               },
4342+               lt: function(elem, i, match){
4343+                       return i < match[3] - 0;
4344+               },
4345+               gt: function(elem, i, match){
4346+                       return i > match[3] - 0;
4347+               },
4348+               nth: function(elem, i, match){
4349+                       return match[3] - 0 == i;
4350+               },
4351+               eq: function(elem, i, match){
4352+                       return match[3] - 0 == i;
4353+               }
4354+       },
4355+       filter: {
4356+               PSEUDO: function(elem, match, i, array){
4357+                       var name = match[1], filter = Expr.filters[ name ];
4358+
4359+                       if ( filter ) {
4360+                               return filter( elem, i, match, array );
4361+                       } else if ( name === "contains" ) {
4362+                               return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
4363+                       } else if ( name === "not" ) {
4364+                               var not = match[3];
4365+
4366+                               for ( var i = 0, l = not.length; i < l; i++ ) {
4367+                                       if ( not[i] === elem ) {
4368+                                               return false;
4369+                                       }
4370+                               }
4371+
4372+                               return true;
4373+                       }
4374+               },
4375+               CHILD: function(elem, match){
4376+                       var type = match[1], node = elem;
4377+                       switch (type) {
4378+                               case 'only':
4379+                               case 'first':
4380+                                       while (node = node.previousSibling)  {
4381+                                               if ( node.nodeType === 1 ) return false;
4382+                                       }
4383+                                       if ( type == 'first') return true;
4384+                                       node = elem;
4385+                               case 'last':
4386+                                       while (node = node.nextSibling)  {
4387+                                               if ( node.nodeType === 1 ) return false;
4388+                                       }
4389+                                       return true;
4390+                               case 'nth':
4391+                                       var first = match[2], last = match[3];
4392+
4393+                                       if ( first == 1 && last == 0 ) {
4394+                                               return true;
4395+                                       }
4396+                                       
4397+                                       var doneName = match[0],
4398+                                               parent = elem.parentNode;
4399+       
4400+                                       if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
4401+                                               var count = 0;
4402+                                               for ( node = parent.firstChild; node; node = node.nextSibling ) {
4403+                                                       if ( node.nodeType === 1 ) {
4404+                                                               node.nodeIndex = ++count;
4405+                                                       }
4406+                                               }
4407+                                               parent.sizcache = doneName;
4408+                                       }
4409+                                       
4410+                                       var diff = elem.nodeIndex - last;
4411+                                       if ( first == 0 ) {
4412+                                               return diff == 0;
4413+                                       } else {
4414+                                               return ( diff % first == 0 && diff / first >= 0 );
4415+                                       }
4416+                       }
4417+               },
4418+               ID: function(elem, match){
4419+                       return elem.nodeType === 1 && elem.getAttribute("id") === match;
4420+               },
4421+               TAG: function(elem, match){
4422+                       return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
4423+               },
4424+               CLASS: function(elem, match){
4425+                       return (" " + (elem.className || elem.getAttribute("class")) + " ")
4426+                               .indexOf( match ) > -1;
4427+               },
4428+               ATTR: function(elem, match){
4429+                       var name = match[1],
4430+                               result = Expr.attrHandle[ name ] ?
4431+                                       Expr.attrHandle[ name ]( elem ) :
4432+                                       elem[ name ] != null ?
4433+                                               elem[ name ] :
4434+                                               elem.getAttribute( name ),
4435+                               value = result + "",
4436+                               type = match[2],
4437+                               check = match[4];
4438+
4439+                       return result == null ?
4440+                               type === "!=" :
4441+                               type === "=" ?
4442+                               value === check :
4443+                               type === "*=" ?
4444+                               value.indexOf(check) >= 0 :
4445+                               type === "~=" ?
4446+                               (" " + value + " ").indexOf(check) >= 0 :
4447+                               !check ?
4448+                               value && result !== false :
4449+                               type === "!=" ?
4450+                               value != check :
4451+                               type === "^=" ?
4452+                               value.indexOf(check) === 0 :
4453+                               type === "$=" ?
4454+                               value.substr(value.length - check.length) === check :
4455+                               type === "|=" ?
4456+                               value === check || value.substr(0, check.length + 1) === check + "-" :
4457+                               false;
4458+               },
4459+               POS: function(elem, match, i, array){
4460+                       var name = match[2], filter = Expr.setFilters[ name ];
4461+
4462+                       if ( filter ) {
4463+                               return filter( elem, i, match, array );
4464+                       }
4465+               }
4466+       }
4467+};
4468+
4469+var origPOS = Expr.match.POS;
4470+
4471+for ( var type in Expr.match ) {
4472+       Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
4473+}
4474+
4475+var makeArray = function(array, results) {
4476+       array = Array.prototype.slice.call( array );
4477+
4478+       if ( results ) {
4479+               results.push.apply( results, array );
4480+               return results;
4481+       }
4482+       
4483+       return array;
4484+};
4485+
4486+// Perform a simple check to determine if the browser is capable of
4487+// converting a NodeList to an array using builtin methods.
4488+try {
4489+       Array.prototype.slice.call( document.documentElement.childNodes );
4490+
4491+// Provide a fallback method if it does not work
4492+} catch(e){
4493+       makeArray = function(array, results) {
4494+               var ret = results || [];
4495+
4496+               if ( toString.call(array) === "[object Array]" ) {
4497+                       Array.prototype.push.apply( ret, array );
4498+               } else {
4499+                       if ( typeof array.length === "number" ) {
4500+                               for ( var i = 0, l = array.length; i < l; i++ ) {
4501+                                       ret.push( array[i] );
4502+                               }
4503+                       } else {
4504+                               for ( var i = 0; array[i]; i++ ) {
4505+                                       ret.push( array[i] );
4506+                               }
4507+                       }
4508+               }
4509+
4510+               return ret;
4511+       };
4512+}
4513+
4514+var sortOrder;
4515+
4516+if ( document.documentElement.compareDocumentPosition ) {
4517+       sortOrder = function( a, b ) {
4518+               var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
4519+               if ( ret === 0 ) {
4520+                       hasDuplicate = true;
4521+               }
4522+               return ret;
4523+       };
4524+} else if ( "sourceIndex" in document.documentElement ) {
4525+       sortOrder = function( a, b ) {
4526+               var ret = a.sourceIndex - b.sourceIndex;
4527+               if ( ret === 0 ) {
4528+                       hasDuplicate = true;
4529+               }
4530+               return ret;
4531+       };
4532+} else if ( document.createRange ) {
4533+       sortOrder = function( a, b ) {
4534+               var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
4535+               aRange.selectNode(a);
4536+               aRange.collapse(true);
4537+               bRange.selectNode(b);
4538+               bRange.collapse(true);
4539+               var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
4540+               if ( ret === 0 ) {
4541+                       hasDuplicate = true;
4542+               }
4543+               return ret;
4544+       };
4545+}
4546+
4547+// Check to see if the browser returns elements by name when
4548+// querying by getElementById (and provide a workaround)
4549+(function(){
4550+       // We're going to inject a fake input element with a specified name
4551+       var form = document.createElement("form"),
4552+               id = "script" + (new Date).getTime();
4553+       form.innerHTML = "<input name='" + id + "'/>";
4554+
4555+       // Inject it into the root element, check its status, and remove it quickly
4556+       var root = document.documentElement;
4557+       root.insertBefore( form, root.firstChild );
4558+
4559+       // The workaround has to do additional checks after a getElementById
4560+       // Which slows things down for other browsers (hence the branching)
4561+       if ( !!document.getElementById( id ) ) {
4562+               Expr.find.ID = function(match, context, isXML){
4563+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
4564+                               var m = context.getElementById(match[1]);
4565+                               return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
4566+                       }
4567+               };
4568+
4569+               Expr.filter.ID = function(elem, match){
4570+                       var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
4571+                       return elem.nodeType === 1 && node && node.nodeValue === match;
4572+               };
4573+       }
4574+
4575+       root.removeChild( form );
4576+})();
4577+
4578+(function(){
4579+       // Check to see if the browser returns only elements
4580+       // when doing getElementsByTagName("*")
4581+
4582+       // Create a fake element
4583+       var div = document.createElement("div");
4584+       div.appendChild( document.createComment("") );
4585+
4586+       // Make sure no comments are found
4587+       if ( div.getElementsByTagName("*").length > 0 ) {
4588+               Expr.find.TAG = function(match, context){
4589+                       var results = context.getElementsByTagName(match[1]);
4590+
4591+                       // Filter out possible comments
4592+                       if ( match[1] === "*" ) {
4593+                               var tmp = [];
4594+
4595+                               for ( var i = 0; results[i]; i++ ) {
4596+                                       if ( results[i].nodeType === 1 ) {
4597+                                               tmp.push( results[i] );
4598+                                       }
4599+                               }
4600+
4601+                               results = tmp;
4602+                       }
4603+
4604+                       return results;
4605+               };
4606+       }
4607+
4608+       // Check to see if an attribute returns normalized href attributes
4609+       div.innerHTML = "<a href='#'></a>";
4610+       if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
4611+                       div.firstChild.getAttribute("href") !== "#" ) {
4612+               Expr.attrHandle.href = function(elem){
4613+                       return elem.getAttribute("href", 2);
4614+               };
4615+       }
4616+})();
4617+
4618+if ( document.querySelectorAll ) (function(){
4619+       var oldSizzle = Sizzle, div = document.createElement("div");
4620+       div.innerHTML = "<p class='TEST'></p>";
4621+
4622+       // Safari can't handle uppercase or unicode characters when
4623+       // in quirks mode.
4624+       if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
4625+               return;
4626+       }
4627+       
4628+       Sizzle = function(query, context, extra, seed){
4629+               context = context || document;
4630+
4631+               // Only use querySelectorAll on non-XML documents
4632+               // (ID selectors don't work in non-HTML documents)
4633+               if ( !seed && context.nodeType === 9 && !isXML(context) ) {
4634+                       try {
4635+                               return makeArray( context.querySelectorAll(query), extra );
4636+                       } catch(e){}
4637+               }
4638+               
4639+               return oldSizzle(query, context, extra, seed);
4640+       };
4641+
4642+       Sizzle.find = oldSizzle.find;
4643+       Sizzle.filter = oldSizzle.filter;
4644+       Sizzle.selectors = oldSizzle.selectors;
4645+       Sizzle.matches = oldSizzle.matches;
4646+})();
4647+
4648+if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
4649+       var div = document.createElement("div");
4650+       div.innerHTML = "<div class='test e'></div><div class='test'></div>";
4651+
4652+       // Opera can't find a second classname (in 9.6)
4653+       if ( div.getElementsByClassName("e").length === 0 )
4654+               return;
4655+
4656+       // Safari caches class attributes, doesn't catch changes (in 3.2)
4657+       div.lastChild.className = "e";
4658+
4659+       if ( div.getElementsByClassName("e").length === 1 )
4660+               return;
4661+
4662+       Expr.order.splice(1, 0, "CLASS");
4663+       Expr.find.CLASS = function(match, context, isXML) {
4664+               if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
4665+                       return context.getElementsByClassName(match[1]);
4666+               }
4667+       };
4668+})();
4669+
4670+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
4671+       var sibDir = dir == "previousSibling" && !isXML;
4672+       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
4673+               var elem = checkSet[i];
4674+               if ( elem ) {
4675+                       if ( sibDir && elem.nodeType === 1 ){
4676+                               elem.sizcache = doneName;
4677+                               elem.sizset = i;
4678+                       }
4679+                       elem = elem[dir];
4680+                       var match = false;
4681+
4682+                       while ( elem ) {
4683+                               if ( elem.sizcache === doneName ) {
4684+                                       match = checkSet[elem.sizset];
4685+                                       break;
4686+                               }
4687+
4688+                               if ( elem.nodeType === 1 && !isXML ){
4689+                                       elem.sizcache = doneName;
4690+                                       elem.sizset = i;
4691+                               }
4692+
4693+                               if ( elem.nodeName === cur ) {
4694+                                       match = elem;
4695+                                       break;
4696+                               }
4697+
4698+                               elem = elem[dir];
4699+                       }
4700+
4701+                       checkSet[i] = match;
4702+               }
4703+       }
4704+}
4705+
4706+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
4707+       var sibDir = dir == "previousSibling" && !isXML;
4708+       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
4709+               var elem = checkSet[i];
4710+               if ( elem ) {
4711+                       if ( sibDir && elem.nodeType === 1 ) {
4712+                               elem.sizcache = doneName;
4713+                               elem.sizset = i;
4714+                       }
4715+                       elem = elem[dir];
4716+                       var match = false;
4717+
4718+                       while ( elem ) {
4719+                               if ( elem.sizcache === doneName ) {
4720+                                       match = checkSet[elem.sizset];
4721+                                       break;
4722+                               }
4723+
4724+                               if ( elem.nodeType === 1 ) {
4725+                                       if ( !isXML ) {
4726+                                               elem.sizcache = doneName;
4727+                                               elem.sizset = i;
4728+                                       }
4729+                                       if ( typeof cur !== "string" ) {
4730+                                               if ( elem === cur ) {
4731+                                                       match = true;
4732+                                                       break;
4733+                                               }
4734+
4735+                                       } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
4736+                                               match = elem;
4737+                                               break;
4738+                                       }
4739+                               }
4740+
4741+                               elem = elem[dir];
4742+                       }
4743+
4744+                       checkSet[i] = match;
4745+               }
4746+       }
4747+}
4748+
4749+var contains = document.compareDocumentPosition ?  function(a, b){
4750+       return a.compareDocumentPosition(b) & 16;
4751+} : function(a, b){
4752+       return a !== b && (a.contains ? a.contains(b) : true);
4753+};
4754+
4755+var isXML = function(elem){
4756+       return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
4757+               !!elem.ownerDocument && isXML( elem.ownerDocument );
4758+};
4759+
4760+var posProcess = function(selector, context){
4761+       var tmpSet = [], later = "", match,
4762+               root = context.nodeType ? [context] : context;
4763+
4764+       // Position selectors must be done after the filter
4765+       // And so must :not(positional) so we move all PSEUDOs to the end
4766+       while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
4767+               later += match[0];
4768+               selector = selector.replace( Expr.match.PSEUDO, "" );
4769+       }
4770+
4771+       selector = Expr.relative[selector] ? selector + "*" : selector;
4772+
4773+       for ( var i = 0, l = root.length; i < l; i++ ) {
4774+               Sizzle( selector, root[i], tmpSet );
4775+       }
4776+
4777+       return Sizzle.filter( later, tmpSet );
4778+};
4779+
4780+// EXPOSE
4781+jQuery.find = Sizzle;
4782+jQuery.filter = Sizzle.filter;
4783+jQuery.expr = Sizzle.selectors;
4784+jQuery.expr[":"] = jQuery.expr.filters;
4785+
4786+Sizzle.selectors.filters.hidden = function(elem){
4787+       return elem.offsetWidth === 0 || elem.offsetHeight === 0;
4788+};
4789+
4790+Sizzle.selectors.filters.visible = function(elem){
4791+       return elem.offsetWidth > 0 || elem.offsetHeight > 0;
4792+};
4793+
4794+Sizzle.selectors.filters.animated = function(elem){
4795+       return jQuery.grep(jQuery.timers, function(fn){
4796+               return elem === fn.elem;
4797+       }).length;
4798+};
4799+
4800+jQuery.multiFilter = function( expr, elems, not ) {
4801+       if ( not ) {
4802+               expr = ":not(" + expr + ")";
4803+       }
4804+
4805+       return Sizzle.matches(expr, elems);
4806+};
4807+
4808+jQuery.dir = function( elem, dir ){
4809+       var matched = [], cur = elem[dir];
4810+       while ( cur && cur != document ) {
4811+               if ( cur.nodeType == 1 )
4812+                       matched.push( cur );
4813+               cur = cur[dir];
4814+       }
4815+       return matched;
4816+};
4817+
4818+jQuery.nth = function(cur, result, dir, elem){
4819+       result = result || 1;
4820+       var num = 0;
4821+
4822+       for ( ; cur; cur = cur[dir] )
4823+               if ( cur.nodeType == 1 && ++num == result )
4824+                       break;
4825+
4826+       return cur;
4827+};
4828+
4829+jQuery.sibling = function(n, elem){
4830+       var r = [];
4831+
4832+       for ( ; n; n = n.nextSibling ) {
4833+               if ( n.nodeType == 1 && n != elem )
4834+                       r.push( n );
4835+       }
4836+
4837+       return r;
4838+};
4839+
4840+return;
4841+
4842+window.Sizzle = Sizzle;
4843+
4844+})();
4845+/*
4846+ * A number of helper functions used for managing events.
4847+ * Many of the ideas behind this code originated from
4848+ * Dean Edwards' addEvent library.
4849+ */
4850+jQuery.event = {
4851+
4852+       // Bind an event to an element
4853+       // Original by Dean Edwards
4854+       add: function(elem, types, handler, data) {
4855+               if ( elem.nodeType == 3 || elem.nodeType == 8 )
4856+                       return;
4857+
4858+               // For whatever reason, IE has trouble passing the window object
4859+               // around, causing it to be cloned in the process
4860+               if ( elem.setInterval && elem != window )
4861+                       elem = window;
4862+
4863+               // Make sure that the function being executed has a unique ID
4864+               if ( !handler.guid )
4865+                       handler.guid = this.guid++;
4866+
4867+               // if data is passed, bind to handler
4868+               if ( data !== undefined ) {
4869+                       // Create temporary function pointer to original handler
4870+                       var fn = handler;
4871+
4872+                       // Create unique handler function, wrapped around original handler
4873+                       handler = this.proxy( fn );
4874+
4875+                       // Store data in unique handler
4876+                       handler.data = data;
4877+               }
4878+
4879+               // Init the element's event structure
4880+               var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
4881+                       handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
4882+                               // Handle the second event of a trigger and when
4883+                               // an event is called after a page has unloaded
4884+                               return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
4885+                                       jQuery.event.handle.apply(arguments.callee.elem, arguments) :
4886+                                       undefined;
4887+                       });
4888+               // Add elem as a property of the handle function
4889+               // This is to prevent a memory leak with non-native
4890+               // event in IE.
4891+               handle.elem = elem;
4892+
4893+               // Handle multiple events separated by a space
4894+               // jQuery(...).bind("mouseover mouseout", fn);
4895+               jQuery.each(types.split(/\s+/), function(index, type) {
4896+                       // Namespaced event handlers
4897+                       var namespaces = type.split(".");
4898+                       type = namespaces.shift();
4899+                       handler.type = namespaces.slice().sort().join(".");
4900+
4901+                       // Get the current list of functions bound to this event
4902+                       var handlers = events[type];
4903+                       
4904+                       if ( jQuery.event.specialAll[type] )
4905+                               jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
4906+
4907+                       // Init the event handler queue
4908+                       if (!handlers) {
4909+                               handlers = events[type] = {};
4910+
4911+                               // Check for a special event handler
4912+                               // Only use addEventListener/attachEvent if the special
4913+                               // events handler returns false
4914+                               if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
4915+                                       // Bind the global event handler to the element
4916+                                       if (elem.addEventListener)
4917+                                               elem.addEventListener(type, handle, false);
4918+                                       else if (elem.attachEvent)
4919+                                               elem.attachEvent("on" + type, handle);
4920+                               }
4921+                       }
4922+
4923+                       // Add the function to the element's handler list
4924+                       handlers[handler.guid] = handler;
4925+
4926+                       // Keep track of which events have been used, for global triggering
4927+                       jQuery.event.global[type] = true;
4928+               });
4929+
4930+               // Nullify elem to prevent memory leaks in IE
4931+               elem = null;
4932+       },
4933+
4934+       guid: 1,
4935+       global: {},
4936+
4937+       // Detach an event or set of events from an element
4938+       remove: function(elem, types, handler) {
4939+               // don't do events on text and comment nodes
4940+               if ( elem.nodeType == 3 || elem.nodeType == 8 )
4941+                       return;
4942+
4943+               var events = jQuery.data(elem, "events"), ret, index;
4944+
4945+               if ( events ) {
4946+                       // Unbind all events for the element
4947+                       if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
4948+                               for ( var type in events )
4949+                                       this.remove( elem, type + (types || "") );
4950+                       else {
4951+                               // types is actually an event object here
4952+                               if ( types.type ) {
4953+                                       handler = types.handler;
4954+                                       types = types.type;
4955+                               }
4956+
4957+                               // Handle multiple events seperated by a space
4958+                               // jQuery(...).unbind("mouseover mouseout", fn);
4959+                               jQuery.each(types.split(/\s+/), function(index, type){
4960+                                       // Namespaced event handlers
4961+                                       var namespaces = type.split(".");
4962+                                       type = namespaces.shift();
4963+                                       var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
4964+
4965+                                       if ( events[type] ) {
4966+                                               // remove the given handler for the given type
4967+                                               if ( handler )
4968+                                                       delete events[type][handler.guid];
4969+
4970+                                               // remove all handlers for the given type
4971+                                               else
4972+                                                       for ( var handle in events[type] )
4973+                                                               // Handle the removal of namespaced events
4974+                                                               if ( namespace.test(events[type][handle].type) )
4975+                                                                       delete events[type][handle];
4976+                                                                       
4977+                                               if ( jQuery.event.specialAll[type] )
4978+                                                       jQuery.event.specialAll[type].teardown.call(elem, namespaces);
4979+
4980+                                               // remove generic event handler if no more handlers exist
4981+                                               for ( ret in events[type] ) break;
4982+                                               if ( !ret ) {
4983+                                                       if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
4984+                                                               if (elem.removeEventListener)
4985+                                                                       elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
4986+                                                               else if (elem.detachEvent)
4987+                                                                       elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
4988+                                                       }
4989+                                                       ret = null;
4990+                                                       delete events[type];
4991+                                               }
4992+                                       }
4993+                               });
4994+                       }
4995+
4996+                       // Remove the expando if it's no longer used
4997+                       for ( ret in events ) break;
4998+                       if ( !ret ) {
4999+                               var handle = jQuery.data( elem, "handle" );
5000+                               if ( handle ) handle.elem = null;
5001+                               jQuery.removeData( elem, "events" );
5002+                               jQuery.removeData( elem, "handle" );
5003+                       }
5004+               }
5005+       },
5006+
5007+       // bubbling is internal
5008+       trigger: function( event, data, elem, bubbling ) {
5009+               // Event object or event type
5010+               var type = event.type || event;
5011+
5012+               if( !bubbling ){
5013+                       event = typeof event === "object" ?
5014+                               // jQuery.Event object
5015+                               event[expando] ? event :
5016+                               // Object literal
5017+                               jQuery.extend( jQuery.Event(type), event ) :
5018+                               // Just the event type (string)
5019+                               jQuery.Event(type);
5020+
5021+                       if ( type.indexOf("!") >= 0 ) {
5022+                               event.type = type = type.slice(0, -1);
5023+                               event.exclusive = true;
5024+                       }
5025+
5026+                       // Handle a global trigger
5027+                       if ( !elem ) {
5028+                               // Don't bubble custom events when global (to avoid too much overhead)
5029+                               event.stopPropagation();
5030+                               // Only trigger if we've ever bound an event for it
5031+                               if ( this.global[type] )
5032+                                       jQuery.each( jQuery.cache, function(){
5033+                                               if ( this.events && this.events[type] )
5034+                                                       jQuery.event.trigger( event, data, this.handle.elem );
5035+                                       });
5036+                       }
5037+
5038+                       // Handle triggering a single element
5039+
5040+                       // don't do events on text and comment nodes
5041+                       if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
5042+                               return undefined;
5043+                       
5044+                       // Clean up in case it is reused
5045+                       event.result = undefined;
5046+                       event.target = elem;
5047+                       
5048+                       // Clone the incoming data, if any
5049+                       data = jQuery.makeArray(data);
5050+                       data.unshift( event );
5051+               }
5052+
5053+               event.currentTarget = elem;
5054+
5055+               // Trigger the event, it is assumed that "handle" is a function
5056+               var handle = jQuery.data(elem, "handle");
5057+               if ( handle )
5058+                       handle.apply( elem, data );
5059+
5060+               // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
5061+               if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
5062+                       event.result = false;
5063+
5064+               // Trigger the native events (except for clicks on links)
5065+               if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
5066+                       this.triggered = true;
5067+                       try {
5068+                               elem[ type ]();
5069+                       // prevent IE from throwing an error for some hidden elements
5070+                       } catch (e) {}
5071+               }
5072+
5073+               this.triggered = false;
5074+
5075+               if ( !event.isPropagationStopped() ) {
5076+                       var parent = elem.parentNode || elem.ownerDocument;
5077+                       if ( parent )
5078+                               jQuery.event.trigger(event, data, parent, true);
5079+               }
5080+       },
5081+
5082+       handle: function(event) {
5083+               // returned undefined or false
5084+               var all, handlers;
5085+
5086+               event = arguments[0] = jQuery.event.fix( event || window.event );
5087+               event.currentTarget = this;
5088+               
5089+               // Namespaced event handlers
5090+               var namespaces = event.type.split(".");
5091+               event.type = namespaces.shift();
5092+
5093+               // Cache this now, all = true means, any handler
5094+               all = !namespaces.length && !event.exclusive;
5095+               
5096+               var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
5097+
5098+               handlers = ( jQuery.data(this, "events") || {} )[event.type];
5099+
5100+               for ( var j in handlers ) {
5101+                       var handler = handlers[j];
5102+
5103+                       // Filter the functions by class
5104+                       if ( all || namespace.test(handler.type) ) {
5105+                               // Pass in a reference to the handler function itself
5106+                               // So that we can later remove it
5107+                               event.handler = handler;
5108+                               event.data = handler.data;
5109+
5110+                               var ret = handler.apply(this, arguments);
5111+
5112+                               if( ret !== undefined ){
5113+                                       event.result = ret;
5114+                                       if ( ret === false ) {
5115+                                               event.preventDefault();
5116+                                               event.stopPropagation();
5117+                                       }
5118+                               }
5119+
5120+                               if( event.isImmediatePropagationStopped() )
5121+                                       break;
5122+
5123+                       }
5124+               }
5125+       },
5126+
5127+       props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
5128+
5129+       fix: function(event) {
5130+               if ( event[expando] )
5131+                       return event;
5132+
5133+               // store a copy of the original event object
5134+               // and "clone" to set read-only properties
5135+               var originalEvent = event;
5136+               event = jQuery.Event( originalEvent );
5137+
5138+               for ( var i = this.props.length, prop; i; ){
5139+                       prop = this.props[ --i ];
5140+                       event[ prop ] = originalEvent[ prop ];
5141+               }
5142+
5143+               // Fix target property, if necessary
5144+               if ( !event.target )
5145+                       event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
5146+
5147+               // check if target is a textnode (safari)
5148+               if ( event.target.nodeType == 3 )
5149+                       event.target = event.target.parentNode;
5150+
5151+               // Add relatedTarget, if necessary
5152+               if ( !event.relatedTarget && event.fromElement )
5153+                       event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
5154+
5155+               // Calculate pageX/Y if missing and clientX/Y available
5156+               if ( event.pageX == null && event.clientX != null ) {
5157+                       var doc = document.documentElement, body = document.body;
5158+                       event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
5159+                       event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
5160+               }
5161+
5162+               // Add which for key events
5163+               if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
5164+                       event.which = event.charCode || event.keyCode;
5165+
5166+               // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
5167+               if ( !event.metaKey && event.ctrlKey )
5168+                       event.metaKey = event.ctrlKey;
5169+
5170+               // Add which for click: 1 == left; 2 == middle; 3 == right
5171+               // Note: button is not normalized, so don't use it
5172+               if ( !event.which && event.button )
5173+                       event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
5174+
5175+               return event;
5176+       },
5177+
5178+       proxy: function( fn, proxy ){
5179+               proxy = proxy || function(){ return fn.apply(this, arguments); };
5180+               // Set the guid of unique handler to the same of original handler, so it can be removed
5181+               proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
5182+               // So proxy can be declared as an argument
5183+               return proxy;
5184+       },
5185+
5186+       special: {
5187+               ready: {
5188+                       // Make sure the ready event is setup
5189+                       setup: bindReady,
5190+                       teardown: function() {}
5191+               }
5192+       },
5193+       
5194+       specialAll: {
5195+               live: {
5196+                       setup: function( selector, namespaces ){
5197+                               jQuery.event.add( this, namespaces[0], liveHandler );
5198+                       },
5199+                       teardown:  function( namespaces ){
5200+                               if ( namespaces.length ) {
5201+                                       var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
5202+                                       
5203+                                       jQuery.each( (jQuery.data(this, "events").live || {}), function(){
5204+                                               if ( name.test(this.type) )
5205+                                                       remove++;
5206+                                       });
5207+                                       
5208+                                       if ( remove < 1 )
5209+                                               jQuery.event.remove( this, namespaces[0], liveHandler );
5210+                               }
5211+                       }
5212+               }
5213+       }
5214+};
5215+
5216+jQuery.Event = function( src ){
5217+       // Allow instantiation without the 'new' keyword
5218+       if( !this.preventDefault )
5219+               return new jQuery.Event(src);
5220+       
5221+       // Event object
5222+       if( src && src.type ){
5223+               this.originalEvent = src;
5224+               this.type = src.type;
5225+       // Event type
5226+       }else
5227+               this.type = src;
5228+
5229+       // timeStamp is buggy for some events on Firefox(#3843)
5230+       // So we won't rely on the native value
5231+       this.timeStamp = now();
5232+       
5233+       // Mark it as fixed
5234+       this[expando] = true;
5235+};
5236+
5237+function returnFalse(){
5238+       return false;
5239+}
5240+function returnTrue(){
5241+       return true;
5242+}
5243+
5244+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
5245+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
5246+jQuery.Event.prototype = {
5247+       preventDefault: function() {
5248+               this.isDefaultPrevented = returnTrue;
5249+
5250+               var e = this.originalEvent;
5251+               if( !e )
5252+                       return;
5253+               // if preventDefault exists run it on the original event
5254+               if (e.preventDefault)
5255+                       e.preventDefault();
5256+               // otherwise set the returnValue property of the original event to false (IE)
5257+               e.returnValue = false;
5258+       },
5259+       stopPropagation: function() {
5260+               this.isPropagationStopped = returnTrue;
5261+
5262+               var e = this.originalEvent;
5263+               if( !e )
5264+                       return;
5265+               // if stopPropagation exists run it on the original event
5266+               if (e.stopPropagation)
5267+                       e.stopPropagation();
5268+               // otherwise set the cancelBubble property of the original event to true (IE)
5269+               e.cancelBubble = true;
5270+       },
5271+       stopImmediatePropagation:function(){
5272+               this.isImmediatePropagationStopped = returnTrue;
5273+               this.stopPropagation();
5274+       },
5275+       isDefaultPrevented: returnFalse,
5276+       isPropagationStopped: returnFalse,
5277+       isImmediatePropagationStopped: returnFalse
5278+};
5279+// Checks if an event happened on an element within another element
5280+// Used in jQuery.event.special.mouseenter and mouseleave handlers
5281+var withinElement = function(event) {
5282+       // Check if mouse(over|out) are still within the same parent element
5283+       var parent = event.relatedTarget;
5284+       // Traverse up the tree
5285+       while ( parent && parent != this )
5286+               try { parent = parent.parentNode; }
5287+               catch(e) { parent = this; }
5288+       
5289+       if( parent != this ){
5290+               // set the correct event type
5291+               event.type = event.data;
5292+               // handle event if we actually just moused on to a non sub-element
5293+               jQuery.event.handle.apply( this, arguments );
5294+       }
5295+};
5296+       
5297+jQuery.each({
5298+       mouseover: 'mouseenter',
5299+       mouseout: 'mouseleave'
5300+}, function( orig, fix ){
5301+       jQuery.event.special[ fix ] = {
5302+               setup: function(){
5303+                       jQuery.event.add( this, orig, withinElement, fix );
5304+               },
5305+               teardown: function(){
5306+                       jQuery.event.remove( this, orig, withinElement );
5307+               }
5308+       };                         
5309+});
5310+
5311+jQuery.fn.extend({
5312+       bind: function( type, data, fn ) {
5313+               return type == "unload" ? this.one(type, data, fn) : this.each(function(){
5314+                       jQuery.event.add( this, type, fn || data, fn && data );
5315+               });
5316+       },
5317+
5318+       one: function( type, data, fn ) {
5319+               var one = jQuery.event.proxy( fn || data, function(event) {
5320+                       jQuery(this).unbind(event, one);
5321+                       return (fn || data).apply( this, arguments );
5322+               });
5323+               return this.each(function(){
5324+                       jQuery.event.add( this, type, one, fn && data);
5325+               });
5326+       },
5327+
5328+       unbind: function( type, fn ) {
5329+               return this.each(function(){
5330+                       jQuery.event.remove( this, type, fn );
5331+               });
5332+       },
5333+
5334+       trigger: function( type, data ) {
5335+               return this.each(function(){
5336+                       jQuery.event.trigger( type, data, this );
5337+               });
5338+       },
5339+
5340+       triggerHandler: function( type, data ) {
5341+               if( this[0] ){
5342+                       var event = jQuery.Event(type);
5343+                       event.preventDefault();
5344+                       event.stopPropagation();
5345+                       jQuery.event.trigger( event, data, this[0] );
5346+                       return event.result;
5347+               }               
5348+       },
5349+
5350+       toggle: function( fn ) {
5351+               // Save reference to arguments for access in closure
5352+               var args = arguments, i = 1;
5353+
5354+               // link all the functions, so any of them can unbind this click handler
5355+               while( i < args.length )
5356+                       jQuery.event.proxy( fn, args[i++] );
5357+
5358+               return this.click( jQuery.event.proxy( fn, function(event) {
5359+                       // Figure out which function to execute
5360+                       this.lastToggle = ( this.lastToggle || 0 ) % i;
5361+
5362+                       // Make sure that clicks stop
5363+                       event.preventDefault();
5364+
5365+                       // and execute the function
5366+                       return args[ this.lastToggle++ ].apply( this, arguments ) || false;
5367+               }));
5368+       },
5369+
5370+       hover: function(fnOver, fnOut) {
5371+               return this.mouseenter(fnOver).mouseleave(fnOut);
5372+       },
5373+
5374+       ready: function(fn) {
5375+               // Attach the listeners
5376+               bindReady();
5377+
5378+               // If the DOM is already ready
5379+               if ( jQuery.isReady )
5380+                       // Execute the function immediately
5381+                       fn.call( document, jQuery );
5382+
5383+               // Otherwise, remember the function for later
5384+               else
5385+                       // Add the function to the wait list
5386+                       jQuery.readyList.push( fn );
5387+
5388+               return this;
5389+       },
5390+       
5391+       live: function( type, fn ){
5392+               var proxy = jQuery.event.proxy( fn );
5393+               proxy.guid += this.selector + type;
5394+
5395+               jQuery(document).bind( liveConvert(type, this.selector), this.selector, proxy );
5396+
5397+               return this;
5398+       },
5399+       
5400+       die: function( type, fn ){
5401+               jQuery(document).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
5402+               return this;
5403+       }
5404+});
5405+
5406+function liveHandler( event ){
5407+       var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
5408+               stop = true,
5409+               elems = [];
5410+
5411+       jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
5412+               if ( check.test(fn.type) ) {
5413+                       var elem = jQuery(event.target).closest(fn.data)[0];
5414+                       if ( elem )
5415+                               elems.push({ elem: elem, fn: fn });
5416+               }
5417+       });
5418+
5419+       elems.sort(function(a,b) {
5420+               return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
5421+       });
5422+       
5423+       jQuery.each(elems, function(){
5424+               if ( this.fn.call(this.elem, event, this.fn.data) === false )
5425+                       return (stop = false);
5426+       });
5427+
5428+       return stop;
5429+}
5430+
5431+function liveConvert(type, selector){
5432+       return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
5433+}
5434+
5435+jQuery.extend({
5436+       isReady: false,
5437+       readyList: [],
5438+       // Handle when the DOM is ready
5439+       ready: function() {
5440+               // Make sure that the DOM is not already loaded
5441+               if ( !jQuery.isReady ) {
5442+                       // Remember that the DOM is ready
5443+                       jQuery.isReady = true;
5444+
5445+                       // If there are functions bound, to execute
5446+                       if ( jQuery.readyList ) {
5447+                               // Execute all of them
5448+                               jQuery.each( jQuery.readyList, function(){
5449+                                       this.call( document, jQuery );
5450+                               });
5451+
5452+                               // Reset the list of functions
5453+                               jQuery.readyList = null;
5454+                       }
5455+
5456+                       // Trigger any bound ready events
5457+                       jQuery(document).triggerHandler("ready");
5458+               }
5459+       }
5460+});
5461+
5462+var readyBound = false;
5463+
5464+function bindReady(){
5465+       if ( readyBound ) return;
5466+       readyBound = true;
5467+
5468+       // Mozilla, Opera and webkit nightlies currently support this event
5469+       if ( document.addEventListener ) {
5470+               // Use the handy event callback
5471+               document.addEventListener( "DOMContentLoaded", function(){
5472+                       document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
5473+                       jQuery.ready();
5474+               }, false );
5475+
5476+       // If IE event model is used
5477+       } else if ( document.attachEvent ) {
5478+               // ensure firing before onload,
5479+               // maybe late but safe also for iframes
5480+               document.attachEvent("onreadystatechange", function(){
5481+                       if ( document.readyState === "complete" ) {
5482+                               document.detachEvent( "onreadystatechange", arguments.callee );
5483+                               jQuery.ready();
5484+                       }
5485+               });
5486+
5487+               // If IE and not an iframe
5488+               // continually check to see if the document is ready
5489+               if ( document.documentElement.doScroll && window == window.top ) (function(){
5490+                       if ( jQuery.isReady ) return;
5491+
5492+                       try {
5493+                               // If IE is used, use the trick by Diego Perini
5494+                               // http://javascript.nwbox.com/IEContentLoaded/
5495+                               document.documentElement.doScroll("left");
5496+                       } catch( error ) {
5497+                               setTimeout( arguments.callee, 0 );
5498+                               return;
5499+                       }
5500+
5501+                       // and execute any waiting functions
5502+                       jQuery.ready();
5503+               })();
5504+       }
5505+
5506+       // A fallback to window.onload, that will always work
5507+       jQuery.event.add( window, "load", jQuery.ready );
5508+}
5509+
5510+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
5511+       "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
5512+       "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
5513+
5514+       // Handle event binding
5515+       jQuery.fn[name] = function(fn){
5516+               return fn ? this.bind(name, fn) : this.trigger(name);
5517+       };
5518+});
5519+
5520+// Prevent memory leaks in IE
5521+// And prevent errors on refresh with events like mouseover in other browsers
5522+// Window isn't included so as not to unbind existing unload events
5523+jQuery( window ).bind( 'unload', function(){
5524+       for ( var id in jQuery.cache )
5525+               // Skip the window
5526+               if ( id != 1 && jQuery.cache[ id ].handle )
5527+                       jQuery.event.remove( jQuery.cache[ id ].handle.elem );
5528+});
5529+(function(){
5530+
5531+       jQuery.support = {};
5532+
5533+       var root = document.documentElement,
5534+               script = document.createElement("script"),
5535+               div = document.createElement("div"),
5536+               id = "script" + (new Date).getTime();
5537+
5538+       div.style.display = "none";
5539+       div.innerHTML = '   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';
5540+
5541+       var all = div.getElementsByTagName("*"),
5542+               a = div.getElementsByTagName("a")[0];
5543+
5544+       // Can't get basic test support
5545+       if ( !all || !all.length || !a ) {
5546+               return;
5547+       }
5548+
5549+       jQuery.support = {
5550+               // IE strips leading whitespace when .innerHTML is used
5551+               leadingWhitespace: div.firstChild.nodeType == 3,
5552+               
5553+               // Make sure that tbody elements aren't automatically inserted
5554+               // IE will insert them into empty tables
5555+               tbody: !div.getElementsByTagName("tbody").length,
5556+               
5557+               // Make sure that you can get all elements in an <object> element
5558+               // IE 7 always returns no results
5559+               objectAll: !!div.getElementsByTagName("object")[0]
5560+                       .getElementsByTagName("*").length,
5561+               
5562+               // Make sure that link elements get serialized correctly by innerHTML
5563+               // This requires a wrapper element in IE
5564+               htmlSerialize: !!div.getElementsByTagName("link").length,
5565+               
5566+               // Get the style information from getAttribute
5567+               // (IE uses .cssText insted)
5568+               style: /red/.test( a.getAttribute("style") ),
5569+               
5570+               // Make sure that URLs aren't manipulated
5571+               // (IE normalizes it by default)
5572+               hrefNormalized: a.getAttribute("href") === "/a",
5573+               
5574+               // Make sure that element opacity exists
5575+               // (IE uses filter instead)
5576+               opacity: a.style.opacity === "0.5",
5577+               
5578+               // Verify style float existence
5579+               // (IE uses styleFloat instead of cssFloat)
5580+               cssFloat: !!a.style.cssFloat,
5581+
5582+               // Will be defined later
5583+               scriptEval: false,
5584+               noCloneEvent: true,
5585+               boxModel: null
5586+       };
5587+       
5588+       script.type = "text/javascript";
5589+       try {
5590+               script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
5591+       } catch(e){}
5592+
5593+       root.insertBefore( script, root.firstChild );
5594+       
5595+       // Make sure that the execution of code works by injecting a script
5596+       // tag with appendChild/createTextNode
5597+       // (IE doesn't support this, fails, and uses .text instead)
5598+       if ( window[ id ] ) {
5599+               jQuery.support.scriptEval = true;
5600+               delete window[ id ];
5601+       }
5602+
5603+       root.removeChild( script );
5604+
5605+       if ( div.attachEvent && div.fireEvent ) {
5606+               div.attachEvent("onclick", function(){
5607+                       // Cloning a node shouldn't copy over any
5608+                       // bound event handlers (IE does this)
5609+                       jQuery.support.noCloneEvent = false;
5610+                       div.detachEvent("onclick", arguments.callee);
5611+               });
5612+               div.cloneNode(true).fireEvent("onclick");
5613+       }
5614+
5615+       // Figure out if the W3C box model works as expected
5616+       // document.body must exist before we can do this
5617+       jQuery(function(){
5618+               var div = document.createElement("div");
5619+               div.style.width = div.style.paddingLeft = "1px";
5620+
5621+               document.body.appendChild( div );
5622+               jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
5623+               document.body.removeChild( div ).style.display = 'none';
5624+       });
5625+})();
5626+
5627+var styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat";
5628+
5629+jQuery.props = {
5630+       "for": "htmlFor",
5631+       "class": "className",
5632+       "float": styleFloat,
5633+       cssFloat: styleFloat,
5634+       styleFloat: styleFloat,
5635+       readonly: "readOnly",
5636+       maxlength: "maxLength",
5637+       cellspacing: "cellSpacing",
5638+       rowspan: "rowSpan",
5639+       tabindex: "tabIndex"
5640+};
5641+jQuery.fn.extend({
5642+       // Keep a copy of the old load
5643+       _load: jQuery.fn.load,
5644+
5645+       load: function( url, params, callback ) {
5646+               if ( typeof url !== "string" )
5647+                       return this._load( url );
5648+
5649+               var off = url.indexOf(" ");
5650+               if ( off >= 0 ) {
5651+                       var selector = url.slice(off, url.length);
5652+                       url = url.slice(0, off);
5653+               }
5654+
5655+               // Default to a GET request
5656+               var type = "GET";
5657+
5658+               // If the second parameter was provided
5659+               if ( params )
5660+                       // If it's a function
5661+                       if ( jQuery.isFunction( params ) ) {
5662+                               // We assume that it's the callback
5663+                               callback = params;
5664+                               params = null;
5665+
5666+                       // Otherwise, build a param string
5667+                       } else if( typeof params === "object" ) {
5668+                               params = jQuery.param( params );
5669+                               type = "POST";
5670+                       }
5671+
5672+               var self = this;
5673+
5674+               // Request the remote document
5675+               jQuery.ajax({
5676+                       url: url,
5677+                       type: type,
5678+                       dataType: "html",
5679+                       data: params,
5680+                       complete: function(res, status){
5681+                               // If successful, inject the HTML into all the matched elements
5682+                               if ( status == "success" || status == "notmodified" )
5683+                                       // See if a selector was specified
5684+                                       self.html( selector ?
5685+                                               // Create a dummy div to hold the results
5686+                                               jQuery("<div/>")
5687+                                                       // inject the contents of the document in, removing the scripts
5688+                                                       // to avoid any 'Permission Denied' errors in IE
5689+                                                       .append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
5690+
5691+                                                       // Locate the specified elements
5692+                                                       .find(selector) :
5693+
5694+                                               // If not, just inject the full result
5695+                                               res.responseText );
5696+
5697+                               if( callback )
5698+                                       self.each( callback, [res.responseText, status, res] );
5699+                       }
5700+               });
5701+               return this;
5702+       },
5703+
5704+       serialize: function() {
5705+               return jQuery.param(this.serializeArray());
5706+       },
5707+       serializeArray: function() {
5708+               return this.map(function(){
5709+                       return this.elements ? jQuery.makeArray(this.elements) : this;
5710+               })
5711+               .filter(function(){
5712+                       return this.name && !this.disabled &&
5713+                               (this.checked || /select|textarea/i.test(this.nodeName) ||
5714+                                       /text|hidden|password|search/i.test(this.type));
5715+               })
5716+               .map(function(i, elem){
5717+                       var val = jQuery(this).val();
5718+                       return val == null ? null :
5719+                               jQuery.isArray(val) ?
5720+                                       jQuery.map( val, function(val, i){
5721+                                               return {name: elem.name, value: val};
5722+                                       }) :
5723+                                       {name: elem.name, value: val};
5724+               }).get();
5725+       }
5726+});
5727+
5728+// Attach a bunch of functions for handling common AJAX events
5729+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
5730+       jQuery.fn[o] = function(f){
5731+               return this.bind(o, f);
5732+       };
5733+});
5734+
5735+var jsc = now();
5736+
5737+jQuery.extend({
5738
5739+       get: function( url, data, callback, type ) {
5740+               // shift arguments if data argument was ommited
5741+               if ( jQuery.isFunction( data ) ) {
5742+                       callback = data;
5743+                       data = null;
5744+               }
5745+
5746+               return jQuery.ajax({
5747+                       type: "GET",
5748+                       url: url,
5749+                       data: data,
5750+                       success: callback,
5751+                       dataType: type
5752+               });
5753+       },
5754+
5755+       getScript: function( url, callback ) {
5756+               return jQuery.get(url, null, callback, "script");
5757+       },
5758+
5759+       getJSON: function( url, data, callback ) {
5760+               return jQuery.get(url, data, callback, "json");
5761+       },
5762+
5763+       post: function( url, data, callback, type ) {
5764+               if ( jQuery.isFunction( data ) ) {
5765+                       callback = data;
5766+                       data = {};
5767+               }
5768+
5769+               return jQuery.ajax({
5770+                       type: "POST",
5771+                       url: url,
5772+                       data: data,
5773+                       success: callback,
5774+                       dataType: type
5775+               });
5776+       },
5777+
5778+       ajaxSetup: function( settings ) {
5779+               jQuery.extend( jQuery.ajaxSettings, settings );
5780+       },
5781+
5782+       ajaxSettings: {
5783+               url: location.href,
5784+               global: true,
5785+               type: "GET",
5786+               contentType: "application/x-www-form-urlencoded",
5787+               processData: true,
5788+               async: true,
5789+               /*
5790+               timeout: 0,
5791+               data: null,
5792+               username: null,
5793+               password: null,
5794+               */
5795+               // Create the request object; Microsoft failed to properly
5796+               // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
5797+               // This function can be overriden by calling jQuery.ajaxSetup
5798+               xhr:function(){
5799+                       return window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
5800+               },
5801+               accepts: {
5802+                       xml: "application/xml, text/xml",
5803+                       html: "text/html",
5804+                       script: "text/javascript, application/javascript",
5805+                       json: "application/json, text/javascript",
5806+                       text: "text/plain",
5807+                       _default: "*/*"
5808+               }
5809+       },
5810+
5811+       // Last-Modified header cache for next request
5812+       lastModified: {},
5813+
5814+       ajax: function( s ) {
5815+               // Extend the settings, but re-extend 's' so that it can be
5816+               // checked again later (in the test suite, specifically)
5817+               s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
5818+
5819+               var jsonp, jsre = /=\?(&|$)/g, status, data,
5820+                       type = s.type.toUpperCase();
5821+
5822+               // convert data if not already a string
5823+               if ( s.data && s.processData && typeof s.data !== "string" )
5824+                       s.data = jQuery.param(s.data);
5825+
5826+               // Handle JSONP Parameter Callbacks
5827+               if ( s.dataType == "jsonp" ) {
5828+                       if ( type == "GET" ) {
5829+                               if ( !s.url.match(jsre) )
5830+                                       s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
5831+                       } else if ( !s.data || !s.data.match(jsre) )
5832+                               s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
5833+                       s.dataType = "json";
5834+               }
5835+
5836+               // Build temporary JSONP function
5837+               if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
5838+                       jsonp = "jsonp" + jsc++;
5839+
5840+                       // Replace the =? sequence both in the query string and the data
5841+                       if ( s.data )
5842+                               s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
5843+                       s.url = s.url.replace(jsre, "=" + jsonp + "$1");
5844+
5845+                       // We need to make sure
5846+                       // that a JSONP style response is executed properly
5847+                       s.dataType = "script";
5848+
5849+                       // Handle JSONP-style loading
5850+                       window[ jsonp ] = function(tmp){
5851+                               data = tmp;
5852+                               success();
5853+                               complete();
5854+                               // Garbage collect
5855+                               window[ jsonp ] = undefined;
5856+                               try{ delete window[ jsonp ]; } catch(e){}
5857+                               if ( head )
5858+                                       head.removeChild( script );
5859+                       };
5860+               }
5861+
5862+               if ( s.dataType == "script" && s.cache == null )
5863+                       s.cache = false;
5864+
5865+               if ( s.cache === false && type == "GET" ) {
5866+                       var ts = now();
5867+                       // try replacing _= if it is there
5868+                       var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
5869+                       // if nothing was replaced, add timestamp to the end
5870+                       s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
5871+               }
5872+
5873+               // If data is available, append data to url for get requests
5874+               if ( s.data && type == "GET" ) {
5875+                       s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
5876+
5877+                       // IE likes to send both get and post data, prevent this
5878+                       s.data = null;
5879+               }
5880+
5881+               // Watch for a new set of requests
5882+               if ( s.global && ! jQuery.active++ )
5883+                       jQuery.event.trigger( "ajaxStart" );
5884+
5885+               // Matches an absolute URL, and saves the domain
5886+               var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec( s.url );
5887+
5888+               // If we're requesting a remote document
5889+               // and trying to load JSON or Script with a GET
5890+               if ( s.dataType == "script" && type == "GET" && parts
5891+                       && ( parts[1] && parts[1] != location.protocol || parts[2] != location.host )){
5892+
5893+                       var head = document.getElementsByTagName("head")[0];
5894+                       var script = document.createElement("script");
5895+                       script.src = s.url;
5896+                       if (s.scriptCharset)
5897+                               script.charset = s.scriptCharset;
5898+
5899+                       // Handle Script loading
5900+                       if ( !jsonp ) {
5901+                               var done = false;
5902+
5903+                               // Attach handlers for all browsers
5904+                               script.onload = script.onreadystatechange = function(){
5905+                                       if ( !done && (!this.readyState ||
5906+                                                       this.readyState == "loaded" || this.readyState == "complete") ) {
5907+                                               done = true;
5908+                                               success();
5909+                                               complete();
5910+
5911+                                               // Handle memory leak in IE
5912+                                               script.onload = script.onreadystatechange = null;
5913+                                               head.removeChild( script );
5914+                                       }
5915+                               };
5916+                       }
5917+
5918+                       head.appendChild(script);
5919+
5920+                       // We handle everything using the script element injection
5921+                       return undefined;
5922+               }
5923+
5924+               var requestDone = false;
5925+
5926+               // Create the request object
5927+               var xhr = s.xhr();
5928+
5929+               // Open the socket
5930+               // Passing null username, generates a login popup on Opera (#2865)
5931+               if( s.username )
5932+                       xhr.open(type, s.url, s.async, s.username, s.password);
5933+               else
5934+                       xhr.open(type, s.url, s.async);
5935+
5936+               // Need an extra try/catch for cross domain requests in Firefox 3
5937+               try {
5938+                       // Set the correct header, if data is being sent
5939+                       if ( s.data )
5940+                               xhr.setRequestHeader("Content-Type", s.contentType);
5941+
5942+                       // Set the If-Modified-Since header, if ifModified mode.
5943+                       if ( s.ifModified )
5944+                               xhr.setRequestHeader("If-Modified-Since",
5945+                                       jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
5946+
5947+                       // Set header so the called script knows that it's an XMLHttpRequest
5948+                       xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
5949+
5950+                       // Set the Accepts header for the server, depending on the dataType
5951+                       xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
5952+                               s.accepts[ s.dataType ] + ", */*" :
5953+                               s.accepts._default );
5954+               } catch(e){}
5955+
5956+               // Allow custom headers/mimetypes and early abort
5957+               if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
5958+                       // Handle the global AJAX counter
5959+                       if ( s.global && ! --jQuery.active )
5960+                               jQuery.event.trigger( "ajaxStop" );
5961+                       // close opended socket
5962+                       xhr.abort();
5963+                       return false;
5964+               }
5965+
5966+               if ( s.global )
5967+                       jQuery.event.trigger("ajaxSend", [xhr, s]);
5968+
5969+               // Wait for a response to come back
5970+               var onreadystatechange = function(isTimeout){
5971+                       // The request was aborted, clear the interval and decrement jQuery.active
5972+                       if (xhr.readyState == 0) {
5973+                               if (ival) {
5974+                                       // clear poll interval
5975+                                       clearInterval(ival);
5976+                                       ival = null;
5977+                                       // Handle the global AJAX counter
5978+                                       if ( s.global && ! --jQuery.active )
5979+                                               jQuery.event.trigger( "ajaxStop" );
5980+                               }
5981+                       // The transfer is complete and the data is available, or the request timed out
5982+                       } else if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
5983+                               requestDone = true;
5984+
5985+                               // clear poll interval
5986+                               if (ival) {
5987+                                       clearInterval(ival);
5988+                                       ival = null;
5989+                               }
5990+
5991+                               status = isTimeout == "timeout" ? "timeout" :
5992+                                       !jQuery.httpSuccess( xhr ) ? "error" :
5993+                                       s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" :
5994+                                       "success";
5995+
5996+                               if ( status == "success" ) {
5997+                                       // Watch for, and catch, XML document parse errors
5998+                                       try {
5999+                                               // process the data (runs the xml through httpData regardless of callback)
6000+                                               data = jQuery.httpData( xhr, s.dataType, s );
6001+                                       } catch(e) {
6002+                                               status = "parsererror";
6003+                                       }
6004+                               }
6005+
6006+                               // Make sure that the request was successful or notmodified
6007+                               if ( status == "success" ) {
6008+                                       // Cache Last-Modified header, if ifModified mode.
6009+                                       var modRes;
6010+                                       try {
6011+                                               modRes = xhr.getResponseHeader("Last-Modified");
6012+                                       } catch(e) {} // swallow exception thrown by FF if header is not available
6013+
6014+                                       if ( s.ifModified && modRes )
6015+                                               jQuery.lastModified[s.url] = modRes;
6016+
6017+                                       // JSONP handles its own success callback
6018+                                       if ( !jsonp )
6019+                                               success();
6020+                               } else
6021+                                       jQuery.handleError(s, xhr, status);
6022+
6023+                               // Fire the complete handlers
6024+                               complete();
6025+
6026+                               if ( isTimeout )
6027+                                       xhr.abort();
6028+
6029+                               // Stop memory leaks
6030+                               if ( s.async )
6031+                                       xhr = null;
6032+                       }
6033+               };
6034+
6035+               if ( s.async ) {
6036+                       // don't attach the handler to the request, just poll it instead
6037+                       var ival = setInterval(onreadystatechange, 13);
6038+
6039+                       // Timeout checker
6040+                       if ( s.timeout > 0 )
6041+                               setTimeout(function(){
6042+                                       // Check to see if the request is still happening
6043+                                       if ( xhr && !requestDone )
6044+                                               onreadystatechange( "timeout" );
6045+                               }, s.timeout);
6046+               }
6047+
6048+               // Send the data
6049+               try {
6050+                       xhr.send(s.data);
6051+               } catch(e) {
6052+                       jQuery.handleError(s, xhr, null, e);
6053+               }
6054+
6055+               // firefox 1.5 doesn't fire statechange for sync requests
6056+               if ( !s.async )
6057+                       onreadystatechange();
6058+
6059+               function success(){
6060+                       // If a local callback was specified, fire it and pass it the data
6061+                       if ( s.success )
6062+                               s.success( data, status );
6063+
6064+                       // Fire the global callback
6065+                       if ( s.global )
6066+                               jQuery.event.trigger( "ajaxSuccess", [xhr, s] );
6067+               }
6068+
6069+               function complete(){
6070+                       // Process result
6071+                       if ( s.complete )
6072+                               s.complete(xhr, status);
6073+
6074+                       // The request was completed
6075+                       if ( s.global )
6076+                               jQuery.event.trigger( "ajaxComplete", [xhr, s] );
6077+
6078+                       // Handle the global AJAX counter
6079+                       if ( s.global && ! --jQuery.active )
6080+                               jQuery.event.trigger( "ajaxStop" );
6081+               }
6082+
6083+               // return XMLHttpRequest to allow aborting the request etc.
6084+               return xhr;
6085+       },
6086+
6087+       handleError: function( s, xhr, status, e ) {
6088+               // If a local callback was specified, fire it
6089+               if ( s.error ) s.error( xhr, status, e );
6090+
6091+               // Fire the global callback
6092+               if ( s.global )
6093+                       jQuery.event.trigger( "ajaxError", [xhr, s, e] );
6094+       },
6095+
6096+       // Counter for holding the number of active queries
6097+       active: 0,
6098+
6099+       // Determines if an XMLHttpRequest was successful or not
6100+       httpSuccess: function( xhr ) {
6101+               try {
6102+                       // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
6103+                       return !xhr.status && location.protocol == "file:" ||
6104+                               ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223;
6105+               } catch(e){}
6106+               return false;
6107+       },
6108+
6109+       // Determines if an XMLHttpRequest returns NotModified
6110+       httpNotModified: function( xhr, url ) {
6111+               try {
6112+                       var xhrRes = xhr.getResponseHeader("Last-Modified");
6113+
6114+                       // Firefox always returns 200. check Last-Modified date
6115+                       return xhr.status == 304 || xhrRes == jQuery.lastModified[url];
6116+               } catch(e){}
6117+               return false;
6118+       },
6119+
6120+       httpData: function( xhr, type, s ) {
6121+               var ct = xhr.getResponseHeader("content-type"),
6122+                       xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
6123+                       data = xml ? xhr.responseXML : xhr.responseText;
6124+
6125+               if ( xml && data.documentElement.tagName == "parsererror" )
6126+                       throw "parsererror";
6127+                       
6128+               // Allow a pre-filtering function to sanitize the response
6129+               // s != null is checked to keep backwards compatibility
6130+               if( s && s.dataFilter )
6131+                       data = s.dataFilter( data, type );
6132+
6133+               // The filter can actually parse the response
6134+               if( typeof data === "string" ){
6135+
6136+                       // If the type is "script", eval it in global context
6137+                       if ( type == "script" )
6138+                               jQuery.globalEval( data );
6139+
6140+                       // Get the JavaScript object, if JSON is used.
6141+                       if ( type == "json" )
6142+                               data = window["eval"]("(" + data + ")");
6143+               }
6144+               
6145+               return data;
6146+       },
6147+
6148+       // Serialize an array of form elements or a set of
6149+       // key/values into a query string
6150+       param: function( a ) {
6151+               var s = [ ];
6152+
6153+               function add( key, value ){
6154+                       s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
6155+               };
6156+
6157+               // If an array was passed in, assume that it is an array
6158+               // of form elements
6159+               if ( jQuery.isArray(a) || a.jquery )
6160+                       // Serialize the form elements
6161+                       jQuery.each( a, function(){
6162+                               add( this.name, this.value );
6163+                       });
6164+
6165+               // Otherwise, assume that it's an object of key/value pairs
6166+               else
6167+                       // Serialize the key/values
6168+                       for ( var j in a )
6169+                               // If the value is an array then the key names need to be repeated
6170+                               if ( jQuery.isArray(a[j]) )
6171+                                       jQuery.each( a[j], function(){
6172+                                               add( j, this );
6173+                                       });
6174+                               else
6175+                                       add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );
6176+
6177+               // Return the resulting serialization
6178+               return s.join("&").replace(/%20/g, "+");
6179+       }
6180+
6181+});
6182+var elemdisplay = {},
6183+       timerId,
6184+       fxAttrs = [
6185+               // height animations
6186+               [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
6187+               // width animations
6188+               [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
6189+               // opacity animations
6190+               [ "opacity" ]
6191+       ];
6192+
6193+function genFx( type, num ){
6194+       var obj = {};
6195+       jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function(){
6196+               obj[ this ] = type;
6197+       });
6198+       return obj;
6199+}
6200+
6201+jQuery.fn.extend({
6202+       show: function(speed,callback){
6203+               if ( speed ) {
6204+                       return this.animate( genFx("show", 3), speed, callback);
6205+               } else {
6206+                       for ( var i = 0, l = this.length; i < l; i++ ){
6207+                               var old = jQuery.data(this[i], "olddisplay");
6208+                               
6209+                               this[i].style.display = old || "";
6210+                               
6211+                               if ( jQuery.css(this[i], "display") === "none" ) {
6212+                                       var tagName = this[i].tagName, display;
6213+                                       
6214+                                       if ( elemdisplay[ tagName ] ) {
6215+                                               display = elemdisplay[ tagName ];
6216+                                       } else {
6217+                                               var elem = jQuery("<" + tagName + " />").appendTo("body");
6218+                                               
6219+                                               display = elem.css("display");
6220+                                               if ( display === "none" )
6221+                                                       display = "block";
6222+                                               
6223+                                               elem.remove();
6224+                                               
6225+                                               elemdisplay[ tagName ] = display;
6226+                                       }
6227+                                       
6228+                                       jQuery.data(this[i], "olddisplay", display);
6229+                               }
6230+                       }
6231+
6232+                       // Set the display of the elements in a second loop
6233+                       // to avoid the constant reflow
6234+                       for ( var i = 0, l = this.length; i < l; i++ ){
6235+                               this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
6236+                       }
6237+                       
6238+                       return this;
6239+               }
6240+       },
6241+
6242+       hide: function(speed,callback){
6243+               if ( speed ) {
6244+                       return this.animate( genFx("hide", 3), speed, callback);
6245+               } else {
6246+                       for ( var i = 0, l = this.length; i < l; i++ ){
6247+                               var old = jQuery.data(this[i], "olddisplay");
6248+                               if ( !old && old !== "none" )
6249+                                       jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
6250+                       }
6251+
6252+                       // Set the display of the elements in a second loop
6253+                       // to avoid the constant reflow
6254+                       for ( var i = 0, l = this.length; i < l; i++ ){
6255+                               this[i].style.display = "none";
6256+                       }
6257+
6258+                       return this;
6259+               }
6260+       },
6261+
6262+       // Save the old toggle function
6263+       _toggle: jQuery.fn.toggle,
6264+
6265+       toggle: function( fn, fn2 ){
6266+               var bool = typeof fn === "boolean";
6267+
6268+               return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
6269+                       this._toggle.apply( this, arguments ) :
6270+                       fn == null || bool ?
6271+                               this.each(function(){
6272+                                       var state = bool ? fn : jQuery(this).is(":hidden");
6273+                                       jQuery(this)[ state ? "show" : "hide" ]();
6274+                               }) :
6275+                               this.animate(genFx("toggle", 3), fn, fn2);
6276+       },
6277+
6278+       fadeTo: function(speed,to,callback){
6279+               return this.animate({opacity: to}, speed, callback);
6280+       },
6281+
6282+       animate: function( prop, speed, easing, callback ) {
6283+               var optall = jQuery.speed(speed, easing, callback);
6284+
6285+               return this[ optall.queue === false ? "each" : "queue" ](function(){
6286+               
6287+                       var opt = jQuery.extend({}, optall), p,
6288+                               hidden = this.nodeType == 1 && jQuery(this).is(":hidden"),
6289+                               self = this;
6290+       
6291+                       for ( p in prop ) {
6292+                               if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
6293+                                       return opt.complete.call(this);
6294+
6295+                               if ( ( p == "height" || p == "width" ) && this.style ) {
6296+                                       // Store display property
6297+                                       opt.display = jQuery.css(this, "display");
6298+
6299+                                       // Make sure that nothing sneaks out
6300+                                       opt.overflow = this.style.overflow;
6301+                               }
6302+                       }
6303+
6304+                       if ( opt.overflow != null )
6305+                               this.style.overflow = "hidden";
6306+
6307+                       opt.curAnim = jQuery.extend({}, prop);
6308+
6309+                       jQuery.each( prop, function(name, val){
6310+                               var e = new jQuery.fx( self, opt, name );
6311+
6312+                               if ( /toggle|show|hide/.test(val) )
6313+                                       e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
6314+                               else {
6315+                                       var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
6316+                                               start = e.cur(true) || 0;
6317+
6318+                                       if ( parts ) {
6319+                                               var end = parseFloat(parts[2]),
6320+                                                       unit = parts[3] || "px";
6321+
6322+                                               // We need to compute starting value
6323+                                               if ( unit != "px" ) {
6324+                                                       self.style[ name ] = (end || 1) + unit;
6325+                                                       start = ((end || 1) / e.cur(true)) * start;
6326+                                                       self.style[ name ] = start + unit;
6327+                                               }
6328+
6329+                                               // If a +=/-= token was provided, we're doing a relative animation
6330+                                               if ( parts[1] )
6331+                                                       end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
6332+
6333+                                               e.custom( start, end, unit );
6334+                                       } else
6335+                                               e.custom( start, val, "" );
6336+                               }
6337+                       });
6338+
6339+                       // For JS strict compliance
6340+                       return true;
6341+               });
6342+       },
6343+
6344+       stop: function(clearQueue, gotoEnd){
6345+               var timers = jQuery.timers;
6346+
6347+               if (clearQueue)
6348+                       this.queue([]);
6349+
6350+               this.each(function(){
6351+                       // go in reverse order so anything added to the queue during the loop is ignored
6352+                       for ( var i = timers.length - 1; i >= 0; i-- )
6353+                               if ( timers[i].elem == this ) {
6354+                                       if (gotoEnd)
6355+                                               // force the next step to be the last
6356+                                               timers[i](true);
6357+                                       timers.splice(i, 1);
6358+                               }
6359+               });
6360+
6361+               // start the next in the queue if the last step wasn't forced
6362+               if (!gotoEnd)
6363+                       this.dequeue();
6364+
6365+               return this;
6366+       }
6367+
6368+});
6369+
6370+// Generate shortcuts for custom animations
6371+jQuery.each({
6372+       slideDown: genFx("show", 1),
6373+       slideUp: genFx("hide", 1),
6374+       slideToggle: genFx("toggle", 1),
6375+       fadeIn: { opacity: "show" },
6376+       fadeOut: { opacity: "hide" }
6377+}, function( name, props ){
6378+       jQuery.fn[ name ] = function( speed, callback ){
6379+               return this.animate( props, speed, callback );
6380+       };
6381+});
6382+
6383+jQuery.extend({
6384+
6385+       speed: function(speed, easing, fn) {
6386+               var opt = typeof speed === "object" ? speed : {
6387+                       complete: fn || !fn && easing ||
6388+                               jQuery.isFunction( speed ) && speed,
6389+                       duration: speed,
6390+                       easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
6391+               };
6392+
6393+               opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
6394+                       jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;
6395+
6396+               // Queueing
6397+               opt.old = opt.complete;
6398+               opt.complete = function(){
6399+                       if ( opt.queue !== false )
6400+                               jQuery(this).dequeue();
6401+                       if ( jQuery.isFunction( opt.old ) )
6402+                               opt.old.call( this );
6403+               };
6404+
6405+               return opt;
6406+       },
6407+
6408+       easing: {
6409+               linear: function( p, n, firstNum, diff ) {
6410+                       return firstNum + diff * p;
6411+               },
6412+               swing: function( p, n, firstNum, diff ) {
6413+                       return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
6414+               }
6415+       },
6416+
6417+       timers: [],
6418+
6419+       fx: function( elem, options, prop ){
6420+               this.options = options;
6421+               this.elem = elem;
6422+               this.prop = prop;
6423+
6424+               if ( !options.orig )
6425+                       options.orig = {};
6426+       }
6427+
6428+});
6429+
6430+jQuery.fx.prototype = {
6431+
6432+       // Simple function for setting a style value
6433+       update: function(){
6434+               if ( this.options.step )
6435+                       this.options.step.call( this.elem, this.now, this );
6436+
6437+               (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
6438+
6439+               // Set display property to block for height/width animations
6440+               if ( ( this.prop == "height" || this.prop == "width" ) && this.elem.style )
6441+                       this.elem.style.display = "block";
6442+       },
6443+
6444+       // Get the current size
6445+       cur: function(force){
6446+               if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) )
6447+                       return this.elem[ this.prop ];
6448+
6449+               var r = parseFloat(jQuery.css(this.elem, this.prop, force));
6450+               return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
6451+       },
6452+
6453+       // Start an animation from one number to another
6454+       custom: function(from, to, unit){
6455+               this.startTime = now();
6456+               this.start = from;
6457+               this.end = to;
6458+               this.unit = unit || this.unit || "px";
6459+               this.now = this.start;
6460+               this.pos = this.state = 0;
6461+
6462+               var self = this;
6463+               function t(gotoEnd){
6464+                       return self.step(gotoEnd);
6465+               }
6466+
6467+               t.elem = this.elem;
6468+
6469+               if ( t() && jQuery.timers.push(t) && !timerId ) {
6470+                       timerId = setInterval(function(){
6471+                               var timers = jQuery.timers;
6472+
6473+                               for ( var i = 0; i < timers.length; i++ )
6474+                                       if ( !timers[i]() )
6475+                                               timers.splice(i--, 1);
6476+
6477+                               if ( !timers.length ) {
6478+                                       clearInterval( timerId );
6479+                                       timerId = undefined;
6480+                               }
6481+                       }, 13);
6482+               }
6483+       },
6484+
6485+       // Simple 'show' function
6486+       show: function(){
6487+               // Remember where we started, so that we can go back to it later
6488+               this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
6489+               this.options.show = true;
6490+
6491+               // Begin the animation
6492+               // Make sure that we start at a small width/height to avoid any
6493+               // flash of content
6494+               this.custom(this.prop == "width" || this.prop == "height" ? 1 : 0, this.cur());
6495+
6496+               // Start by showing the element
6497+               jQuery(this.elem).show();
6498+       },
6499+
6500+       // Simple 'hide' function
6501+       hide: function(){
6502+               // Remember where we started, so that we can go back to it later
6503+               this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
6504+               this.options.hide = true;
6505+
6506+               // Begin the animation
6507+               this.custom(this.cur(), 0);
6508+       },
6509+
6510+       // Each step of an animation
6511+       step: function(gotoEnd){
6512+               var t = now();
6513+
6514+               if ( gotoEnd || t >= this.options.duration + this.startTime ) {
6515+                       this.now = this.end;
6516+                       this.pos = this.state = 1;
6517+                       this.update();
6518+
6519+                       this.options.curAnim[ this.prop ] = true;
6520+
6521+                       var done = true;
6522+                       for ( var i in this.options.curAnim )
6523+                               if ( this.options.curAnim[i] !== true )
6524+                                       done = false;
6525+
6526+                       if ( done ) {
6527+                               if ( this.options.display != null ) {
6528+                                       // Reset the overflow
6529+                                       this.elem.style.overflow = this.options.overflow;
6530+
6531+                                       // Reset the display
6532+                                       this.elem.style.display = this.options.display;
6533+                                       if ( jQuery.css(this.elem, "display") == "none" )
6534+                                               this.elem.style.display = "block";
6535+                               }
6536+
6537+                               // Hide the element if the "hide" operation was done
6538+                               if ( this.options.hide )
6539+                                       jQuery(this.elem).hide();
6540+
6541+                               // Reset the properties, if the item has been hidden or shown
6542+                               if ( this.options.hide || this.options.show )
6543+                                       for ( var p in this.options.curAnim )
6544+                                               jQuery.attr(this.elem.style, p, this.options.orig[p]);
6545+                                       
6546+                               // Execute the complete function
6547+                               this.options.complete.call( this.elem );
6548+                       }
6549+
6550+                       return false;
6551+               } else {
6552+                       var n = t - this.startTime;
6553+                       this.state = n / this.options.duration;
6554+
6555+                       // Perform the easing function, defaults to swing
6556+                       this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
6557+                       this.now = this.start + ((this.end - this.start) * this.pos);
6558+
6559+                       // Perform the next step of the animation
6560+                       this.update();
6561+               }
6562+
6563+               return true;
6564+       }
6565+
6566+};
6567+
6568+jQuery.extend( jQuery.fx, {
6569+       speeds:{
6570+               slow: 600,
6571+               fast: 200,
6572+               // Default speed
6573+               _default: 400
6574+       },
6575+       step: {
6576+
6577+               opacity: function(fx){
6578+                       jQuery.attr(fx.elem.style, "opacity", fx.now);
6579+               },
6580+
6581+               _default: function(fx){
6582+                       if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
6583+                               fx.elem.style[ fx.prop ] = fx.now + fx.unit;
6584+                       else
6585+                               fx.elem[ fx.prop ] = fx.now;
6586+               }
6587+       }
6588+});
6589+if ( document.documentElement["getBoundingClientRect"] )
6590+       jQuery.fn.offset = function() {
6591+               if ( !this[0] ) return { top: 0, left: 0 };
6592+               if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
6593+               var box  = this[0].getBoundingClientRect(), doc = this[0].ownerDocument, body = doc.body, docElem = doc.documentElement,
6594+                       clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
6595+                       top  = box.top  + (self.pageYOffset || jQuery.boxModel && docElem.scrollTop  || body.scrollTop ) - clientTop,
6596+                       left = box.left + (self.pageXOffset || jQuery.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
6597+               return { top: top, left: left };
6598+       };
6599+else
6600+       jQuery.fn.offset = function() {
6601+               if ( !this[0] ) return { top: 0, left: 0 };
6602+               if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
6603+               jQuery.offset.initialized || jQuery.offset.initialize();
6604+
6605+               var elem = this[0], offsetParent = elem.offsetParent, prevOffsetParent = elem,
6606+                       doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
6607+                       body = doc.body, defaultView = doc.defaultView,
6608+                       prevComputedStyle = defaultView.getComputedStyle(elem, null),
6609+                       top = elem.offsetTop, left = elem.offsetLeft;
6610+
6611+               while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
6612+                       computedStyle = defaultView.getComputedStyle(elem, null);
6613+                       top -= elem.scrollTop, left -= elem.scrollLeft;
6614+                       if ( elem === offsetParent ) {
6615+                               top += elem.offsetTop, left += elem.offsetLeft;
6616+                               if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) )
6617+                                       top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
6618+                                       left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
6619+                               prevOffsetParent = offsetParent, offsetParent = elem.offsetParent;
6620+                       }
6621+                       if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" )
6622+                               top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
6623+                               left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
6624+                       prevComputedStyle = computedStyle;
6625+               }
6626+
6627+               if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" )
6628+                       top  += body.offsetTop,
6629+                       left += body.offsetLeft;
6630+
6631+               if ( prevComputedStyle.position === "fixed" )
6632+                       top  += Math.max(docElem.scrollTop, body.scrollTop),
6633+                       left += Math.max(docElem.scrollLeft, body.scrollLeft);
6634+
6635+               return { top: top, left: left };
6636+       };
6637+
6638+jQuery.offset = {
6639+       initialize: function() {
6640+               if ( this.initialized ) return;
6641+               var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop,
6642+                       html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';
6643+
6644+               rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
6645+               for ( prop in rules ) container.style[prop] = rules[prop];
6646+
6647+               container.innerHTML = html;
6648+               body.insertBefore(container, body.firstChild);
6649+               innerDiv = container.firstChild, checkDiv = innerDiv.firstChild, td = innerDiv.nextSibling.firstChild.firstChild;
6650+
6651+               this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
6652+               this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
6653+
6654+               innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative';
6655+               this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
6656+
6657+               body.style.marginTop = '1px';
6658+               this.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0);
6659+               body.style.marginTop = bodyMarginTop;
6660+
6661+               body.removeChild(container);
6662+               this.initialized = true;
6663+       },
6664+
6665+       bodyOffset: function(body) {
6666+               jQuery.offset.initialized || jQuery.offset.initialize();
6667+               var top = body.offsetTop, left = body.offsetLeft;
6668+               if ( jQuery.offset.doesNotIncludeMarginInBodyOffset )
6669+                       top  += parseInt( jQuery.curCSS(body, 'marginTop',  true), 10 ) || 0,
6670+                       left += parseInt( jQuery.curCSS(body, 'marginLeft', true), 10 ) || 0;
6671+               return { top: top, left: left };
6672+       }
6673+};
6674+
6675+
6676+jQuery.fn.extend({
6677+       position: function() {
6678+               var left = 0, top = 0, results;
6679+
6680+               if ( this[0] ) {
6681+                       // Get *real* offsetParent
6682+                       var offsetParent = this.offsetParent(),
6683+
6684+                       // Get correct offsets
6685+                       offset       = this.offset(),
6686+                       parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
6687+
6688+                       // Subtract element margins
6689+                       // note: when an element has margin: auto the offsetLeft and marginLeft
6690+                       // are the same in Safari causing offset.left to incorrectly be 0
6691+                       offset.top  -= num( this, 'marginTop'  );
6692+                       offset.left -= num( this, 'marginLeft' );
6693+
6694+                       // Add offsetParent borders
6695+                       parentOffset.top  += num( offsetParent, 'borderTopWidth'  );
6696+                       parentOffset.left += num( offsetParent, 'borderLeftWidth' );
6697+
6698+                       // Subtract the two offsets
6699+                       results = {
6700+                               top:  offset.top  - parentOffset.top,
6701+                               left: offset.left - parentOffset.left
6702+                       };
6703+               }
6704+
6705+               return results;
6706+       },
6707+
6708+       offsetParent: function() {
6709+               var offsetParent = this[0].offsetParent || document.body;
6710+               while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
6711+                       offsetParent = offsetParent.offsetParent;
6712+               return jQuery(offsetParent);
6713+       }
6714+});
6715+
6716+
6717+// Create scrollLeft and scrollTop methods
6718+jQuery.each( ['Left', 'Top'], function(i, name) {
6719+       var method = 'scroll' + name;
6720+       
6721+       jQuery.fn[ method ] = function(val) {
6722+               if (!this[0]) return null;
6723+
6724+               return val !== undefined ?
6725+
6726+                       // Set the scroll offset
6727+                       this.each(function() {
6728+                               this == window || this == document ?
6729+                                       window.scrollTo(
6730+                                               !i ? val : jQuery(window).scrollLeft(),
6731+                                                i ? val : jQuery(window).scrollTop()
6732+                                       ) :
6733+                                       this[ method ] = val;
6734+                       }) :
6735+
6736+                       // Return the scroll offset
6737+                       this[0] == window || this[0] == document ?
6738+                               self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
6739+                                       jQuery.boxModel && document.documentElement[ method ] ||
6740+                                       document.body[ method ] :
6741+                               this[0][ method ];
6742+       };
6743+});
6744+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
6745+jQuery.each([ "Height", "Width" ], function(i, name){
6746+
6747+       var tl = i ? "Left"  : "Top",  // top or left
6748+               br = i ? "Right" : "Bottom", // bottom or right
6749+               lower = name.toLowerCase();
6750+
6751+       // innerHeight and innerWidth
6752+       jQuery.fn["inner" + name] = function(){
6753+               return this[0] ?
6754+                       jQuery.css( this[0], lower, false, "padding" ) :
6755+                       null;
6756+       };
6757+
6758+       // outerHeight and outerWidth
6759+       jQuery.fn["outer" + name] = function(margin) {
6760+               return this[0] ?
6761+                       jQuery.css( this[0], lower, false, margin ? "margin" : "border" ) :
6762+                       null;
6763+       };
6764+       
6765+       var type = name.toLowerCase();
6766+
6767+       jQuery.fn[ type ] = function( size ) {
6768+               // Get window width or height
6769+               return this[0] == window ?
6770+                       // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
6771+                       document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] ||
6772+                       document.body[ "client" + name ] :
6773+
6774+                       // Get document width or height
6775+                       this[0] == document ?
6776+                               // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
6777+                               Math.max(
6778+                                       document.documentElement["client" + name],
6779+                                       document.body["scroll" + name], document.documentElement["scroll" + name],
6780+                                       document.body["offset" + name], document.documentElement["offset" + name]
6781+                               ) :
6782+
6783+                               // Get or set width or height on the element
6784+                               size === undefined ?
6785+                                       // Get width or height on the element
6786+                                       (this.length ? jQuery.css( this[0], type ) : null) :
6787+
6788+                                       // Set the width or height on the element (default to pixels if value is unitless)
6789+                                       this.css( type, typeof size === "string" ? size : size + "px" );
6790+       };
6791+
6792+});
6793+})();
6794addfile ./src/allmydata/web/protovis-r3.2.js
6795hunk ./src/allmydata/web/protovis-r3.2.js 1
6796+// fba9dc2
6797+var a;if(!Array.prototype.map)Array.prototype.map=function(b,c){for(var d=this.length,f=new Array(d),g=0;g<d;g++)if(g in this)f[g]=b.call(c,this[g],g,this);return f};if(!Array.prototype.filter)Array.prototype.filter=function(b,c){for(var d=this.length,f=[],g=0;g<d;g++)if(g in this){var h=this[g];b.call(c,h,g,this)&&f.push(h)}return f};if(!Array.prototype.forEach)Array.prototype.forEach=function(b,c){for(var d=this.length>>>0,f=0;f<d;f++)f in this&&b.call(c,this[f],f,this)};
6798+if(!Array.prototype.reduce)Array.prototype.reduce=function(b,c){var d=this.length;if(!d&&arguments.length==1)throw new Error("reduce: empty array, no initial value");var f=0;if(arguments.length<2)for(;;){if(f in this){c=this[f++];break}if(++f>=d)throw new Error("reduce: no values, no initial value");}for(;f<d;f++)if(f in this)c=b(c,this[f],f,this);return c};var pv={};pv.version={major:3,minor:2};pv.identity=function(b){return b};pv.index=function(){return this.index};pv.child=function(){return this.childIndex};
6799+pv.parent=function(){return this.parent.index};pv.extend=function(b){function c(){}c.prototype=b.prototype||b;return new c};
6800+try{eval("pv.parse = function(x) x;")}catch(e){pv.parse=function(b){for(var c=new RegExp("function\\s*(\\b\\w+)?\\s*\\([^)]*\\)\\s*","mg"),d,f,g=0,h="";d=c.exec(b);){d=d.index+d[0].length;if(b.charAt(d)!="{"){h+=b.substring(g,d)+"{return ";g=d;for(var i=0;i>=0&&d<b.length;d++){var j=b.charAt(d);switch(j){case '"':case "'":for(;++d<b.length&&(f=b.charAt(d))!=j;)f=="\\"&&d++;break;case "[":case "(":i++;break;case "]":case ")":i--;break;case ";":case ",":i==0&&i--;break}}h+=pv.parse(b.substring(g,--d))+
6801+";}";g=d}c.lastIndex=d}h+=b.substring(g);return h}}pv.css=function(b,c){return window.getComputedStyle?window.getComputedStyle(b,null).getPropertyValue(c):b.currentStyle[c]};pv.error=function(b){typeof console=="undefined"?alert(b):console.error(b)};pv.listen=function(b,c,d){d=pv.listener(d);return b.addEventListener?b.addEventListener(c,d,false):b.attachEvent("on"+c,d)};pv.listener=function(b){return b.$listener||(b.$listener=function(c){try{pv.event=c;return b.call(this,c)}finally{delete pv.event}})};
6802+pv.ancestor=function(b,c){for(;c;){if(c==b)return true;c=c.parentNode}return false};pv.id=function(){var b=1;return function(){return b++}}();pv.functor=function(b){return typeof b=="function"?b:function(){return b}};pv.listen(window,"load",function(){for(pv.$={i:0,x:document.getElementsByTagName("script")};pv.$.i<pv.$.x.length;pv.$.i++){pv.$.s=pv.$.x[pv.$.i];if(pv.$.s.type=="text/javascript+protovis")try{window.eval(pv.parse(pv.$.s.text))}catch(b){pv.error(b)}}delete pv.$});pv.Format={};
6803+pv.Format.re=function(b){return b.replace(/[\\\^\$\*\+\?\[\]\(\)\.\{\}]/g,"\\$&")};pv.Format.pad=function(b,c,d){c=c-String(d).length;return c<1?d:(new Array(c+1)).join(b)+d};
6804+pv.Format.date=function(b){function c(f){return b.replace(/%[a-zA-Z0-9]/g,function(g){switch(g){case "%a":return["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][f.getDay()];case "%A":return["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][f.getDay()];case "%h":case "%b":return["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][f.getMonth()];case "%B":return["January","February","March","April","May","June","July","August","September","October","November","December"][f.getMonth()];
6805+case "%c":return f.toLocaleString();case "%C":return d("0",2,Math.floor(f.getFullYear()/100)%100);case "%d":return d("0",2,f.getDate());case "%x":case "%D":return d("0",2,f.getMonth()+1)+"/"+d("0",2,f.getDate())+"/"+d("0",2,f.getFullYear()%100);case "%e":return d(" ",2,f.getDate());case "%H":return d("0",2,f.getHours());case "%I":return(g=f.getHours()%12)?d("0",2,g):12;case "%m":return d("0",2,f.getMonth()+1);case "%M":return d("0",2,f.getMinutes());case "%n":return"\n";case "%p":return f.getHours()<
6806+12?"AM":"PM";case "%T":case "%X":case "%r":g=f.getHours()%12;return(g?d("0",2,g):12)+":"+d("0",2,f.getMinutes())+":"+d("0",2,f.getSeconds())+" "+(f.getHours()<12?"AM":"PM");case "%R":return d("0",2,f.getHours())+":"+d("0",2,f.getMinutes());case "%S":return d("0",2,f.getSeconds());case "%Q":return d("0",3,f.getMilliseconds());case "%t":return"\t";case "%u":return(g=f.getDay())?g:1;case "%w":return f.getDay();case "%y":return d("0",2,f.getFullYear()%100);case "%Y":return f.getFullYear();case "%%":return"%"}return g})}
6807+var d=pv.Format.pad;c.format=c;c.parse=function(f){var g=1970,h=0,i=1,j=0,l=0,k=0,q=[function(){}],o=pv.Format.re(b).replace(/%[a-zA-Z0-9]/g,function(n){switch(n){case "%b":q.push(function(m){h={Jan:0,Feb:1,Mar:2,Apr:3,May:4,Jun:5,Jul:6,Aug:7,Sep:8,Oct:9,Nov:10,Dec:11}[m]});return"([A-Za-z]+)";case "%h":case "%B":q.push(function(m){h={January:0,February:1,March:2,April:3,May:4,June:5,July:6,August:7,September:8,October:9,November:10,December:11}[m]});return"([A-Za-z]+)";case "%e":case "%d":q.push(function(m){i=
6808+m});return"([0-9]+)";case "%I":case "%H":q.push(function(m){j=m});return"([0-9]+)";case "%m":q.push(function(m){h=m-1});return"([0-9]+)";case "%M":q.push(function(m){l=m});return"([0-9]+)";case "%p":q.push(function(m){if(j==12){if(m=="am")j=0}else if(m=="pm")j=Number(j)+12});return"(am|pm)";case "%S":q.push(function(m){k=m});return"([0-9]+)";case "%y":q.push(function(m){m=Number(m);g=m+(0<=m&&m<69?2E3:m>=69&&m<100?1900:0)});return"([0-9]+)";case "%Y":q.push(function(m){g=m});return"([0-9]+)";case "%%":q.push(function(){});
6809+return"%"}return n});(f=f.match(o))&&f.forEach(function(n,m){q[m](n)});return new Date(g,h,i,j,l,k)};return c};
6810+pv.Format.time=function(b){function c(f){f=Number(f);switch(b){case "short":if(f>=31536E6)return(f/31536E6).toFixed(1)+" years";else if(f>=6048E5)return(f/6048E5).toFixed(1)+" weeks";else if(f>=864E5)return(f/864E5).toFixed(1)+" days";else if(f>=36E5)return(f/36E5).toFixed(1)+" hours";else if(f>=6E4)return(f/6E4).toFixed(1)+" minutes";return(f/1E3).toFixed(1)+" seconds";case "long":var g=[],h=f%36E5/6E4>>0;g.push(d("0",2,f%6E4/1E3>>0));if(f>=36E5){var i=f%864E5/36E5>>0;g.push(d("0",2,h));if(f>=864E5){g.push(d("0",
6811+2,i));g.push(Math.floor(f/864E5).toFixed())}else g.push(i.toFixed())}else g.push(h.toFixed());return g.reverse().join(":")}}var d=pv.Format.pad;c.format=c;c.parse=function(f){switch(b){case "short":for(var g=/([0-9,.]+)\s*([a-z]+)/g,h,i=0;h=g.exec(f);){var j=parseFloat(h[0].replace(",","")),l=0;switch(h[2].toLowerCase()){case "year":case "years":l=31536E6;break;case "week":case "weeks":l=6048E5;break;case "day":case "days":l=864E5;break;case "hour":case "hours":l=36E5;break;case "minute":case "minutes":l=
6812+6E4;break;case "second":case "seconds":l=1E3;break}i+=j*l}return i;case "long":h=f.replace(",","").split(":").reverse();i=0;if(h.length)i+=parseFloat(h[0])*1E3;if(h.length>1)i+=parseFloat(h[1])*6E4;if(h.length>2)i+=parseFloat(h[2])*36E5;if(h.length>3)i+=parseFloat(h[3])*864E5;return i}};return c};
6813+pv.Format.number=function(){function b(n){if(Infinity>h)n=Math.round(n*i)/i;var m=String(Math.abs(n)).split("."),r=m[0];n=n<0?"-":"";if(r.length>d)r=r.substring(r.length-d);if(k&&r.length<c)r=n+(new Array(c-r.length+1)).join(j)+r;if(r.length>3)r=r.replace(/\B(?=(?:\d{3})+(?!\d))/g,o);if(!k&&r.length<f)r=(new Array(f-r.length+1)).join(j)+n+r;m[0]=r;r=m[1]||"";if(r.length<g)m[1]=r+(new Array(g-r.length+1)).join(l);return m.join(q)}var c=0,d=Infinity,f=0,g=0,h=0,i=1,j="0",l="0",k=true,q=".",o=",";b.format=
6814+b;b.parse=function(n){var m=pv.Format.re;n=String(n).replace(new RegExp("^("+m(j)+")*"),"").replace(new RegExp("("+m(l)+")*$"),"").split(q);m=n[0].replace(new RegExp(m(o),"g"),"");if(m.length>d)m=m.substring(m.length-d);n=n[1]?Number("0."+n[1]):0;if(Infinity>h)n=Math.round(n*i)/i;return Math.round(m)+n};b.integerDigits=function(n,m){if(arguments.length){c=Number(n);d=arguments.length>1?Number(m):c;f=c+Math.floor(c/3)*o.length;return this}return[c,d]};b.fractionDigits=function(n,m){if(arguments.length){g=
6815+Number(n);h=arguments.length>1?Number(m):g;i=Math.pow(10,h);return this}return[g,h]};b.integerPad=function(n){if(arguments.length){j=String(n);k=/\d/.test(j);return this}return j};b.fractionPad=function(n){if(arguments.length){l=String(n);return this}return l};b.decimal=function(n){if(arguments.length){q=String(n);return this}return q};b.group=function(n){if(arguments.length){o=n?String(n):"";f=c+Math.floor(c/3)*o.length;return this}return o};return b};
6816+pv.map=function(b,c){var d={};return c?b.map(function(f,g){d.index=g;return c.call(d,f)}):b.slice()};pv.repeat=function(b,c){if(arguments.length==1)c=2;return pv.blend(pv.range(c).map(function(){return b}))};pv.cross=function(b,c){for(var d=[],f=0,g=b.length,h=c.length;f<g;f++)for(var i=0,j=b[f];i<h;i++)d.push([j,c[i]]);return d};pv.blend=function(b){return Array.prototype.concat.apply([],b)};
6817+pv.transpose=function(b){var c=b.length,d=pv.max(b,function(i){return i.length});if(d>c){b.length=d;for(var f=c;f<d;f++)b[f]=new Array(c);for(f=0;f<c;f++)for(var g=f+1;g<d;g++){var h=b[f][g];b[f][g]=b[g][f];b[g][f]=h}}else{for(f=0;f<d;f++)b[f].length=c;for(f=0;f<c;f++)for(g=0;g<f;g++){h=b[f][g];b[f][g]=b[g][f];b[g][f]=h}}b.length=d;for(f=0;f<d;f++)b[f].length=c;return b};pv.normalize=function(b,c){b=pv.map(b,c);c=pv.sum(b);for(var d=0;d<b.length;d++)b[d]/=c;return b};
6818+pv.permute=function(b,c,d){if(!d)d=pv.identity;var f=new Array(c.length),g={};c.forEach(function(h,i){g.index=h;f[i]=d.call(g,b[h])});return f};pv.numerate=function(b,c){if(!c)c=pv.identity;var d={},f={};b.forEach(function(g,h){f.index=h;d[c.call(f,g)]=h});return d};pv.uniq=function(b,c){if(!c)c=pv.identity;var d={},f=[],g={},h;b.forEach(function(i,j){g.index=j;h=c.call(g,i);h in d||(d[h]=f.push(h))});return f};pv.naturalOrder=function(b,c){return b<c?-1:b>c?1:0};
6819+pv.reverseOrder=function(b,c){return c<b?-1:c>b?1:0};pv.search=function(b,c,d){if(!d)d=pv.identity;for(var f=0,g=b.length-1;f<=g;){var h=f+g>>1,i=d(b[h]);if(i<c)f=h+1;else if(i>c)g=h-1;else return h}return-f-1};pv.search.index=function(b,c,d){b=pv.search(b,c,d);return b<0?-b-1:b};
6820+pv.range=function(b,c,d){if(arguments.length==1){c=b;b=0}if(d==undefined)d=1;if((c-b)/d==Infinity)throw new Error("range must be finite");var f=[],g=0,h;if(d<0)for(;(h=b+d*g++)>c;)f.push(h);else for(;(h=b+d*g++)<c;)f.push(h);return f};pv.random=function(b,c,d){if(arguments.length==1){c=b;b=0}if(d==undefined)d=1;return d?Math.floor(Math.random()*(c-b)/d)*d+b:Math.random()*(c-b)+b};
6821+pv.sum=function(b,c){var d={};return b.reduce(c?function(f,g,h){d.index=h;return f+c.call(d,g)}:function(f,g){return f+g},0)};pv.max=function(b,c){if(c==pv.index)return b.length-1;return Math.max.apply(null,c?pv.map(b,c):b)};pv.max.index=function(b,c){if(!b.length)return-1;if(c==pv.index)return b.length-1;if(!c)c=pv.identity;for(var d=0,f=-Infinity,g={},h=0;h<b.length;h++){g.index=h;var i=c.call(g,b[h]);if(i>f){f=i;d=h}}return d};
6822+pv.min=function(b,c){if(c==pv.index)return 0;return Math.min.apply(null,c?pv.map(b,c):b)};pv.min.index=function(b,c){if(!b.length)return-1;if(c==pv.index)return 0;if(!c)c=pv.identity;for(var d=0,f=Infinity,g={},h=0;h<b.length;h++){g.index=h;var i=c.call(g,b[h]);if(i<f){f=i;d=h}}return d};pv.mean=function(b,c){return pv.sum(b,c)/b.length};
6823+pv.median=function(b,c){if(c==pv.index)return(b.length-1)/2;b=pv.map(b,c).sort(pv.naturalOrder);if(b.length%2)return b[Math.floor(b.length/2)];c=b.length/2;return(b[c-1]+b[c])/2};pv.variance=function(b,c){if(b.length<1)return NaN;if(b.length==1)return 0;var d=pv.mean(b,c),f=0,g={};if(!c)c=pv.identity;for(var h=0;h<b.length;h++){g.index=h;var i=c.call(g,b[h])-d;f+=i*i}return f};pv.deviation=function(b,c){return Math.sqrt(pv.variance(b,c)/(b.length-1))};pv.log=function(b,c){return Math.log(b)/Math.log(c)};
6824+pv.logSymmetric=function(b,c){return b==0?0:b<0?-pv.log(-b,c):pv.log(b,c)};pv.logAdjusted=function(b,c){if(!isFinite(b))return b;var d=b<0;if(b<c)b+=(c-b)/c;return d?-pv.log(b,c):pv.log(b,c)};pv.logFloor=function(b,c){return b>0?Math.pow(c,Math.floor(pv.log(b,c))):-Math.pow(c,-Math.floor(-pv.log(-b,c)))};pv.logCeil=function(b,c){return b>0?Math.pow(c,Math.ceil(pv.log(b,c))):-Math.pow(c,-Math.ceil(-pv.log(-b,c)))};
6825+(function(){var b=Math.PI/180,c=180/Math.PI;pv.radians=function(d){return b*d};pv.degrees=function(d){return c*d}})();pv.keys=function(b){var c=[];for(var d in b)c.push(d);return c};pv.entries=function(b){var c=[];for(var d in b)c.push({key:d,value:b[d]});return c};pv.values=function(b){var c=[];for(var d in b)c.push(b[d]);return c};pv.dict=function(b,c){for(var d={},f={},g=0;g<b.length;g++)if(g in b){var h=b[g];f.index=g;d[h]=c.call(f,h)}return d};pv.dom=function(b){return new pv.Dom(b)};
6826+pv.Dom=function(b){this.$map=b};pv.Dom.prototype.$leaf=function(b){return typeof b!="object"};pv.Dom.prototype.leaf=function(b){if(arguments.length){this.$leaf=b;return this}return this.$leaf};pv.Dom.prototype.root=function(b){function c(g){var h=new pv.Dom.Node;for(var i in g){var j=g[i];h.appendChild(d(j)?new pv.Dom.Node(j):c(j)).nodeName=i}return h}var d=this.$leaf,f=c(this.$map);f.nodeName=b;return f};pv.Dom.prototype.nodes=function(){return this.root().nodes()};
6827+pv.Dom.Node=function(b){this.nodeValue=b;this.childNodes=[]};a=pv.Dom.Node.prototype;a.parentNode=null;a.firstChild=null;a.lastChild=null;a.previousSibling=null;a.nextSibling=null;
6828+a.removeChild=function(b){var c=this.childNodes.indexOf(b);if(c==-1)throw new Error("child not found");this.childNodes.splice(c,1);if(b.previousSibling)b.previousSibling.nextSibling=b.nextSibling;else this.firstChild=b.nextSibling;if(b.nextSibling)b.nextSibling.previousSibling=b.previousSibling;else this.lastChild=b.previousSibling;delete b.nextSibling;delete b.previousSibling;delete b.parentNode;return b};
6829+a.appendChild=function(b){b.parentNode&&b.parentNode.removeChild(b);b.parentNode=this;if(b.previousSibling=this.lastChild)this.lastChild.nextSibling=b;else this.firstChild=b;this.lastChild=b;this.childNodes.push(b);return b};
6830+a.insertBefore=function(b,c){if(!c)return this.appendChild(b);var d=this.childNodes.indexOf(c);if(d==-1)throw new Error("child not found");b.parentNode&&b.parentNode.removeChild(b);b.parentNode=this;b.nextSibling=c;if(b.previousSibling=c.previousSibling)c.previousSibling.nextSibling=b;else{if(c==this.lastChild)this.lastChild=b;this.firstChild=b}this.childNodes.splice(d,0,b);return b};
6831+a.replaceChild=function(b,c){var d=this.childNodes.indexOf(c);if(d==-1)throw new Error("child not found");b.parentNode&&b.parentNode.removeChild(b);b.parentNode=this;b.nextSibling=c.nextSibling;if(b.previousSibling=c.previousSibling)c.previousSibling.nextSibling=b;else this.firstChild=b;if(c.nextSibling)c.nextSibling.previousSibling=b;else this.lastChild=b;this.childNodes[d]=b;return c};a.visitBefore=function(b){function c(d,f){b(d,f);for(d=d.firstChild;d;d=d.nextSibling)c(d,f+1)}c(this,0)};
6832+a.visitAfter=function(b){function c(d,f){for(var g=d.firstChild;g;g=g.nextSibling)c(g,f+1);b(d,f)}c(this,0)};a.sort=function(b){if(this.firstChild){this.childNodes.sort(b);var c=this.firstChild=this.childNodes[0],d;delete c.previousSibling;for(var f=1;f<this.childNodes.length;f++){c.sort(b);d=this.childNodes[f];d.previousSibling=c;c=c.nextSibling=d}this.lastChild=c;delete c.nextSibling;c.sort(b)}return this};
6833+a.reverse=function(){var b=[];this.visitAfter(function(c){for(;c.lastChild;)b.push(c.removeChild(c.lastChild));for(var d;d=b.pop();)c.insertBefore(d,c.firstChild)});return this};a.nodes=function(){function b(d){c.push(d);d.childNodes.forEach(b)}var c=[];b(this,c);return c};
6834+a.toggle=function(b){if(b)return this.toggled?this.visitBefore(function(d){d.toggled&&d.toggle()}):this.visitAfter(function(d){d.toggled||d.toggle()});b=this;if(b.toggled){for(var c;c=b.toggled.pop();)b.appendChild(c);delete b.toggled}else if(b.lastChild)for(b.toggled=[];b.lastChild;)b.toggled.push(b.removeChild(b.lastChild))};pv.nodes=function(b){for(var c=new pv.Dom.Node,d=0;d<b.length;d++)c.appendChild(new pv.Dom.Node(b[d]));return c.nodes()};pv.tree=function(b){return new pv.Tree(b)};
6835+pv.Tree=function(b){this.array=b};pv.Tree.prototype.keys=function(b){this.k=b;return this};pv.Tree.prototype.value=function(b){this.v=b;return this};pv.Tree.prototype.map=function(){for(var b={},c={},d=0;d<this.array.length;d++){c.index=d;for(var f=this.array[d],g=this.k.call(c,f),h=b,i=0;i<g.length-1;i++)h=h[g[i]]||(h[g[i]]={});h[g[i]]=this.v?this.v.call(c,f):f}return b};pv.nest=function(b){return new pv.Nest(b)};pv.Nest=function(b){this.array=b;this.keys=[]};a=pv.Nest.prototype;
6836+a.key=function(b){this.keys.push(b);return this};a.sortKeys=function(b){this.keys[this.keys.length-1].order=b||pv.naturalOrder;return this};a.sortValues=function(b){this.order=b||pv.naturalOrder;return this};a.map=function(){for(var b={},c=[],d,f=0;f<this.array.length;f++){var g=this.array[f],h=b;for(d=0;d<this.keys.length-1;d++){var i=this.keys[d](g);h[i]||(h[i]={});h=h[i]}i=this.keys[d](g);if(!h[i]){d=[];c.push(d);h[i]=d}h[i].push(g)}if(this.order)for(d=0;d<c.length;d++)c[d].sort(this.order);return b};
6837+a.entries=function(){function b(d){var f=[];for(var g in d){var h=d[g];f.push({key:g,values:h instanceof Array?h:b(h)})}return f}function c(d,f){var g=this.keys[f].order;g&&d.sort(function(i,j){return g(i.key,j.key)});if(++f<this.keys.length)for(var h=0;h<d.length;h++)c.call(this,d[h].values,f);return d}return c.call(this,b(this.map()),0)};a.rollup=function(b){function c(d){for(var f in d){var g=d[f];if(g instanceof Array)d[f]=b(g);else c(g)}return d}return c(this.map())};pv.flatten=function(b){return new pv.Flatten(b)};
6838+pv.Flatten=function(b){this.map=b;this.keys=[]};pv.Flatten.prototype.key=function(b,c){this.keys.push({name:b,value:c});delete this.$leaf;return this};pv.Flatten.prototype.leaf=function(b){this.keys.length=0;this.$leaf=b;return this};
6839+pv.Flatten.prototype.array=function(){function b(i,j){if(j<f.length-1)for(var l in i){d.push(l);b(i[l],j+1);d.pop()}else c.push(d.concat(i))}var c=[],d=[],f=this.keys,g=this.$leaf;if(g){function h(i,j){if(g(i))c.push({keys:d.slice(),value:i});else for(var l in i){d.push(l);h(i[l],j+1);d.pop()}}h(this.map,0);return c}b(this.map,0);return c.map(function(i){for(var j={},l=0;l<f.length;l++){var k=f[l],q=i[l];j[k.name]=k.value?k.value.call(null,q):q}return j})};
6840+pv.vector=function(b,c){return new pv.Vector(b,c)};pv.Vector=function(b,c){this.x=b;this.y=c};a=pv.Vector.prototype;a.perp=function(){return new pv.Vector(-this.y,this.x)};a.norm=function(){var b=this.length();return this.times(b?1/b:1)};a.length=function(){return Math.sqrt(this.x*this.x+this.y*this.y)};a.times=function(b){return new pv.Vector(this.x*b,this.y*b)};a.plus=function(b,c){return arguments.length==1?new pv.Vector(this.x+b.x,this.y+b.y):new pv.Vector(this.x+b,this.y+c)};
6841+a.minus=function(b,c){return arguments.length==1?new pv.Vector(this.x-b.x,this.y-b.y):new pv.Vector(this.x-b,this.y-c)};a.dot=function(b,c){return arguments.length==1?this.x*b.x+this.y*b.y:this.x*b+this.y*c};pv.Transform=function(){};pv.Transform.prototype={k:1,x:0,y:0};pv.Transform.identity=new pv.Transform;pv.Transform.prototype.translate=function(b,c){var d=new pv.Transform;d.k=this.k;d.x=this.k*b+this.x;d.y=this.k*c+this.y;return d};
6842+pv.Transform.prototype.scale=function(b){var c=new pv.Transform;c.k=this.k*b;c.x=this.x;c.y=this.y;return c};pv.Transform.prototype.invert=function(){var b=new pv.Transform,c=1/this.k;b.k=c;b.x=-this.x*c;b.y=-this.y*c;return b};pv.Transform.prototype.times=function(b){var c=new pv.Transform;c.k=this.k*b.k;c.x=this.k*b.x+this.x;c.y=this.k*b.y+this.y;return c};pv.Scale=function(){};
6843+pv.Scale.interpolator=function(b,c){if(typeof b=="number")return function(d){return d*(c-b)+b};b=pv.color(b).rgb();c=pv.color(c).rgb();return function(d){var f=b.a*(1-d)+c.a*d;if(f<1.0E-5)f=0;return b.a==0?pv.rgb(c.r,c.g,c.b,f):c.a==0?pv.rgb(b.r,b.g,b.b,f):pv.rgb(Math.round(b.r*(1-d)+c.r*d),Math.round(b.g*(1-d)+c.g*d),Math.round(b.b*(1-d)+c.b*d),f)}};
6844+pv.Scale.quantitative=function(){function b(o){return new Date(o)}function c(o){var n=pv.search(d,o);if(n<0)n=-n-2;n=Math.max(0,Math.min(h.length-1,n));return h[n]((l(o)-f[n])/(f[n+1]-f[n]))}var d=[0,1],f=[0,1],g=[0,1],h=[pv.identity],i=Number,j=false,l=pv.identity,k=pv.identity,q=String;c.transform=function(o,n){l=function(m){return j?-o(-m):o(m)};k=function(m){return j?-n(-m):n(m)};f=d.map(l);return this};c.domain=function(o,n,m){if(arguments.length){var r;if(o instanceof Array){if(arguments.length<
6845+2)n=pv.identity;if(arguments.length<3)m=n;r=o.length&&n(o[0]);d=o.length?[pv.min(o,n),pv.max(o,m)]:[]}else{r=o;d=Array.prototype.slice.call(arguments).map(Number)}if(d.length){if(d.length==1)d=[d[0],d[0]]}else d=[-Infinity,Infinity];j=(d[0]||d[d.length-1])<0;f=d.map(l);i=r instanceof Date?b:Number;return this}return d.map(i)};c.range=function(){if(arguments.length){g=Array.prototype.slice.call(arguments);if(g.length){if(g.length==1)g=[g[0],g[0]]}else g=[-Infinity,Infinity];h=[];for(var o=0;o<g.length-
6846+1;o++)h.push(pv.Scale.interpolator(g[o],g[o+1]));return this}return g};c.invert=function(o){var n=pv.search(g,o);if(n<0)n=-n-2;n=Math.max(0,Math.min(h.length-1,n));return i(k(f[n]+(o-g[n])/(g[n+1]-g[n])*(f[n+1]-f[n])))};c.ticks=function(o){var n=d[0],m=d[d.length-1],r=m<n,s=r?m:n;m=r?n:m;var u=m-s;if(!u||!isFinite(u)){if(i==b)q=pv.Format.date("%x");return[i(s)]}if(i==b){function x(w,y){switch(y){case 31536E6:w.setMonth(0);case 2592E6:w.setDate(1);case 6048E5:y==6048E5&&w.setDate(w.getDate()-w.getDay());
6847+case 864E5:w.setHours(0);case 36E5:w.setMinutes(0);case 6E4:w.setSeconds(0);case 1E3:w.setMilliseconds(0)}}var t,p,v=1;if(u>=94608E6){n=31536E6;t="%Y";p=function(w){w.setFullYear(w.getFullYear()+v)}}else if(u>=7776E6){n=2592E6;t="%m/%Y";p=function(w){w.setMonth(w.getMonth()+v)}}else if(u>=18144E5){n=6048E5;t="%m/%d";p=function(w){w.setDate(w.getDate()+7*v)}}else if(u>=2592E5){n=864E5;t="%m/%d";p=function(w){w.setDate(w.getDate()+v)}}else if(u>=108E5){n=36E5;t="%I:%M %p";p=function(w){w.setHours(w.getHours()+
6848+v)}}else if(u>=18E4){n=6E4;t="%I:%M %p";p=function(w){w.setMinutes(w.getMinutes()+v)}}else if(u>=3E3){n=1E3;t="%I:%M:%S";p=function(w){w.setSeconds(w.getSeconds()+v)}}else{n=1;t="%S.%Qs";p=function(w){w.setTime(w.getTime()+v)}}q=pv.Format.date(t);s=new Date(s);t=[];x(s,n);u=u/n;if(u>10)switch(n){case 36E5:v=u>20?6:3;s.setHours(Math.floor(s.getHours()/v)*v);break;case 2592E6:v=3;s.setMonth(Math.floor(s.getMonth()/v)*v);break;case 6E4:v=u>30?15:u>15?10:5;s.setMinutes(Math.floor(s.getMinutes()/v)*v);
6849+break;case 1E3:v=u>90?15:u>60?10:5;s.setSeconds(Math.floor(s.getSeconds()/v)*v);break;case 1:v=u>1E3?250:u>200?100:u>100?50:u>50?25:5;s.setMilliseconds(Math.floor(s.getMilliseconds()/v)*v);break;default:v=pv.logCeil(u/15,10);if(u/v<2)v/=5;else if(u/v<5)v/=2;s.setFullYear(Math.floor(s.getFullYear()/v)*v);break}for(;;){p(s);if(s>m)break;t.push(new Date(s))}return r?t.reverse():t}arguments.length||(o=10);v=pv.logFloor(u/o,10);n=o/(u/v);if(n<=0.15)v*=10;else if(n<=0.35)v*=5;else if(n<=0.75)v*=2;n=Math.ceil(s/
6850+v)*v;m=Math.floor(m/v)*v;q=pv.Format.number().fractionDigits(Math.max(0,-Math.floor(pv.log(v,10)+0.01)));m=pv.range(n,m+v,v);return r?m.reverse():m};c.tickFormat=function(o){return q(o)};c.nice=function(){if(d.length!=2)return this;var o=d[0],n=d[d.length-1],m=n<o,r=m?n:o;o=m?o:n;n=o-r;if(!n||!isFinite(n))return this;n=Math.pow(10,Math.round(Math.log(n)/Math.log(10))-1);d=[Math.floor(r/n)*n,Math.ceil(o/n)*n];m&&d.reverse();f=d.map(l);return this};c.by=function(o){function n(){return c(o.apply(this,
6851+arguments))}for(var m in c)n[m]=c[m];return n};c.domain.apply(c,arguments);return c};pv.Scale.linear=function(){var b=pv.Scale.quantitative();b.domain.apply(b,arguments);return b};
6852+pv.Scale.log=function(){var b=pv.Scale.quantitative(1,10),c,d,f=function(h){return Math.log(h)/d},g=function(h){return Math.pow(c,h)};b.ticks=function(){var h=b.domain(),i=h[0]<0,j=Math.floor(i?-f(-h[0]):f(h[0])),l=Math.ceil(i?-f(-h[1]):f(h[1])),k=[];if(i)for(k.push(-g(-j));j++<l;)for(i=c-1;i>0;i--)k.push(-g(-j)*i);else{for(;j<l;j++)for(i=1;i<c;i++)k.push(g(j)*i);k.push(g(j))}for(j=0;k[j]<h[0];j++);for(l=k.length;k[l-1]>h[1];l--);return k.slice(j,l)};b.tickFormat=function(h){return h.toPrecision(1)};
6853+b.nice=function(){var h=b.domain();return b.domain(pv.logFloor(h[0],c),pv.logCeil(h[1],c))};b.base=function(h){if(arguments.length){c=Number(h);d=Math.log(c);b.transform(f,g);return this}return c};b.domain.apply(b,arguments);return b.base(10)};pv.Scale.root=function(){var b=pv.Scale.quantitative();b.power=function(c){if(arguments.length){var d=Number(c),f=1/d;b.transform(function(g){return Math.pow(g,f)},function(g){return Math.pow(g,d)});return this}return d};b.domain.apply(b,arguments);return b.power(2)};
6854+pv.Scale.ordinal=function(){function b(g){g in d||(d[g]=c.push(g)-1);return f[d[g]%f.length]}var c=[],d={},f=[];b.domain=function(g,h){if(arguments.length){g=g instanceof Array?arguments.length>1?pv.map(g,h):g:Array.prototype.slice.call(arguments);c=[];for(var i={},j=0;j<g.length;j++){var l=g[j];if(!(l in i)){i[l]=true;c.push(l)}}d=pv.numerate(c);return this}return c};b.range=function(g,h){if(arguments.length){f=g instanceof Array?arguments.length>1?pv.map(g,h):g:Array.prototype.slice.call(arguments);
6855+if(typeof f[0]=="string")f=f.map(pv.color);return this}return f};b.split=function(g,h){var i=(h-g)/this.domain().length;f=pv.range(g+i/2,h,i);return this};b.splitFlush=function(g,h){var i=this.domain().length,j=(h-g)/(i-1);f=i==1?[(g+h)/2]:pv.range(g,h+j/2,j);return this};b.splitBanded=function(g,h,i){if(arguments.length<3)i=1;if(i<0){var j=this.domain().length;j=(h-g- -i*j)/(j+1);f=pv.range(g+j,h,j-i);f.band=-i}else{j=(h-g)/(this.domain().length+(1-i));f=pv.range(g+j*(1-i),h,j);f.band=j*i}return this};
6856+b.by=function(g){function h(){return b(g.apply(this,arguments))}for(var i in b)h[i]=b[i];return h};b.domain.apply(b,arguments);return b};
6857+pv.Scale.quantile=function(){function b(i){return h(Math.max(0,Math.min(d,pv.search.index(f,i)-1))/d)}var c=-1,d=-1,f=[],g=[],h=pv.Scale.linear();b.quantiles=function(i){if(arguments.length){c=Number(i);if(c<0){f=[g[0]].concat(g);d=g.length-1}else{f=[];f[0]=g[0];for(var j=1;j<=c;j++)f[j]=g[~~(j*(g.length-1)/c)];d=c-1}return this}return f};b.domain=function(i,j){if(arguments.length){g=i instanceof Array?pv.map(i,j):Array.prototype.slice.call(arguments);g.sort(pv.naturalOrder);b.quantiles(c);return this}return g};
6858+b.range=function(){if(arguments.length){h.range.apply(h,arguments);return this}return h.range()};b.by=function(i){function j(){return b(i.apply(this,arguments))}for(var l in b)j[l]=b[l];return j};b.domain.apply(b,arguments);return b};
6859+pv.histogram=function(b,c){var d=true;return{bins:function(f){var g=pv.map(b,c),h=[];arguments.length||(f=pv.Scale.linear(g).ticks());for(var i=0;i<f.length-1;i++){var j=h[i]=[];j.x=f[i];j.dx=f[i+1]-f[i];j.y=0}for(i=0;i<g.length;i++){j=pv.search.index(f,g[i])-1;j=h[Math.max(0,Math.min(h.length-1,j))];j.y++;j.push(b[i])}if(!d)for(i=0;i<h.length;i++)h[i].y/=g.length;return h},frequency:function(f){if(arguments.length){d=Boolean(f);return this}return d}}};
6860+pv.color=function(b){if(b.rgb)return b.rgb();var c=/([a-z]+)\((.*)\)/i.exec(b);if(c){var d=c[2].split(","),f=1;switch(c[1]){case "hsla":case "rgba":f=parseFloat(d[3]);if(!f)return pv.Color.transparent;break}switch(c[1]){case "hsla":case "hsl":b=parseFloat(d[0]);var g=parseFloat(d[1])/100;d=parseFloat(d[2])/100;return(new pv.Color.Hsl(b,g,d,f)).rgb();case "rgba":case "rgb":function h(l){var k=parseFloat(l);return l[l.length-1]=="%"?Math.round(k*2.55):k}g=h(d[0]);var i=h(d[1]),j=h(d[2]);return pv.rgb(g,
6861+i,j,f)}}if(f=pv.Color.names[b])return f;if(b.charAt(0)=="#"){if(b.length==4){g=b.charAt(1);g+=g;i=b.charAt(2);i+=i;j=b.charAt(3);j+=j}else if(b.length==7){g=b.substring(1,3);i=b.substring(3,5);j=b.substring(5,7)}return pv.rgb(parseInt(g,16),parseInt(i,16),parseInt(j,16),1)}return new pv.Color(b,1)};pv.Color=function(b,c){this.color=b;this.opacity=c};pv.Color.prototype.brighter=function(b){return this.rgb().brighter(b)};pv.Color.prototype.darker=function(b){return this.rgb().darker(b)};
6862+pv.rgb=function(b,c,d,f){return new pv.Color.Rgb(b,c,d,arguments.length==4?f:1)};pv.Color.Rgb=function(b,c,d,f){pv.Color.call(this,f?"rgb("+b+","+c+","+d+")":"none",f);this.r=b;this.g=c;this.b=d;this.a=f};pv.Color.Rgb.prototype=pv.extend(pv.Color);a=pv.Color.Rgb.prototype;a.red=function(b){return pv.rgb(b,this.g,this.b,this.a)};a.green=function(b){return pv.rgb(this.r,b,this.b,this.a)};a.blue=function(b){return pv.rgb(this.r,this.g,b,this.a)};
6863+a.alpha=function(b){return pv.rgb(this.r,this.g,this.b,b)};a.rgb=function(){return this};a.brighter=function(b){b=Math.pow(0.7,arguments.length?b:1);var c=this.r,d=this.g,f=this.b;if(!c&&!d&&!f)return pv.rgb(30,30,30,this.a);if(c&&c<30)c=30;if(d&&d<30)d=30;if(f&&f<30)f=30;return pv.rgb(Math.min(255,Math.floor(c/b)),Math.min(255,Math.floor(d/b)),Math.min(255,Math.floor(f/b)),this.a)};
6864+a.darker=function(b){b=Math.pow(0.7,arguments.length?b:1);return pv.rgb(Math.max(0,Math.floor(b*this.r)),Math.max(0,Math.floor(b*this.g)),Math.max(0,Math.floor(b*this.b)),this.a)};pv.hsl=function(b,c,d,f){return new pv.Color.Hsl(b,c,d,arguments.length==4?f:1)};pv.Color.Hsl=function(b,c,d,f){pv.Color.call(this,"hsl("+b+","+c*100+"%,"+d*100+"%)",f);this.h=b;this.s=c;this.l=d;this.a=f};pv.Color.Hsl.prototype=pv.extend(pv.Color);a=pv.Color.Hsl.prototype;
6865+a.hue=function(b){return pv.hsl(b,this.s,this.l,this.a)};a.saturation=function(b){return pv.hsl(this.h,b,this.l,this.a)};a.lightness=function(b){return pv.hsl(this.h,this.s,b,this.a)};a.alpha=function(b){return pv.hsl(this.h,this.s,this.l,b)};
6866+a.rgb=function(){function b(j){if(j>360)j-=360;else if(j<0)j+=360;if(j<60)return i+(h-i)*j/60;if(j<180)return h;if(j<240)return i+(h-i)*(240-j)/60;return i}function c(j){return Math.round(b(j)*255)}var d=this.h,f=this.s,g=this.l;d%=360;if(d<0)d+=360;f=Math.max(0,Math.min(f,1));g=Math.max(0,Math.min(g,1));var h=g<=0.5?g*(1+f):g+f-g*f,i=2*g-h;return pv.rgb(c(d+120),c(d),c(d-120),this.a)};
6867+pv.Color.names={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",
6868+darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",
6869+ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",
6870+lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",
6871+moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",
6872+seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32",transparent:pv.Color.transparent=pv.rgb(0,0,0,0)};(function(){var b=pv.Color.names;for(var c in b)b[c]=pv.color(b[c])})();
6873+pv.colors=function(){var b=pv.Scale.ordinal();b.range.apply(b,arguments);return b};pv.Colors={};pv.Colors.category10=function(){var b=pv.colors("#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd","#8c564b","#e377c2","#7f7f7f","#bcbd22","#17becf");b.domain.apply(b,arguments);return b};
6874+pv.Colors.category20=function(){var b=pv.colors("#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5");b.domain.apply(b,arguments);return b};
6875+pv.Colors.category19=function(){var b=pv.colors("#9c9ede","#7375b5","#4a5584","#cedb9c","#b5cf6b","#8ca252","#637939","#e7cb94","#e7ba52","#bd9e39","#8c6d31","#e7969c","#d6616b","#ad494a","#843c39","#de9ed6","#ce6dbd","#a55194","#7b4173");b.domain.apply(b,arguments);return b};pv.ramp=function(){var b=pv.Scale.linear();b.range.apply(b,arguments);return b};
6876+pv.Scene=pv.SvgScene={svg:"http://www.w3.org/2000/svg",xmlns:"http://www.w3.org/2000/xmlns",xlink:"http://www.w3.org/1999/xlink",xhtml:"http://www.w3.org/1999/xhtml",scale:1,events:["DOMMouseScroll","mousewheel","mousedown","mouseup","mouseover","mouseout","mousemove","click","dblclick"],implicit:{svg:{"shape-rendering":"auto","pointer-events":"painted",x:0,y:0,dy:0,"text-anchor":"start",transform:"translate(0,0)",fill:"none","fill-opacity":1,stroke:"none","stroke-opacity":1,"stroke-width":1.5,"stroke-linejoin":"miter"},
6877+css:{font:"10px sans-serif"}}};pv.SvgScene.updateAll=function(b){if(b.length&&b[0].reverse&&b.type!="line"&&b.type!="area"){for(var c=pv.extend(b),d=0,f=b.length-1;f>=0;d++,f--)c[d]=b[f];b=c}this.removeSiblings(this[b.type](b))};pv.SvgScene.create=function(b){return document.createElementNS(this.svg,b)};
6878+pv.SvgScene.expect=function(b,c,d,f){if(b){if(b.tagName=="a")b=b.firstChild;if(b.tagName!=c){c=this.create(c);b.parentNode.replaceChild(c,b);b=c}}else b=this.create(c);for(var g in d){c=d[g];if(c==this.implicit.svg[g])c=null;c==null?b.removeAttribute(g):b.setAttribute(g,c)}for(g in f){c=f[g];if(c==this.implicit.css[g])c=null;if(c==null)b.style.removeProperty(g);else b.style[g]=c}return b};
6879+pv.SvgScene.append=function(b,c,d){b.$scene={scenes:c,index:d};b=this.title(b,c[d]);b.parentNode||c.$g.appendChild(b);return b.nextSibling};pv.SvgScene.title=function(b,c){var d=b.parentNode;if(d&&d.tagName!="a")d=null;if(c.title){if(!d){d=this.create("a");b.parentNode&&b.parentNode.replaceChild(d,b);d.appendChild(b)}d.setAttributeNS(this.xlink,"title",c.title);return d}d&&d.parentNode.replaceChild(b,d);return b};
6880+pv.SvgScene.dispatch=pv.listener(function(b){var c=b.target.$scene;if(c){var d=b.type;switch(d){case "DOMMouseScroll":d="mousewheel";b.wheel=-480*b.detail;break;case "mousewheel":b.wheel=(window.opera?12:1)*b.wheelDelta;break}pv.Mark.dispatch(d,c.scenes,c.index)&&b.preventDefault()}});pv.SvgScene.removeSiblings=function(b){for(;b;){var c=b.nextSibling;b.parentNode.removeChild(b);b=c}};pv.SvgScene.undefined=function(){};
6881+pv.SvgScene.pathBasis=function(){function b(f,g,h,i,j){return{x:f[0]*g.left+f[1]*h.left+f[2]*i.left+f[3]*j.left,y:f[0]*g.top+f[1]*h.top+f[2]*i.top+f[3]*j.top}}var c=[[1/6,2/3,1/6,0],[0,2/3,1/3,0],[0,1/3,2/3,0],[0,1/6,2/3,1/6]],d=function(f,g,h,i){var j=b(c[1],f,g,h,i),l=b(c[2],f,g,h,i);f=b(c[3],f,g,h,i);return"C"+j.x+","+j.y+","+l.x+","+l.y+","+f.x+","+f.y};d.segment=function(f,g,h,i){var j=b(c[0],f,g,h,i),l=b(c[1],f,g,h,i),k=b(c[2],f,g,h,i);f=b(c[3],f,g,h,i);return"M"+j.x+","+j.y+"C"+l.x+","+l.y+
6882+","+k.x+","+k.y+","+f.x+","+f.y};return d}();pv.SvgScene.curveBasis=function(b){if(b.length<=2)return"";var c="",d=b[0],f=d,g=d,h=b[1];c+=this.pathBasis(d,f,g,h);for(var i=2;i<b.length;i++){d=f;f=g;g=h;h=b[i];c+=this.pathBasis(d,f,g,h)}c+=this.pathBasis(f,g,h,h);c+=this.pathBasis(g,h,h,h);return c};
6883+pv.SvgScene.curveBasisSegments=function(b){if(b.length<=2)return"";var c=[],d=b[0],f=d,g=d,h=b[1],i=this.pathBasis.segment(d,f,g,h);d=f;f=g;g=h;h=b[2];c.push(i+this.pathBasis(d,f,g,h));for(i=3;i<b.length;i++){d=f;f=g;g=h;h=b[i];c.push(this.pathBasis.segment(d,f,g,h))}c.push(this.pathBasis.segment(f,g,h,h)+this.pathBasis(g,h,h,h));return c};
6884+pv.SvgScene.curveHermite=function(b,c){if(c.length<1||b.length!=c.length&&b.length!=c.length+2)return"";var d=b.length!=c.length,f="",g=b[0],h=b[1],i=c[0],j=i,l=1;if(d){f+="Q"+(h.left-i.x*2/3)+","+(h.top-i.y*2/3)+","+h.left+","+h.top;g=b[1];l=2}if(c.length>1){j=c[1];h=b[l];l++;f+="C"+(g.left+i.x)+","+(g.top+i.y)+","+(h.left-j.x)+","+(h.top-j.y)+","+h.left+","+h.top;for(g=2;g<c.length;g++,l++){h=b[l];j=c[g];f+="S"+(h.left-j.x)+","+(h.top-j.y)+","+h.left+","+h.top}}if(d){b=b[l];f+="Q"+(h.left+j.x*2/
6885+3)+","+(h.top+j.y*2/3)+","+b.left+","+b.top}return f};
6886+pv.SvgScene.curveHermiteSegments=function(b,c){if(c.length<1||b.length!=c.length&&b.length!=c.length+2)return[];var d=b.length!=c.length,f=[],g=b[0],h=g,i=c[0],j=i,l=1;if(d){h=b[1];f.push("M"+g.left+","+g.top+"Q"+(h.left-j.x*2/3)+","+(h.top-j.y*2/3)+","+h.left+","+h.top);l=2}for(var k=1;k<c.length;k++,l++){g=h;i=j;h=b[l];j=c[k];f.push("M"+g.left+","+g.top+"C"+(g.left+i.x)+","+(g.top+i.y)+","+(h.left-j.x)+","+(h.top-j.y)+","+h.left+","+h.top)}if(d){b=b[l];f.push("M"+h.left+","+h.top+"Q"+(h.left+j.x*
6887+2/3)+","+(h.top+j.y*2/3)+","+b.left+","+b.top)}return f};pv.SvgScene.cardinalTangents=function(b,c){var d=[];c=(1-c)/2;for(var f=b[0],g=b[1],h=b[2],i=3;i<b.length;i++){d.push({x:c*(h.left-f.left),y:c*(h.top-f.top)});f=g;g=h;h=b[i]}d.push({x:c*(h.left-f.left),y:c*(h.top-f.top)});return d};pv.SvgScene.curveCardinal=function(b,c){if(b.length<=2)return"";return this.curveHermite(b,this.cardinalTangents(b,c))};
6888+pv.SvgScene.curveCardinalSegments=function(b,c){if(b.length<=2)return"";return this.curveHermiteSegments(b,this.cardinalTangents(b,c))};
6889+pv.SvgScene.monotoneTangents=function(b){var c=[],d=[],f=[],g=[],h=0;for(h=0;h<b.length-1;h++)d[h]=(b[h+1].top-b[h].top)/(b[h+1].left-b[h].left);f[0]=d[0];g[0]=b[1].left-b[0].left;for(h=1;h<b.length-1;h++){f[h]=(d[h-1]+d[h])/2;g[h]=(b[h+1].left-b[h-1].left)/2}f[h]=d[h-1];g[h]=b[h].left-b[h-1].left;for(h=0;h<b.length-1;h++)if(d[h]==0){f[h]=0;f[h+1]=0}for(h=0;h<b.length-1;h++)if(!(Math.abs(f[h])<1.0E-5||Math.abs(f[h+1])<1.0E-5)){var i=f[h]/d[h],j=f[h+1]/d[h],l=i*i+j*j;if(l>9){l=3/Math.sqrt(l);f[h]=
6890+l*i*d[h];f[h+1]=l*j*d[h]}}for(h=0;h<b.length;h++){d=1+f[h]*f[h];c.push({x:g[h]/3/d,y:f[h]*g[h]/3/d})}return c};pv.SvgScene.curveMonotone=function(b){if(b.length<=2)return"";return this.curveHermite(b,this.monotoneTangents(b))};pv.SvgScene.curveMonotoneSegments=function(b){if(b.length<=2)return"";return this.curveHermiteSegments(b,this.monotoneTangents(b))};
6891+pv.SvgScene.area=function(b){function c(o,n){for(var m=[],r=[],s=n;o<=s;o++,n--){var u=b[o],x=b[n];u=u.left+","+u.top;x=x.left+x.width+","+(x.top+x.height);if(o<s){var t=b[o+1],p=b[n-1];switch(g.interpolate){case "step-before":u+="V"+t.top;x+="H"+(p.left+p.width);break;case "step-after":u+="H"+t.left;x+="V"+(p.top+p.height);break}}m.push(u);r.push(x)}return m.concat(r).join("L")}function d(o,n){for(var m=[],r=[],s=n;o<=s;o++,n--){var u=b[n];m.push(b[o]);r.push({left:u.left+u.width,top:u.top+u.height})}if(g.interpolate==
6892+"basis"){o=pv.SvgScene.curveBasis(m);n=pv.SvgScene.curveBasis(r)}else if(g.interpolate=="cardinal"){o=pv.SvgScene.curveCardinal(m,g.tension);n=pv.SvgScene.curveCardinal(r,g.tension)}else{o=pv.SvgScene.curveMonotone(m);n=pv.SvgScene.curveMonotone(r)}return m[0].left+","+m[0].top+o+"L"+r[0].left+","+r[0].top+n}var f=b.$g.firstChild;if(!b.length)return f;var g=b[0];if(g.segmented)return this.areaSegment(b);if(!g.visible)return f;var h=g.fillStyle,i=g.strokeStyle;if(!h.opacity&&!i.opacity)return f;for(var j=
6893+[],l,k=0;k<b.length;k++){l=b[k];if(l.width||l.height){for(var q=k+1;q<b.length;q++){l=b[q];if(!l.width&&!l.height)break}k&&g.interpolate!="step-after"&&k--;q<b.length&&g.interpolate!="step-before"&&q++;j.push((q-k>2&&(g.interpolate=="basis"||g.interpolate=="cardinal"||g.interpolate=="monotone")?d:c)(k,q-1));k=q-1}}if(!j.length)return f;f=this.expect(f,"path",{"shape-rendering":g.antialias?null:"crispEdges","pointer-events":g.events,cursor:g.cursor,d:"M"+j.join("ZM")+"Z",fill:h.color,"fill-opacity":h.opacity||
6894+null,stroke:i.color,"stroke-opacity":i.opacity||null,"stroke-width":i.opacity?g.lineWidth/this.scale:null});return this.append(f,b,0)};
6895+pv.SvgScene.areaSegment=function(b){var c=b.$g.firstChild,d=b[0],f,g;if(d.interpolate=="basis"||d.interpolate=="cardinal"||d.interpolate=="monotone"){f=[];g=[];for(var h=0,i=b.length;h<i;h++){var j=b[i-h-1];f.push(b[h]);g.push({left:j.left+j.width,top:j.top+j.height})}if(d.interpolate=="basis"){f=this.curveBasisSegments(f);g=this.curveBasisSegments(g)}else if(d.interpolate=="cardinal"){f=this.curveCardinalSegments(f,d.tension);g=this.curveCardinalSegments(g,d.tension)}else{f=this.curveMonotoneSegments(f);
6896+g=this.curveMonotoneSegments(g)}}h=0;for(i=b.length-1;h<i;h++){d=b[h];var l=b[h+1];if(d.visible&&l.visible){var k=d.fillStyle,q=d.strokeStyle;if(k.opacity||q.opacity){if(f){j=f[h];l="L"+g[i-h-1].substr(1);j=j+l+"Z"}else{var o=d;j=l;switch(d.interpolate){case "step-before":o=l;break;case "step-after":j=d;break}j="M"+d.left+","+o.top+"L"+l.left+","+j.top+"L"+(l.left+l.width)+","+(j.top+j.height)+"L"+(d.left+d.width)+","+(o.top+o.height)+"Z"}c=this.expect(c,"path",{"shape-rendering":d.antialias?null:
6897+"crispEdges","pointer-events":d.events,cursor:d.cursor,d:j,fill:k.color,"fill-opacity":k.opacity||null,stroke:q.color,"stroke-opacity":q.opacity||null,"stroke-width":q.opacity?d.lineWidth/this.scale:null});c=this.append(c,b,h)}}}return c};
6898+pv.SvgScene.bar=function(b){for(var c=b.$g.firstChild,d=0;d<b.length;d++){var f=b[d];if(f.visible){var g=f.fillStyle,h=f.strokeStyle;if(g.opacity||h.opacity){c=this.expect(c,"rect",{"shape-rendering":f.antialias?null:"crispEdges","pointer-events":f.events,cursor:f.cursor,x:f.left,y:f.top,width:Math.max(1.0E-10,f.width),height:Math.max(1.0E-10,f.height),fill:g.color,"fill-opacity":g.opacity||null,stroke:h.color,"stroke-opacity":h.opacity||null,"stroke-width":h.opacity?f.lineWidth/this.scale:null});
6899+c=this.append(c,b,d)}}}return c};
6900+pv.SvgScene.dot=function(b){for(var c=b.$g.firstChild,d=0;d<b.length;d++){var f=b[d];if(f.visible){var g=f.fillStyle,h=f.strokeStyle;if(g.opacity||h.opacity){var i=f.radius,j=null;switch(f.shape){case "cross":j="M"+-i+","+-i+"L"+i+","+i+"M"+i+","+-i+"L"+-i+","+i;break;case "triangle":j=i;var l=i*1.1547;j="M0,"+j+"L"+l+","+-j+" "+-l+","+-j+"Z";break;case "diamond":i*=Math.SQRT2;j="M0,"+-i+"L"+i+",0 0,"+i+" "+-i+",0Z";break;case "square":j="M"+-i+","+-i+"L"+i+","+-i+" "+i+","+i+" "+-i+","+i+"Z";break;
6901+case "tick":j="M0,0L0,"+-f.size;break;case "bar":j="M0,"+f.size/2+"L0,"+-(f.size/2);break}g={"shape-rendering":f.antialias?null:"crispEdges","pointer-events":f.events,cursor:f.cursor,fill:g.color,"fill-opacity":g.opacity||null,stroke:h.color,"stroke-opacity":h.opacity||null,"stroke-width":h.opacity?f.lineWidth/this.scale:null};if(j){g.transform="translate("+f.left+","+f.top+")";if(f.angle)g.transform+=" rotate("+180*f.angle/Math.PI+")";g.d=j;c=this.expect(c,"path",g)}else{g.cx=f.left;g.cy=f.top;g.r=
6902+i;c=this.expect(c,"circle",g)}c=this.append(c,b,d)}}}return c};
6903+pv.SvgScene.image=function(b){for(var c=b.$g.firstChild,d=0;d<b.length;d++){var f=b[d];if(f.visible){c=this.fill(c,b,d);if(f.image){c=this.expect(c,"foreignObject",{cursor:f.cursor,x:f.left,y:f.top,width:f.width,height:f.height});var g=c.firstChild||c.appendChild(document.createElementNS(this.xhtml,"canvas"));g.$scene={scenes:b,index:d};g.style.width=f.width;g.style.height=f.height;g.width=f.imageWidth;g.height=f.imageHeight;g.getContext("2d").putImageData(f.image,0,0)}else{c=this.expect(c,"image",
6904+{preserveAspectRatio:"none",cursor:f.cursor,x:f.left,y:f.top,width:f.width,height:f.height});c.setAttributeNS(this.xlink,"href",f.url)}c=this.append(c,b,d);c=this.stroke(c,b,d)}}return c};
6905+pv.SvgScene.label=function(b){for(var c=b.$g.firstChild,d=0;d<b.length;d++){var f=b[d];if(f.visible){var g=f.textStyle;if(g.opacity&&f.text){var h=0,i=0,j=0,l="start";switch(f.textBaseline){case "middle":j=".35em";break;case "top":j=".71em";i=f.textMargin;break;case "bottom":i="-"+f.textMargin;break}switch(f.textAlign){case "right":l="end";h="-"+f.textMargin;break;case "center":l="middle";break;case "left":h=f.textMargin;break}c=this.expect(c,"text",{"pointer-events":f.events,cursor:f.cursor,x:h,
6906+y:i,dy:j,transform:"translate("+f.left+","+f.top+")"+(f.textAngle?" rotate("+180*f.textAngle/Math.PI+")":"")+(this.scale!=1?" scale("+1/this.scale+")":""),fill:g.color,"fill-opacity":g.opacity||null,"text-anchor":l},{font:f.font,"text-shadow":f.textShadow,"text-decoration":f.textDecoration});if(c.firstChild)c.firstChild.nodeValue=f.text;else c.appendChild(document.createTextNode(f.text));c=this.append(c,b,d)}}}return c};
6907+pv.SvgScene.line=function(b){var c=b.$g.firstChild;if(b.length<2)return c;var d=b[0];if(d.segmented)return this.lineSegment(b);if(!d.visible)return c;var f=d.fillStyle,g=d.strokeStyle;if(!f.opacity&&!g.opacity)return c;var h="M"+d.left+","+d.top;if(b.length>2&&(d.interpolate=="basis"||d.interpolate=="cardinal"||d.interpolate=="monotone"))switch(d.interpolate){case "basis":h+=this.curveBasis(b);break;case "cardinal":h+=this.curveCardinal(b,d.tension);break;case "monotone":h+=this.curveMonotone(b);
6908+break}else for(var i=1;i<b.length;i++)h+=this.pathSegment(b[i-1],b[i]);c=this.expect(c,"path",{"shape-rendering":d.antialias?null:"crispEdges","pointer-events":d.events,cursor:d.cursor,d:h,fill:f.color,"fill-opacity":f.opacity||null,stroke:g.color,"stroke-opacity":g.opacity||null,"stroke-width":g.opacity?d.lineWidth/this.scale:null,"stroke-linejoin":d.lineJoin});return this.append(c,b,0)};
6909+pv.SvgScene.lineSegment=function(b){var c=b.$g.firstChild,d=b[0],f;switch(d.interpolate){case "basis":f=this.curveBasisSegments(b);break;case "cardinal":f=this.curveCardinalSegments(b,d.tension);break;case "monotone":f=this.curveMonotoneSegments(b);break}d=0;for(var g=b.length-1;d<g;d++){var h=b[d],i=b[d+1];if(h.visible&&i.visible){var j=h.strokeStyle,l=pv.Color.transparent;if(j.opacity){if(h.interpolate=="linear"&&h.lineJoin=="miter"){l=j;j=pv.Color.transparent;i=this.pathJoin(b[d-1],h,i,b[d+2])}else i=
6910+f?f[d]:"M"+h.left+","+h.top+this.pathSegment(h,i);c=this.expect(c,"path",{"shape-rendering":h.antialias?null:"crispEdges","pointer-events":h.events,cursor:h.cursor,d:i,fill:l.color,"fill-opacity":l.opacity||null,stroke:j.color,"stroke-opacity":j.opacity||null,"stroke-width":j.opacity?h.lineWidth/this.scale:null,"stroke-linejoin":h.lineJoin});c=this.append(c,b,d)}}}return c};
6911+pv.SvgScene.pathSegment=function(b,c){var d=1;switch(b.interpolate){case "polar-reverse":d=0;case "polar":var f=c.left-b.left,g=c.top-b.top;b=1-b.eccentricity;f=Math.sqrt(f*f+g*g)/(2*b);if(b<=0||b>1)break;return"A"+f+","+f+" 0 0,"+d+" "+c.left+","+c.top;case "step-before":return"V"+c.top+"H"+c.left;case "step-after":return"H"+c.left+"V"+c.top}return"L"+c.left+","+c.top};pv.SvgScene.lineIntersect=function(b,c,d,f){return b.plus(c.times(d.minus(b).dot(f.perp())/c.dot(f.perp())))};
6912+pv.SvgScene.pathJoin=function(b,c,d,f){var g=pv.vector(c.left,c.top);d=pv.vector(d.left,d.top);var h=d.minus(g),i=h.perp().norm(),j=i.times(c.lineWidth/(2*this.scale));c=g.plus(j);var l=d.plus(j),k=d.minus(j);j=g.minus(j);if(b&&b.visible){b=g.minus(b.left,b.top).perp().norm().plus(i);j=this.lineIntersect(g,b,j,h);c=this.lineIntersect(g,b,c,h)}if(f&&f.visible){f=pv.vector(f.left,f.top).minus(d).perp().norm().plus(i);k=this.lineIntersect(d,f,k,h);l=this.lineIntersect(d,f,l,h)}return"M"+c.x+","+c.y+
6913+"L"+l.x+","+l.y+" "+k.x+","+k.y+" "+j.x+","+j.y};
6914+pv.SvgScene.panel=function(b){for(var c=b.$g,d=c&&c.firstChild,f=0;f<b.length;f++){var g=b[f];if(g.visible){if(!b.parent){g.canvas.style.display="inline-block";if(c&&c.parentNode!=g.canvas)d=(c=g.canvas.firstChild)&&c.firstChild;if(!c){c=g.canvas.appendChild(this.create("svg"));c.setAttribute("font-size","10px");c.setAttribute("font-family","sans-serif");c.setAttribute("fill","none");c.setAttribute("stroke","none");c.setAttribute("stroke-width",1.5);for(var h=0;h<this.events.length;h++)c.addEventListener(this.events[h],
6915+this.dispatch,false);d=c.firstChild}b.$g=c;c.setAttribute("width",g.width+g.left+g.right);c.setAttribute("height",g.height+g.top+g.bottom)}if(g.overflow=="hidden"){h=pv.id().toString(36);var i=this.expect(d,"g",{"clip-path":"url(#"+h+")"});i.parentNode||c.appendChild(i);b.$g=c=i;d=i.firstChild;d=this.expect(d,"clipPath",{id:h});h=d.firstChild||d.appendChild(this.create("rect"));h.setAttribute("x",g.left);h.setAttribute("y",g.top);h.setAttribute("width",g.width);h.setAttribute("height",g.height);d.parentNode||
6916+c.appendChild(d);d=d.nextSibling}d=this.fill(d,b,f);var j=this.scale,l=g.transform,k=g.left+l.x,q=g.top+l.y;this.scale*=l.k;for(h=0;h<g.children.length;h++){g.children[h].$g=d=this.expect(d,"g",{transform:"translate("+k+","+q+")"+(l.k!=1?" scale("+l.k+")":"")});this.updateAll(g.children[h]);d.parentNode||c.appendChild(d);d=d.nextSibling}this.scale=j;d=this.stroke(d,b,f);if(g.overflow=="hidden"){b.$g=c=i.parentNode;d=i.nextSibling}}}return d};
6917+pv.SvgScene.fill=function(b,c,d){var f=c[d],g=f.fillStyle;if(g.opacity||f.events=="all"){b=this.expect(b,"rect",{"shape-rendering":f.antialias?null:"crispEdges","pointer-events":f.events,cursor:f.cursor,x:f.left,y:f.top,width:f.width,height:f.height,fill:g.color,"fill-opacity":g.opacity,stroke:null});b=this.append(b,c,d)}return b};
6918+pv.SvgScene.stroke=function(b,c,d){var f=c[d],g=f.strokeStyle;if(g.opacity||f.events=="all"){b=this.expect(b,"rect",{"shape-rendering":f.antialias?null:"crispEdges","pointer-events":f.events=="all"?"stroke":f.events,cursor:f.cursor,x:f.left,y:f.top,width:Math.max(1.0E-10,f.width),height:Math.max(1.0E-10,f.height),fill:null,stroke:g.color,"stroke-opacity":g.opacity,"stroke-width":f.lineWidth/this.scale});b=this.append(b,c,d)}return b};
6919+pv.SvgScene.rule=function(b){for(var c=b.$g.firstChild,d=0;d<b.length;d++){var f=b[d];if(f.visible){var g=f.strokeStyle;if(g.opacity){c=this.expect(c,"line",{"shape-rendering":f.antialias?null:"crispEdges","pointer-events":f.events,cursor:f.cursor,x1:f.left,y1:f.top,x2:f.left+f.width,y2:f.top+f.height,stroke:g.color,"stroke-opacity":g.opacity,"stroke-width":f.lineWidth/this.scale});c=this.append(c,b,d)}}}return c};
6920+pv.SvgScene.wedge=function(b){for(var c=b.$g.firstChild,d=0;d<b.length;d++){var f=b[d];if(f.visible){var g=f.fillStyle,h=f.strokeStyle;if(g.opacity||h.opacity){var i=f.innerRadius,j=f.outerRadius,l=Math.abs(f.angle);if(l>=2*Math.PI)i=i?"M0,"+j+"A"+j+","+j+" 0 1,1 0,"+-j+"A"+j+","+j+" 0 1,1 0,"+j+"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"Z":"M0,"+j+"A"+j+","+j+" 0 1,1 0,"+-j+"A"+j+","+j+" 0 1,1 0,"+j+"Z";else{var k=Math.min(f.startAngle,f.endAngle),q=Math.max(f.startAngle,f.endAngle),
6921+o=Math.cos(k),n=Math.cos(q);k=Math.sin(k);q=Math.sin(q);i=i?"M"+j*o+","+j*k+"A"+j+","+j+" 0 "+(l<Math.PI?"0":"1")+",1 "+j*n+","+j*q+"L"+i*n+","+i*q+"A"+i+","+i+" 0 "+(l<Math.PI?"0":"1")+",0 "+i*o+","+i*k+"Z":"M"+j*o+","+j*k+"A"+j+","+j+" 0 "+(l<Math.PI?"0":"1")+",1 "+j*n+","+j*q+"L0,0Z"}c=this.expect(c,"path",{"shape-rendering":f.antialias?null:"crispEdges","pointer-events":f.events,cursor:f.cursor,transform:"translate("+f.left+","+f.top+")",d:i,fill:g.color,"fill-rule":"evenodd","fill-opacity":g.opacity||
6922+null,stroke:h.color,"stroke-opacity":h.opacity||null,"stroke-width":h.opacity?f.lineWidth/this.scale:null});c=this.append(c,b,d)}}}return c};pv.Mark=function(){this.$properties=[];this.$handlers={}};pv.Mark.prototype.properties={};pv.Mark.cast={};pv.Mark.prototype.property=function(b,c){if(!this.hasOwnProperty("properties"))this.properties=pv.extend(this.properties);this.properties[b]=true;pv.Mark.prototype.propertyMethod(b,false,pv.Mark.cast[b]=c);return this};
6923+pv.Mark.prototype.propertyMethod=function(b,c,d){d||(d=pv.Mark.cast[b]);this[b]=function(f){if(c&&this.scene){var g=this.scene.defs;if(arguments.length){g[b]={id:f==null?0:pv.id(),value:f!=null&&d?d(f):f};return this}return g[b]?g[b].value:null}if(arguments.length){g=!c<<1|typeof f=="function";this.propertyValue(b,g&1&&d?function(){var h=f.apply(this,arguments);return h!=null?d(h):null}:f!=null&&d?d(f):f).type=g;return this}return this.instance()[b]}};
6924+pv.Mark.prototype.propertyValue=function(b,c){var d=this.$properties;c={name:b,id:pv.id(),value:c};for(var f=0;f<d.length;f++)if(d[f].name==b){d.splice(f,1);break}d.push(c);return c};pv.Mark.prototype.property("data").property("visible",Boolean).property("left",Number).property("right",Number).property("top",Number).property("bottom",Number).property("cursor",String).property("title",String).property("reverse",Boolean).property("antialias",Boolean).property("events",String);a=pv.Mark.prototype;
6925+a.childIndex=-1;a.index=-1;a.scale=1;a.defaults=(new pv.Mark).data(function(b){return[b]}).visible(true).antialias(true).events("painted");a.extend=function(b){this.proto=b;return this};a.add=function(b){return this.parent.add(b).extend(this)};a.def=function(b,c){this.propertyMethod(b,true);return this[b](arguments.length>1?c:null)};
6926+a.anchor=function(b){function c(g){for(var h=d,i=[];!(f=h.scene);){g=g.parent;i.push({index:g.index,childIndex:h.childIndex});h=h.parent}for(;i.length;){g=i.pop();f=f[g.index].children[g.childIndex]}if(d.hasOwnProperty("index")){i=pv.extend(f[d.index]);i.right=i.top=i.left=i.bottom=0;return[i]}return f}var d=this,f;b||(b="center");return(new pv.Anchor(this)).name(b).def("$mark.anchor",function(){f=this.scene.target=c(this)}).data(function(){return f.map(function(g){return g.data})}).visible(function(){return f[this.index].visible}).left(function(){var g=
6927+f[this.index],h=g.width||0;switch(this.name()){case "bottom":case "top":case "center":return g.left+h/2;case "left":return null}return g.left+h}).top(function(){var g=f[this.index],h=g.height||0;switch(this.name()){case "left":case "right":case "center":return g.top+h/2;case "top":return null}return g.top+h}).right(function(){var g=f[this.index];return this.name()=="left"?g.right+(g.width||0):null}).bottom(function(){var g=f[this.index];return this.name()=="top"?g.bottom+(g.height||0):null}).textAlign(function(){switch(this.name()){case "bottom":case "top":case "center":return"center";
6928+case "right":return"right"}return"left"}).textBaseline(function(){switch(this.name()){case "right":case "left":case "center":return"middle";case "top":return"top"}return"bottom"})};a.anchorTarget=function(){return this.proto.anchorTarget()};a.margin=function(b){return this.left(b).right(b).top(b).bottom(b)};a.instance=function(b){var c=this.scene||this.parent.instance(-1).children[this.childIndex],d=!arguments.length||this.hasOwnProperty("index")?this.index:b;return c[d<0?c.length-1:d]};a.first=function(){return this.scene[0]};
6929+a.last=function(){return this.scene[this.scene.length-1]};a.sibling=function(){return this.index==0?null:this.scene[this.index-1]};a.cousin=function(){var b=this.parent;return(b=b&&b.sibling())&&b.children?b.children[this.childIndex][this.index]:null};
6930+a.render=function(){function b(i,j,l){i.scale=l;if(j<g.length){f.unshift(null);if(i.hasOwnProperty("index"))c(i,j,l);else{for(var k=0,q=i.scene.length;k<q;k++){i.index=k;c(i,j,l)}delete i.index}f.shift()}else{i.build();pv.Scene.scale=l;pv.Scene.updateAll(i.scene)}delete i.scale}function c(i,j,l){var k=i.scene[i.index],q;if(k.visible){var o=g[j],n=i.children[o];for(q=0;q<o;q++)i.children[q].scene=k.children[q];f[0]=k.data;if(n.scene)b(n,j+1,l*k.transform.k);else{n.scene=k.children[o];b(n,j+1,l*k.transform.k);
6931+delete n.scene}for(q=0;q<o;q++)delete i.children[q].scene}}var d=this.parent,f=pv.Mark.stack;if(d&&!this.root.scene)this.root.render();else{for(var g=[],h=this;h.parent;h=h.parent)g.unshift(h.childIndex);for(this.bind();d&&!d.hasOwnProperty("index");)d=d.parent;this.context(d?d.scene:undefined,d?d.index:-1,function(){b(this.root,0,1)})}};pv.Mark.stack=[];a=pv.Mark.prototype;
6932+a.bind=function(){function b(j){do for(var l=j.$properties,k=l.length-1;k>=0;k--){var q=l[k];if(!(q.name in c)){c[q.name]=q;switch(q.name){case "data":f=q;break;case "visible":g=q;break;default:d[q.type].push(q);break}}}while(j=j.proto)}var c={},d=[[],[],[],[]],f,g;b(this);b(this.defaults);d[1].reverse();d[3].reverse();var h=this;do for(var i in h.properties)i in c||d[2].push(c[i]={name:i,type:2,value:null});while(h=h.proto);h=d[0].concat(d[1]);for(i=0;i<h.length;i++)this.propertyMethod(h[i].name,
6933+true);this.binds={properties:c,data:f,defs:h,required:[g],optional:pv.blend(d)}};
6934+a.build=function(){var b=this.scene,c=pv.Mark.stack;if(!b){b=this.scene=[];b.mark=this;b.type=this.type;b.childIndex=this.childIndex;if(this.parent){b.parent=this.parent.scene;b.parentIndex=this.parent.index}}if(this.binds.defs.length){var d=b.defs;if(!d)b.defs=d={};for(var f=0;f<this.binds.defs.length;f++){var g=this.binds.defs[f],h=d[g.name];if(!h||g.id>h.id)d[g.name]={id:0,value:g.type&1?g.value.apply(this,c):g.value}}}d=this.binds.data;d=d.type&1?d.value.apply(this,c):d.value;c.unshift(null);
6935+b.length=d.length;for(f=0;f<d.length;f++){pv.Mark.prototype.index=this.index=f;(g=b[f])||(b[f]=g={});g.data=c[0]=d[f];this.buildInstance(g)}pv.Mark.prototype.index=-1;delete this.index;c.shift();return this};a.buildProperties=function(b,c){for(var d=0,f=c.length;d<f;d++){var g=c[d],h=g.value;switch(g.type){case 0:case 1:h=this.scene.defs[g.name].value;break;case 3:h=h.apply(this,pv.Mark.stack);break}b[g.name]=h}};
6936+a.buildInstance=function(b){this.buildProperties(b,this.binds.required);if(b.visible){this.buildProperties(b,this.binds.optional);this.buildImplied(b)}};
6937+a.buildImplied=function(b){var c=b.left,d=b.right,f=b.top,g=b.bottom,h=this.properties,i=h.width?b.width:0,j=h.height?b.height:0,l=this.parent?this.parent.width():i+c+d;if(i==null)i=l-(d=d||0)-(c=c||0);else if(d==null)d=l-i-(c=c||0);else if(c==null)c=l-i-(d=d||0);l=this.parent?this.parent.height():j+f+g;if(j==null)j=l-(f=f||0)-(g=g||0);else if(g==null)g=l-j-(f=f||0);else if(f==null)f=l-j-(g=g||0);b.left=c;b.right=d;b.top=f;b.bottom=g;if(h.width)b.width=i;if(h.height)b.height=j;if(h.textStyle&&!b.textStyle)b.textStyle=
6938+pv.Color.transparent;if(h.fillStyle&&!b.fillStyle)b.fillStyle=pv.Color.transparent;if(h.strokeStyle&&!b.strokeStyle)b.strokeStyle=pv.Color.transparent};
6939+a.mouse=function(){var b=pv.event.pageX||0,c=pv.event.pageY||0,d=this.root.canvas();do{b-=d.offsetLeft;c-=d.offsetTop}while(d=d.offsetParent);d=pv.Transform.identity;var f=this.properties.transform?this:this.parent,g=[];do g.push(f);while(f=f.parent);for(;f=g.pop();)d=d.translate(f.left(),f.top()).times(f.transform());d=d.invert();return pv.vector(b*d.k+d.x,c*d.k+d.y)};a.event=function(b,c){this.$handlers[b]=pv.functor(c);return this};
6940+a.context=function(b,c,d){function f(k,q){pv.Mark.scene=k;h.index=q;if(k){var o=k.mark,n=o,m=[];do{m.push(n);i.push(k[q].data);n.index=q;n.scene=k;q=k.parentIndex;k=k.parent}while(n=n.parent);k=m.length-1;for(q=1;k>0;k--){n=m[k];n.scale=q;q*=n.scene[n.index].transform.k}if(o.children){k=0;for(m=o.children.length;k<m;k++){n=o.children[k];n.scene=o.scene[o.index].children[k];n.scale=q}}}}function g(k){if(k){k=k.mark;var q;if(k.children)for(var o=0,n=k.children.length;o<n;o++){q=k.children[o];delete q.scene;
6941+delete q.scale}q=k;do{i.pop();if(q.parent){delete q.scene;delete q.scale}delete q.index}while(q=q.parent)}}var h=pv.Mark.prototype,i=pv.Mark.stack,j=pv.Mark.scene,l=h.index;g(j,l);f(b,c);try{d.apply(this,i)}finally{g(b,c);f(j,l)}};pv.Mark.dispatch=function(b,c,d){var f=c.mark,g=c.parent,h=f.$handlers[b];if(!h)return g&&pv.Mark.dispatch(b,g,c.parentIndex);f.context(c,d,function(){(f=h.apply(f,pv.Mark.stack))&&f.render&&f.render()});return true};
6942+pv.Anchor=function(b){pv.Mark.call(this);this.target=b;this.parent=b.parent};pv.Anchor.prototype=pv.extend(pv.Mark).property("name",String);pv.Anchor.prototype.anchorTarget=function(){return this.target};pv.Area=function(){pv.Mark.call(this)};
6943+pv.Area.prototype=pv.extend(pv.Mark).property("width",Number).property("height",Number).property("lineWidth",Number).property("strokeStyle",pv.color).property("fillStyle",pv.color).property("segmented",Boolean).property("interpolate",String).property("tension",Number);pv.Area.prototype.type="area";pv.Area.prototype.defaults=(new pv.Area).extend(pv.Mark.prototype.defaults).lineWidth(1.5).fillStyle(pv.Colors.category20().by(pv.parent)).interpolate("linear").tension(0.7);
6944+pv.Area.prototype.buildImplied=function(b){if(b.height==null)b.height=0;if(b.width==null)b.width=0;pv.Mark.prototype.buildImplied.call(this,b)};pv.Area.fixed={lineWidth:1,lineJoin:1,strokeStyle:1,fillStyle:1,segmented:1,interpolate:1,tension:1};
6945+pv.Area.prototype.bind=function(){pv.Mark.prototype.bind.call(this);var b=this.binds,c=b.required;b=b.optional;for(var d=0,f=b.length;d<f;d++){var g=b[d];g.fixed=g.name in pv.Area.fixed;if(g.name=="segmented"){c.push(g);b.splice(d,1);d--;f--}}this.binds.$required=c;this.binds.$optional=b};
6946+pv.Area.prototype.buildInstance=function(b){var c=this.binds;if(this.index){var d=c.fixed;if(!d){d=c.fixed=[];function f(i){return!i.fixed||(d.push(i),false)}c.required=c.required.filter(f);if(!this.scene[0].segmented)c.optional=c.optional.filter(f)}c=0;for(var g=d.length;c<g;c++){var h=d[c].name;b[h]=this.scene[0][h]}}else{c.required=c.$required;c.optional=c.$optional;c.fixed=null}pv.Mark.prototype.buildInstance.call(this,b)};
6947+pv.Area.prototype.anchor=function(b){var c;return pv.Mark.prototype.anchor.call(this,b).def("$area.anchor",function(){c=this.scene.target}).interpolate(function(){return c[this.index].interpolate}).eccentricity(function(){return c[this.index].eccentricity}).tension(function(){return c[this.index].tension})};pv.Bar=function(){pv.Mark.call(this)};
6948+pv.Bar.prototype=pv.extend(pv.Mark).property("width",Number).property("height",Number).property("lineWidth",Number).property("strokeStyle",pv.color).property("fillStyle",pv.color);pv.Bar.prototype.type="bar";pv.Bar.prototype.defaults=(new pv.Bar).extend(pv.Mark.prototype.defaults).lineWidth(1.5).fillStyle(pv.Colors.category20().by(pv.parent));pv.Dot=function(){pv.Mark.call(this)};
6949+pv.Dot.prototype=pv.extend(pv.Mark).property("size",Number).property("radius",Number).property("shape",String).property("angle",Number).property("lineWidth",Number).property("strokeStyle",pv.color).property("fillStyle",pv.color);pv.Dot.prototype.type="dot";pv.Dot.prototype.defaults=(new pv.Dot).extend(pv.Mark.prototype.defaults).size(20).shape("circle").lineWidth(1.5).strokeStyle(pv.Colors.category10().by(pv.parent));
6950+pv.Dot.prototype.anchor=function(b){var c;return pv.Mark.prototype.anchor.call(this,b).def("$wedge.anchor",function(){c=this.scene.target}).left(function(){var d=c[this.index];switch(this.name()){case "bottom":case "top":case "center":return d.left;case "left":return null}return d.left+d.radius}).right(function(){var d=c[this.index];return this.name()=="left"?d.right+d.radius:null}).top(function(){var d=c[this.index];switch(this.name()){case "left":case "right":case "center":return d.top;case "top":return null}return d.top+
6951+d.radius}).bottom(function(){var d=c[this.index];return this.name()=="top"?d.bottom+d.radius:null}).textAlign(function(){switch(this.name()){case "left":return"right";case "bottom":case "top":case "center":return"center"}return"left"}).textBaseline(function(){switch(this.name()){case "right":case "left":case "center":return"middle";case "bottom":return"top"}return"bottom"})};
6952+pv.Dot.prototype.buildImplied=function(b){if(b.radius==null)b.radius=Math.sqrt(b.size);else if(b.size==null)b.size=b.radius*b.radius;pv.Mark.prototype.buildImplied.call(this,b)};pv.Label=function(){pv.Mark.call(this)};
6953+pv.Label.prototype=pv.extend(pv.Mark).property("text",String).property("font",String).property("textAngle",Number).property("textStyle",pv.color).property("textAlign",String).property("textBaseline",String).property("textMargin",Number).property("textDecoration",String).property("textShadow",String);pv.Label.prototype.type="label";pv.Label.prototype.defaults=(new pv.Label).extend(pv.Mark.prototype.defaults).events("none").text(pv.identity).font("10px sans-serif").textAngle(0).textStyle("black").textAlign("left").textBaseline("bottom").textMargin(3);
6954+pv.Line=function(){pv.Mark.call(this)};pv.Line.prototype=pv.extend(pv.Mark).property("lineWidth",Number).property("lineJoin",String).property("strokeStyle",pv.color).property("fillStyle",pv.color).property("segmented",Boolean).property("interpolate",String).property("eccentricity",Number).property("tension",Number);a=pv.Line.prototype;a.type="line";a.defaults=(new pv.Line).extend(pv.Mark.prototype.defaults).lineJoin("miter").lineWidth(1.5).strokeStyle(pv.Colors.category10().by(pv.parent)).interpolate("linear").eccentricity(0).tension(0.7);
6955+a.bind=pv.Area.prototype.bind;a.buildInstance=pv.Area.prototype.buildInstance;a.anchor=function(b){return pv.Area.prototype.anchor.call(this,b).textAlign(function(){switch(this.name()){case "left":return"right";case "bottom":case "top":case "center":return"center";case "right":return"left"}}).textBaseline(function(){switch(this.name()){case "right":case "left":case "center":return"middle";case "top":return"bottom";case "bottom":return"top"}})};pv.Rule=function(){pv.Mark.call(this)};
6956+pv.Rule.prototype=pv.extend(pv.Mark).property("width",Number).property("height",Number).property("lineWidth",Number).property("strokeStyle",pv.color);pv.Rule.prototype.type="rule";pv.Rule.prototype.defaults=(new pv.Rule).extend(pv.Mark.prototype.defaults).lineWidth(1).strokeStyle("black").antialias(false);pv.Rule.prototype.anchor=pv.Line.prototype.anchor;
6957+pv.Rule.prototype.buildImplied=function(b){var c=b.left,d=b.right;if(b.width!=null||c==null&&d==null||d!=null&&c!=null)b.height=0;else b.width=0;pv.Mark.prototype.buildImplied.call(this,b)};pv.Panel=function(){pv.Bar.call(this);this.children=[];this.root=this;this.$dom=pv.$&&pv.$.s};pv.Panel.prototype=pv.extend(pv.Bar).property("transform").property("overflow",String).property("canvas",function(b){return typeof b=="string"?document.getElementById(b):b});a=pv.Panel.prototype;a.type="panel";
6958+a.defaults=(new pv.Panel).extend(pv.Bar.prototype.defaults).fillStyle(null).overflow("visible");a.anchor=function(b){b=pv.Bar.prototype.anchor.call(this,b);b.parent=this;return b};a.add=function(b){b=new b;b.parent=this;b.root=this.root;b.childIndex=this.children.length;this.children.push(b);return b};a.bind=function(){pv.Mark.prototype.bind.call(this);for(var b=0;b<this.children.length;b++)this.children[b].bind()};
6959+a.buildInstance=function(b){pv.Bar.prototype.buildInstance.call(this,b);if(b.visible){if(!b.children)b.children=[];var c=this.scale*b.transform.k,d,f=this.children.length;pv.Mark.prototype.index=-1;for(var g=0;g<f;g++){d=this.children[g];d.scene=b.children[g];d.scale=c;d.build()}for(g=0;g<f;g++){d=this.children[g];b.children[g]=d.scene;delete d.scene;delete d.scale}b.children.length=f}};
6960+a.buildImplied=function(b){if(!this.parent){var c=b.canvas;if(c){if(c.$panel!=this)for(c.$panel=this;c.lastChild;)c.removeChild(c.lastChild);var d;if(b.width==null){d=parseFloat(pv.css(c,"width"));b.width=d-b.left-b.right}if(b.height==null){d=parseFloat(pv.css(c,"height"));b.height=d-b.top-b.bottom}}else{d=this.$canvas||(this.$canvas=[]);if(!(c=d[this.index])){c=d[this.index]=document.createElement("span");if(this.$dom)this.$dom.parentNode.insertBefore(c,this.$dom);else{for(d=document.body;d.lastChild&&
6961+d.lastChild.tagName;)d=d.lastChild;if(d!=document.body)d=d.parentNode;d.appendChild(c)}}}b.canvas=c}if(!b.transform)b.transform=pv.Transform.identity;pv.Mark.prototype.buildImplied.call(this,b)};pv.Image=function(){pv.Bar.call(this)};pv.Image.prototype=pv.extend(pv.Bar).property("url",String).property("imageWidth",Number).property("imageHeight",Number);a=pv.Image.prototype;a.type="image";a.defaults=(new pv.Image).extend(pv.Bar.prototype.defaults).fillStyle(null);
6962+a.image=function(b){this.$image=function(){var c=b.apply(this,arguments);return c==null?pv.Color.transparent:typeof c=="string"?pv.color(c):c};return this};a.bind=function(){pv.Bar.prototype.bind.call(this);var b=this.binds,c=this;do b.image=c.$image;while(!b.image&&(c=c.proto))};
6963+a.buildImplied=function(b){pv.Bar.prototype.buildImplied.call(this,b);if(b.visible){if(b.imageWidth==null)b.imageWidth=b.width;if(b.imageHeight==null)b.imageHeight=b.height;if(b.url==null&&this.binds.image){var c=this.$canvas||(this.$canvas=document.createElement("canvas")),d=c.getContext("2d"),f=b.imageWidth,g=b.imageHeight,h=pv.Mark.stack;c.width=f;c.height=g;b=(b.image=d.createImageData(f,g)).data;h.unshift(null,null);for(d=c=0;c<g;c++){h[1]=c;for(var i=0;i<f;i++){h[0]=i;var j=this.binds.image.apply(this,
6964+h);b[d++]=j.r;b[d++]=j.g;b[d++]=j.b;b[d++]=255*j.a}}h.splice(0,2)}}};pv.Wedge=function(){pv.Mark.call(this)};pv.Wedge.prototype=pv.extend(pv.Mark).property("startAngle",Number).property("endAngle",Number).property("angle",Number).property("innerRadius",Number).property("outerRadius",Number).property("lineWidth",Number).property("strokeStyle",pv.color).property("fillStyle",pv.color);a=pv.Wedge.prototype;a.type="wedge";
6965+a.defaults=(new pv.Wedge).extend(pv.Mark.prototype.defaults).startAngle(function(){var b=this.sibling();return b?b.endAngle:-Math.PI/2}).innerRadius(0).lineWidth(1.5).strokeStyle(null).fillStyle(pv.Colors.category20().by(pv.index));a.midRadius=function(){return(this.innerRadius()+this.outerRadius())/2};a.midAngle=function(){return(this.startAngle()+this.endAngle())/2};
6966+a.anchor=function(b){function c(h){return h.innerRadius||h.angle<2*Math.PI}function d(h){return(h.innerRadius+h.outerRadius)/2}function f(h){return(h.startAngle+h.endAngle)/2}var g;return pv.Mark.prototype.anchor.call(this,b).def("$wedge.anchor",function(){g=this.scene.target}).left(function(){var h=g[this.index];if(c(h))switch(this.name()){case "outer":return h.left+h.outerRadius*Math.cos(f(h));case "inner":return h.left+h.innerRadius*Math.cos(f(h));case "start":return h.left+d(h)*Math.cos(h.startAngle);
6967+case "center":return h.left+d(h)*Math.cos(f(h));case "end":return h.left+d(h)*Math.cos(h.endAngle)}return h.left}).top(function(){var h=g[this.index];if(c(h))switch(this.name()){case "outer":return h.top+h.outerRadius*Math.sin(f(h));case "inner":return h.top+h.innerRadius*Math.sin(f(h));case "start":return h.top+d(h)*Math.sin(h.startAngle);case "center":return h.top+d(h)*Math.sin(f(h));case "end":return h.top+d(h)*Math.sin(h.endAngle)}return h.top}).textAlign(function(){var h=g[this.index];if(c(h))switch(this.name()){case "outer":return pv.Wedge.upright(f(h))?
6968+"right":"left";case "inner":return pv.Wedge.upright(f(h))?"left":"right"}return"center"}).textBaseline(function(){var h=g[this.index];if(c(h))switch(this.name()){case "start":return pv.Wedge.upright(h.startAngle)?"top":"bottom";case "end":return pv.Wedge.upright(h.endAngle)?"bottom":"top"}return"middle"}).textAngle(function(){var h=g[this.index],i=0;if(c(h))switch(this.name()){case "center":case "inner":case "outer":i=f(h);break;case "start":i=h.startAngle;break;case "end":i=h.endAngle;break}return pv.Wedge.upright(i)?
6969+i:i+Math.PI})};pv.Wedge.upright=function(b){b%=2*Math.PI;b=b<0?2*Math.PI+b:b;return b<Math.PI/2||b>=3*Math.PI/2};pv.Wedge.prototype.buildImplied=function(b){if(b.angle==null)b.angle=b.endAngle-b.startAngle;else if(b.endAngle==null)b.endAngle=b.startAngle+b.angle;pv.Mark.prototype.buildImplied.call(this,b)};pv.simulation=function(b){return new pv.Simulation(b)};pv.Simulation=function(b){for(var c=0;c<b.length;c++)this.particle(b[c])};a=pv.Simulation.prototype;
6970+a.particle=function(b){b.next=this.particles;if(isNaN(b.px))b.px=b.x;if(isNaN(b.py))b.py=b.y;if(isNaN(b.fx))b.fx=0;if(isNaN(b.fy))b.fy=0;this.particles=b;return this};a.force=function(b){b.next=this.forces;this.forces=b;return this};a.constraint=function(b){b.next=this.constraints;this.constraints=b;return this};
6971+a.stabilize=function(b){var c;arguments.length||(b=3);for(var d=0;d<b;d++){var f=new pv.Quadtree(this.particles);for(c=this.constraints;c;c=c.next)c.apply(this.particles,f)}for(c=this.particles;c;c=c.next){c.px=c.x;c.py=c.y}return this};
6972+a.step=function(){var b;for(b=this.particles;b;b=b.next){var c=b.px,d=b.py;b.px=b.x;b.py=b.y;b.x+=b.vx=b.x-c+b.fx;b.y+=b.vy=b.y-d+b.fy}c=new pv.Quadtree(this.particles);for(b=this.constraints;b;b=b.next)b.apply(this.particles,c);for(b=this.particles;b;b=b.next)b.fx=b.fy=0;for(b=this.forces;b;b=b.next)b.apply(this.particles,c)};
6973+pv.Quadtree=function(b){function c(k,q,o,n,m,r){if(!(isNaN(q.x)||isNaN(q.y)))if(k.leaf)if(k.p){if(!(Math.abs(k.p.x-q.x)+Math.abs(k.p.y-q.y)<0.01)){var s=k.p;k.p=null;d(k,s,o,n,m,r)}d(k,q,o,n,m,r)}else k.p=q;else d(k,q,o,n,m,r)}function d(k,q,o,n,m,r){var s=(o+m)*0.5,u=(n+r)*0.5,x=q.x>=s,t=q.y>=u;k.leaf=false;switch((t<<1)+x){case 0:k=k.c1||(k.c1=new pv.Quadtree.Node);break;case 1:k=k.c2||(k.c2=new pv.Quadtree.Node);break;case 2:k=k.c3||(k.c3=new pv.Quadtree.Node);break;case 3:k=k.c4||(k.c4=new pv.Quadtree.Node);
6974+break}if(x)o=s;else m=s;if(t)n=u;else r=u;c(k,q,o,n,m,r)}var f,g=Number.POSITIVE_INFINITY,h=g,i=Number.NEGATIVE_INFINITY,j=i;for(f=b;f;f=f.next){if(f.x<g)g=f.x;if(f.y<h)h=f.y;if(f.x>i)i=f.x;if(f.y>j)j=f.y}f=i-g;var l=j-h;if(f>l)j=h+f;else i=g+l;this.xMin=g;this.yMin=h;this.xMax=i;this.yMax=j;this.root=new pv.Quadtree.Node;for(f=b;f;f=f.next)c(this.root,f,g,h,i,j)};pv.Quadtree.Node=function(){this.leaf=true;this.p=this.c4=this.c3=this.c2=this.c1=null};pv.Force={};
6975+pv.Force.charge=function(b){function c(k){function q(m){c(m);k.cn+=m.cn;o+=m.cn*m.cx;n+=m.cn*m.cy}var o=0,n=0;k.cn=0;if(!k.leaf){k.c1&&q(k.c1);k.c2&&q(k.c2);k.c3&&q(k.c3);k.c4&&q(k.c4)}if(k.p){k.cn+=b;o+=b*k.p.x;n+=b*k.p.y}k.cx=o/k.cn;k.cy=n/k.cn}function d(k,q,o,n,m,r){var s=k.cx-q.x,u=k.cy-q.y,x=1/Math.sqrt(s*s+u*u);if(k.leaf&&k.p!=q||(m-o)*x<j){if(!(x<i)){if(x>g)x=g;k=k.cn*x*x*x;s=s*k;u=u*k;q.fx+=s;q.fy+=u}}else if(!k.leaf){var t=(o+m)*0.5,p=(n+r)*0.5;k.c1&&d(k.c1,q,o,n,t,p);k.c2&&d(k.c2,q,t,n,
6976+m,p);k.c3&&d(k.c3,q,o,p,t,r);k.c4&&d(k.c4,q,t,p,m,r);if(!(x<i)){if(x>g)x=g;if(k.p&&k.p!=q){k=b*x*x*x;s=s*k;u=u*k;q.fx+=s;q.fy+=u}}}}var f=2,g=1/f,h=500,i=1/h,j=0.9,l={};arguments.length||(b=-40);l.constant=function(k){if(arguments.length){b=Number(k);return l}return b};l.domain=function(k,q){if(arguments.length){f=Number(k);g=1/f;h=Number(q);i=1/h;return l}return[f,h]};l.theta=function(k){if(arguments.length){j=Number(k);return l}return j};l.apply=function(k,q){c(q.root);for(k=k;k;k=k.next)d(q.root,
6977+k,q.xMin,q.yMin,q.xMax,q.yMax)};return l};pv.Force.drag=function(b){var c={};arguments.length||(b=0.1);c.constant=function(d){if(arguments.length){b=d;return c}return b};c.apply=function(d){if(b)for(d=d;d;d=d.next){d.fx-=b*d.vx;d.fy-=b*d.vy}};return c};
6978+pv.Force.spring=function(b){var c=0.1,d=20,f,g,h={};arguments.length||(b=0.1);h.links=function(i){if(arguments.length){f=i;g=i.map(function(j){return 1/Math.sqrt(Math.max(j.sourceNode.linkDegree,j.targetNode.linkDegree))});return h}return f};h.constant=function(i){if(arguments.length){b=Number(i);return h}return b};h.damping=function(i){if(arguments.length){c=Number(i);return h}return c};h.length=function(i){if(arguments.length){d=Number(i);return h}return d};h.apply=function(){for(var i=0;i<f.length;i++){var j=
6979+f[i].sourceNode,l=f[i].targetNode,k=j.x-l.x,q=j.y-l.y,o=Math.sqrt(k*k+q*q),n=o?1/o:1;n=(b*g[i]*(o-d)+c*g[i]*(k*(j.vx-l.vx)+q*(j.vy-l.vy))*n)*n;k=-n*(o?k:0.01*(0.5-Math.random()));q=-n*(o?q:0.01*(0.5-Math.random()));j.fx+=k;j.fy+=q;l.fx-=k;l.fy-=q}};return h};pv.Constraint={};
6980+pv.Constraint.collision=function(b){function c(k,q,o,n,m,r){if(!k.leaf){var s=(o+m)*0.5,u=(n+r)*0.5,x=u<j,t=s>g,p=s<i;if(u>h){k.c1&&t&&c(k.c1,q,o,n,s,u);k.c2&&p&&c(k.c2,q,s,n,m,u)}if(x){k.c3&&t&&c(k.c3,q,o,u,s,r);k.c4&&p&&c(k.c4,q,s,u,m,r)}}if(k.p&&k.p!=q){o=q.x-k.p.x;n=q.y-k.p.y;m=Math.sqrt(o*o+n*n);r=f+b(k.p);if(m<r){m=(m-r)/m*0.5;o*=m;n*=m;q.x-=o;q.y-=n;k.p.x+=o;k.p.y+=n}}}var d=1,f,g,h,i,j,l={};arguments.length||(f=10);l.repeat=function(k){if(arguments.length){d=Number(k);return l}return d};l.apply=
6981+function(k,q){var o,n,m=-Infinity;for(o=k;o;o=o.next){n=b(o);if(n>m)m=n}for(var r=0;r<d;r++)for(o=k;o;o=o.next){n=(f=b(o))+m;g=o.x-n;i=o.x+n;h=o.y-n;j=o.y+n;c(q.root,o,q.xMin,q.yMin,q.xMax,q.yMax)}};return l};pv.Constraint.position=function(b){var c=1,d={};arguments.length||(b=function(f){return f.fix});d.alpha=function(f){if(arguments.length){c=Number(f);return d}return c};d.apply=function(f){for(f=f;f;f=f.next){var g=b(f);if(g){f.x+=(g.x-f.x)*c;f.y+=(g.y-f.y)*c;f.fx=f.fy=f.vx=f.vy=0}}};return d};
6982+pv.Constraint.bound=function(){var b={},c,d;b.x=function(f,g){if(arguments.length){c={min:Math.min(f,g),max:Math.max(f,g)};return this}return c};b.y=function(f,g){if(arguments.length){d={min:Math.min(f,g),max:Math.max(f,g)};return this}return d};b.apply=function(f){if(c)for(var g=f;g;g=g.next)g.x=g.x<c.min?c.min:g.x>c.max?c.max:g.x;if(d)for(g=f;g;g=g.next)g.y=g.y<d.min?d.min:g.y>d.max?d.max:g.y};return b};pv.Layout=function(){pv.Panel.call(this)};pv.Layout.prototype=pv.extend(pv.Panel);
6983+pv.Layout.prototype.property=function(b,c){if(!this.hasOwnProperty("properties"))this.properties=pv.extend(this.properties);this.properties[b]=true;this.propertyMethod(b,false,pv.Mark.cast[b]=c);return this};
6984+pv.Layout.Network=function(){pv.Layout.call(this);var b=this;this.$id=pv.id();(this.node=(new pv.Mark).data(function(){return b.nodes()}).strokeStyle("#1f77b4").fillStyle("#fff").left(function(c){return c.x}).top(function(c){return c.y})).parent=this;this.link=(new pv.Mark).extend(this.node).data(function(c){return[c.sourceNode,c.targetNode]}).fillStyle(null).lineWidth(function(c,d){return d.linkValue*1.5}).strokeStyle("rgba(0,0,0,.2)");this.link.add=function(c){return b.add(pv.Panel).data(function(){return b.links()}).add(c).extend(this)};
6985+(this.label=(new pv.Mark).extend(this.node).textMargin(7).textBaseline("middle").text(function(c){return c.nodeName||c.nodeValue}).textAngle(function(c){c=c.midAngle;return pv.Wedge.upright(c)?c:c+Math.PI}).textAlign(function(c){return pv.Wedge.upright(c.midAngle)?"left":"right"})).parent=this};
6986+pv.Layout.Network.prototype=pv.extend(pv.Layout).property("nodes",function(b){return b.map(function(c,d){if(typeof c!="object")c={nodeValue:c};c.index=d;c.linkDegree=0;return c})}).property("links",function(b){return b.map(function(c){if(isNaN(c.linkValue))c.linkValue=isNaN(c.value)?1:c.value;return c})});pv.Layout.Network.prototype.reset=function(){this.$id=pv.id();return this};
6987+pv.Layout.Network.prototype.buildProperties=function(b,c){if((b.$id||0)<this.$id)pv.Layout.prototype.buildProperties.call(this,b,c)};pv.Layout.Network.prototype.buildImplied=function(b){pv.Layout.prototype.buildImplied.call(this,b);if(b.$id>=this.$id)return true;b.$id=this.$id;b.links.forEach(function(c){var d=c.linkValue;(c.sourceNode||(c.sourceNode=b.nodes[c.source])).linkDegree+=d;(c.targetNode||(c.targetNode=b.nodes[c.target])).linkDegree+=d})};
6988+pv.Layout.Hierarchy=function(){pv.Layout.Network.call(this);this.link.strokeStyle("#ccc")};pv.Layout.Hierarchy.prototype=pv.extend(pv.Layout.Network);pv.Layout.Hierarchy.prototype.buildImplied=function(b){if(!b.links)b.links=pv.Layout.Hierarchy.links.call(this);pv.Layout.Network.prototype.buildImplied.call(this,b)};pv.Layout.Hierarchy.links=function(){return this.nodes().filter(function(b){return b.parentNode}).map(function(b){return{sourceNode:b,targetNode:b.parentNode,linkValue:1}})};
6989+pv.Layout.Hierarchy.NodeLink={buildImplied:function(b){function c(m){return m.parentNode?m.depth*(o-q)+q:0}function d(m){return m.parentNode?(m.breadth-0.25)*2*Math.PI:0}function f(m){switch(i){case "left":return m.depth*l;case "right":return l-m.depth*l;case "top":return m.breadth*l;case "bottom":return l-m.breadth*l;case "radial":return l/2+c(m)*Math.cos(m.midAngle)}}function g(m){switch(i){case "left":return m.breadth*k;case "right":return k-m.breadth*k;case "top":return m.depth*k;case "bottom":return k-
6990+m.depth*k;case "radial":return k/2+c(m)*Math.sin(m.midAngle)}}var h=b.nodes,i=b.orient,j=/^(top|bottom)$/.test(i),l=b.width,k=b.height;if(i=="radial"){var q=b.innerRadius,o=b.outerRadius;if(q==null)q=0;if(o==null)o=Math.min(l,k)/2}for(b=0;b<h.length;b++){var n=h[b];n.midAngle=i=="radial"?d(n):j?Math.PI/2:0;n.x=f(n);n.y=g(n);if(n.firstChild)n.midAngle+=Math.PI}}};
6991+pv.Layout.Hierarchy.Fill={constructor:function(){this.node.strokeStyle("#fff").fillStyle("#ccc").width(function(b){return b.dx}).height(function(b){return b.dy}).innerRadius(function(b){return b.innerRadius}).outerRadius(function(b){return b.outerRadius}).startAngle(function(b){return b.startAngle}).angle(function(b){return b.angle});this.label.textAlign("center").left(function(b){return b.x+b.dx/2}).top(function(b){return b.y+b.dy/2});delete this.link},buildImplied:function(b){function c(p,v){return(p+
6992+v)/(1+v)}function d(p){switch(o){case "left":return c(p.minDepth,s)*m;case "right":return(1-c(p.maxDepth,s))*m;case "top":return p.minBreadth*m;case "bottom":return(1-p.maxBreadth)*m;case "radial":return m/2}}function f(p){switch(o){case "left":return p.minBreadth*r;case "right":return(1-p.maxBreadth)*r;case "top":return c(p.minDepth,s)*r;case "bottom":return(1-c(p.maxDepth,s))*r;case "radial":return r/2}}function g(p){switch(o){case "left":case "right":return(p.maxDepth-p.minDepth)/(1+s)*m;case "top":case "bottom":return(p.maxBreadth-
6993+p.minBreadth)*m;case "radial":return p.parentNode?(p.innerRadius+p.outerRadius)*Math.cos(p.midAngle):0}}function h(p){switch(o){case "left":case "right":return(p.maxBreadth-p.minBreadth)*r;case "top":case "bottom":return(p.maxDepth-p.minDepth)/(1+s)*r;case "radial":return p.parentNode?(p.innerRadius+p.outerRadius)*Math.sin(p.midAngle):0}}function i(p){return Math.max(0,c(p.minDepth,s/2))*(x-u)+u}function j(p){return c(p.maxDepth,s/2)*(x-u)+u}function l(p){return(p.parentNode?p.minBreadth-0.25:0)*
6994+2*Math.PI}function k(p){return(p.parentNode?p.maxBreadth-p.minBreadth:1)*2*Math.PI}var q=b.nodes,o=b.orient,n=/^(top|bottom)$/.test(o),m=b.width,r=b.height,s=-q[0].minDepth;if(o=="radial"){var u=b.innerRadius,x=b.outerRadius;if(u==null)u=0;if(u)s*=2;if(x==null)x=Math.min(m,r)/2}for(b=0;b<q.length;b++){var t=q[b];t.x=d(t);t.y=f(t);if(o=="radial"){t.innerRadius=i(t);t.outerRadius=j(t);t.startAngle=l(t);t.angle=k(t);t.midAngle=t.startAngle+t.angle/2}else t.midAngle=n?-Math.PI/2:0;t.dx=g(t);t.dy=h(t)}}};
6995+pv.Layout.Grid=function(){pv.Layout.call(this);var b=this;(this.cell=(new pv.Mark).data(function(){return b.scene[b.index].$grid}).width(function(){return b.width()/b.cols()}).height(function(){return b.height()/b.rows()}).left(function(){return this.width()*(this.index%b.cols())}).top(function(){return this.height()*Math.floor(this.index/b.cols())})).parent=this};pv.Layout.Grid.prototype=pv.extend(pv.Layout).property("rows").property("cols");pv.Layout.Grid.prototype.defaults=(new pv.Layout.Grid).extend(pv.Layout.prototype.defaults).rows(1).cols(1);
6996+pv.Layout.Grid.prototype.buildImplied=function(b){pv.Layout.prototype.buildImplied.call(this,b);var c=b.rows,d=b.cols;if(typeof d=="object")c=pv.transpose(d);if(typeof c=="object"){b.$grid=pv.blend(c);b.rows=c.length;b.cols=c[0]?c[0].length:0}else b.$grid=pv.repeat([b.data],c*d)};
6997+pv.Layout.Stack=function(){function b(i){return function(){return f[i](this.parent.index,this.index)}}pv.Layout.call(this);var c=this,d=function(){return null},f={t:d,l:d,r:d,b:d,w:d,h:d},g,h=c.buildImplied;this.buildImplied=function(i){h.call(this,i);var j=i.layers,l=j.length,k,q=i.orient,o=/^(top|bottom)\b/.test(q),n=this.parent[o?"height":"width"](),m=[],r=[],s=[],u=pv.Mark.stack,x={parent:{parent:this}};u.unshift(null);g=[];for(var t=0;t<l;t++){s[t]=[];r[t]=[];x.parent.index=t;u[0]=j[t];g[t]=
6998+this.$values.apply(x.parent,u);if(!t)k=g[t].length;u.unshift(null);for(var p=0;p<k;p++){u[0]=g[t][p];x.index=p;t||(m[p]=this.$x.apply(x,u));s[t][p]=this.$y.apply(x,u)}u.shift()}u.shift();switch(i.order){case "inside-out":var v=s.map(function(A){return pv.max.index(A)});x=pv.range(l).sort(function(A,D){return v[A]-v[D]});j=s.map(function(A){return pv.sum(A)});var w=u=0,y=[],z=[];for(t=0;t<l;t++){p=x[t];if(u<w){u+=j[p];y.push(p)}else{w+=j[p];z.push(p)}}j=z.reverse().concat(y);break;case "reverse":j=
6999+pv.range(l-1,-1,-1);break;default:j=pv.range(l);break}switch(i.offset){case "silohouette":for(p=0;p<k;p++){for(t=x=0;t<l;t++)x+=s[t][p];r[j[0]][p]=(n-x)/2}break;case "wiggle":for(t=x=0;t<l;t++)x+=s[t][0];r[j[0]][0]=x=(n-x)/2;for(p=1;p<k;p++){u=n=0;w=m[p]-m[p-1];for(t=0;t<l;t++)n+=s[t][p];for(t=0;t<l;t++){y=(s[j[t]][p]-s[j[t]][p-1])/(2*w);for(i=0;i<t;i++)y+=(s[j[i]][p]-s[j[i]][p-1])/w;u+=y*s[j[t]][p]}r[j[0]][p]=x-=n?u/n*w:0}break;case "expand":for(p=0;p<k;p++){for(t=i=r[j[0]][p]=0;t<l;t++)i+=s[t][p];
7000+if(i){i=n/i;for(t=0;t<l;t++)s[t][p]*=i}else{i=n/l;for(t=0;t<l;t++)s[t][p]=i}}break;default:for(p=0;p<k;p++)r[j[0]][p]=0;break}for(p=0;p<k;p++){x=r[j[0]][p];for(t=1;t<l;t++){x+=s[j[t-1]][p];r[j[t]][p]=x}}t=q.indexOf("-");l=o?"h":"w";o=t<0?o?"l":"b":q.charAt(t+1);q=q.charAt(0);for(var C in f)f[C]=d;f[o]=function(A,D){return m[D]};f[q]=function(A,D){return r[A][D]};f[l]=function(A,D){return s[A][D]}};this.layer=(new pv.Mark).data(function(){return g[this.parent.index]}).top(b("t")).left(b("l")).right(b("r")).bottom(b("b")).width(b("w")).height(b("h"));
7001+this.layer.add=function(i){return c.add(pv.Panel).data(function(){return c.layers()}).add(i).extend(this)}};pv.Layout.Stack.prototype=pv.extend(pv.Layout).property("orient",String).property("offset",String).property("order",String).property("layers");a=pv.Layout.Stack.prototype;a.defaults=(new pv.Layout.Stack).extend(pv.Layout.prototype.defaults).orient("bottom-left").offset("zero").layers([[]]);a.$x=pv.Layout.Stack.prototype.$y=function(){return 0};a.x=function(b){this.$x=pv.functor(b);return this};
7002+a.y=function(b){this.$y=pv.functor(b);return this};a.$values=pv.identity;a.values=function(b){this.$values=pv.functor(b);return this};
7003+pv.Layout.Treemap=function(){pv.Layout.Hierarchy.call(this);this.node.strokeStyle("#fff").fillStyle("rgba(31, 119, 180, .25)").width(function(b){return b.dx}).height(function(b){return b.dy});this.label.visible(function(b){return!b.firstChild}).left(function(b){return b.x+b.dx/2}).top(function(b){return b.y+b.dy/2}).textAlign("center").textAngle(function(b){return b.dx>b.dy?0:-Math.PI/2});(this.leaf=(new pv.Mark).extend(this.node).fillStyle(null).strokeStyle(null).visible(function(b){return!b.firstChild})).parent=
7004+this;delete this.link};pv.Layout.Treemap.prototype=pv.extend(pv.Layout.Hierarchy).property("round",Boolean).property("paddingLeft",Number).property("paddingRight",Number).property("paddingTop",Number).property("paddingBottom",Number).property("mode",String).property("order",String);a=pv.Layout.Treemap.prototype;a.defaults=(new pv.Layout.Treemap).extend(pv.Layout.Hierarchy.prototype.defaults).mode("squarify").order("ascending");a.padding=function(b){return this.paddingLeft(b).paddingRight(b).paddingTop(b).paddingBottom(b)};
7005+a.$size=function(b){return Number(b.nodeValue)};a.size=function(b){this.$size=pv.functor(b);return this};
7006+a.buildImplied=function(b){function c(r,s,u,x,t,p,v){for(var w=0,y=0;w<r.length;w++){var z=r[w];if(u){z.x=x+y;z.y=t;y+=z.dx=n(p*z.size/s);z.dy=v}else{z.x=x;z.y=t+y;z.dx=p;y+=z.dy=n(v*z.size/s)}}if(z)if(u)z.dx+=p-y;else z.dy+=v-y}function d(r,s){for(var u=-Infinity,x=Infinity,t=0,p=0;p<r.length;p++){var v=r[p].size;if(v<x)x=v;if(v>u)u=v;t+=v}t*=t;s*=s;return Math.max(s*u/t,t/(s*x))}function f(r,s){function u(A){var D=p==y,G=pv.sum(A,o),E=y?n(G/y):0;c(A,G,D,x,t,D?p:E,D?E:v);if(D){t+=E;v-=E}else{x+=
7007+E;p-=E}y=Math.min(p,v);return D}var x=r.x+j,t=r.y+k,p=r.dx-j-l,v=r.dy-k-q;if(m!="squarify")c(r.childNodes,r.size,m=="slice"?true:m=="dice"?false:s&1,x,t,p,v);else{var w=[];s=Infinity;var y=Math.min(p,v),z=p*v/r.size;if(!(r.size<=0)){r.visitBefore(function(A){A.size*=z});for(r=r.childNodes.slice();r.length;){var C=r[r.length-1];if(C.size){w.push(C);z=d(w,y);if(z<=s){r.pop();s=z}else{w.pop();u(w);w.length=0;s=Infinity}}else r.pop()}if(u(w))for(s=0;s<w.length;s++)w[s].dy+=v;else for(s=0;s<w.length;s++)w[s].dx+=
7008+p}}}if(!pv.Layout.Hierarchy.prototype.buildImplied.call(this,b)){var g=this,h=b.nodes[0],i=pv.Mark.stack,j=b.paddingLeft,l=b.paddingRight,k=b.paddingTop,q=b.paddingBottom,o=function(r){return r.size},n=b.round?Math.round:Number,m=b.mode;i.unshift(null);h.visitAfter(function(r,s){r.depth=s;r.x=r.y=r.dx=r.dy=0;r.size=r.firstChild?pv.sum(r.childNodes,function(u){return u.size}):g.$size.apply(g,(i[0]=r,i))});i.shift();switch(b.order){case "ascending":h.sort(function(r,s){return r.size-s.size});break;
7009+case "descending":h.sort(function(r,s){return s.size-r.size});break;case "reverse":h.reverse();break}h.x=0;h.y=0;h.dx=b.width;h.dy=b.height;h.visitBefore(f)}};pv.Layout.Tree=function(){pv.Layout.Hierarchy.call(this)};pv.Layout.Tree.prototype=pv.extend(pv.Layout.Hierarchy).property("group",Number).property("breadth",Number).property("depth",Number).property("orient",String);pv.Layout.Tree.prototype.defaults=(new pv.Layout.Tree).extend(pv.Layout.Hierarchy.prototype.defaults).group(1).breadth(15).depth(60).orient("top");
7010+pv.Layout.Tree.prototype.buildImplied=function(b){function c(p){var v,w,y;if(p.firstChild){v=p.firstChild;w=p.lastChild;for(var z=y=v;z;z=z.nextSibling){c(z);y=f(z,y)}j(p);w=0.5*(v.prelim+w.prelim);if(v=p.previousSibling){p.prelim=v.prelim+k(p.depth,true);p.mod=p.prelim-w}else p.prelim=w}else if(v=p.previousSibling)p.prelim=v.prelim+k(p.depth,true)}function d(p,v,w){p.breadth=p.prelim+v;v+=p.mod;for(p=p.firstChild;p;p=p.nextSibling)d(p,v,w)}function f(p,v){var w=p.previousSibling;if(w){var y=p,z=
7011+p,C=w;w=p.parentNode.firstChild;var A=y.mod,D=z.mod,G=C.mod,E=w.mod;C=h(C);for(y=g(y);C&&y;){C=C;y=y;w=g(w);z=h(z);z.ancestor=p;var B=C.prelim+G-(y.prelim+A)+k(C.depth,false);if(B>0){i(l(C,p,v),p,B);A+=B;D+=B}G+=C.mod;A+=y.mod;E+=w.mod;D+=z.mod;C=h(C);y=g(y)}if(C&&!h(z)){z.thread=C;z.mod+=G-D}if(y&&!g(w)){w.thread=y;w.mod+=A-E;v=p}}return v}function g(p){return p.firstChild||p.thread}function h(p){return p.lastChild||p.thread}function i(p,v,w){var y=v.number-p.number;v.change-=w/y;v.shift+=w;p.change+=
7012+w/y;v.prelim+=w;v.mod+=w}function j(p){var v=0,w=0;for(p=p.lastChild;p;p=p.previousSibling){p.prelim+=v;p.mod+=v;w+=p.change;v+=p.shift+w}}function l(p,v,w){return p.ancestor.parentNode==v.parentNode?p.ancestor:w}function k(p,v){return(v?1:u+1)/(m=="radial"?p:1)}function q(p){return m=="radial"?p.breadth/r:0}function o(p){switch(m){case "left":return p.depth;case "right":return x-p.depth;case "top":case "bottom":return p.breadth+x/2;case "radial":return x/2+p.depth*Math.cos(q(p))}}function n(p){switch(m){case "left":case "right":return p.breadth+
7013+t/2;case "top":return p.depth;case "bottom":return t-p.depth;case "radial":return t/2+p.depth*Math.sin(q(p))}}if(!pv.Layout.Hierarchy.prototype.buildImplied.call(this,b)){var m=b.orient,r=b.depth,s=b.breadth,u=b.group,x=b.width,t=b.height;b=b.nodes[0];b.visitAfter(function(p,v){p.ancestor=p;p.prelim=0;p.mod=0;p.change=0;p.shift=0;p.number=p.previousSibling?p.previousSibling.number+1:0;p.depth=v});c(b);d(b,-b.prelim,0);b.visitAfter(function(p){p.breadth*=s;p.depth*=r;p.midAngle=q(p);p.x=o(p);p.y=n(p);
7014+if(p.firstChild)p.midAngle+=Math.PI;delete p.breadth;delete p.depth;delete p.ancestor;delete p.prelim;delete p.mod;delete p.change;delete p.shift;delete p.number;delete p.thread})}};pv.Layout.Indent=function(){pv.Layout.Hierarchy.call(this);this.link.interpolate("step-after")};pv.Layout.Indent.prototype=pv.extend(pv.Layout.Hierarchy).property("depth",Number).property("breadth",Number);pv.Layout.Indent.prototype.defaults=(new pv.Layout.Indent).extend(pv.Layout.Hierarchy.prototype.defaults).depth(15).breadth(15);
7015+pv.Layout.Indent.prototype.buildImplied=function(b){function c(i,j,l){i.x=g+l++*f;i.y=h+j++*d;i.midAngle=0;for(i=i.firstChild;i;i=i.nextSibling)j=c(i,j,l);return j}if(!pv.Layout.Hierarchy.prototype.buildImplied.call(this,b)){var d=b.breadth,f=b.depth,g=0,h=0;c(b.nodes[0],1,1)}};pv.Layout.Pack=function(){pv.Layout.Hierarchy.call(this);this.node.radius(function(b){return b.radius}).strokeStyle("rgb(31, 119, 180)").fillStyle("rgba(31, 119, 180, .25)");this.label.textAlign("center");delete this.link};
7016+pv.Layout.Pack.prototype=pv.extend(pv.Layout.Hierarchy).property("spacing",Number).property("order",String);pv.Layout.Pack.prototype.defaults=(new pv.Layout.Pack).extend(pv.Layout.Hierarchy.prototype.defaults).spacing(1).order("ascending");pv.Layout.Pack.prototype.$radius=function(){return 1};pv.Layout.Pack.prototype.size=function(b){this.$radius=typeof b=="function"?function(){return Math.sqrt(b.apply(this,arguments))}:(b=Math.sqrt(b),function(){return b});return this};
7017+pv.Layout.Pack.prototype.buildImplied=function(b){function c(o){var n=pv.Mark.stack;n.unshift(null);for(var m=0,r=o.length;m<r;m++){var s=o[m];if(!s.firstChild)s.radius=i.$radius.apply(i,(n[0]=s,n))}n.shift()}function d(o){var n=[];for(o=o.firstChild;o;o=o.nextSibling){if(o.firstChild)o.radius=d(o);o.n=o.p=o;n.push(o)}switch(b.order){case "ascending":n.sort(function(m,r){return m.radius-r.radius});break;case "descending":n.sort(function(m,r){return r.radius-m.radius});break;case "reverse":n.reverse();
7018+break}return f(n)}function f(o){function n(B){u=Math.min(B.x-B.radius,u);x=Math.max(B.x+B.radius,x);t=Math.min(B.y-B.radius,t);p=Math.max(B.y+B.radius,p)}function m(B,F){var H=B.n;B.n=F;F.p=B;F.n=H;H.p=F}function r(B,F){B.n=F;F.p=B}function s(B,F){var H=F.x-B.x,I=F.y-B.y;B=B.radius+F.radius;return B*B-H*H-I*I>0.0010}var u=Infinity,x=-Infinity,t=Infinity,p=-Infinity,v,w,y,z,C;v=o[0];v.x=-v.radius;v.y=0;n(v);if(o.length>1){w=o[1];w.x=w.radius;w.y=0;n(w);if(o.length>2){y=o[2];g(v,w,y);n(y);m(v,y);v.p=
7019+y;m(y,w);w=v.n;for(var A=3;A<o.length;A++){g(v,w,y=o[A]);var D=0,G=1,E=1;for(z=w.n;z!=w;z=z.n,G++)if(s(z,y)){D=1;break}if(D==1)for(C=v.p;C!=z.p;C=C.p,E++)if(s(C,y)){if(E<G){D=-1;z=C}break}if(D==0){m(v,y);w=y;n(y)}else if(D>0){r(v,z);w=z;A--}else if(D<0){r(z,w);v=z;A--}}}}v=(u+x)/2;w=(t+p)/2;for(A=y=0;A<o.length;A++){z=o[A];z.x-=v;z.y-=w;y=Math.max(y,z.radius+Math.sqrt(z.x*z.x+z.y*z.y))}return y+b.spacing}function g(o,n,m){var r=n.radius+m.radius,s=o.radius+m.radius,u=n.x-o.x;n=n.y-o.y;var x=Math.sqrt(u*
7020+u+n*n),t=(s*s+x*x-r*r)/(2*s*x);r=Math.acos(t);t=t*s;s=Math.sin(r)*s;u/=x;n/=x;m.x=o.x+t*u+s*n;m.y=o.y+t*n-s*u}function h(o,n,m,r){for(var s=o.firstChild;s;s=s.nextSibling){s.x+=o.x;s.y+=o.y;h(s,n,m,r)}o.x=n+r*o.x;o.y=m+r*o.y;o.radius*=r}if(!pv.Layout.Hierarchy.prototype.buildImplied.call(this,b)){var i=this,j=b.nodes,l=j[0];c(j);l.x=0;l.y=0;l.radius=d(l);j=this.width();var k=this.height(),q=1/Math.max(2*l.radius/j,2*l.radius/k);h(l,j/2,k/2,q)}};
7021+pv.Layout.Force=function(){pv.Layout.Network.call(this);this.link.lineWidth(function(b,c){return Math.sqrt(c.linkValue)*1.5});this.label.textAlign("center")};
7022+pv.Layout.Force.prototype=pv.extend(pv.Layout.Network).property("bound",Boolean).property("iterations",Number).property("dragConstant",Number).property("chargeConstant",Number).property("chargeMinDistance",Number).property("chargeMaxDistance",Number).property("chargeTheta",Number).property("springConstant",Number).property("springDamping",Number).property("springLength",Number);pv.Layout.Force.prototype.defaults=(new pv.Layout.Force).extend(pv.Layout.Network.prototype.defaults).dragConstant(0.1).chargeConstant(-40).chargeMinDistance(2).chargeMaxDistance(500).chargeTheta(0.9).springConstant(0.1).springDamping(0.3).springLength(20);
7023+pv.Layout.Force.prototype.buildImplied=function(b){function c(q){return q.fix?1:q.vx*q.vx+q.vy*q.vy}if(pv.Layout.Network.prototype.buildImplied.call(this,b)){if(b=b.$force){b.next=this.binds.$force;this.binds.$force=b}}else{for(var d=this,f=b.nodes,g=b.links,h=b.iterations,i=b.width,j=b.height,l=0,k;l<f.length;l++){k=f[l];if(isNaN(k.x))k.x=i/2+40*Math.random()-20;if(isNaN(k.y))k.y=j/2+40*Math.random()-20}k=pv.simulation(f);k.force(pv.Force.drag(b.dragConstant));k.force(pv.Force.charge(b.chargeConstant).domain(b.chargeMinDistance,
7024+b.chargeMaxDistance).theta(b.chargeTheta));k.force(pv.Force.spring(b.springConstant).damping(b.springDamping).length(b.springLength).links(g));k.constraint(pv.Constraint.position());b.bound&&k.constraint(pv.Constraint.bound().x(6,i-6).y(6,j-6));if(h==null){k.step();k.step();b.$force=this.binds.$force={next:this.binds.$force,nodes:f,min:1.0E-4*(g.length+1),sim:k};if(!this.$timer)this.$timer=setInterval(function(){for(var q=false,o=d.binds.$force;o;o=o.next)if(pv.max(o.nodes,c)>o.min){o.sim.step();
7025+q=true}q&&d.render()},42)}else for(l=0;l<h;l++)k.step()}};pv.Layout.Cluster=function(){pv.Layout.Hierarchy.call(this);var b,c=this.buildImplied;this.buildImplied=function(d){c.call(this,d);b=/^(top|bottom)$/.test(d.orient)?"step-before":/^(left|right)$/.test(d.orient)?"step-after":"linear"};this.link.interpolate(function(){return b})};
7026+pv.Layout.Cluster.prototype=pv.extend(pv.Layout.Hierarchy).property("group",Number).property("orient",String).property("innerRadius",Number).property("outerRadius",Number);pv.Layout.Cluster.prototype.defaults=(new pv.Layout.Cluster).extend(pv.Layout.Hierarchy.prototype.defaults).group(0).orient("top");
7027+pv.Layout.Cluster.prototype.buildImplied=function(b){if(!pv.Layout.Hierarchy.prototype.buildImplied.call(this,b)){var c=b.nodes[0],d=b.group,f,g,h=0,i=0.5-d/2,j=undefined;c.visitAfter(function(l){if(l.firstChild)l.depth=1+pv.max(l.childNodes,function(k){return k.depth});else{if(d&&j!=l.parentNode){j=l.parentNode;h+=d}h++;l.depth=0}});f=1/h;g=1/c.depth;j=undefined;c.visitAfter(function(l){if(l.firstChild)l.breadth=pv.mean(l.childNodes,function(k){return k.breadth});else{if(d&&j!=l.parentNode){j=l.parentNode;
7028+i+=d}l.breadth=f*i++}l.depth=1-l.depth*g});c.visitAfter(function(l){l.minBreadth=l.firstChild?l.firstChild.minBreadth:l.breadth-f/2;l.maxBreadth=l.firstChild?l.lastChild.maxBreadth:l.breadth+f/2});c.visitBefore(function(l){l.minDepth=l.parentNode?l.parentNode.maxDepth:0;l.maxDepth=l.parentNode?l.depth+c.depth:l.minDepth+2*c.depth});c.minDepth=-g;pv.Layout.Hierarchy.NodeLink.buildImplied.call(this,b)}};pv.Layout.Cluster.Fill=function(){pv.Layout.Cluster.call(this);pv.Layout.Hierarchy.Fill.constructor.call(this)};
7029+pv.Layout.Cluster.Fill.prototype=pv.extend(pv.Layout.Cluster);pv.Layout.Cluster.Fill.prototype.buildImplied=function(b){pv.Layout.Cluster.prototype.buildImplied.call(this,b)||pv.Layout.Hierarchy.Fill.buildImplied.call(this,b)};pv.Layout.Partition=function(){pv.Layout.Hierarchy.call(this)};pv.Layout.Partition.prototype=pv.extend(pv.Layout.Hierarchy).property("order",String).property("orient",String).property("innerRadius",Number).property("outerRadius",Number);
7030+pv.Layout.Partition.prototype.defaults=(new pv.Layout.Partition).extend(pv.Layout.Hierarchy.prototype.defaults).orient("top");pv.Layout.Partition.prototype.$size=function(){return 1};pv.Layout.Partition.prototype.size=function(b){this.$size=b;return this};
7031+pv.Layout.Partition.prototype.buildImplied=function(b){if(!pv.Layout.Hierarchy.prototype.buildImplied.call(this,b)){var c=this,d=b.nodes[0],f=pv.Mark.stack,g=0;f.unshift(null);d.visitAfter(function(i,j){if(j>g)g=j;i.size=i.firstChild?pv.sum(i.childNodes,function(l){return l.size}):c.$size.apply(c,(f[0]=i,f))});f.shift();switch(b.order){case "ascending":d.sort(function(i,j){return i.size-j.size});break;case "descending":d.sort(function(i,j){return j.size-i.size});break}var h=1/g;d.minBreadth=0;d.breadth=
7032+0.5;d.maxBreadth=1;d.visitBefore(function(i){for(var j=i.minBreadth,l=i.maxBreadth-j,k=i.firstChild;k;k=k.nextSibling){k.minBreadth=j;k.maxBreadth=j+=k.size/i.size*l;k.breadth=(j+k.minBreadth)/2}});d.visitAfter(function(i,j){i.minDepth=(j-1)*h;i.maxDepth=i.depth=j*h});pv.Layout.Hierarchy.NodeLink.buildImplied.call(this,b)}};pv.Layout.Partition.Fill=function(){pv.Layout.Partition.call(this);pv.Layout.Hierarchy.Fill.constructor.call(this)};pv.Layout.Partition.Fill.prototype=pv.extend(pv.Layout.Partition);
7033+pv.Layout.Partition.Fill.prototype.buildImplied=function(b){pv.Layout.Partition.prototype.buildImplied.call(this,b)||pv.Layout.Hierarchy.Fill.buildImplied.call(this,b)};pv.Layout.Arc=function(){pv.Layout.Network.call(this);var b,c,d,f=this.buildImplied;this.buildImplied=function(g){f.call(this,g);c=g.directed;b=g.orient=="radial"?"linear":"polar";d=g.orient=="right"||g.orient=="top"};this.link.data(function(g){var h=g.sourceNode;g=g.targetNode;return d!=(c||h.breadth<g.breadth)?[h,g]:[g,h]}).interpolate(function(){return b})};
7034+pv.Layout.Arc.prototype=pv.extend(pv.Layout.Network).property("orient",String).property("directed",Boolean);pv.Layout.Arc.prototype.defaults=(new pv.Layout.Arc).extend(pv.Layout.Network.prototype.defaults).orient("bottom");pv.Layout.Arc.prototype.sort=function(b){this.$sort=b;return this};
7035+pv.Layout.Arc.prototype.buildImplied=function(b){function c(m){switch(h){case "top":return-Math.PI/2;case "bottom":return Math.PI/2;case "left":return Math.PI;case "right":return 0;case "radial":return(m-0.25)*2*Math.PI}}function d(m){switch(h){case "top":case "bottom":return m*l;case "left":return 0;case "right":return l;case "radial":return l/2+q*Math.cos(c(m))}}function f(m){switch(h){case "top":return 0;case "bottom":return k;case "left":case "right":return m*k;case "radial":return k/2+q*Math.sin(c(m))}}
7036+if(!pv.Layout.Network.prototype.buildImplied.call(this,b)){var g=b.nodes,h=b.orient,i=this.$sort,j=pv.range(g.length),l=b.width,k=b.height,q=Math.min(l,k)/2;i&&j.sort(function(m,r){return i(g[m],g[r])});for(b=0;b<g.length;b++){var o=g[j[b]],n=o.breadth=(b+0.5)/g.length;o.x=d(n);o.y=f(n);o.midAngle=c(n)}}};
7037+pv.Layout.Horizon=function(){pv.Layout.call(this);var b=this,c,d,f,g,h,i,j=this.buildImplied;this.buildImplied=function(l){j.call(this,l);c=l.bands;d=l.mode;f=Math.round((d=="color"?0.5:1)*l.height);g=l.backgroundStyle;h=pv.ramp(g,l.negativeStyle).domain(0,c);i=pv.ramp(g,l.positiveStyle).domain(0,c)};c=(new pv.Panel).data(function(){return pv.range(c*2)}).overflow("hidden").height(function(){return f}).top(function(l){return d=="color"?(l&1)*f:0}).fillStyle(function(l){return l?null:g});this.band=
7038+(new pv.Mark).top(function(l,k){return d=="mirror"&&k&1?(k+1>>1)*f:null}).bottom(function(l,k){return d=="mirror"?k&1?null:(k+1>>1)*-f:(k&1||-1)*(k+1>>1)*f}).fillStyle(function(l,k){return(k&1?h:i)((k>>1)+1)});this.band.add=function(l){return b.add(pv.Panel).extend(c).add(l).extend(this)}};pv.Layout.Horizon.prototype=pv.extend(pv.Layout).property("bands",Number).property("mode",String).property("backgroundStyle",pv.color).property("positiveStyle",pv.color).property("negativeStyle",pv.color);
7039+pv.Layout.Horizon.prototype.defaults=(new pv.Layout.Horizon).extend(pv.Layout.prototype.defaults).bands(2).mode("offset").backgroundStyle("white").positiveStyle("#1f77b4").negativeStyle("#d62728");
7040+pv.Layout.Rollup=function(){pv.Layout.Network.call(this);var b=this,c,d,f=b.buildImplied;this.buildImplied=function(g){f.call(this,g);c=g.$rollup.nodes;d=g.$rollup.links};this.node.data(function(){return c}).size(function(g){return g.nodes.length*20});this.link.interpolate("polar").eccentricity(0.8);this.link.add=function(g){return b.add(pv.Panel).data(function(){return d}).add(g).extend(this)}};pv.Layout.Rollup.prototype=pv.extend(pv.Layout.Network).property("directed",Boolean);
7041+pv.Layout.Rollup.prototype.x=function(b){this.$x=pv.functor(b);return this};pv.Layout.Rollup.prototype.y=function(b){this.$y=pv.functor(b);return this};
7042+pv.Layout.Rollup.prototype.buildImplied=function(b){function c(r){return i[r]+","+j[r]}if(!pv.Layout.Network.prototype.buildImplied.call(this,b)){var d=b.nodes,f=b.links,g=b.directed,h=d.length,i=[],j=[],l=0,k={},q={},o=pv.Mark.stack,n={parent:this};o.unshift(null);for(var m=0;m<h;m++){n.index=m;o[0]=d[m];i[m]=this.$x.apply(n,o);j[m]=this.$y.apply(n,o)}o.shift();for(m=0;m<d.length;m++){h=c(m);o=k[h];if(!o){o=k[h]=pv.extend(d[m]);o.index=l++;o.x=i[m];o.y=j[m];o.nodes=[]}o.nodes.push(d[m])}for(m=0;m<
7043+f.length;m++){l=f[m].targetNode;d=k[c(f[m].sourceNode.index)];l=k[c(l.index)];h=!g&&d.index>l.index?l.index+","+d.index:d.index+","+l.index;(o=q[h])||(o=q[h]={sourceNode:d,targetNode:l,linkValue:0,links:[]});o.links.push(f[m]);o.linkValue+=f[m].linkValue}b.$rollup={nodes:pv.values(k),links:pv.values(q)}}};
7044+pv.Layout.Matrix=function(){pv.Layout.Network.call(this);var b,c,d,f,g,h=this.buildImplied;this.buildImplied=function(i){h.call(this,i);b=i.nodes.length;c=i.width/b;d=i.height/b;f=i.$matrix.labels;g=i.$matrix.pairs};this.link.data(function(){return g}).left(function(){return c*(this.index%b)}).top(function(){return d*Math.floor(this.index/b)}).width(function(){return c}).height(function(){return d}).lineWidth(1.5).strokeStyle("#fff").fillStyle(function(i){return i.linkValue?"#555":"#eee"}).parent=
7045+this;delete this.link.add;this.label.data(function(){return f}).left(function(){return this.index&1?c*((this.index>>1)+0.5):null}).top(function(){return this.index&1?null:d*((this.index>>1)+0.5)}).textMargin(4).textAlign(function(){return this.index&1?"left":"right"}).textAngle(function(){return this.index&1?-Math.PI/2:0});delete this.node};pv.Layout.Matrix.prototype=pv.extend(pv.Layout.Network).property("directed",Boolean);pv.Layout.Matrix.prototype.sort=function(b){this.$sort=b;return this};
7046+pv.Layout.Matrix.prototype.buildImplied=function(b){if(!pv.Layout.Network.prototype.buildImplied.call(this,b)){var c=b.nodes,d=b.links,f=this.$sort,g=c.length,h=pv.range(g),i=[],j=[],l={};b.$matrix={labels:i,pairs:j};f&&h.sort(function(m,r){return f(c[m],c[r])});for(var k=0;k<g;k++)for(var q=0;q<g;q++){var o=h[k],n=h[q];j.push(l[o+"."+n]={row:k,col:q,sourceNode:c[o],targetNode:c[n],linkValue:0})}for(k=0;k<g;k++){o=h[k];i.push(c[o],c[o])}for(k=0;k<d.length;k++){i=d[k];g=i.sourceNode.index;h=i.targetNode.index;
7047+i=i.linkValue;l[g+"."+h].linkValue+=i;b.directed||(l[h+"."+g].linkValue+=i)}}};
7048+pv.Layout.Bullet=function(){pv.Layout.call(this);var b=this,c=b.buildImplied,d=b.x=pv.Scale.linear(),f,g,h,i,j;this.buildImplied=function(l){c.call(this,j=l);f=l.orient;g=/^left|right$/.test(f);h=pv.ramp("#bbb","#eee").domain(0,Math.max(1,j.ranges.length-1));i=pv.ramp("steelblue","lightsteelblue").domain(0,Math.max(1,j.measures.length-1))};(this.range=new pv.Mark).data(function(){return j.ranges}).reverse(true).left(function(){return f=="left"?0:null}).top(function(){return f=="top"?0:null}).right(function(){return f==
7049+"right"?0:null}).bottom(function(){return f=="bottom"?0:null}).width(function(l){return g?d(l):null}).height(function(l){return g?null:d(l)}).fillStyle(function(){return h(this.index)}).antialias(false).parent=b;(this.measure=new pv.Mark).extend(this.range).data(function(){return j.measures}).left(function(){return f=="left"?0:g?null:this.parent.width()/3.25}).top(function(){return f=="top"?0:g?this.parent.height()/3.25:null}).right(function(){return f=="right"?0:g?null:this.parent.width()/3.25}).bottom(function(){return f==
7050+"bottom"?0:g?this.parent.height()/3.25:null}).fillStyle(function(){return i(this.index)}).parent=b;(this.marker=new pv.Mark).data(function(){return j.markers}).left(function(l){return f=="left"?d(l):g?null:this.parent.width()/2}).top(function(l){return f=="top"?d(l):g?this.parent.height()/2:null}).right(function(l){return f=="right"?d(l):null}).bottom(function(l){return f=="bottom"?d(l):null}).strokeStyle("black").shape("bar").angle(function(){return g?0:Math.PI/2}).parent=b;(this.tick=new pv.Mark).data(function(){return d.ticks(7)}).left(function(l){return f==
7051+"left"?d(l):null}).top(function(l){return f=="top"?d(l):null}).right(function(l){return f=="right"?d(l):g?null:-6}).bottom(function(l){return f=="bottom"?d(l):g?-8:null}).height(function(){return g?6:null}).width(function(){return g?null:6}).parent=b};pv.Layout.Bullet.prototype=pv.extend(pv.Layout).property("orient",String).property("ranges").property("markers").property("measures").property("maximum",Number);pv.Layout.Bullet.prototype.defaults=(new pv.Layout.Bullet).extend(pv.Layout.prototype.defaults).orient("left").ranges([]).markers([]).measures([]);
7052+pv.Layout.Bullet.prototype.buildImplied=function(b){pv.Layout.prototype.buildImplied.call(this,b);var c=this.parent[/^left|right$/.test(b.orient)?"width":"height"]();b.maximum=b.maximum||pv.max([].concat(b.ranges,b.markers,b.measures));this.x.domain(0,b.maximum).range(0,c)};pv.Behavior={};
7053+pv.Behavior.drag=function(){function b(l){g=this.index;f=this.scene;var k=this.mouse();i=((h=l).fix=pv.vector(l.x,l.y)).minus(k);j={x:this.parent.width()-(l.dx||0),y:this.parent.height()-(l.dy||0)};f.mark.context(f,g,function(){this.render()});pv.Mark.dispatch("dragstart",f,g)}function c(){if(f){f.mark.context(f,g,function(){var l=this.mouse();h.x=h.fix.x=Math.max(0,Math.min(i.x+l.x,j.x));h.y=h.fix.y=Math.max(0,Math.min(i.y+l.y,j.y));this.render()});pv.Mark.dispatch("drag",f,g)}}function d(){if(f){h.fix=
7054+null;f.mark.context(f,g,function(){this.render()});pv.Mark.dispatch("dragend",f,g);f=null}}var f,g,h,i,j;pv.listen(window,"mousemove",c);pv.listen(window,"mouseup",d);return b};
7055+pv.Behavior.point=function(b){function c(k,q){k=k[q];q={cost:Infinity};for(var o=0,n=k.visible&&k.children.length;o<n;o++){var m=k.children[o],r=m.mark,s;if(r.type=="panel"){r.scene=m;for(var u=0,x=m.length;u<x;u++){r.index=u;s=c(m,u);if(s.cost<q.cost)q=s}delete r.scene;delete r.index}else if(r.$handlers.point){r=r.mouse();u=0;for(x=m.length;u<x;u++){var t=m[u];s=r.x-t.left-(t.width||0)/2;t=r.y-t.top-(t.height||0)/2;var p=i*s*s+j*t*t;if(p<q.cost){q.distance=s*s+t*t;q.cost=p;q.scene=m;q.index=u}}}}return q}
7056+function d(){var k=c(this.scene,this.index);if(k.cost==Infinity||k.distance>l)k=null;if(g){if(k&&g.scene==k.scene&&g.index==k.index)return;pv.Mark.dispatch("unpoint",g.scene,g.index)}if(g=k){pv.Mark.dispatch("point",k.scene,k.index);pv.listen(this.root.canvas(),"mouseout",f)}}function f(k){if(g&&!pv.ancestor(this,k.relatedTarget)){pv.Mark.dispatch("unpoint",g.scene,g.index);g=null}}var g,h=null,i=1,j=1,l=arguments.length?b*b:900;d.collapse=function(k){if(arguments.length){h=String(k);switch(h){case "y":i=
7057+1;j=0;break;case "x":i=0;j=1;break;default:j=i=1;break}return d}return h};return d};
7058+pv.Behavior.select=function(){function b(j){g=this.index;f=this.scene;i=this.mouse();h=j;h.x=i.x;h.y=i.y;h.dx=h.dy=0;pv.Mark.dispatch("selectstart",f,g)}function c(){if(f){f.mark.context(f,g,function(){var j=this.mouse();h.x=Math.max(0,Math.min(i.x,j.x));h.y=Math.max(0,Math.min(i.y,j.y));h.dx=Math.min(this.width(),Math.max(j.x,i.x))-h.x;h.dy=Math.min(this.height(),Math.max(j.y,i.y))-h.y;this.render()});pv.Mark.dispatch("select",f,g)}}function d(){if(f){pv.Mark.dispatch("selectend",f,g);f=null}}var f,
7059+g,h,i;pv.listen(window,"mousemove",c);pv.listen(window,"mouseup",d);return b};
7060+pv.Behavior.resize=function(b){function c(l){h=this.index;g=this.scene;j=this.mouse();i=l;switch(b){case "left":j.x=i.x+i.dx;break;case "right":j.x=i.x;break;case "top":j.y=i.y+i.dy;break;case "bottom":j.y=i.y;break}pv.Mark.dispatch("resizestart",g,h)}function d(){if(g){g.mark.context(g,h,function(){var l=this.mouse();i.x=Math.max(0,Math.min(j.x,l.x));i.y=Math.max(0,Math.min(j.y,l.y));i.dx=Math.min(this.parent.width(),Math.max(l.x,j.x))-i.x;i.dy=Math.min(this.parent.height(),Math.max(l.y,j.y))-i.y;
7061+this.render()});pv.Mark.dispatch("resize",g,h)}}function f(){if(g){pv.Mark.dispatch("resizeend",g,h);g=null}}var g,h,i,j;pv.listen(window,"mousemove",d);pv.listen(window,"mouseup",f);return c};
7062+pv.Behavior.pan=function(){function b(){g=this.index;f=this.scene;i=pv.vector(pv.event.pageX,pv.event.pageY);h=this.transform();j=1/(h.k*this.scale);if(l)l={x:(1-h.k)*this.width(),y:(1-h.k)*this.height()}}function c(){if(f){f.mark.context(f,g,function(){var k=h.translate((pv.event.pageX-i.x)*j,(pv.event.pageY-i.y)*j);if(l){k.x=Math.max(l.x,Math.min(0,k.x));k.y=Math.max(l.y,Math.min(0,k.y))}this.transform(k).render()});pv.Mark.dispatch("pan",f,g)}}function d(){f=null}var f,g,h,i,j,l;b.bound=function(k){if(arguments.length){l=
7063+Boolean(k);return this}return Boolean(l)};pv.listen(window,"mousemove",c);pv.listen(window,"mouseup",d);return b};
7064+pv.Behavior.zoom=function(b){function c(){var f=this.mouse(),g=pv.event.wheel*b;f=this.transform().translate(f.x,f.y).scale(g<0?1E3/(1E3-g):(1E3+g)/1E3).translate(-f.x,-f.y);if(d){f.k=Math.max(1,f.k);f.x=Math.max((1-f.k)*this.width(),Math.min(0,f.x));f.y=Math.max((1-f.k)*this.height(),Math.min(0,f.y))}this.transform(f).render();pv.Mark.dispatch("zoom",this.scene,this.index)}var d;arguments.length||(b=1/48);c.bound=function(f){if(arguments.length){d=Boolean(f);return this}return Boolean(d)};return c};
7065+pv.Geo=function(){};
7066+pv.Geo.projections={mercator:{project:function(b){return{x:b.lng/180,y:b.lat>85?1:b.lat<-85?-1:Math.log(Math.tan(Math.PI/4+pv.radians(b.lat)/2))/Math.PI}},invert:function(b){return{lng:b.x*180,lat:pv.degrees(2*Math.atan(Math.exp(b.y*Math.PI))-Math.PI/2)}}},"gall-peters":{project:function(b){return{x:b.lng/180,y:Math.sin(pv.radians(b.lat))}},invert:function(b){return{lng:b.x*180,lat:pv.degrees(Math.asin(b.y))}}},sinusoidal:{project:function(b){return{x:pv.radians(b.lng)*Math.cos(pv.radians(b.lat))/Math.PI,
7067+y:b.lat/90}},invert:function(b){return{lng:pv.degrees(b.x*Math.PI/Math.cos(b.y*Math.PI/2)),lat:b.y*90}}},aitoff:{project:function(b){var c=pv.radians(b.lng);b=pv.radians(b.lat);var d=Math.acos(Math.cos(b)*Math.cos(c/2));return{x:2*(d?Math.cos(b)*Math.sin(c/2)*d/Math.sin(d):0)/Math.PI,y:2*(d?Math.sin(b)*d/Math.sin(d):0)/Math.PI}},invert:function(b){var c=b.y*Math.PI/2;return{lng:pv.degrees(b.x*Math.PI/2/Math.cos(c)),lat:pv.degrees(c)}}},hammer:{project:function(b){var c=pv.radians(b.lng);b=pv.radians(b.lat);
7068+var d=Math.sqrt(1+Math.cos(b)*Math.cos(c/2));return{x:2*Math.SQRT2*Math.cos(b)*Math.sin(c/2)/d/3,y:Math.SQRT2*Math.sin(b)/d/1.5}},invert:function(b){var c=b.x*3;b=b.y*1.5;var d=Math.sqrt(1-c*c/16-b*b/4);return{lng:pv.degrees(2*Math.atan2(d*c,2*(2*d*d-1))),lat:pv.degrees(Math.asin(d*b))}}},identity:{project:function(b){return{x:b.lng/180,y:b.lat/90}},invert:function(b){return{lng:b.x*180,lat:b.y*90}}}};
7069+pv.Geo.scale=function(b){function c(m){if(!o||m.lng!=o.lng||m.lat!=o.lat){o=m;m=d(m);n={x:l(m.x),y:k(m.y)}}return n}function d(m){return j.project({lng:m.lng-q.lng,lat:m.lat})}function f(m){m=j.invert(m);m.lng+=q.lng;return m}var g={x:0,y:0},h={x:1,y:1},i=[],j=pv.Geo.projections.identity,l=pv.Scale.linear(-1,1).range(0,1),k=pv.Scale.linear(-1,1).range(1,0),q={lng:0,lat:0},o,n;c.x=function(m){return c(m).x};c.y=function(m){return c(m).y};c.ticks={lng:function(m){var r;if(i.length>1){var s=pv.Scale.linear();
7070+if(m==undefined)m=10;r=s.domain(i,function(u){return u.lat}).ticks(m);m=s.domain(i,function(u){return u.lng}).ticks(m)}else{r=pv.range(-80,81,10);m=pv.range(-180,181,10)}return m.map(function(u){return r.map(function(x){return{lat:x,lng:u}})})},lat:function(m){return pv.transpose(c.ticks.lng(m))}};c.invert=function(m){return f({x:l.invert(m.x),y:k.invert(m.y)})};c.domain=function(m,r){if(arguments.length){i=m instanceof Array?arguments.length>1?pv.map(m,r):m:Array.prototype.slice.call(arguments);
7071+if(i.length>1){var s=i.map(function(x){return x.lng}),u=i.map(function(x){return x.lat});q={lng:(pv.max(s)+pv.min(s))/2,lat:(pv.max(u)+pv.min(u))/2};s=i.map(d);l.domain(s,function(x){return x.x});k.domain(s,function(x){return x.y})}else{q={lng:0,lat:0};l.domain(-1,1);k.domain(-1,1)}o=null;return this}return i};c.range=function(m,r){if(arguments.length){if(typeof m=="object"){g={x:Number(m.x),y:Number(m.y)};h={x:Number(r.x),y:Number(r.y)}}else{g={x:0,y:0};h={x:Number(m),y:Number(r)}}l.range(g.x,h.x);
7072+k.range(h.y,g.y);o=null;return this}return[g,h]};c.projection=function(m){if(arguments.length){j=typeof m=="string"?pv.Geo.projections[m]||pv.Geo.projections.identity:m;return this.domain(i)}return m};c.by=function(m){function r(){return c(m.apply(this,arguments))}for(var s in c)r[s]=c[s];return r};arguments.length&&c.projection(b);return c};
7073hunk ./src/allmydata/web/root.py 167
7074         self.child_named = FileHandler(client)
7075         self.child_status = status.Status(client.get_history())
7076         self.child_statistics = status.Statistics(client.stats_provider)
7077+        def f(name):
7078+            return nevow_File(resource_filename('allmydata.web', name))
7079+        self.putChild("jquery.js", f("jquery.js"))
7080+        self.putChild("download_status_timeline.js", f("download_status_timeline.js"))
7081+        self.putChild("protovis-r3.2.js", f("protovis-r3.2.js"))
7082 
7083     def child_helper_status(self, ctx):
7084         # the Helper isn't attached until after the Tub starts, so this child
7085hunk ./src/allmydata/web/status.py 334
7086         d.addCallback(_render)
7087         return d
7088 
7089+def tfmt(when):
7090+    #return when * 1000.0 # stupid JS timestamps
7091+    return "%.6f" % when
7092+    # the timeline markers represent UTC. To make these events line up, we
7093+    # must supply the "Z" suffix.
7094+    #return "%.2f" % (1000*when)
7095+    t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(when))
7096+    fract = "%.6f" % (when % 1.0)
7097+    if fract.startswith("0."):
7098+        fract = fract[2:]
7099+    return t+"."+fract+"Z"
7100+
7101 class DownloadStatusPage(DownloadResultsRendererMixin, rend.Page):
7102     docFactory = getxmlfile("download-status.xhtml")
7103 
7104hunk ./src/allmydata/web/status.py 353
7105         rend.Page.__init__(self, data)
7106         self.download_status = data
7107 
7108+    def child_timeline(self, ctx):
7109+        return DownloadStatusTimelinePage(self.download_status)
7110+
7111     def download_results(self):
7112         return defer.maybeDeferred(self.download_status.get_results)
7113 
7114hunk ./src/allmydata/web/status.py 362
7115     def relative_time(self, t):
7116         if t is None:
7117             return t
7118-        if self.download_status.started is not None:
7119-            return t - self.download_status.started
7120+        if self.download_status.first_timestamp is not None:
7121+            return t - self.download_status.first_timestamp
7122         return t
7123     def short_relative_time(self, t):
7124         t = self.relative_time(t)
7125hunk ./src/allmydata/web/status.py 371
7126             return ""
7127         return "+%.6fs" % t
7128 
7129-    def renderHTTP(self, ctx):
7130-        req = inevow.IRequest(ctx)
7131-        t = get_arg(req, "t")
7132-        if t == "json":
7133-            return self.json(req)
7134-        return rend.Page.renderHTTP(self, ctx)
7135+    def _find_overlap(self, events, start_key, end_key):
7136+        # given a list of event dicts, return a new list in which each event
7137+        # has an extra "row" key (an int, starting at 0). This is a hint to
7138+        # our JS frontend about how to overlap the parts of the graph it is
7139+        # drawing.
7140+
7141+        # we must always make a copy, since we're going to be adding "row"
7142+        # keys and don't want to change the original objects. If we're
7143+        # stringifying serverids, we'll also be changing the serverid keys.
7144+        new_events = []
7145+        rows = []
7146+        for ev in events:
7147+            ev = ev.copy()
7148+            if "serverid" in ev:
7149+                ev["serverid"] = base32.b2a(ev["serverid"])
7150+            # find an empty slot in the rows
7151+            free_slot = None
7152+            for row,finished in enumerate(rows):
7153+                if finished is not None:
7154+                    if ev[start_key] > finished:
7155+                        free_slot = row
7156+                        break
7157+            if free_slot is None:
7158+                free_slot = len(rows)
7159+                rows.append(ev[end_key])
7160+            else:
7161+                rows[free_slot] = ev[end_key]
7162+            ev["row"] = free_slot
7163+            new_events.append(ev)
7164+        return new_events
7165 
7166hunk ./src/allmydata/web/status.py 402
7167-    def json(self, req):
7168-        req.setHeader("content-type", "text/plain")
7169-        data = {}
7170-        dyhb_events = []
7171-        for serverid,requests in self.download_status.dyhb_requests.iteritems():
7172-            for req in requests:
7173-                dyhb_events.append( (base32.b2a(serverid),) + req )
7174-        dyhb_events.sort(key=lambda req: req[1])
7175-        data["dyhb"] = dyhb_events
7176-        request_events = []
7177-        for serverid,requests in self.download_status.requests.iteritems():
7178-            for req in requests:
7179-                request_events.append( (base32.b2a(serverid),) + req )
7180-        request_events.sort(key=lambda req: (req[4],req[1]))
7181-        data["requests"] = request_events
7182-        data["segment"] = self.download_status.segment_events
7183-        data["read"] = self.download_status.read_events
7184+    def _find_overlap_requests(self, events):
7185+        """We compute a three-element 'row tuple' for each event: (serverid,
7186+        shnum, row). All elements are ints. The first is a mapping from
7187+        serverid to group number, the second is a mapping from shnum to
7188+        subgroup number. The third is a row within the subgroup.
7189+
7190+        We also return a list of lists of rowcounts, so renderers can decide
7191+        how much vertical space to give to each row.
7192+        """
7193+
7194+        serverid_to_group = {}
7195+        groupnum_to_rows = {} # maps groupnum to a table of rows. Each table
7196+                              # is a list with an element for each row number
7197+                              # (int starting from 0) that contains a
7198+                              # finish_time, indicating that the row is empty
7199+                              # beyond that time. If finish_time is None, it
7200+                              # indicate a response that has not yet
7201+                              # completed, so the row cannot be reused.
7202+        new_events = []
7203+        for ev in events:
7204+            # DownloadStatus promises to give us events in temporal order
7205+            ev = ev.copy()
7206+            ev["serverid"] = base32.b2a(ev["serverid"])
7207+            if ev["serverid"] not in serverid_to_group:
7208+                groupnum = len(serverid_to_group)
7209+                serverid_to_group[ev["serverid"]] = groupnum
7210+            groupnum = serverid_to_group[ev["serverid"]]
7211+            if groupnum not in groupnum_to_rows:
7212+                groupnum_to_rows[groupnum] = []
7213+            rows = groupnum_to_rows[groupnum]
7214+            # find an empty slot in the rows
7215+            free_slot = None
7216+            for row,finished in enumerate(rows):
7217+                if finished is not None:
7218+                    if ev["start_time"] > finished:
7219+                        free_slot = row
7220+                        break
7221+            if free_slot is None:
7222+                free_slot = len(rows)
7223+                rows.append(ev["finish_time"])
7224+            else:
7225+                rows[free_slot] = ev["finish_time"]
7226+            ev["row"] = (groupnum, free_slot)
7227+            new_events.append(ev)
7228+        # maybe also return serverid_to_group, groupnum_to_rows, and some
7229+        # indication of the highest finish_time
7230+        #
7231+        # actually, return the highest rownum for each groupnum
7232+        highest_rownums = [len(groupnum_to_rows[groupnum])
7233+                           for groupnum in range(len(serverid_to_group))]
7234+        return new_events, highest_rownums
7235+
7236+    def child_timeline_parameters(self, ctx):
7237+        ds = self.download_status
7238+        d = { "start": tfmt(ds.started),
7239+              "end": tfmt(ds.started+2.0),
7240+              }
7241+        return simplejson.dumps(d, indent=1) + "\n"
7242+
7243+    def child_event_json(self, ctx):
7244+        inevow.IRequest(ctx).setHeader("content-type", "text/plain")
7245+        data = { } # this will be returned to the GET
7246+        ds = self.download_status
7247+
7248+        data["read"] = self._find_overlap(ds.read_events,
7249+                                          "start_time", "finish_time")
7250+        data["segment"] = self._find_overlap(ds.segment_events,
7251+                                             "start_time", "finish_time")
7252+        data["dyhb"] = self._find_overlap(ds.dyhb_requests,
7253+                                          "start_time", "finish_time")
7254+        data["block"],data["block_rownums"] = self._find_overlap_requests(ds.block_requests)
7255+
7256+        servernums = {}
7257+        serverid_strings = {}
7258+        for d_ev in data["dyhb"]:
7259+            if d_ev["serverid"] not in servernums:
7260+                servernum = len(servernums)
7261+                servernums[d_ev["serverid"]] = servernum
7262+                #title= "%s: %s" % ( ",".join([str(shnum) for shnum in shnums]))
7263+                serverid_strings[servernum] = d_ev["serverid"][:4]
7264+        data["server_info"] = dict([(serverid, {"num": servernums[serverid],
7265+                                                "color": self.color(base32.a2b(serverid)),
7266+                                                "short": serverid_strings[servernum],
7267+                                                })
7268+                                   for serverid in servernums.keys()])
7269+        data["num_serverids"] = len(serverid_strings)
7270+        data["serverids"] = serverid_strings;
7271+        data["bounds"] = {"min": ds.first_timestamp,
7272+                          "max": ds.last_timestamp,
7273+                          }
7274+        # for testing
7275+        ## data["bounds"]["max"] = tfmt(max([d_ev["finish_time"]
7276+        ##                                   for d_ev in data["dyhb"]
7277+        ##                                   if d_ev["finish_time"] is not None]
7278+        ##                                  ))
7279         return simplejson.dumps(data, indent=1) + "\n"
7280 
7281hunk ./src/allmydata/web/status.py 499
7282+    def render_timeline_link(self, ctx, data):
7283+        from nevow import url
7284+        return T.a(href=url.URL.fromContext(ctx).child("timeline"))["timeline"]
7285+
7286+    def _rate_and_time(self, bytes, seconds):
7287+        time_s = self.render_time(None, seconds)
7288+        if seconds != 0:
7289+            rate = self.render_rate(None, 1.0 * bytes / seconds)
7290+            return T.span(title=rate)[time_s]
7291+        return T.span[time_s]
7292+
7293     def render_events(self, ctx, data):
7294         if not self.download_status.storage_index:
7295             return
7296hunk ./src/allmydata/web/status.py 519
7297         t = T.table(class_="status-download-events")
7298         t[T.tr[T.td["serverid"], T.td["sent"], T.td["received"],
7299                T.td["shnums"], T.td["RTT"]]]
7300-        dyhb_events = []
7301-        for serverid,requests in self.download_status.dyhb_requests.iteritems():
7302-            for req in requests:
7303-                dyhb_events.append( (serverid,) + req )
7304-        dyhb_events.sort(key=lambda req: req[1])
7305-        for d_ev in dyhb_events:
7306-            (serverid, sent, shnums, received) = d_ev
7307+        for d_ev in self.download_status.dyhb_requests:
7308+            serverid = d_ev["serverid"]
7309+            sent = d_ev["start_time"]
7310+            shnums = d_ev["response_shnums"]
7311+            received = d_ev["finish_time"]
7312             serverid_s = idlib.shortnodeid_b2a(serverid)
7313             rtt = None
7314             if received is not None:
7315hunk ./src/allmydata/web/status.py 542
7316                T.td["time"], T.td["decrypttime"], T.td["pausedtime"],
7317                T.td["speed"]]]
7318         for r_ev in self.download_status.read_events:
7319-            (start, length, requesttime, finishtime, bytes, decrypt, paused) = r_ev
7320-            if finishtime is not None:
7321-                rtt = finishtime - requesttime - paused
7322+            start = r_ev["start"]
7323+            length = r_ev["length"]
7324+            bytes = r_ev["bytes_returned"]
7325+            decrypt_time = ""
7326+            if bytes:
7327+                decrypt_time = self._rate_and_time(bytes, r_ev["decrypt_time"])
7328+            speed, rtt = "",""
7329+            if r_ev["finish_time"] is not None:
7330+                rtt = r_ev["finish_time"] - r_ev["start_time"] - r_ev["paused_time"]
7331                 speed = self.render_rate(None, compute_rate(bytes, rtt))
7332                 rtt = self.render_time(None, rtt)
7333hunk ./src/allmydata/web/status.py 553
7334-                decrypt = self.render_time(None, decrypt)
7335-                paused = self.render_time(None, paused)
7336-            else:
7337-                speed, rtt, decrypt, paused = "","","",""
7338+            paused = self.render_time(None, r_ev["paused_time"])
7339+
7340             t[T.tr[T.td["[%d:+%d]" % (start, length)],
7341hunk ./src/allmydata/web/status.py 556
7342-                   T.td[srt(requesttime)], T.td[srt(finishtime)],
7343-                   T.td[bytes], T.td[rtt], T.td[decrypt], T.td[paused],
7344+                   T.td[srt(r_ev["start_time"])], T.td[srt(r_ev["finish_time"])],
7345+                   T.td[bytes], T.td[rtt],
7346+                   T.td[decrypt_time], T.td[paused],
7347                    T.td[speed],
7348                    ]]
7349         l["Read Events:", t]
7350hunk ./src/allmydata/web/status.py 564
7351 
7352         t = T.table(class_="status-download-events")
7353-        t[T.tr[T.td["type"], T.td["segnum"], T.td["when"], T.td["range"],
7354+        t[T.tr[T.td["segnum"], T.td["start"], T.td["active"], T.td["finish"],
7355+               T.td["range"],
7356                T.td["decodetime"], T.td["segtime"], T.td["speed"]]]
7357hunk ./src/allmydata/web/status.py 567
7358-        reqtime = (None, None)
7359         for s_ev in self.download_status.segment_events:
7360hunk ./src/allmydata/web/status.py 568
7361-            (etype, segnum, when, segstart, seglen, decodetime) = s_ev
7362-            if etype == "request":
7363-                t[T.tr[T.td["request"], T.td["seg%d" % segnum],
7364-                       T.td[srt(when)]]]
7365-                reqtime = (segnum, when)
7366-            elif etype == "delivery":
7367-                if reqtime[0] == segnum:
7368-                    segtime = when - reqtime[1]
7369+            range_s = ""
7370+            segtime_s = ""
7371+            speed = ""
7372+            decode_time = ""
7373+            if s_ev["finish_time"] is not None:
7374+                if s_ev["success"]:
7375+                    segtime = s_ev["finish_time"] - s_ev["active_time"]
7376+                    segtime_s = self.render_time(None, segtime)
7377+                    seglen = s_ev["segment_length"]
7378+                    range_s = "[%d:+%d]" % (s_ev["segment_start"], seglen)
7379                     speed = self.render_rate(None, compute_rate(seglen, segtime))
7380hunk ./src/allmydata/web/status.py 579
7381-                    segtime = self.render_time(None, segtime)
7382+                    decode_time = self._rate_and_time(seglen, s_ev["decode_time"])
7383                 else:
7384hunk ./src/allmydata/web/status.py 581
7385-                    segtime, speed = "", ""
7386-                t[T.tr[T.td["delivery"], T.td["seg%d" % segnum],
7387-                       T.td[srt(when)],
7388-                       T.td["[%d:+%d]" % (segstart, seglen)],
7389-                       T.td[self.render_time(None,decodetime)],
7390-                       T.td[segtime], T.td[speed]]]
7391-            elif etype == "error":
7392-                t[T.tr[T.td["error"], T.td["seg%d" % segnum]]]
7393+                    # error
7394+                    range_s = "error"
7395+            else:
7396+                # not finished yet
7397+                pass
7398+
7399+            t[T.tr[T.td["seg%d" % s_ev["segment_number"]],
7400+                   T.td[srt(s_ev["start_time"])],
7401+                   T.td[srt(s_ev["active_time"])],
7402+                   T.td[srt(s_ev["finish_time"])],
7403+                   T.td[range_s],
7404+                   T.td[decode_time],
7405+                   T.td[segtime_s], T.td[speed]]]
7406         l["Segment Events:", t]
7407 
7408         t = T.table(border="1")
7409hunk ./src/allmydata/web/status.py 598
7410         t[T.tr[T.td["serverid"], T.td["shnum"], T.td["range"],
7411-               T.td["txtime"], T.td["rxtime"], T.td["received"], T.td["RTT"]]]
7412-        reqtime = (None, None)
7413-        request_events = []
7414-        for serverid,requests in self.download_status.requests.iteritems():
7415-            for req in requests:
7416-                request_events.append( (serverid,) + req )
7417-        request_events.sort(key=lambda req: (req[4],req[1]))
7418-        for r_ev in request_events:
7419-            (peerid, shnum, start, length, sent, receivedlen, received) = r_ev
7420+               T.td["txtime"], T.td["rxtime"],
7421+               T.td["received"], T.td["RTT"]]]
7422+        for r_ev in self.download_status.block_requests:
7423             rtt = None
7424hunk ./src/allmydata/web/status.py 602
7425-            if received is not None:
7426-                rtt = received - sent
7427-            peerid_s = idlib.shortnodeid_b2a(peerid)
7428-            t[T.tr(style="background: %s" % self.color(peerid))[
7429-                T.td[peerid_s], T.td[shnum],
7430-                T.td["[%d:+%d]" % (start, length)],
7431-                T.td[srt(sent)], T.td[srt(received)], T.td[receivedlen],
7432+            if r_ev["finish_time"] is not None:
7433+                rtt = r_ev["finish_time"] - r_ev["start_time"]
7434+            serverid_s = idlib.shortnodeid_b2a(r_ev["serverid"])
7435+            t[T.tr(style="background: %s" % self.color(r_ev["serverid"]))[
7436+                T.td[serverid_s], T.td[r_ev["shnum"]],
7437+                T.td["[%d:+%d]" % (r_ev["start"], r_ev["length"])],
7438+                T.td[srt(r_ev["start_time"])], T.td[srt(r_ev["finish_time"])],
7439+                T.td[r_ev["response_length"] or ""],
7440                 T.td[self.render_time(None, rtt)],
7441                 ]]
7442         l["Requests:", t]
7443hunk ./src/allmydata/web/status.py 660
7444     def render_status(self, ctx, data):
7445         return data.get_status()
7446 
7447+class DownloadStatusTimelinePage(rend.Page):
7448+    docFactory = getxmlfile("download-status-timeline.xhtml")
7449+
7450+    def render_started(self, ctx, data):
7451+        TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
7452+        started_s = time.strftime(TIME_FORMAT,
7453+                                  time.localtime(data.get_started()))
7454+        return started_s + " (%s)" % data.get_started()
7455+
7456+    def render_si(self, ctx, data):
7457+        si_s = base32.b2a_or_none(data.get_storage_index())
7458+        if si_s is None:
7459+            si_s = "(None)"
7460+        return si_s
7461+
7462+    def render_helper(self, ctx, data):
7463+        return {True: "Yes",
7464+                False: "No"}[data.using_helper()]
7465+
7466+    def render_total_size(self, ctx, data):
7467+        size = data.get_size()
7468+        if size is None:
7469+            return "(unknown)"
7470+        return size
7471+
7472+    def render_progress(self, ctx, data):
7473+        progress = data.get_progress()
7474+        # TODO: make an ascii-art bar
7475+        return "%.1f%%" % (100.0 * progress)
7476+
7477+    def render_status(self, ctx, data):
7478+        return data.get_status()
7479+
7480 class RetrieveStatusPage(rend.Page, RateAndTimeMixin):
7481     docFactory = getxmlfile("retrieve-status.xhtml")
7482 
7483}
7484
7485Context:
7486
7487[_auto_deps.py: change pycrypto version requirement to avoid https://bugs.launchpad.net/pycrypto/+bug/620253
7488david-sarah@jacaranda.org**20100829230038
7489 Ignore-this: e58f98ef262444067fc4b31fad23e40b
7490] 
7491[web: refactor rate computation, fixes #1166
7492francois@ctrlaltdel.ch**20100815141933
7493 Ignore-this: d25491858e137894142eaa67c75b0439
7494] 
7495[docs: update NEWS a bit about New-Downloader
7496zooko@zooko.com**20100819021446
7497 Ignore-this: 31a6e2fb0a6e3d19f73481e99070da7a
7498] 
7499[misc: add benchmarking tool for spans
7500zooko@zooko.com**20100819021420
7501 Ignore-this: 569327a1908a07e5fb634526bed515b2
7502] 
7503[docs: doc of the download status page
7504zooko@zooko.com**20100814054117
7505 Ignore-this: a82ec33da3c39a7c0d47a7a6b5f81bbb
7506 ref: http://tahoe-lafs.org/trac/tahoe-lafs/ticket/1169#comment:1
7507] 
7508[docs: NEWS: edit English usage, remove ticket numbers for regressions vs. 1.7.1 that were fixed again before 1.8.0c2
7509zooko@zooko.com**20100811071758
7510 Ignore-this: 993f5a1e6a9535f5b7a0bd77b93b66d0
7511] 
7512[docs: NEWS: more detail about new-downloader
7513zooko@zooko.com**20100811071303
7514 Ignore-this: 9f07da4dce9d794ce165aae287f29a1e
7515] 
7516[TAG allmydata-tahoe-1.8.0c2
7517david-sarah@jacaranda.org**20100810073847
7518 Ignore-this: c37f732b0e45f9ebfdc2f29c0899aeec
7519] 
7520Patch bundle hash:
75218e19720e8840e7027ad01013f9634b19b075fb99