├── LICENSE
├── README.md
├── codemap
├── __init__.py
├── codemap.py
├── server
│ ├── __init__.py
│ ├── socketserver.py
│ └── webserver.py
└── ui
│ ├── dygraph.js
│ ├── interaction.js
│ └── skel.htm
├── idapythonrc.py
└── install.py
/LICENSE:
--------------------------------------------------------------------------------
1 | Codemap is free to non-commercial individual users.
2 | redistribution, using for commercial purpose is prohibited.
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CODEMAP
2 |
3 | ## Introduction
4 |
5 | Codemap is a binary analysis tool for "run-trace visualization" provided as IDA plugin.
6 |
7 | Unlike DBI(Dynamic Binary Instrumentation) based tools such as Intel PIN or QEMU,
8 | Codemap uses 'tracepoints' for generating the register/memory information while
9 | the program's control flow passes the tracepoint.
10 |
11 | If the program hits a tracepoint, Codemap call-back handler is invoked
12 | then proper action for trace is taken; and program continues.
13 | This might sound like a slow/inefficient approach for execution tracing.
14 | However, there are two major advantages by tracing the binary in this manner.
15 |
16 | ### 1. Selective Tracing
17 |
18 | When you trace a binary with Codemap, you can "selectively set break-points only against instructions of your interest".
19 |
20 | These selective break-points avoid unneccessary tracing against meaningless part (out of your interest) of program.
21 | In most of case when you reverse-engineer a software, you want to analyze a very specific portion of the binary.
22 | For example, if you are analyzing a reason of program crash, you probably want to
23 | extract the execution log around that particular crashing point only.
24 | The "selective tracing" capability of Codemap perfectly suits for such cases.
25 |
26 |
27 | ### 2. Semantic Tracing
28 |
29 | Note that 'program execution trace' is not only thing that Codemap can trace.
30 | In fact, Codemap can trace entire register context.
31 | This enables Codemap to generate very concrete and flexible trace results.
32 | We refer this feature as 'Semantic Tracing'
33 |
34 | For example, to trace the "size of allocated heap chunks", you can set a break-point against an instruction which references
35 | a register that contains the size of heap chunk. Likely candidate would be an instruction from the starting part of malloc().
36 |
37 | For another example, to trace the entire "heap chunk address", you can set a break-point at the end of malloc()
38 | and trace the EAX register value(always holds the chunk location at that moment) to visualize the overall location of heap chunks.
39 |
40 | As long as you understand the 'semantic of register context' while specific instruction is being executed
41 | you can effectivly trace/visualize these semantics, which can be very helpful to understand the behaviour of binary.
42 | you can also make your own SQL statement and specify how to visualize these results.
43 |
44 |
45 | ## Requirements (essential)
46 | - IDA Pro 6.5 or after. (6.6 is recommended, don't use cracked version, Codemap might not work)
47 | - Python 2.x
48 |
49 | ## Requirements (recommended)
50 | - Chrome web browser
51 | - Large screen / Dual monitor environment
52 |
53 | ## How to install
54 | - run `python install.py` from Codemap home directory
55 |
56 | ## How to use
57 | Basically, codemap hooks IDA and place its own break-point event handler.
58 | every time when a program hits break-point, codemap will save register/memory information of that moment into DB.
59 | Later, codemap visualizes this trace information in the web-browser with SQL query.
60 |
61 | There are 5 commands for Codemap.
62 |
63 | ### ALT-1 : Start/Pause Codemap
64 |
65 | This button starts the Codemap tracing.
66 | before you press this button, make sure setup break-points where you wan to trace the binary.
67 | if you press this button again, Codemap will pause. to resume, press the button again.
68 | You must pause the Codemap to see accurate result. If you don't pause,
69 | some trace information can be buffered and not showed in browser screen.
70 |
71 | ### ALT-2 : Set Function BP
72 |
73 | This button set break-points against entire instruction inside the function that you currently put cursor.
74 | note that, the function (pointed by your cursor) should be recognized by IDA.
75 | - in order to force IDA to disassemble the byte stream, put the cursor on the byte that you think as the
76 | starting point of instructions, then press 'C' button.
77 | - in order to force IDA to recognize a function, put the cursor on the instruction that you think as the
78 | starting point of function, then press 'P' button.
79 |
80 |
81 | ### ALT-3 : Set Range BP
82 |
83 | This button will ask you the address range in which the break-points will be set.
84 | By using this button, you can set break-points against instructions in the range of 0x8048100 ~ 0x8048200 per se.
85 |
86 |
87 |
88 | ### ALT-4 : Create/Setup Module BP
89 |
90 | This button helps you to setup breakpoints against starting point of entire functions inside a module(.dll, or .so).
91 | There are two steps in order to do this.
92 |
93 | First, you need to open up the module(dll or so) file with IDA.
94 | After IDA finishes the static analysis of module, press this button to 'Create break-point information and save it into a file'.
95 | Codemap will ask you the file name of this information.
96 |
97 | Second, while you are debugging an application which uses the module(dll or so),
98 | put your cursor inside the memory area of the loaded module and press this button again.
99 | then Codemap will ask you the name of the file that you stored the break-point information for this module.
100 |
101 |
102 | ### ALT-5 : Connect Codemap Browser Screen to IDA View
103 |
104 | This button allows you to connect the IDA with Codemap graph browser.
105 | if you press this button and refresh the Codemap graph browser, the browser and IDA will be connected.
106 | after the connection, the IDA screen will follow your cursor from the graph browser.
107 |
108 | If you used 'module breakpoint' feature, Codemap will use the base address of the module as the
109 | base address of EIP, so in such case, you need to specify the 'static module base' address.
110 | (see video explanation in codemap.kr)
111 |
112 |
113 | ### Limitation
114 |
115 | Codemap supports all binaries that can be debugged with IDA Pro.
116 | However, ARM binary is not supported yet. (will be supported in future)
117 |
118 |
119 | ### Example Usage
120 |
121 | 1. Attach/Run binary with IDA Pro.
122 | 2. Setup breakpoints where you want to trace with Codemap while program is paused
123 | 3. Press alt-1 (Browser will popup and codemap will continue the program)
124 | 4. Let program run and generate trace results
125 | 5. Press alt-1 again (codemap will pause the program)
126 | 6. Execute your SQL statement to visualize the trace.
127 |
128 | You can use following SQL statements for example.
129 | - select eip from trace
130 | - select eax,ebx,ecx from trace where eax > ebx - ecx
131 | - select eax from trace where ebx*3 = edx
132 | - select eip from trace limit 100
133 | - select eax from trace order by eax
134 | - select eip from trace where m_edi like 'bla' (m_ prefix means the memory dump pointed by register)
135 | - select eip from trace where m_arg1 like 'bla' (in x86, arg1~arg4 is equivalent to *(esp+4) ~ *(esp+16))
136 | - select ecx from trace where m_ecx like 'bla' order by edx limit 10
137 | - select edx+1000 from trace where esi < edi and eax > 100
138 |
139 |
140 | ### Contact for developers
141 |
142 | - daehee87@kaist.ac.kr
143 | - zzoru@kaist.ac.kr
144 | - dinggul@kaist.ac.kr
145 |
146 |
147 | ### Academic Reference
148 |
149 | KAIST CysecLab (Graduate School of Information Security, School of Computing)
150 | Advisor (Prof. B. Kang)
151 |
152 |
153 | ### Visit http://codemap.kr for more details.
154 |
--------------------------------------------------------------------------------
/codemap/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/c0demap/codemap/c0a2f0079f19b5e8b56dd89da7471d61f21f49bb/codemap/__init__.py
--------------------------------------------------------------------------------
/codemap/codemap.py:
--------------------------------------------------------------------------------
1 | from idaapi import *
2 | import idautils
3 | import idc
4 | import time
5 | import os
6 | import platform
7 | import threading
8 | import datetime
9 | import sqlite3
10 | from server.webserver import CodemapHTTPServer
11 | from server.webserver import CodemapHTTPRequestHandler
12 | from server.socketserver import *
13 |
14 |
15 | class Codemap(object):
16 | def __init__(self):
17 | self.homedir = ''
18 | sysname = platform.system()
19 | if sysname == 'Darwin' or sysname == 'Linux':
20 | self.homedir = '%s/.idapro/codemap/' % os.environ['HOME']
21 | elif sysname == 'Windows':
22 | self.homedir = '%s\\Hex-Rays\\IDA Pro\\codemap\\' % os.environ['APPDATA']
23 |
24 | self.server = '127.0.0.1'
25 | self.port = 9165
26 | self.bpevent_bufsize = 1
27 | self.bpevent_bufsize_max = 128
28 | self.bpevent_buffer = []
29 | self.func_min_size = 16
30 | self.start = False
31 | self.pause = False
32 | self.data = ''
33 | self.mem_size = 48
34 | self.base = 0
35 | self.arch = 'x86'
36 | self.uid = None
37 | self.prev_uids = [] # remember previous uids for multi-client
38 | self.sqlite_conn = None
39 | self.sqlite_cursor = None
40 | self.dygraph = None
41 | self.skel = None
42 | self.interaction = None
43 | self.regs = '"id","eip"'
44 | self.thread_lock = threading.Lock()
45 | self.thread_http = None
46 | self.thread_ws = None
47 | self.db_name = 'codemap.db'
48 | self.websocket_server = None
49 | self.web_server = None
50 | self.seq_dict = {}
51 | self.init_codemap()
52 |
53 | self.query = ''
54 |
55 | if os.path.exists(self.homedir + 'codemap.db'):
56 | try:
57 | os.remove(self.homedir + 'codemap.db')
58 | except:
59 | print 'Codemap Database is locked.'
60 | print 'Please close another instance of Codemap and run IDA again'
61 |
62 | # init_arch func must called in BP.
63 | def init_arch(self):
64 | try:
65 | a = idc.GetRegValue('rip')
66 | self.arch = X64()
67 | except:
68 | self.arch = X86()
69 |
70 | def init_codemap(self):
71 | if not os.path.exists(self.homedir):
72 | os.makedirs(self.homedir)
73 |
74 | self.bpevent_buffer = []
75 | self.bpevent_bufsize = 1
76 | self.start = False
77 | self.pause = False
78 | self.regs = '"id","eip"'
79 | return
80 |
81 | def is_buffer_full(self):
82 | return len(self.bpevent_buffer) >= self.bpevent_bufsize
83 |
84 | def clear_bpevent_buffer(self):
85 | self.bpevent_buffer = []
86 | return
87 |
88 | def db_create(self):
89 | try:
90 | if self.sqlite_conn is None:
91 | self.sqlite_conn = sqlite3.connect(self.homedir + self.db_name)
92 | self.sqlite_cursor = self.sqlite_conn.cursor()
93 |
94 | if self.arch.name is 'x86':
95 | state_create = 'CREATE TABLE trace{0}'.format(
96 | self.uid) + '(id INTEGER PRIMARY KEY AUTOINCREMENT, '
97 | for i in range(0, len(self.arch.reg_list)):
98 | state_create += self.arch.reg_list[i]
99 | state_create += ' INT8, '
100 | state_create += 'm_' + self.arch.reg_list[i]
101 | state_create += ' VARCHAR(2048),'
102 | state_create = state_create.rstrip(',')
103 | state_create += ');'
104 | print state_create
105 | self.sqlite_cursor.execute(state_create)
106 |
107 | # sqlite3 does not support UINT8... fuck...
108 | if self.arch.name is 'x64':
109 | state_create = 'CREATE TABLE trace{0}'.format(
110 | self.uid) + '(id INTEGER PRIMARY KEY AUTOINCREMENT, '
111 | for i in range(0, len(self.arch.reg_list)):
112 | if i == len(self.arch.reg_list) - 1:
113 | state_create += self.arch.reg_list[i]
114 | state_create += ' VARCHAR(32), '
115 | state_create += 'm_' + self.arch.reg_list[i]
116 | state_create += ' VARCHAR(2048)'
117 | else:
118 | state_create += self.arch.reg_list[i]
119 | state_create += ' VARCHAR(32), '
120 | state_create += 'm_' + self.arch.reg_list[i]
121 | state_create += ' VARCHAR(2048),'
122 | state_create += ');'
123 | self.sqlite_cursor.execute(state_create)
124 |
125 | return True
126 | except sqlite3.Error as e:
127 | print 'DB create error! Error message:' + e.args[0]
128 | print 'Create statement: ' + state_create
129 | return False
130 |
131 | def db_insert_queue(self):
132 | _dict = self.arch.dict_all()
133 | self.bpevent_buffer.append(_dict)
134 | if self.is_buffer_full():
135 | self.db_insert()
136 | if self.bpevent_bufsize < self.bpevent_bufsize_max:
137 | self.bpevent_bufsize *= 2
138 | print 'buffser size up to ', self.bpevent_bufsize
139 |
140 | def db_insert(self):
141 | try:
142 | if self.sqlite_conn is None:
143 | self.sqlite_conn = sqlite3.connect(self.homedir + self.db_name)
144 | self.sqlite_cursor = self.sqlite_conn.cursor()
145 | state_insert = "insert into trace{0} ".format(self.uid)
146 |
147 | state_cols = '('
148 | for i in range(0, len(self.arch.reg_list)):
149 | if(i == len(self.arch.reg_list) - 1):
150 | state_cols += self.arch.reg_list[i] + ', '
151 | state_cols += 'm_' + self.arch.reg_list[i]
152 | else:
153 | state_cols += self.arch.reg_list[i] + ', '
154 | state_cols += 'm_' + self.arch.reg_list[i] + ', '
155 | state_cols += ')'
156 |
157 | state_vals = 'VALUES('
158 | for i in range(0, len(self.arch.reg_list)):
159 | if(i == len(self.arch.reg_list) - 1):
160 | state_vals += ":" + self.arch.reg_list[i] + ', '
161 | state_vals += ":m_" + self.arch.reg_list[i]
162 | else:
163 | state_vals += ":" + self.arch.reg_list[i] + ', '
164 | state_vals += ":m_" + self.arch.reg_list[i] + ', '
165 | state_vals += ')'
166 |
167 | state_insert = state_insert + ' ' + state_cols + ' ' + state_vals
168 | with self.thread_lock:
169 | self.sqlite_cursor.executemany(
170 | state_insert, self.bpevent_buffer)
171 | self.sqlite_conn.commit()
172 | self.clear_bpevent_buffer()
173 |
174 | return True
175 | except sqlite3.Error as e:
176 | print 'DB insert error! Error message: ' + e.args[0]
177 | print 'Insert statement: ' + state_insert
178 | return False
179 |
180 | def set_data(self):
181 | self.arch.set_reg()
182 | self.arch.set_memory(self.mem_size)
183 |
184 | def start_webserver(self):
185 | def webthread_start():
186 | print time.asctime(), "Server Starts - %s:%s" % (self.server, self.port)
187 | self.web_server = CodemapHTTPServer(
188 | (self.server, self.port), CodemapHTTPRequestHandler)
189 | self.web_server.set_codemap(self)
190 | self.seq_dict.update({self.uid: []})
191 | try:
192 | self.web_server.serve_forever()
193 | except KeyboardInterrupt:
194 | pass
195 | self.web_server.server_close()
196 | print time.asctime(), "Server Stops - %s:%s" % (self.server, self.port)
197 |
198 | if self.thread_http is not None:
199 | pass
200 | self.thread_http = threading.Thread(target=webthread_start)
201 | self.thread_http.daemon = True
202 | self.thread_http.start()
203 |
204 | def start_websocketserver(self):
205 | def ws_start():
206 | self.websocket_server = SimpleWebSocketServer('', 4116, CodemapWSD)
207 | self.websocket_server.serveforever()
208 | if self.thread_ws is not None:
209 | return
210 | self.thread_ws = threading.Thread(target=ws_start)
211 | self.thread_ws.daemon = True
212 | self.thread_ws.start()
213 |
214 | def ws_send(msg):
215 | if self.websocket_server is not None:
216 | for conn in self.wsbsocket_server.connections.itervalues():
217 | conn.sendMessage(msg)
218 |
219 |
220 | class BasicArchitecture(object):
221 | def __init__(self):
222 | self.name = ""
223 | self.reg_list = []
224 | self.reg = {}
225 | self.memory = {}
226 |
227 | # default set_reg handles everything as string(x64 use this).
228 | def set_reg(self):
229 | for i in self.reg_list:
230 | self.reg[i] = str(idc.GetRegValue(i))
231 |
232 | def set_memory(self, mem_size):
233 | for i in self.reg_list:
234 | # consider type of reg[i].
235 | self.memory[i] = dbg_read_memory(int(self.reg[i]), mem_size)
236 | if self.memory[i] is not None:
237 | self.memory[i] = self.memory[i].encode('hex')
238 | else:
239 | self.memory[i] = ''
240 |
241 | # TODO: Rename!!!!
242 | def dict_all(self):
243 | _dict = {}
244 | _dict.update(self.reg)
245 | for k in self.memory.keys():
246 | _dict.update({'m_' + k: self.memory[k]})
247 | return _dict
248 |
249 |
250 | class X86(BasicArchitecture):
251 | def __init__(self):
252 | self.name = "x86"
253 | self.reg_list = ['eip', 'eax', 'ebx', 'ecx', 'edx', 'esi',
254 | 'edi', 'ebp', 'esp', 'arg1', 'arg2', 'arg3', 'arg4']
255 | self.reg = {}
256 | self.memory = {}
257 | for i in self.reg_list:
258 | self.reg[i] = 0 # int
259 | self.memory[i] = ''
260 |
261 | def get_stack_arg(self, n):
262 | esp = idc.GetRegValue('esp')
263 | esp += n * 4
264 | val = dbg_read_memory(esp, 4)[::-1].encode('hex')
265 | return int(val, 16)
266 |
267 | # x86 overrides set_reg for integer version? -> maybe
268 | def set_reg(self):
269 | for i in self.reg_list:
270 | if 'arg' not in i:
271 | self.reg[i] = idc.GetRegValue(i)
272 |
273 | self.reg['arg1'] = self.get_stack_arg(1)
274 | self.reg['arg2'] = self.get_stack_arg(2)
275 | self.reg['arg3'] = self.get_stack_arg(3)
276 | self.reg['arg4'] = self.get_stack_arg(4)
277 |
278 |
279 | class X64(BasicArchitecture):
280 | def __init__(self):
281 | self.name = "x64"
282 | self.reg_list = ['rip', 'rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi',
283 | 'rbp', 'rsp', 'r8', 'r9', 'r10', 'r11', 'r12',
284 | 'r13', 'r14', 'r15']
285 | self.reg = {}
286 | self.memory = {}
287 | for i in self.reg_list:
288 | self.reg[i] = '' # string
289 | self.memory[i] = ''
290 |
--------------------------------------------------------------------------------
/codemap/server/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/c0demap/codemap/c0a2f0079f19b5e8b56dd89da7471d61f21f49bb/codemap/server/__init__.py
--------------------------------------------------------------------------------
/codemap/server/socketserver.py:
--------------------------------------------------------------------------------
1 | # websocket server for IDA static view interaction.
2 | # SimpleWebSocketServer
3 | # https://github.com/opiate/SimpleWebSocketServer.git
4 | '''
5 | The MIT License (MIT)
6 | Copyright (c) 2013 Dave P.
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 | '''
11 | from idaapi import *
12 | import hashlib
13 | import base64
14 | import socket
15 | import struct
16 | import time
17 | import traceback
18 | import errno
19 | from BaseHTTPServer import BaseHTTPRequestHandler
20 | from StringIO import StringIO
21 | from select import select
22 |
23 |
24 | class HTTPRequest(BaseHTTPRequestHandler):
25 | def __init__(self, request_text):
26 | self.rfile = StringIO(request_text)
27 | self.raw_requestline = self.rfile.readline()
28 | self.error_code = self.error_message = None
29 | self.parse_request()
30 |
31 |
32 | class WebSocket(object):
33 |
34 | handshakeStr = (
35 | "HTTP/1.1 101 Switching Protocols\r\n"
36 | "Upgrade: WebSocket\r\n"
37 | "Connection: Upgrade\r\n"
38 | "Sec-WebSocket-Protocol: codemap\r\n"
39 | "Sec-WebSocket-Accept: %(acceptstr)s\r\n\r\n"
40 | )
41 |
42 | hixiehandshakedStr = (
43 | "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
44 | "Upgrade: WebSocket\r\n"
45 | "Connection: Upgrade\r\n"
46 | "Sec-WebSocket-Origin: %(origin)s\r\n"
47 | "Sec-WebSocket-Protocol: codemap\r\n"
48 | "Sec-WebSocket-Location: %(type)s://%(host)s%(location)s\r\n\r\n"
49 | )
50 |
51 | GUIDStr = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
52 |
53 | STREAM = 0x0
54 | TEXT = 0x1
55 | BINARY = 0x2
56 | CLOSE = 0x8
57 | PING = 0x9
58 | PONG = 0xA
59 |
60 | HEADERB1 = 1
61 | HEADERB2 = 3
62 | LENGTHSHORT = 4
63 | LENGTHLONG = 5
64 | MASK = 6
65 | PAYLOAD = 7
66 |
67 | def __init__(self, server, sock, address):
68 | self.server = server
69 | self.client = sock
70 | self.address = address
71 |
72 | self.handshaked = False
73 | self.headerbuffer = ''
74 | self.readdraftkey = False
75 | self.draftkey = ''
76 | self.headertoread = 2048
77 | self.hixie76 = False
78 |
79 | self.fin = 0
80 | self.data = None
81 | self.opcode = 0
82 | self.hasmask = 0
83 | self.maskarray = None
84 | self.length = 0
85 | self.lengtharray = None
86 | self.index = 0
87 | self.request = None
88 | self.usingssl = False
89 |
90 | self.state = self.HEADERB1
91 |
92 | # restrict the size of header and payload for security reasons
93 | self.maxheader = 65536
94 | self.maxpayload = 4194304
95 |
96 | def close(self):
97 | self.client.close()
98 | self.state = self.HEADERB1
99 | self.hasmask = False
100 | self.handshaked = False
101 | self.readdraftkey = False
102 | self.hixie76 = False
103 | self.headertoread = 2048
104 | self.headerbuffer = ''
105 | self.data = ''
106 |
107 | def handleMessage(self):
108 | pass
109 |
110 | def handleConnected(self):
111 | pass
112 |
113 | def handleClose(self):
114 | pass
115 |
116 | def handlePacket(self):
117 | if self.opcode == self.CLOSE:
118 | self.sendClose()
119 | raise Exception("received client close")
120 | elif self.opcode == self.PING:
121 | pass
122 | elif self.opcode == self.PONG:
123 | pass
124 | elif self.opcode == self.STREAM or self.opcode == self.TEXT or self.opcode == self.BINARY:
125 | self.handleMessage()
126 |
127 | def handleData(self):
128 | # do the HTTP header and handshake
129 | if self.handshaked is False:
130 | data = self.client.recv(self.headertoread)
131 | if data:
132 | # accumulate
133 | self.headerbuffer += data
134 | if len(self.headerbuffer) >= self.maxheader:
135 | raise Exception('header exceeded allowable size')
136 | # we need to read the entire 8 bytes of after the HTTP header, ensure we do
137 | if self.readdraftkey is True:
138 | self.draftkey += self.headerbuffer
139 | read = self.headertoread - len(self.headerbuffer)
140 | if read != 0:
141 | self.headertoread = read
142 | else:
143 | # complete hixie76 handshake
144 | self.handshake_hixie76()
145 | # indicates end of HTTP header
146 | elif '\r\n\r\n' in self.headerbuffer:
147 | self.request = HTTPRequest(self.headerbuffer)
148 | # hixie handshake
149 | if self.request.headers.has_key('Sec-WebSocket-Key1'.lower()) and self.request.headers.has_key('Sec-WebSocket-Key2'.lower()):
150 | # check if we have the key in our buffer
151 | index = self.headerbuffer.find('\r\n\r\n') + 4
152 | # determine how much of the 8 byte key we have
153 | read = len(self.headerbuffer) - index
154 | # do we have all the 8 bytes we need?
155 | if read < 8:
156 | self.headertoread = 8 - read
157 | self.readdraftkey = True
158 | if read > 0:
159 | self.draftkey += self.headerbuffer[index:index + read]
160 | else:
161 | # get the key
162 | self.draftkey += self.headerbuffer[index:index + 8]
163 | # complete hixie handshake
164 | self.handshake_hixie76()
165 | # handshake rfc 6455
166 | elif self.request.headers.has_key('Sec-WebSocket-Key'.lower()):
167 | key = self.request.headers['Sec-WebSocket-Key'.lower()]
168 | hStr = self.handshakeStr % {
169 | 'acceptstr': base64.b64encode(hashlib.sha1(key + self.GUIDStr).digest())}
170 | self.sendBuffer(hStr)
171 | self.handshaked = True
172 | self.headerbuffer = ''
173 | try:
174 | self.handleConnected()
175 | except:
176 | pass
177 | else:
178 | raise Exception('Sec-WebSocket-Key does not exist')
179 | # remote connection has been closed
180 | else:
181 | raise Exception("remote socket closed")
182 | # else do normal data
183 | else:
184 | data = self.client.recv(2048)
185 | if data:
186 | for val in data:
187 | if self.hixie76 is False:
188 | self.parseMessage(ord(val))
189 | else:
190 | self.parseMessage_hixie76(ord(val))
191 | else:
192 | raise Exception("remote socket closed")
193 |
194 | def handshake_hixie76(self):
195 | k1 = self.request.headers['Sec-WebSocket-Key1'.lower()]
196 | k2 = self.request.headers['Sec-WebSocket-Key2'.lower()]
197 | spaces1 = k1.count(" ")
198 | spaces2 = k2.count(" ")
199 | num1 = int("".join([c for c in k1 if c.isdigit()])) / spaces1
200 | num2 = int("".join([c for c in k2 if c.isdigit()])) / spaces2
201 | key = ''
202 | key += struct.pack('>I', num1)
203 | key += struct.pack('>I', num2)
204 | key += self.draftkey
205 | typestr = 'ws'
206 | if self.usingssl is True:
207 | typestr = 'wss'
208 | response = self.hixiehandshakedStr % {'type': typestr, 'origin': self.request.headers[
209 | 'Origin'.lower()], 'host': self.request.headers['Host'.lower()], 'location': self.request.path}
210 | self.sendBuffer(response)
211 | self.sendBuffer(hashlib.md5(key).digest())
212 | self.handshaked = True
213 | self.hixie76 = True
214 | self.headerbuffer = ''
215 |
216 | try:
217 | self.handleConnected()
218 | except:
219 | pass
220 |
221 | def sendClose(self):
222 | msg = bytearray()
223 | if self.hixie76 is False:
224 | msg.append(0x88)
225 | msg.append(0x00)
226 | self.sendBuffer(msg)
227 | else:
228 | pass
229 |
230 | def sendBuffer(self, buff):
231 | size = len(buff)
232 | tosend = size
233 | index = 0
234 |
235 | while tosend > 0:
236 | try:
237 | # i should be able to send a bytearray
238 | sent = self.client.send(str(buff[index:size]))
239 | if sent == 0:
240 | raise RuntimeError("socket connection broken")
241 |
242 | index += sent
243 | tosend -= sent
244 |
245 | except socket.error as e:
246 | # if we have full buffers then wait for them to drain and try
247 | # again
248 | if e.errno == errno.EAGAIN:
249 | time.sleep(0.001)
250 | else:
251 | raise e
252 |
253 | # if s is a string then websocket TEXT is sent else BINARY
254 | def sendMessage(self, s):
255 |
256 | if self.hixie76 is False:
257 | header = bytearray()
258 | isString = isinstance(s, str)
259 | if isString is True:
260 | header.append(0x81)
261 | else:
262 | header.append(0x82)
263 |
264 | b2 = 0
265 | length = len(s)
266 |
267 | if length <= 125:
268 | b2 |= length
269 | header.append(b2)
270 | elif length >= 126 and length <= 65535:
271 | b2 |= 126
272 | header.append(b2)
273 | header.extend(struct.pack("!H", length))
274 | else:
275 | b2 |= 127
276 | header.append(b2)
277 | header.extend(struct.pack("!Q", length))
278 |
279 | if length > 0:
280 | self.sendBuffer(header + s)
281 | else:
282 | self.sendBuffer(header)
283 | header = None
284 |
285 | else:
286 | msg = bytearray()
287 | msg.append(0)
288 | if len(s) > 0:
289 | msg.extend(str(s).encode("UTF8"))
290 | msg.append(0xFF)
291 |
292 | self.sendBuffer(msg)
293 | msg = None
294 |
295 | def parseMessage_hixie76(self, byte):
296 | if self.state == self.HEADERB1:
297 | if byte == 0:
298 | self.state = self.PAYLOAD
299 | self.data = bytearray()
300 |
301 | elif self.state == self.PAYLOAD:
302 | if byte == 0xFF:
303 | self.opcode = 1
304 | self.length = len(self.data)
305 | try:
306 | self.handlePacket()
307 | finally:
308 | self.data = None
309 | self.state = self.HEADERB1
310 | else:
311 | self.data.append(byte)
312 | # if length exceeds allowable size then we except and remove
313 | # the connection
314 | if len(self.data) >= self.maxpayload:
315 | raise Exception('payload exceeded allowable size')
316 |
317 | def parseMessage(self, byte):
318 | # read in the header
319 | if self.state == self.HEADERB1:
320 | # fin
321 | self.fin = (byte & 0x80)
322 | # get opcode
323 | self.opcode = (byte & 0x0F)
324 |
325 | self.state = self.HEADERB2
326 |
327 | elif self.state == self.HEADERB2:
328 | mask = byte & 0x80
329 | length = byte & 0x7F
330 |
331 | if mask == 128:
332 | self.hasmask = True
333 | else:
334 | self.hasmask = False
335 |
336 | if length <= 125:
337 | self.length = length
338 |
339 | # if we have a mask we must read it
340 | if self.hasmask is True:
341 | self.maskarray = bytearray()
342 | self.state = self.MASK
343 | else:
344 | # if there is no mask and no payload we are done
345 | if self.length <= 0:
346 | try:
347 | self.handlePacket()
348 | finally:
349 | self.state = self.HEADERB1
350 | self.data = None
351 |
352 | # we have no mask and some payload
353 | else:
354 | self.index = 0
355 | self.data = bytearray()
356 | self.state = self.PAYLOAD
357 |
358 | elif length == 126:
359 | self.lengtharray = bytearray()
360 | self.state = self.LENGTHSHORT
361 |
362 | elif length == 127:
363 | self.lengtharray = bytearray()
364 | self.state = self.LENGTHLONG
365 |
366 | elif self.state == self.LENGTHSHORT:
367 | self.lengtharray.append(byte)
368 |
369 | if len(self.lengtharray) > 2:
370 | raise Exception('short length exceeded allowable size')
371 |
372 | if len(self.lengtharray) == 2:
373 | self.length = struct.unpack_from(
374 | '!H', str(self.lengtharray))[0]
375 |
376 | if self.hasmask is True:
377 | self.maskarray = bytearray()
378 | self.state = self.MASK
379 | else:
380 | # if there is no mask and no payload we are done
381 | if self.length <= 0:
382 | try:
383 | self.handlePacket()
384 | finally:
385 | self.state = self.HEADERB1
386 | self.data = None
387 |
388 | # we have no mask and some payload
389 | else:
390 | self.index = 0
391 | self.data = bytearray()
392 | self.state = self.PAYLOAD
393 |
394 | elif self.state == self.LENGTHLONG:
395 | self.lengtharray.append(byte)
396 | if len(self.lengtharray) > 8:
397 | raise Exception('long length exceeded allowable size')
398 |
399 | if len(self.lengtharray) == 8:
400 | self.length = struct.unpack_from(
401 | '!Q', str(self.lengtharray))[0]
402 |
403 | if self.hasmask is True:
404 | self.maskarray = bytearray()
405 | self.state = self.MASK
406 | else:
407 | # if there is no mask and no payload we are done
408 | if self.length <= 0:
409 | try:
410 | self.handlePacket()
411 | finally:
412 | self.state = self.HEADERB1
413 | self.data = None
414 |
415 | # we have no mask and some payload
416 | else:
417 | self.index = 0
418 | self.data = bytearray()
419 | self.state = self.PAYLOAD
420 |
421 | # MASK STATE
422 | elif self.state == self.MASK:
423 | self.maskarray.append(byte)
424 |
425 | if len(self.maskarray) > 4:
426 | raise Exception('mask exceeded allowable size')
427 |
428 | if len(self.maskarray) == 4:
429 | # if there is no mask and no payload we are done
430 | if self.length <= 0:
431 | try:
432 | self.handlePacket()
433 | finally:
434 | self.state = self.HEADERB1
435 | self.data = None
436 |
437 | # we have no mask and some payload
438 | else:
439 | self.index = 0
440 | self.data = bytearray()
441 | self.state = self.PAYLOAD
442 |
443 | # PAYLOAD STATE
444 | elif self.state == self.PAYLOAD:
445 | if self.hasmask is True:
446 | self.data.append(byte ^ self.maskarray[self.index % 4])
447 | else:
448 | self.data.append(byte)
449 |
450 | # if length exceeds allowable size then we except and remove the
451 | # connection
452 | if len(self.data) >= self.maxpayload:
453 | raise Exception('payload exceeded allowable size')
454 |
455 | # check if we have processed length bytes; if so we are done
456 | if (self.index + 1) == self.length:
457 | try:
458 | self.handlePacket()
459 | finally:
460 | self.state = self.HEADERB1
461 | self.data = None
462 | else:
463 | self.index += 1
464 |
465 |
466 | class SimpleWebSocketServer(object):
467 | def __init__(self, host, port, websocketclass):
468 | self.websocketclass = websocketclass
469 | self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
470 | self.serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
471 | self.serversocket.bind((host, port))
472 | self.serversocket.listen(5)
473 | self.connections = {}
474 | self.listeners = [self.serversocket]
475 | self.forceclose = False
476 |
477 | def decorateSocket(self, sock):
478 | return sock
479 |
480 | def constructWebSocket(self, sock, address):
481 | return self.websocketclass(self, sock, address)
482 |
483 | def close(self):
484 | self.serversocket.close()
485 | for conn in self.connections.itervalues():
486 | try:
487 | conn.handleClose()
488 | except:
489 | pass
490 | conn.close()
491 | self.forceclose = True
492 |
493 | def serveforever(self):
494 | while True:
495 | rList, wList, xList = select(self.listeners, [], self.listeners, 1)
496 | if self.forceclose:
497 | break
498 | for ready in rList:
499 | if ready == self.serversocket:
500 | try:
501 | sock, address = self.serversocket.accept()
502 | newsock = self.decorateSocket(sock)
503 | newsock.setblocking(0)
504 | fileno = newsock.fileno()
505 | self.listeners.append(fileno)
506 | self.connections[fileno] = self.constructWebSocket(
507 | newsock, address)
508 | except:
509 | # logging.debug(str(address) + ' ' + str(n))
510 | if sock is not None:
511 | sock.close()
512 | else:
513 | client = self.connections[ready]
514 | try:
515 | client.handleData()
516 | except:
517 | # logging.debug(str(client.address) + ' ' + str(n))
518 | try:
519 | client.handleClose()
520 | except:
521 | pass
522 | client.close()
523 | del self.connections[ready]
524 | self.listeners.remove(ready)
525 |
526 | for failed in xList:
527 | if failed == self.serversocket:
528 | self.close()
529 | raise Exception("server socket failed")
530 | else:
531 | client = self.connections[failed]
532 |
533 | try:
534 | client.handleClose()
535 | except:
536 | pass
537 |
538 | client.close()
539 |
540 | del self.connections[failed]
541 | self.listeners.remove(failed)
542 |
543 |
544 | class CodemapWSD(WebSocket):
545 | jump_ip = 0
546 |
547 | def handleMessage(self):
548 | dat = self.data.split(" ")
549 | if dat[0] == "move":
550 | try:
551 | self.jump_ip = idaapi.toEA(0, int(str(dat[1])))
552 |
553 | def my_jump():
554 | try:
555 | jumpto(int(self.jump_ip)) # this is important.
556 | except:
557 | print traceback.print_exc(file=sys.stdout)
558 | # this is important.
559 | idaapi.execute_sync(my_jump, idaapi.MFF_FAST)
560 | except e:
561 | idaapi.msg("Error processing the address\n")
562 |
563 | def handleConnected(self):
564 | print "Codemap is connected"
565 |
566 | def handleClose(self):
567 | print "Codemap disconnected"
568 |
--------------------------------------------------------------------------------
/codemap/server/webserver.py:
--------------------------------------------------------------------------------
1 | # http server for dygraph visualization.
2 | import sqlite3
3 | import BaseHTTPServer
4 |
5 |
6 | def hexdump(src, length=16):
7 | FILTER = ''.join(
8 | [(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)])
9 | lines = []
10 | for c in xrange(0, len(src), length):
11 | chars = src[c:c + length]
12 | hex = ' '.join(["%02x" % ord(x) for x in chars])
13 | printable = ''.join(
14 | ["%s" % ((ord(x) <= 127 and FILTER[ord(x)]) or '.') for x in chars])
15 | lines.append("%04x %-*s %s\n" % (c, length * 3, hex, printable))
16 |
17 | res = ''.join(lines)
18 | res = res.replace('<', '<')
19 | res = res.replace('>', '>')
20 | res = res.replace('\n', '')
21 | res += '' * (2 - res.count(''))
22 | return res
23 |
24 |
25 | class CodemapHTTPServer(BaseHTTPServer.HTTPServer):
26 | def set_codemap(self, codemap):
27 | self.codemap = codemap
28 |
29 |
30 | class CodemapHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
31 |
32 | def log_message(self, format, *args):
33 | return
34 |
35 | def do_GET(self):
36 | page = self.path[1:]
37 | codemap = self.server.codemap
38 |
39 | # find the requested uid(r_uid) among current uid + prev_uids.
40 | r_uid = 'invalid_request'
41 | if page.find(codemap.uid) != -1:
42 | r_uid = codemap.uid
43 | else:
44 | for uid in codemap.prev_uids:
45 | if page.find(uid) != -1:
46 | r_uid = uid
47 | break
48 |
49 | if page.startswith(r_uid):
50 | if len(page.split('?')) > 1:
51 | params = page.split('?')[1].split('&')
52 | for p in params:
53 | if p.startswith('sql='):
54 | sql = p.split('sql=')[1]
55 | # sql query is encoded with b64
56 | codemap.query = sql.decode('base64')
57 | codemap.query = codemap.query.lower()
58 |
59 | # if query does not start with [select], consider as hex string search parameter.
60 | if not codemap.query.startswith('select'):
61 | tmpsql = ''
62 | for i in range(0, len(codemap.arch.reg_list)): # skip ip register.
63 | if i==0:
64 | tmpsql += 'select '+codemap.arch.reg_list[i]+' from trace'+r_uid+' where 2=1 '
65 | continue
66 | tmpsql += "or m_{0} like '%{1}%' ".format(codemap.arch.reg_list[i], codemap.query.replace(' ',''))
67 |
68 | codemap.query = tmpsql
69 |
70 | regs = codemap.query.split(
71 | 'select')[1].split('from')[0].split(',')
72 | codemap.regs = '"id",'
73 | for r in regs:
74 | codemap.regs += '"{0}",'.format(r)
75 | codemap.regs = codemap.regs[:-1]
76 |
77 | self.send_response(200)
78 | self.send_header("Content-type", "text/html")
79 | self.end_headers()
80 |
81 | skel = open(codemap.homedir + 'ui/skel.htm', 'rb').read()
82 | skel = skel.replace('--REPLACE--', codemap.uid)
83 | skel = skel.replace('--ARCH--', codemap.arch.name)
84 | # if no baseaddr is configured then 0
85 | if codemap.base == 0:
86 | skel = skel.replace('--BASEADDR--', '0')
87 | else:
88 | skel = skel.replace(
89 | '--BASEADDR--', hex(codemap.base).replace('0x', ''))
90 | skel = skel.replace(
91 | '--REGISTERS--', codemap.regs).replace('--SQL--', codemap.query)
92 |
93 | self.wfile.write( skel )
94 |
95 | # dynamically generate csv data set.
96 | elif page == 'data' + r_uid + '.csv':
97 | codemap.seq_dict[r_uid] = []
98 | self.send_response(200)
99 | self.send_header("Content-type", "text/html")
100 | self.end_headers()
101 |
102 | con = sqlite3.connect(codemap.homedir + "codemap.db")
103 | cur = con.cursor()
104 |
105 | sql = codemap.query.replace('select', 'select id,')
106 | cur.execute(sql)
107 |
108 | result = bytearray()
109 | seq = 1
110 |
111 | while True:
112 | r = cur.fetchone()
113 | if r is None:
114 | break
115 | line = '{0},'.format(seq)
116 | for i in xrange(len(codemap.regs.split(','))):
117 | if i == 0:
118 | codemap.seq_dict[r_uid].append(r[i])
119 | continue
120 | line += '{0},'.format(r[i])
121 |
122 | result = result + line[:-1] + '\n' # exception
123 | seq += 1
124 | self.wfile.write(str(result))
125 | con.close()
126 |
127 | elif page == 'dygraph-combined.jself.map':
128 | self.send_response(200)
129 | self.send_header("Content-type", "text/html")
130 | self.end_headers()
131 | self.wfile.write('')
132 |
133 | elif page == 'favicon.ico':
134 | self.send_response(200)
135 | self.send_header("Content-type", "text/html")
136 | self.end_headers()
137 | self.wfile.write('')
138 |
139 | elif page == 'dygraph.js':
140 | dygraph = open(codemap.homedir + 'ui/dygraph.js', 'rb').read()
141 | self.send_response(200)
142 | self.send_header("Content-type", "text/javascript")
143 | self.end_headers()
144 | self.wfile.write(dygraph)
145 |
146 | elif page == 'interaction.js':
147 | interaction = open(codemap.homedir + 'ui/interaction.js', 'rb').read()
148 | self.send_response(200)
149 | self.send_header("Content-type", "text/javascript")
150 | self.end_headers()
151 | self.wfile.write(interaction)
152 |
153 | elif page.startswith('mapx86.php?'):
154 | params = page.split('?')[1].split('&')
155 | base = 0
156 | guid = 0
157 | param = 0
158 | for p in params:
159 | if p.startswith('base='):
160 | base = p.split('=')[1]
161 | if p.startswith('guid='):
162 | guid = p.split('=')[1]
163 | if p.startswith('param='):
164 | param = p.split('=')[1]
165 |
166 | # implement mapx86.php
167 | sql = "select * from trace{0} where id={1}".format(
168 | guid, codemap.seq_dict[guid][int(param) - 1])
169 |
170 | with codemap.thread_lock:
171 | con = sqlite3.connect(codemap.homedir + "codemap.db")
172 | con.row_factory = sqlite3.Row
173 | cur = con.cursor()
174 | cur.execute(sql)
175 | r = cur.fetchone()
176 |
177 | response = '''
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | arg1[{arg1}] {m_arg1} | arg2[{arg2}] {m_arg2} |
186 | arg3[{arg3}] {m_arg3} | arg4[{arg4}] {m_arg4} |
187 | eax[{eax}] {m_eax} | ebx[{ebx}] {m_ebx} |
188 | ecx[{ecx}] {m_ecx} | edx[{edx}] {m_edx} |
189 | esi[{esi}] {m_esi} | edi[{edi}] {m_edi} |
190 | ebp[{ebp}] {m_ebp} | esp[{esp}] {m_esp} |
191 |
192 |
193 |
194 | '''.format(
195 | eip=hex(int(r['eip'])),
196 | arg1=hex(int(r['arg1'])), m_arg1=hexdump(r['m_arg1'].decode('hex')), arg2=hex(int(r['arg2'])), m_arg2=hexdump(r['m_arg2'].decode('hex')),
197 | arg3=hex(int(r['arg3'])), m_arg3=hexdump(r['m_arg3'].decode('hex')), arg4=hex(int(r['arg4'])), m_arg4=hexdump(r['m_arg4'].decode('hex')),
198 | eax=hex(int(r['eax'])), m_eax=hexdump(r['m_eax'].decode('hex')), ebx=hex(int(r['ebx'])), m_ebx=hexdump(r['m_ebx'].decode('hex')),
199 | ecx=hex(int(r['ecx'])), m_ecx=hexdump(r['m_ecx'].decode('hex')), edx=hex(int(r['edx'])), m_edx=hexdump(r['m_edx'].decode('hex')),
200 | esi=hex(int(r['esi'])), m_esi=hexdump(r['m_esi'].decode('hex')), edi=hex(int(r['edi'])), m_edi=hexdump(r['m_edi'].decode('hex')),
201 | ebp=hex(int(r['ebp'])), m_ebp=hexdump(r['m_ebp'].decode('hex')), esp=hex(int(r['esp'])), m_esp=hexdump(r['m_esp'].decode('hex'))
202 | )
203 | con.close()
204 |
205 | self.send_response(200)
206 | self.send_header("Content-type", "text/html")
207 | self.end_headers()
208 | self.wfile.write(response)
209 |
210 | elif page.startswith('mapx64.php?'):
211 | params = page.split('?')[1].split('&')
212 | base = 0
213 | guid = 0
214 | param = 0
215 | for p in params:
216 | if p.startswith('base='):
217 | base = p.split('=')[1]
218 | if p.startswith('guid='):
219 | guid = p.split('=')[1]
220 | if p.startswith('param='):
221 | param = p.split('=')[1]
222 | # implement mapx64.php
223 | with codemap.thread_lock:
224 | con = sqlite3.connect(codemap.homedir + "codemap.db")
225 | con.row_factory = sqlite3.Row
226 | cur = con.cursor()
227 | sql = "select * from trace{0} where id={1}".format(
228 | guid, codemap.seq_dict[guid][int(param) - 1])
229 | cur.execute(sql)
230 | r = cur.fetchone()
231 | response = '''
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 | rax[{rax}] {m_rax} | rbx[{rbx}] {m_rbx} |
240 | rcx[{rcx}] {m_rcx} | rdx[{rdx}] {m_rdx} |
241 | rsi[{rsi}] {m_rsi} | rdi[{rdi}] {m_rdi} |
242 | rbp[{rbp}] {m_rbp} | rsp[{rsp}] {m_rsp} |
243 | r8[{r8}] {m_r8} | r9[{r9}] {m_r9} |
244 | r10[{r10}] {m_r11} | r11[{r11}] {m_r11} |
245 | r12[{r12}] {m_r12} | r13[{r13}] {m_r13} |
246 | r14[{r14}] {m_r14} | r15[{r15}] {m_r15} |
247 |
248 |
249 |
250 | '''.format(
251 | rip=hex(int(r['rip'])),
252 | rax=hex(int(r['rax'])), m_rax=hexdump(r['m_rax'].decode('hex')), rbx=hex(int(r['rbx'])), m_rbx=hexdump(r['m_rbx'].decode('hex')),
253 | rcx=hex(int(r['rcx'])), m_rcx=hexdump(r['m_rcx'].decode('hex')), rdx=hex(int(r['rdx'])), m_rdx=hexdump(r['m_rdx'].decode('hex')),
254 | rsi=hex(int(r['rsi'])), m_rsi=hexdump(r['m_rsi'].decode('hex')), rdi=hex(int(r['rdi'])), m_rdi=hexdump(r['m_rdi'].decode('hex')),
255 | rbp=hex(int(r['rbp'])), m_rbp=hexdump(r['m_rbp'].decode('hex')), rsp=hex(int(r['rsp'])), m_rsp=hexdump(r['m_rsp'].decode('hex')),
256 | r8=hex(int(r['r8'])), m_r8=hexdump(r['m_r8'].decode('hex')), r9=hex(int(r['r9'])), m_r9=hexdump(r['m_r9'].decode('hex')),
257 | r10=hex(int(r['r10'])), m_r10=hexdump(r['m_r10'].decode('hex')), r11=hex(int(r['r11'])), m_r11=hexdump(r['m_r11'].decode('hex')),
258 | r12=hex(int(r['r12'])), m_r12=hexdump(r['m_r12'].decode('hex')), r13=hex(int(r['r13'])), m_r13=hexdump(r['m_r13'].decode('hex')),
259 | r14=hex(int(r['r14'])), m_r14=hexdump(r['m_r14'].decode('hex')), r15=hex(int(r['r15'])), m_r15=hexdump(r['m_r15'].decode('hex'))
260 | )
261 | con.close()
262 |
263 | self.send_response(200)
264 | self.send_header("Content-type", "text/html")
265 | self.end_headers()
266 | self.wfile.write(response)
267 |
268 | else:
269 | self.send_response(404)
270 | self.send_header("Content-type", "text/html")
271 | self.end_headers()
272 | self.wfile.write('')
273 | print 'unknown page ', page
274 |
--------------------------------------------------------------------------------
/codemap/ui/dygraph.js:
--------------------------------------------------------------------------------
1 | /*! @license Copyright 2014 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */
2 | !function(t){"use strict";for(var e,a,i={},r=function(){},n="memory".split(","),o="assert,clear,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn".split(",");e=n.pop();)t[e]=t[e]||i;for(;a=o.pop();)t[a]=t[a]||r}(this.console=this.console||{}),function(){"use strict";CanvasRenderingContext2D.prototype.installPattern=function(t){if("undefined"!=typeof this.isPatternInstalled)throw"Must un-install old line pattern before installing a new one.";this.isPatternInstalled=!0;var e=[0,0],a=[],i=this.beginPath,r=this.lineTo,n=this.moveTo,o=this.stroke;this.uninstallPattern=function(){this.beginPath=i,this.lineTo=r,this.moveTo=n,this.stroke=o,this.uninstallPattern=void 0,this.isPatternInstalled=void 0},this.beginPath=function(){a=[],i.call(this)},this.moveTo=function(t,e){a.push([[t,e]]),n.call(this,t,e)},this.lineTo=function(t,e){var i=a[a.length-1];i.push([t,e])},this.stroke=function(){if(0===a.length)return void o.call(this);for(var i=0;if;){var x=t[v];f+=e[1]?e[1]:x,f>y?(e=[v,f-y],f=y):e=[(v+1)%t.length,0],v%2===0?r.call(this,f,0):n.call(this,f,0),v=(v+1)%t.length}this.restore(),l=g,h=d}o.call(this),a=[]}},CanvasRenderingContext2D.prototype.uninstallPattern=function(){throw"Must install a line pattern before uninstalling it."}}();var DygraphOptions=function(){return function(){"use strict";var t=function(t){this.dygraph_=t,this.yAxes_=[],this.xAxis_={},this.series_={},this.global_=this.dygraph_.attrs_,this.user_=this.dygraph_.user_attrs_||{},this.labels_=[],this.highlightSeries_=this.get("highlightSeriesOpts")||{},this.reparseSeries()};t.AXIS_STRING_MAPPINGS_={y:0,Y:0,y1:0,Y1:0,y2:1,Y2:1},t.axisToIndex_=function(e){if("string"==typeof e){if(t.AXIS_STRING_MAPPINGS_.hasOwnProperty(e))return t.AXIS_STRING_MAPPINGS_[e];throw"Unknown axis : "+e}if("number"==typeof e){if(0===e||1===e)return e;throw"Dygraphs only supports two y-axes, indexed from 0-1."}if(e)throw"Unknown axis : "+e;return 0},t.prototype.reparseSeries=function(){var e=this.get("labels");if(e){this.labels_=e.slice(1),this.yAxes_=[{series:[],options:{}}],this.xAxis_={options:{}},this.series_={};var a=!this.user_.series;if(a){for(var i=0,r=0;r1&&Dygraph.update(this.yAxes_[1].options,h.y2||{}),Dygraph.update(this.xAxis_.options,h.x||{})}},t.prototype.get=function(t){var e=this.getGlobalUser_(t);return null!==e?e:this.getGlobalDefault_(t)},t.prototype.getGlobalUser_=function(t){return this.user_.hasOwnProperty(t)?this.user_[t]:null},t.prototype.getGlobalDefault_=function(t){return this.global_.hasOwnProperty(t)?this.global_[t]:Dygraph.DEFAULT_ATTRS.hasOwnProperty(t)?Dygraph.DEFAULT_ATTRS[t]:null},t.prototype.getForAxis=function(t,e){var a,i;if("number"==typeof e)a=e,i=0===a?"y":"y2";else{if("y1"==e&&(e="y"),"y"==e)a=0;else if("y2"==e)a=1;else{if("x"!=e)throw"Unknown axis "+e;a=-1}i=e}var r=-1==a?this.xAxis_:this.yAxes_[a];if(r){var n=r.options;if(n.hasOwnProperty(t))return n[t]}if("x"!==e||"logscale"!==t){var o=this.getGlobalUser_(t);if(null!==o)return o}var s=Dygraph.DEFAULT_ATTRS.axes[i];return s.hasOwnProperty(t)?s[t]:this.getGlobalDefault_(t)},t.prototype.getForSeries=function(t,e){if(e===this.dygraph_.getHighlightSeries()&&this.highlightSeries_.hasOwnProperty(t))return this.highlightSeries_[t];if(!this.series_.hasOwnProperty(e))throw"Unknown series: "+e;var a=this.series_[e],i=a.options;return i.hasOwnProperty(t)?i[t]:this.getForAxis(t,a.yAxis)},t.prototype.numAxes=function(){return this.yAxes_.length},t.prototype.axisForSeries=function(t){return this.series_[t].yAxis},t.prototype.axisOptions=function(t){return this.yAxes_[t].options},t.prototype.seriesForAxis=function(t){return this.yAxes_[t].series},t.prototype.seriesNames=function(){return this.labels_};return t}()}(),DygraphLayout=function(){"use strict";var t=function(t){this.dygraph_=t,this.points=[],this.setNames=[],this.annotations=[],this.yAxes_=null,this.xTicks_=null,this.yTicks_=null};return t.prototype.addDataset=function(t,e){this.points.push(e),this.setNames.push(t)},t.prototype.getPlotArea=function(){return this.area_},t.prototype.computePlotArea=function(){var t={x:0,y:0};t.w=this.dygraph_.width_-t.x-this.dygraph_.getOption("rightGap"),t.h=this.dygraph_.height_;var e={chart_div:this.dygraph_.graphDiv,reserveSpaceLeft:function(e){var a={x:t.x,y:t.y,w:e,h:t.h};return t.x+=e,t.w-=e,a},reserveSpaceRight:function(e){var a={x:t.x+t.w-e,y:t.y,w:e,h:t.h};return t.w-=e,a},reserveSpaceTop:function(e){var a={x:t.x,y:t.y,w:t.w,h:e};return t.y+=e,t.h-=e,a},reserveSpaceBottom:function(e){var a={x:t.x,y:t.y+t.h-e,w:t.w,h:e};return t.h-=e,a},chartRect:function(){return{x:t.x,y:t.y,w:t.w,h:t.h}}};this.dygraph_.cascadeEvents_("layout",e),this.area_=t},t.prototype.setAnnotations=function(t){this.annotations=[];for(var e=this.dygraph_.getOption("xValueParser")||function(t){return t},a=0;a=0&&1>i&&this.xticks.push([i,a]);for(this.yticks=[],t=0;t0&&1>=i&&this.yticks.push([t,i,a])},t.prototype._evaluateAnnotations=function(){var t,e={};for(t=0;t=0;i--)a.childNodes[i].className==e&&a.removeChild(a.childNodes[i]);for(var r=document.bgColor,n=this.dygraph_.graphDiv;n!=document;){var o=n.currentStyle.backgroundColor;if(o&&"transparent"!=o){r=o;break}n=n.parentNode}var s=this.area;t({x:0,y:0,w:s.x,h:this.height}),t({x:s.x,y:0,w:this.width-s.x,h:s.y}),t({x:s.x+s.w,y:0,w:this.width-s.x-s.w,h:this.height}),t({x:s.x,y:s.y+s.h,w:this.width-s.x,h:this.height-s.h-s.y})},t._getIteratorPredicate=function(e){return e?t._predicateThatSkipsEmptyPoints:null},t._predicateThatSkipsEmptyPoints=function(t,e){return null!==t[e].yval},t._drawStyledLine=function(e,a,i,r,n,o,s){var l=e.dygraph,h=l.getBooleanOption("stepPlot",e.setName);Dygraph.isArrayLike(r)||(r=null);var p=l.getBooleanOption("drawGapEdgePoints",e.setName),g=e.points,d=e.setName,u=Dygraph.createIterator(g,0,g.length,t._getIteratorPredicate(l.getBooleanOption("connectSeparatedPoints",d))),c=r&&r.length>=2,y=e.drawingContext;y.save(),c&&y.installPattern(r);var _=t._drawSeries(e,u,i,s,n,p,h,a);t._drawPointsOnLine(e,_,o,a,s),c&&y.uninstallPattern(),y.restore()},t._drawSeries=function(t,e,a,i,r,n,o,s){var l,h,p=null,g=null,d=null,u=[],c=!0,y=t.drawingContext;y.beginPath(),y.strokeStyle=s,y.lineWidth=a;for(var _=e.array_,v=e.end_,f=e.predicate_,x=e.start_;v>x;x++){if(h=_[x],f){for(;v>x&&!f(_,x);)x++;if(x==v)break;h=_[x]}if(null===h.canvasy||h.canvasy!=h.canvasy)o&&null!==p&&(y.moveTo(p,g),y.lineTo(h.canvasx,g)),p=g=null;else{if(l=!1,n||!p){e.nextIdx_=x,e.next(),d=e.hasNext?e.peek.canvasy:null;var m=null===d||d!=d;l=!p&&m,n&&(!c&&!p||e.hasNext&&m)&&(l=!0)}null!==p?a&&(o&&(y.moveTo(p,g),y.lineTo(h.canvasx,g)),y.lineTo(h.canvasx,h.canvasy)):y.moveTo(h.canvasx,h.canvasy),(r||l)&&u.push([h.canvasx,h.canvasy,h.idx]),p=h.canvasx,g=h.canvasy}c=!1}return y.stroke(),u},t._drawPointsOnLine=function(t,e,a,i,r){for(var n=t.drawingContext,o=0;o0;a--){var i=e[a];if(i[0]==n){var o=e[a-1];o[1]==i[1]&&o[2]==i[2]&&e.splice(a,1)}}for(var a=0;a2&&!t){var s=0;e[0][0]==n&&s++;for(var l=null,h=null,a=s;ae[h][2]&&(h=a)}}var g=e[l],d=e[h];e.splice(s,e.length-s),h>l?(e.push(g),e.push(d)):l>h?(e.push(d),e.push(g)):e.push(g)}}},l=function(a){s(a);for(var l=0,h=e.length;h>l;l++){var p=e[l];p[0]==r?t.lineTo(p[1],p[2]):p[0]==n&&t.moveTo(p[1],p[2])}e.length&&(i=e[e.length-1][1]),o+=e.length,e=[]},h=function(t,r,n){var o=Math.round(r);if(null===a||o!=a){var s=a-i>1,h=o-a>1,p=s||h;l(p),a=o}e.push([t,r,n])};return{moveTo:function(t,e){h(n,t,e)},lineTo:function(t,e){h(r,t,e)},stroke:function(){l(!0),t.stroke()},fill:function(){l(!0),t.fill()},beginPath:function(){l(!0),t.beginPath()},closePath:function(){l(!0),t.closePath()},_count:function(){return o}}},t._fillPlotter=function(e){if(!e.singleSeriesName&&0===e.seriesIndex){for(var a=e.dygraph,i=a.getLabels().slice(1),r=i.length;r>=0;r--)a.visibility()[r]||i.splice(r,1);var n=function(){for(var t=0;t=0;r--){var n=i[r];t.lineTo(n[0],n[1])}},_=p-1;_>=0;_--){var v=e.drawingContext,f=i[_];if(a.getBooleanOption("fillGraph",f)){var x=a.getBooleanOption("stepPlot",f),m=u[_],D=a.axisPropertiesForSeries(f),w=1+D.minyval*D.yscale;0>w?w=0:w>1&&(w=1),w=l.h*w+l.y;var A,b=h[_],T=Dygraph.createIterator(b,0,b.length,t._getIteratorPredicate(a.getBooleanOption("connectSeparatedPoints",f))),E=0/0,C=[-1,-1],L=Dygraph.toRGB_(m),P="rgba("+L.r+","+L.g+","+L.b+","+g+")";v.fillStyle=P,v.beginPath();var S,O=!0;(b.length>2*a.width_||Dygraph.FORCE_FAST_PROXY)&&(v=t._fastCanvasProxy(v));for(var M,R=[];T.hasNext;)if(M=T.next(),Dygraph.isOK(M.y)||x){if(d){if(!O&&S==M.xval)continue;O=!1,S=M.xval,o=c[M.canvasx];var F;F=void 0===o?w:s?o[0]:o,A=[M.canvasy,F],x?-1===C[0]?c[M.canvasx]=[M.canvasy,w]:c[M.canvasx]=[M.canvasy,C[0]]:c[M.canvasx]=M.canvasy}else A=isNaN(M.canvasy)&&x?[l.y+l.h,w]:[M.canvasy,w];isNaN(E)?(v.moveTo(M.canvasx,A[1]),v.lineTo(M.canvasx,A[0])):(x?(v.lineTo(M.canvasx,C[0]),v.lineTo(M.canvasx,A[0])):v.lineTo(M.canvasx,A[0]),d&&(R.push([E,C[1]]),R.push(s&&o?[M.canvasx,o[1]]:[M.canvasx,A[1]]))),C=A,E=M.canvasx}else y(v,E,C[1],R),R=[],E=0/0,null===M.y_stacked||isNaN(M.y_stacked)||(c[M.canvasx]=l.h*M.y_stacked+l.y);s=x,A&&M&&(y(v,M.canvasx,A[1],R),R=[]),v.fill()}}}},t}(),Dygraph=function(){"use strict";var t=function(t,e,a,i){this.is_initial_draw_=!0,this.readyFns_=[],void 0!==i?(console.warn("Using deprecated four-argument dygraph constructor"),this.__old_init__(t,e,a,i)):this.__init__(t,e,a)};return t.NAME="Dygraph",t.VERSION="1.1.1",t.__repr__=function(){return"["+t.NAME+" "+t.VERSION+"]"},t.toString=function(){return t.__repr__()},t.DEFAULT_ROLL_PERIOD=1,t.DEFAULT_WIDTH=480,t.DEFAULT_HEIGHT=320,t.ANIMATION_STEPS=12,t.ANIMATION_DURATION=200,t.KMB_LABELS=["K","M","B","T","Q"],t.KMG2_BIG_LABELS=["k","M","G","T","P","E","Z","Y"],t.KMG2_SMALL_LABELS=["m","u","n","p","f","a","z","y"],t.numberValueFormatter=function(e,a){var i=a("sigFigs");if(null!==i)return t.floatFormat(e,i);var r,n=a("digitsAfterDecimal"),o=a("maxNumberWidth"),s=a("labelsKMB"),l=a("labelsKMG2");if(r=0!==e&&(Math.abs(e)>=Math.pow(10,o)||Math.abs(e)=0;c--,u/=h)if(d>=u){r=t.round_(e/u,n)+p[c];break}if(l){var y=String(e.toExponential()).split("e-");2===y.length&&y[1]>=3&&y[1]<=24&&(r=y[1]%3>0?t.round_(y[0]/t.pow(10,y[1]%3),n):Number(y[0]).toFixed(2),r+=g[Math.floor(y[1]/3)-1])}}return r},t.numberAxisLabelFormatter=function(e,a,i){return t.numberValueFormatter.call(this,e,i)},t.SHORT_MONTH_NAMES_=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],t.dateAxisLabelFormatter=function(e,a,i){var r=i("labelsUTC"),n=r?t.DateAccessorsUTC:t.DateAccessorsLocal,o=n.getFullYear(e),s=n.getMonth(e),l=n.getDate(e),h=n.getHours(e),p=n.getMinutes(e),g=n.getSeconds(e),d=n.getSeconds(e);if(a>=t.DECADAL)return""+o;if(a>=t.MONTHLY)return t.SHORT_MONTH_NAMES_[s]+" "+o;var u=3600*h+60*p+g+.001*d;return 0===u||a>=t.DAILY?t.zeropad(l)+" "+t.SHORT_MONTH_NAMES_[s]:t.hmsString_(h,p,g)},t.dateAxisFormatter=t.dateAxisLabelFormatter,t.dateValueFormatter=function(e,a){return t.dateString_(e,a("labelsUTC"))},t.Plotters=DygraphCanvasRenderer._Plotters,t.DEFAULT_ATTRS={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:.5,labelsDivWidth:250,labelsDivStyles:{},labelsSeparateLines:!1,labelsShowZeroValues:!0,labelsKMB:!1,labelsKMG2:!1,showLabelsOnHighlight:!0,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,rightGap:5,showRoller:!1,xValueParser:t.dateParser,delimiter:",",sigma:2,errorBars:!1,fractions:!1,wilsonInterval:!0,customBars:!1,fillGraph:!1,fillAlpha:.15,connectSeparatedPoints:!1,stackedGraph:!1,stackedGraphNaNFill:"all",hideOverlayOnMouseOut:!0,legend:"onmouseover",stepPlot:!1,avoidMinZero:!1,xRangePad:0,yRangePad:null,drawAxesAtZero:!1,titleHeight:28,xLabelHeight:18,yLabelWidth:18,drawXAxis:!0,drawYAxis:!0,axisLineColor:"black",axisLineWidth:.3,gridLineWidth:.3,axisLabelColor:"black",axisLabelWidth:50,drawYGrid:!0,drawXGrid:!0,gridLineColor:"rgb(128,128,128)",interactionModel:null,animatedZooms:!1,showRangeSelector:!1,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillColor:"#A7B1C4",showInRangeSelector:null,plotter:[t.Plotters.fillPlotter,t.Plotters.errorPlotter,t.Plotters.linePlotter],plugins:[],axes:{x:{pixelsPerLabel:70,axisLabelWidth:60,axisLabelFormatter:t.dateAxisLabelFormatter,valueFormatter:t.dateValueFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:null},y:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:t.numberValueFormatter,axisLabelFormatter:t.numberAxisLabelFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:null},y2:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:t.numberValueFormatter,axisLabelFormatter:t.numberAxisLabelFormatter,drawAxis:!0,drawGrid:!1,independentTicks:!1,ticker:null}}},t.HORIZONTAL=1,t.VERTICAL=2,t.PLUGINS=[],t.addedAnnotationCSS=!1,t.prototype.__old_init__=function(e,a,i,r){if(null!==i){for(var n=["Date"],o=0;o=0;n--){var o=r[n][0],s=r[n][1];if(s.call(o,i),i.propagationStopped)break}return i.defaultPrevented},t.prototype.getPluginInstance_=function(t){for(var e=0;et||t>=this.axes_.length)return null;var e=this.axes_[t];return[e.computedValueRange[0],e.computedValueRange[1]]},t.prototype.yAxisRanges=function(){for(var t=[],e=0;et||t>this.rawData_.length?null:0>e||e>this.rawData_[t].length?null:this.rawData_[t][e]},t.prototype.createInterface_=function(){var e=this.maindiv_;this.graphDiv=document.createElement("div"),this.graphDiv.style.textAlign="left",this.graphDiv.style.position="relative",e.appendChild(this.graphDiv),this.canvas_=t.createCanvas(),this.canvas_.style.position="absolute",this.hidden_=this.createPlotKitCanvas_(this.canvas_),this.canvas_ctx_=t.getContext(this.canvas_),this.hidden_ctx_=t.getContext(this.hidden_),this.resizeElements_(),this.graphDiv.appendChild(this.hidden_),this.graphDiv.appendChild(this.canvas_),this.mouseEventElement_=this.createMouseEventElement_(),this.layout_=new DygraphLayout(this);var a=this;this.mouseMoveHandler_=function(t){a.mouseMove_(t)},this.mouseOutHandler_=function(e){var i=e.target||e.fromElement,r=e.relatedTarget||e.toElement;t.isNodeContainedBy(i,a.graphDiv)&&!t.isNodeContainedBy(r,a.graphDiv)&&a.mouseOut_(e)},this.addAndTrackEvent(window,"mouseout",this.mouseOutHandler_),this.addAndTrackEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),this.resizeHandler_||(this.resizeHandler_=function(t){a.resize()},this.addAndTrackEvent(window,"resize",this.resizeHandler_))},t.prototype.resizeElements_=function(){this.graphDiv.style.width=this.width_+"px",this.graphDiv.style.height=this.height_+"px";var e=t.getContextPixelRatio(this.canvas_ctx_);this.canvas_.width=this.width_*e,this.canvas_.height=this.height_*e,this.canvas_.style.width=this.width_+"px",this.canvas_.style.height=this.height_+"px",1!==e&&this.canvas_ctx_.scale(e,e);var a=t.getContextPixelRatio(this.hidden_ctx_);this.hidden_.width=this.width_*a,this.hidden_.height=this.height_*a,this.hidden_.style.width=this.width_+"px",this.hidden_.style.height=this.height_+"px",1!==a&&this.hidden_ctx_.scale(a,a)},t.prototype.destroy=function(){this.canvas_ctx_.restore(),this.hidden_ctx_.restore();for(var e=this.plugins_.length-1;e>=0;e--){var a=this.plugins_.pop();a.plugin.destroy&&a.plugin.destroy()}var i=function(t){for(;t.hasChildNodes();)i(t.firstChild),t.removeChild(t.firstChild)};this.removeTrackedEvents_(),t.removeEvent(window,"mouseout",this.mouseOutHandler_),t.removeEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),t.removeEvent(window,"resize",this.resizeHandler_),this.resizeHandler_=null,i(this.maindiv_);var r=function(t){for(var e in t)"object"==typeof t[e]&&(t[e]=null)};r(this.layout_),r(this.plotter_),r(this)},t.prototype.createPlotKitCanvas_=function(e){var a=t.createCanvas();return a.style.position="absolute",a.style.top=e.style.top,a.style.left=e.style.left,a.width=this.width_,a.height=this.height_,a.style.width=this.width_+"px",a.style.height=this.height_+"px",a},t.prototype.createMouseEventElement_=function(){if(this.isUsingExcanvas_){var t=document.createElement("div");return t.style.position="absolute",t.style.backgroundColor="white",t.style.filter="alpha(opacity=0)",t.style.width=this.width_+"px",t.style.height=this.height_+"px",this.graphDiv.appendChild(t),t}return this.canvas_},t.prototype.setColors_=function(){var e=this.getLabels(),a=e.length-1;this.colors_=[],this.colorsMap_={};for(var i=this.getNumericOption("colorSaturation")||1,r=this.getNumericOption("colorValue")||.5,n=Math.ceil(a/2),o=this.getOption("colors"),s=this.visibility(),l=0;a>l;l++)if(s[l]){
3 | var h=e[l+1],p=this.attributes_.getForSeries("color",h);if(!p)if(o)p=o[l%o.length];else{var g=l%2?n+(l+1)/2:Math.ceil((l+1)/2),d=1*g/(1+a);p=t.hsvToRGB(d,i,r)}this.colors_.push(p),this.colorsMap_[h]=p}},t.prototype.getColors=function(){return this.colors_},t.prototype.getPropertiesForSeries=function(t){for(var e=-1,a=this.getLabels(),i=1;i=o;o++)s=t.zoomAnimationFunction(o,l),h[o-1]=[e[0]*(1-s)+s*a[0],e[1]*(1-s)+s*a[1]];if(null!==i&&null!==r)for(o=1;l>=o;o++){s=t.zoomAnimationFunction(o,l);for(var g=[],d=0;dl;l++){var h=o[l];if(t.isValidPoint(h,!0)){var p=Math.abs(h.canvasx-e);a>p&&(a=p,i=h.idx)}}return i},t.prototype.findClosestPoint=function(e,a){for(var i,r,n,o,s,l,h,p=1/0,g=this.layout_.points.length-1;g>=0;--g)for(var d=this.layout_.points[g],u=0;ui&&(p=i,s=o,l=g,h=o.idx));var c=this.layout_.setNames[l];return{row:h,seriesName:c,point:s}},t.prototype.findStackedPoint=function(e,a){for(var i,r,n=this.findClosestRow(e),o=0;o=h.length)){var p=h[l];if(t.isValidPoint(p)){var g=p.canvasy;if(e>p.canvasx&&l+10){var c=(e-p.canvasx)/u;g+=c*(d.canvasy-p.canvasy)}}}else if(e0){var y=h[l-1];if(t.isValidPoint(y)){var u=p.canvasx-y.canvasx;if(u>0){var c=(p.canvasx-e)/u;g+=c*(y.canvasy-p.canvasy)}}}(0===o||a>g)&&(i=p,r=o)}}}var _=this.layout_.setNames[r];return{row:n,seriesName:_,point:i}},t.prototype.mouseMove_=function(t){var e=this.layout_.points;if(void 0!==e&&null!==e){var a=this.eventToDomCoords(t),i=a[0],r=a[1],n=this.getOption("highlightSeriesOpts"),o=!1;if(n&&!this.isSeriesLocked()){var s;s=this.getBooleanOption("stackedGraph")?this.findStackedPoint(i,r):this.findClosestPoint(i,r),o=this.setSelection(s.row,s.seriesName)}else{var l=this.findClosestRow(i);o=this.setSelection(l)}var h=this.getFunctionOption("highlightCallback");h&&o&&h.call(this,t,this.lastx_,this.selPoints_,this.lastRow_,this.highlightSet_)}},t.prototype.getLeftBoundary_=function(t){if(this.boundaryIds_[t])return this.boundaryIds_[t][0];for(var e=0;ee?r:a-r;if(0>=n)return void(this.fadeLevel&&this.updateSelection_(1));var o=++this.animateId,s=this;t.repeatAndCleanup(function(t){s.animateId==o&&(s.fadeLevel+=e,0===s.fadeLevel?s.clearSelection():s.updateSelection_(s.fadeLevel/a))},n,i,function(){})},t.prototype.updateSelection_=function(e){this.cascadeEvents_("select",{selectedRow:this.lastRow_,selectedX:this.lastx_,selectedPoints:this.selPoints_});var a,i=this.canvas_ctx_;if(this.getOption("highlightSeriesOpts")){i.clearRect(0,0,this.width_,this.height_);var r=1-this.getNumericOption("highlightSeriesBackgroundAlpha");if(r){var n=!0;if(n){if(void 0===e)return void this.animateSelection_(1);r*=e}i.fillStyle="rgba(255,255,255,"+r+")",i.fillRect(0,0,this.width_,this.height_)}this.plotter_._renderLineChart(this.highlightSet_,i)}else if(this.previousVerticalX_>=0){var o=0,s=this.attr_("labels");for(a=1;ao&&(o=l)}var h=this.previousVerticalX_;i.clearRect(h-o-1,0,2*o+2,this.height_)}if(this.isUsingExcanvas_&&this.currentZoomRectArgs_&&t.prototype.drawZoomRect_.apply(this,this.currentZoomRectArgs_),this.selPoints_.length>0){var p=this.selPoints_[0].canvasx;for(i.save(),a=0;a=0){t!=this.lastRow_&&(i=!0),this.lastRow_=t;for(var r=0;r=0&&(i=!0),this.lastRow_=-1;return this.selPoints_.length?this.lastx_=this.selPoints_[0].xval:this.lastx_=-1,void 0!==e&&(this.highlightSet_!==e&&(i=!0),this.highlightSet_=e),void 0!==a&&(this.lockedSet_=a),i&&this.updateSelection_(void 0),i},t.prototype.mouseOut_=function(t){this.getFunctionOption("unhighlightCallback")&&this.getFunctionOption("unhighlightCallback").call(this,t),this.getBooleanOption("hideOverlayOnMouseOut")&&!this.lockedSet_&&this.clearSelection()},t.prototype.clearSelection=function(){return this.cascadeEvents_("deselect",{}),this.lockedSet_=!1,this.fadeLevel?void this.animateSelection_(-1):(this.canvas_ctx_.clearRect(0,0,this.width_,this.height_),this.fadeLevel=0,this.selPoints_=[],this.lastx_=-1,this.lastRow_=-1,void(this.highlightSet_=null))},t.prototype.getSelection=function(){if(!this.selPoints_||this.selPoints_.length<1)return-1;for(var t=0;t1&&(a=this.dataHandler_.rollingAverage(a,this.rollPeriod_,this.attributes_)),this.rolledSeries_.push(a)}this.drawGraph_();var i=new Date;this.drawingTimeMs_=i-t},t.PointType=void 0,t.stackPoints_=function(t,e,a,i){for(var r=null,n=null,o=null,s=-1,l=function(e){if(!(s>=e))for(var a=e;aa[1]&&(a[1]=u),u=1;i--)if(this.visibility()[i-1]){if(a){l=e[i];var c=a[0],y=a[1];for(n=null,o=null,r=0;r=c&&null===n&&(n=r),l[r][0]<=y&&(o=r);null===n&&(n=0);for(var _=n,v=!0;v&&_>0;)_--,v=null===l[_][1];null===o&&(o=l.length-1);var f=o;for(v=!0;v&&f0&&(this.setIndexByName_[n[0]]=0);for(var o=0,s=1;s0;){var a=this.readyFns_.pop();a(this)}},t.prototype.computeYAxes_=function(){var e,a,i,r,n;if(void 0!==this.axes_&&this.user_attrs_.hasOwnProperty("valueRange")===!1)for(e=[],i=0;ii;i++)this.axes_[i].valueWindow=e[i]}for(a=0;al;l++){var h=this.axes_[l],p=this.attributes_.getForAxis("logscale",l),g=this.attributes_.getForAxis("includeZero",l),d=this.attributes_.getForAxis("independentTicks",l);if(i=this.attributes_.seriesForAxis(l),e=!0,r=.1,null!==this.getNumericOption("yRangePad")&&(e=!1,r=this.getNumericOption("yRangePad")/this.plotter_.area.h),0===i.length)h.extremeRange=[0,1];else{for(var u,c,y=1/0,_=-(1/0),v=0;v0&&(y=0),0>_&&(_=0)),y==1/0&&(y=0),_==-(1/0)&&(_=1),a=_-y,0===a&&(0!==_?a=Math.abs(_):(_=1,a=1));var f,x;if(p)if(e)f=_+r*a,x=y;else{var m=Math.exp(Math.log(a)*r);f=_*m,x=y/m}else f=_+r*a,x=y-r*a,e&&!this.getBooleanOption("avoidMinZero")&&(0>x&&y>=0&&(x=0),f>0&&0>=_&&(f=0));h.extremeRange=[x,f]}if(h.valueWindow)h.computedValueRange=[h.valueWindow[0],h.valueWindow[1]];else if(h.valueRange){var D=o(h.valueRange[0])?h.extremeRange[0]:h.valueRange[0],w=o(h.valueRange[1])?h.extremeRange[1]:h.valueRange[1];if(!e)if(h.logscale){var m=Math.exp(Math.log(a)*r);D*=m,w/=m}else a=w-D,D-=a*r,w+=a*r;h.computedValueRange=[D,w]}else h.computedValueRange=h.extremeRange;if(d){h.independentTicks=d;var A=this.optionsViewForAxis_("y"+(l?"2":"")),b=A("ticker");h.ticks=b(h.computedValueRange[0],h.computedValueRange[1],this.plotter_.area.h,A,this),n||(n=h)}}if(void 0===n)throw'Configuration Error: At least one axis has to have the "independentTicks" option activated.';for(var l=0;s>l;l++){var h=this.axes_[l];if(!h.independentTicks){for(var A=this.optionsViewForAxis_("y"+(l?"2":"")),b=A("ticker"),T=n.ticks,E=n.computedValueRange[1]-n.computedValueRange[0],C=h.computedValueRange[1]-h.computedValueRange[0],L=[],P=0;P0&&"e"!=t[a-1]&&"E"!=t[a-1]||t.indexOf("/")>=0||isNaN(parseFloat(t))?e=!0:8==t.length&&t>"19700101"&&"20371231">t&&(e=!0),this.setXAxisOptions_(e)},t.prototype.setXAxisOptions_=function(e){e?(this.attrs_.xValueParser=t.dateParser,this.attrs_.axes.x.valueFormatter=t.dateValueFormatter,this.attrs_.axes.x.ticker=t.dateTicker,this.attrs_.axes.x.axisLabelFormatter=t.dateAxisLabelFormatter):(this.attrs_.xValueParser=function(t){return parseFloat(t)},this.attrs_.axes.x.valueFormatter=function(t){return t},this.attrs_.axes.x.ticker=t.numericTicks,this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter)},t.prototype.parseCSV_=function(e){var a,i,r=[],n=t.detectLineDelimiter(e),o=e.split(n||"\n"),s=this.getStringOption("delimiter");-1==o[0].indexOf(s)&&o[0].indexOf(" ")>=0&&(s=" ");var l=0;"labels"in this.user_attrs_||(l=1,this.attrs_.labels=o[0].split(s),this.attributes_.reparseSeries());for(var h,p=0,g=!1,d=this.attr_("labels").length,u=!1,c=l;c0&&v[0]0;)e=String.fromCharCode(65+(t-1)%26)+e.toLowerCase(),t=Math.floor((t-1)/26);return e},i=e.getNumberOfColumns(),r=e.getNumberOfRows(),n=e.getColumnType(0);if("date"==n||"datetime"==n)this.attrs_.xValueParser=t.dateParser,this.attrs_.axes.x.valueFormatter=t.dateValueFormatter,this.attrs_.axes.x.ticker=t.dateTicker,this.attrs_.axes.x.axisLabelFormatter=t.dateAxisLabelFormatter;else{if("number"!=n)return console.error("only 'date', 'datetime' and 'number' types are supported for column 1 of DataTable input (Got '"+n+"')"),null;this.attrs_.xValueParser=function(t){return parseFloat(t)},this.attrs_.axes.x.valueFormatter=function(t){return t},this.attrs_.axes.x.ticker=t.numericTicks,this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}var o,s,l=[],h={},p=!1;for(o=1;i>o;o++){var g=e.getColumnType(o);if("number"==g)l.push(o);else if("string"==g&&this.getBooleanOption("displayAnnotations")){var d=l[l.length-1];h.hasOwnProperty(d)?h[d].push(o):h[d]=[o],p=!0}else console.error("Only 'number' is supported as a dependent type with Gviz. 'string' is only supported if displayAnnotations is true")}var u=[e.getColumnLabel(0)];for(o=0;oo;o++){var v=[];if("undefined"!=typeof e.getValue(o,0)&&null!==e.getValue(o,0)){if(v.push("date"==n||"datetime"==n?e.getValue(o,0).getTime():e.getValue(o,0)),this.getBooleanOption("errorBars"))for(s=0;i-1>s;s++)v.push([e.getValue(o,1+2*s),e.getValue(o,2+2*s)]);else{for(s=0;s0&&v[0]0&&this.setAnnotations(_,!0),this.attributes_.reparseSeries()},t.prototype.cascadeDataDidUpdateEvent_=function(){this.cascadeEvents_("dataDidUpdate",{})},t.prototype.start_=function(){var e=this.file_;if("function"==typeof e&&(e=e()),t.isArrayLike(e))this.rawData_=this.parseArray_(e),this.cascadeDataDidUpdateEvent_(),this.predraw_();else if("object"==typeof e&&"function"==typeof e.getColumnRange)this.parseDataTable_(e),this.cascadeDataDidUpdateEvent_(),this.predraw_();else if("string"==typeof e){var a=t.detectLineDelimiter(e);if(a)this.loadedEvent_(e);else{var i;i=window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");var r=this;i.onreadystatechange=function(){4==i.readyState&&(200===i.status||0===i.status)&&r.loadedEvent_(i.responseText)},i.open("GET",e,!0),i.send(null)}}else console.error("Unknown data format: "+typeof e)},t.prototype.updateOptions=function(e,a){"undefined"==typeof a&&(a=!1);var i=e.file,r=t.mapLegacyOptions_(e);"rollPeriod"in r&&(this.rollPeriod_=r.rollPeriod),"dateWindow"in r&&(this.dateWindow_=r.dateWindow,"isZoomedIgnoreProgrammaticZoom"in r||(this.zoomed_x_=null!==r.dateWindow)),"valueRange"in r&&!("isZoomedIgnoreProgrammaticZoom"in r)&&(this.zoomed_y_=null!==r.valueRange);var n=t.isPixelChangingOptionList(this.attr_("labels"),r);t.updateDeep(this.user_attrs_,r),this.attributes_.reparseSeries(),i?(this.cascadeEvents_("dataWillUpdate",{}),this.file_=i,a||this.start_()):a||(n?this.predraw_():this.renderGraph_(!1))},t.mapLegacyOptions_=function(t){var e={};for(var a in t)t.hasOwnProperty(a)&&"file"!=a&&t.hasOwnProperty(a)&&(e[a]=t[a]);var i=function(t,a,i){e.axes||(e.axes={}),e.axes[t]||(e.axes[t]={}),e.axes[t][a]=i},r=function(a,r,n){"undefined"!=typeof t[a]&&(console.warn("Option "+a+" is deprecated. Use the "+n+" option for the "+r+" axis instead. (e.g. { axes : { "+r+" : { "+n+" : ... } } } (see http://dygraphs.com/per-axis.html for more information."),i(r,n,t[a]),delete e[a])};return r("xValueFormatter","x","valueFormatter"),r("pixelsPerXLabel","x","pixelsPerLabel"),r("xAxisLabelFormatter","x","axisLabelFormatter"),r("xTicker","x","ticker"),r("yValueFormatter","y","valueFormatter"),r("pixelsPerYLabel","y","pixelsPerLabel"),r("yAxisLabelFormatter","y","axisLabelFormatter"),r("yTicker","y","ticker"),r("drawXGrid","x","drawGrid"),r("drawXAxis","x","drawAxis"),r("drawYGrid","y","drawGrid"),r("drawYAxis","y","drawAxis"),r("xAxisLabelWidth","x","axisLabelWidth"),r("yAxisLabelWidth","y","axisLabelWidth"),e},t.prototype.resize=function(t,e){if(!this.resize_lock){this.resize_lock=!0,null===t!=(null===e)&&(console.warn("Dygraph.resize() should be called with zero parameters or two non-NULL parameters. Pretending it was zero."),t=e=null);var a=this.width_,i=this.height_;t?(this.maindiv_.style.width=t+"px",this.maindiv_.style.height=e+"px",this.width_=t,this.height_=e):(this.width_=this.maindiv_.clientWidth,this.height_=this.maindiv_.clientHeight),(a!=this.width_||i!=this.height_)&&(this.resizeElements_(),this.predraw_()),this.resize_lock=!1}},t.prototype.adjustRoll=function(t){this.rollPeriod_=t,this.predraw_()},t.prototype.visibility=function(){for(this.getOption("visibility")||(this.attrs_.visibility=[]);this.getOption("visibility").lengtht||t>=a.length?console.warn("invalid series number in setVisibility: "+t):(a[t]=e,this.predraw_())},t.prototype.size=function(){return{width:this.width_,height:this.height_}},t.prototype.setAnnotations=function(e,a){return t.addAnnotationRule(),this.annotations_=e,this.layout_?(this.layout_.setAnnotations(this.annotations_),void(a||this.predraw_())):void console.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a ready() block. See dygraphs.com/tests/annotation.html")},t.prototype.annotations=function(){return this.annotations_},t.prototype.getLabels=function(){var t=this.attr_("labels");return t?t.slice():null},t.prototype.indexFromSetName=function(t){return this.setIndexByName_[t]},t.prototype.ready=function(t){this.is_initial_draw_?this.readyFns_.push(t):t.call(this,this)},t.addAnnotationRule=function(){if(!t.addedAnnotationCSS){var e="border: 1px solid black; background-color: white; text-align: center;",a=document.createElement("style");a.type="text/css",document.getElementsByTagName("head")[0].appendChild(a);for(var i=0;it?"0"+t:""+t},Dygraph.DateAccessorsLocal={getFullYear:function(t){return t.getFullYear()},getMonth:function(t){return t.getMonth()},getDate:function(t){return t.getDate()},getHours:function(t){return t.getHours()},getMinutes:function(t){return t.getMinutes()},getSeconds:function(t){return t.getSeconds()},getMilliseconds:function(t){return t.getMilliseconds()},getDay:function(t){return t.getDay()},makeDate:function(t,e,a,i,r,n,o){return new Date(t,e,a,i,r,n,o)}},Dygraph.DateAccessorsUTC={getFullYear:function(t){return t.getUTCFullYear()},getMonth:function(t){return t.getUTCMonth()},getDate:function(t){return t.getUTCDate()},getHours:function(t){return t.getUTCHours()},getMinutes:function(t){return t.getUTCMinutes()},getSeconds:function(t){return t.getUTCSeconds()},getMilliseconds:function(t){return t.getUTCMilliseconds()},getDay:function(t){return t.getUTCDay()},makeDate:function(t,e,a,i,r,n,o){return new Date(Date.UTC(t,e,a,i,r,n,o))}},Dygraph.hmsString_=function(t,e,a){var i=Dygraph.zeropad,r=i(t)+":"+i(e);return a&&(r+=":"+i(a)),r},Dygraph.dateString_=function(t,e){var a=Dygraph.zeropad,i=e?Dygraph.DateAccessorsUTC:Dygraph.DateAccessorsLocal,r=new Date(t),n=i.getFullYear(r),o=i.getMonth(r),s=i.getDate(r),l=i.getHours(r),h=i.getMinutes(r),p=i.getSeconds(r),g=""+n,d=a(o+1),u=a(s),c=3600*l+60*h+p,y=g+"/"+d+"/"+u;return c&&(y+=" "+Dygraph.hmsString_(l,h,p)),y},Dygraph.round_=function(t,e){var a=Math.pow(10,e);return Math.round(t*a)/a},Dygraph.binarySearch=function(t,e,a,i,r){if((null===i||void 0===i||null===r||void 0===r)&&(i=0,r=e.length-1),i>r)return-1;(null===a||void 0===a)&&(a=0);var n,o=function(t){return t>=0&&tt?a>0&&(n=s-1,o(n)&&e[n]l?0>a&&(n=s+1,o(n)&&e[n]>t)?s:Dygraph.binarySearch(t,e,a,s+1,r):-1},Dygraph.dateParser=function(t){var e,a;if((-1==t.search("-")||-1!=t.search("T")||-1!=t.search("Z"))&&(a=Dygraph.dateStrToMillis(t),a&&!isNaN(a)))return a;if(-1!=t.search("-")){for(e=t.replace("-","/","g");-1!=e.search("-");)e=e.replace("-","/");a=Dygraph.dateStrToMillis(e)}else 8==t.length?(e=t.substr(0,4)+"/"+t.substr(4,2)+"/"+t.substr(6,2),a=Dygraph.dateStrToMillis(e)):a=Dygraph.dateStrToMillis(t);return(!a||isNaN(a))&&console.error("Couldn't parse "+t+" as a date"),a},Dygraph.dateStrToMillis=function(t){return new Date(t).getTime()},Dygraph.update=function(t,e){if("undefined"!=typeof e&&null!==e)for(var a in e)e.hasOwnProperty(a)&&(t[a]=e[a]);return t},Dygraph.updateDeep=function(t,e){function a(t){return"object"==typeof Node?t instanceof Node:"object"==typeof t&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName}if("undefined"!=typeof e&&null!==e)for(var i in e)e.hasOwnProperty(i)&&(null===e[i]?t[i]=null:Dygraph.isArrayLike(e[i])?t[i]=e[i].slice():a(e[i])?t[i]=e[i]:"object"==typeof e[i]?(("object"!=typeof t[i]||null===t[i])&&(t[i]={}),Dygraph.updateDeep(t[i],e[i])):t[i]=e[i]);return t},Dygraph.isArrayLike=function(t){var e=typeof t;return"object"!=e&&("function"!=e||"function"!=typeof t.item)||null===t||"number"!=typeof t.length||3===t.nodeType?!1:!0},Dygraph.isDateLike=function(t){return"object"!=typeof t||null===t||"function"!=typeof t.getTime?!1:!0},Dygraph.clone=function(t){for(var e=[],a=0;a=e||Dygraph.requestAnimFrame.call(window,function(){var e=(new Date).getTime(),h=e-o;r=n,n=Math.floor(h/a);var p=n-r,g=n+p>s;g||n>=s?(t(s),i()):(0!==p&&t(n),l())})}()};var e={annotationClickHandler:!0,annotationDblClickHandler:!0,annotationMouseOutHandler:!0,annotationMouseOverHandler:!0,axisLabelColor:!0,axisLineColor:!0,axisLineWidth:!0,clickCallback:!0,drawCallback:!0,drawHighlightPointCallback:!0,drawPoints:!0,drawPointCallback:!0,drawXGrid:!0,drawYGrid:!0,fillAlpha:!0,gridLineColor:!0,gridLineWidth:!0,hideOverlayOnMouseOut:!0,highlightCallback:!0,highlightCircleSize:!0,interactionModel:!0,isZoomedIgnoreProgrammaticZoom:!0,labelsDiv:!0,labelsDivStyles:!0,labelsDivWidth:!0,labelsKMB:!0,labelsKMG2:!0,labelsSeparateLines:!0,labelsShowZeroValues:!0,legend:!0,panEdgeFraction:!0,pixelsPerYLabel:!0,pointClickCallback:!0,pointSize:!0,rangeSelectorPlotFillColor:!0,rangeSelectorPlotStrokeColor:!0,showLabelsOnHighlight:!0,showRoller:!0,strokeWidth:!0,underlayCallback:!0,unhighlightCallback:!0,zoomCallback:!0};Dygraph.isPixelChangingOptionList=function(t,a){var i={};if(t)for(var r=1;re?1/Math.pow(t,-e):Math.pow(t,e)};var a=/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*([01](?:\.\d+)?))?\)$/;Dygraph.toRGB_=function(e){var a=t(e);if(a)return a;var i=document.createElement("div");i.style.backgroundColor=e,i.style.visibility="hidden",document.body.appendChild(i);var r;return r=window.getComputedStyle?window.getComputedStyle(i,null).backgroundColor:i.currentStyle.backgroundColor,document.body.removeChild(i),t(r)},Dygraph.isCanvasSupported=function(t){var e;try{e=t||document.createElement("canvas"),e.getContext("2d")}catch(a){var i=navigator.appVersion.match(/MSIE (\d\.\d)/),r=-1!=navigator.userAgent.toLowerCase().indexOf("opera");return!i||i[1]<6||r?!1:!0}return!0},Dygraph.parseFloat_=function(t,e,a){var i=parseFloat(t);if(!isNaN(i))return i;if(/^ *$/.test(t))return null;if(/^ *nan *$/i.test(t))return 0/0;var r="Unable to parse '"+t+"' as a number";return void 0!==a&&void 0!==e&&(r+=" on line "+(1+(e||0))+" ('"+a+"') of CSV."),console.error(r),null}}(),function(){"use strict";Dygraph.GVizChart=function(t){this.container=t},Dygraph.GVizChart.prototype.draw=function(t,e){this.container.innerHTML="","undefined"!=typeof this.date_graph&&this.date_graph.destroy(),this.date_graph=new Dygraph(this.container,t,e)},Dygraph.GVizChart.prototype.setSelection=function(t){var e=!1;t.length&&(e=t[0].row),this.date_graph.setSelection(e)},Dygraph.GVizChart.prototype.getSelection=function(){var t=[],e=this.date_graph.getSelection();if(0>e)return t;for(var a=this.date_graph.layout_.points,i=0;ii&&2>r&&void 0!==e.lastx_&&-1!=e.lastx_&&Dygraph.Interaction.treatMouseOpAsClick(e,t,a),a.regionWidth=i,a.regionHeight=r},Dygraph.Interaction.startPan=function(t,e,a){var i,r;a.isPanning=!0;var n=e.xAxisRange();if(e.getOptionForAxis("logscale","x")?(a.initialLeftmostDate=Dygraph.log10(n[0]),a.dateRange=Dygraph.log10(n[1])-Dygraph.log10(n[0])):(a.initialLeftmostDate=n[0],a.dateRange=n[1]-n[0]),a.xUnitsPerPixel=a.dateRange/(e.plotter_.area.w-1),e.getNumericOption("panEdgeFraction")){var o=e.width_*e.getNumericOption("panEdgeFraction"),s=e.xAxisExtremes(),l=e.toDomXCoord(s[0])-o,h=e.toDomXCoord(s[1])+o,p=e.toDataXCoord(l),g=e.toDataXCoord(h);a.boundedDates=[p,g];var d=[],u=e.height_*e.getNumericOption("panEdgeFraction");for(i=0;ia.boundedDates[1]&&(i-=r-a.boundedDates[1],r=i+a.dateRange),e.getOptionForAxis("logscale","x")?e.dateWindow_=[Math.pow(Dygraph.LOG_SCALE,i),Math.pow(Dygraph.LOG_SCALE,r)]:e.dateWindow_=[i,r],a.is2DPan)for(var n=a.dragEndY-a.dragStartY,o=0;oi?Dygraph.VERTICAL:Dygraph.HORIZONTAL,e.drawZoomRect_(a.dragDirection,a.dragStartX,a.dragEndX,a.dragStartY,a.dragEndY,a.prevDragDirection,a.prevEndX,a.prevEndY),a.prevEndX=a.dragEndX,a.prevEndY=a.dragEndY,a.prevDragDirection=a.dragDirection},Dygraph.Interaction.treatMouseOpAsClick=function(t,e,a){for(var i=t.getFunctionOption("clickCallback"),r=t.getFunctionOption("pointClickCallback"),n=null,o=-1,s=Number.MAX_VALUE,l=0;lp)&&(s=p,o=l)}var g=t.getNumericOption("highlightCircleSize")+2;if(g*g>=s&&(n=t.selPoints_[o]),n){var d={cancelable:!0,point:n,canvasx:a.dragEndX,canvasy:a.dragEndY},u=t.cascadeEvents_("pointClick",d);if(u)return;r&&r.call(t,e,n)}var d={cancelable:!0,xval:t.lastx_,pts:t.selPoints_,canvasx:a.dragEndX,canvasy:a.dragEndY};t.cascadeEvents_("click",d)||i&&i.call(t,e,t.lastx_,t.selPoints_)},Dygraph.Interaction.endZoom=function(t,e,a){e.clearZoomRect_(),a.isZooming=!1,Dygraph.Interaction.maybeTreatMouseOpAsClick(t,e,a);var i=e.getArea();if(a.regionWidth>=10&&a.dragDirection==Dygraph.HORIZONTAL){var r=Math.min(a.dragStartX,a.dragEndX),n=Math.max(a.dragStartX,a.dragEndX);r=Math.max(r,i.x),n=Math.min(n,i.x+i.w),n>r&&e.doZoomX_(r,n),a.cancelNextDblclick=!0}else if(a.regionHeight>=10&&a.dragDirection==Dygraph.VERTICAL){var o=Math.min(a.dragStartY,a.dragEndY),s=Math.max(a.dragStartY,a.dragEndY);o=Math.max(o,i.y),s=Math.min(s,i.y+i.h),s>o&&e.doZoomY_(o,s),a.cancelNextDblclick=!0}a.dragStartX=null,a.dragStartY=null},Dygraph.Interaction.startTouch=function(t,e,a){t.preventDefault(),t.touches.length>1&&(a.startTimeForDoubleTapMs=null);for(var i=[],r=0;r=2){a.initialPinchCenter={pageX:.5*(i[0].pageX+i[1].pageX),pageY:.5*(i[0].pageY+i[1].pageY),dataX:.5*(i[0].dataX+i[1].dataX),dataY:.5*(i[0].dataY+i[1].dataY)};var o=180/Math.PI*Math.atan2(a.initialPinchCenter.pageY-i[0].pageY,i[0].pageX-a.initialPinchCenter.pageX);o=Math.abs(o),o>90&&(o=90-o),a.touchDirections={x:67.5>o,y:o>22.5}}a.initialRange={x:e.xAxisRange(),y:e.yAxisRange()}},Dygraph.Interaction.moveTouch=function(t,e,a){a.startTimeForDoubleTapMs=null;var i,r=[];for(i=0;i=2){var c=s[1].pageX-l.pageX;d=(r[1].pageX-o.pageX)/c;var y=s[1].pageY-l.pageY;u=(r[1].pageY-o.pageY)/y}d=Math.min(8,Math.max(.125,d)),u=Math.min(8,Math.max(.125,u));var _=!1;if(a.touchDirections.x&&(e.dateWindow_=[l.dataX-h.dataX+(a.initialRange.x[0]-l.dataX)/d,l.dataX-h.dataX+(a.initialRange.x[1]-l.dataX)/d],_=!0),a.touchDirections.y)for(i=0;1>i;i++){var v=e.axes_[i],f=e.attributes_.getForAxis("logscale",i);f||(v.valueWindow=[l.dataY-h.dataY+(a.initialRange.y[0]-l.dataY)/u,l.dataY-h.dataY+(a.initialRange.y[1]-l.dataY)/u],_=!0)}if(e.drawGraph_(!1),_&&r.length>1&&e.getFunctionOption("zoomCallback")){var x=e.xAxisRange();e.getFunctionOption("zoomCallback").call(e,x[0],x[1],e.yAxisRanges())}},Dygraph.Interaction.endTouch=function(t,e,a){if(0!==t.touches.length)Dygraph.Interaction.startTouch(t,e,a);else if(1==t.changedTouches.length){var i=(new Date).getTime(),r=t.changedTouches[0];a.startTimeForDoubleTapMs&&i-a.startTimeForDoubleTapMs<500&&a.doubleTapX&&Math.abs(a.doubleTapX-r.screenX)<50&&a.doubleTapY&&Math.abs(a.doubleTapY-r.screenY)<50?e.resetZoom():(a.startTimeForDoubleTapMs=i,a.doubleTapX=r.screenX,a.doubleTapY=r.screenY)}};var e=function(t,e,a){return e>t?e-t:t>a?t-a:0},a=function(t,a){var i=Dygraph.findPos(a.canvas_),r={left:i.x,right:i.x+a.canvas_.offsetWidth,top:i.y,bottom:i.y+a.canvas_.offsetHeight},n={x:Dygraph.pageX(t),y:Dygraph.pageY(t)},o=e(n.x,r.left,r.right),s=e(n.y,r.top,r.bottom);return Math.max(o,s)};Dygraph.Interaction.defaultModel={mousedown:function(e,i,r){if(!e.button||2!=e.button){r.initializeMouseDown(e,i,r),e.altKey||e.shiftKey?Dygraph.startPan(e,i,r):Dygraph.startZoom(e,i,r);var n=function(e){if(r.isZooming){var n=a(e,i);t>n?Dygraph.moveZoom(e,i,r):null!==r.dragEndX&&(r.dragEndX=null,r.dragEndY=null,i.clearZoomRect_())}else r.isPanning&&Dygraph.movePan(e,i,r)},o=function(t){r.isZooming?null!==r.dragEndX?Dygraph.endZoom(t,i,r):Dygraph.Interaction.maybeTreatMouseOpAsClick(t,i,r):r.isPanning&&Dygraph.endPan(t,i,r),Dygraph.removeEvent(document,"mousemove",n),Dygraph.removeEvent(document,"mouseup",o),r.destroy()};i.addAndTrackEvent(document,"mousemove",n),i.addAndTrackEvent(document,"mouseup",o)}},willDestroyContextMyself:!0,touchstart:function(t,e,a){Dygraph.Interaction.startTouch(t,e,a)},touchmove:function(t,e,a){Dygraph.Interaction.moveTouch(t,e,a)},touchend:function(t,e,a){Dygraph.Interaction.endTouch(t,e,a)},dblclick:function(t,e,a){if(a.cancelNextDblclick)return void(a.cancelNextDblclick=!1);var i={canvasx:a.dragEndX,canvasy:a.dragEndY};e.cascadeEvents_("dblclick",i)||t.altKey||t.shiftKey||e.resetZoom()}},Dygraph.DEFAULT_ATTRS.interactionModel=Dygraph.Interaction.defaultModel,Dygraph.defaultInteractionModel=Dygraph.Interaction.defaultModel,Dygraph.endZoom=Dygraph.Interaction.endZoom,Dygraph.moveZoom=Dygraph.Interaction.moveZoom,Dygraph.startZoom=Dygraph.Interaction.startZoom,Dygraph.endPan=Dygraph.Interaction.endPan,Dygraph.movePan=Dygraph.Interaction.movePan,Dygraph.startPan=Dygraph.Interaction.startPan,Dygraph.Interaction.nonInteractiveModel_={mousedown:function(t,e,a){a.initializeMouseDown(t,e,a)},mouseup:Dygraph.Interaction.maybeTreatMouseOpAsClick},Dygraph.Interaction.dragIsPanInteractionModel={mousedown:function(t,e,a){a.initializeMouseDown(t,e,a),Dygraph.startPan(t,e,a)},mousemove:function(t,e,a){a.isPanning&&Dygraph.movePan(t,e,a)},mouseup:function(t,e,a){a.isPanning&&Dygraph.endPan(t,e,a)}}}(),function(){"use strict";Dygraph.TickList=void 0,Dygraph.Ticker=void 0,Dygraph.numericLinearTicks=function(t,e,a,i,r,n){var o=function(t){return"logscale"===t?!1:i(t)};return Dygraph.numericTicks(t,e,a,o,r,n)},Dygraph.numericTicks=function(t,e,a,i,r,n){var o,s,l,h,p=i("pixelsPerLabel"),g=[];if(n)for(o=0;o=h/4){for(var y=u;y>=d;y--){var _=Dygraph.PREFERRED_LOG_TICK_VALUES[y],v=Math.log(_/t)/Math.log(e/t)*a,f={v:_};null===c?c={tickValue:_,pixel_coord:v}:Math.abs(v-c.pixel_coord)>=p?c={tickValue:_,pixel_coord:v}:f.label="",g.push(f)}g.reverse()}}if(0===g.length){var x,m,D=i("labelsKMG2");D?(x=[1,2,4,8,16,32,64,128,256],m=16):(x=[1,2,5,10,20,50,100],m=10);var w,A,b,T,E=Math.ceil(a/p),C=Math.abs(e-t)/E,L=Math.floor(Math.log(C)/Math.log(m)),P=Math.pow(m,L);for(s=0;sp));s++);for(A>b&&(w*=-1),o=0;h>=o;o++)l=A+o*w,g.push({v:l})}}var S=i("axisLabelFormatter");for(o=0;o=0?Dygraph.getDateAxis(t,e,o,i,r):[]},Dygraph.SECONDLY=0,Dygraph.TWO_SECONDLY=1,Dygraph.FIVE_SECONDLY=2,Dygraph.TEN_SECONDLY=3,Dygraph.THIRTY_SECONDLY=4,Dygraph.MINUTELY=5,Dygraph.TWO_MINUTELY=6,Dygraph.FIVE_MINUTELY=7,Dygraph.TEN_MINUTELY=8,Dygraph.THIRTY_MINUTELY=9,Dygraph.HOURLY=10,Dygraph.TWO_HOURLY=11,Dygraph.SIX_HOURLY=12,Dygraph.DAILY=13,Dygraph.TWO_DAILY=14,Dygraph.WEEKLY=15,Dygraph.MONTHLY=16,Dygraph.QUARTERLY=17,Dygraph.BIANNUAL=18,Dygraph.ANNUAL=19,Dygraph.DECADAL=20,Dygraph.CENTENNIAL=21,Dygraph.NUM_GRANULARITIES=22,Dygraph.DATEFIELD_Y=0,Dygraph.DATEFIELD_M=1,Dygraph.DATEFIELD_D=2,Dygraph.DATEFIELD_HH=3,Dygraph.DATEFIELD_MM=4,Dygraph.DATEFIELD_SS=5,Dygraph.DATEFIELD_MS=6,Dygraph.NUM_DATEFIELDS=7,Dygraph.TICK_PLACEMENT=[],Dygraph.TICK_PLACEMENT[Dygraph.SECONDLY]={datefield:Dygraph.DATEFIELD_SS,step:1,spacing:1e3},Dygraph.TICK_PLACEMENT[Dygraph.TWO_SECONDLY]={datefield:Dygraph.DATEFIELD_SS,step:2,spacing:2e3},Dygraph.TICK_PLACEMENT[Dygraph.FIVE_SECONDLY]={datefield:Dygraph.DATEFIELD_SS,step:5,spacing:5e3},Dygraph.TICK_PLACEMENT[Dygraph.TEN_SECONDLY]={datefield:Dygraph.DATEFIELD_SS,step:10,spacing:1e4},Dygraph.TICK_PLACEMENT[Dygraph.THIRTY_SECONDLY]={datefield:Dygraph.DATEFIELD_SS,step:30,spacing:3e4},Dygraph.TICK_PLACEMENT[Dygraph.MINUTELY]={datefield:Dygraph.DATEFIELD_MM,step:1,spacing:6e4},Dygraph.TICK_PLACEMENT[Dygraph.TWO_MINUTELY]={datefield:Dygraph.DATEFIELD_MM,step:2,spacing:12e4},Dygraph.TICK_PLACEMENT[Dygraph.FIVE_MINUTELY]={datefield:Dygraph.DATEFIELD_MM,step:5,spacing:3e5},Dygraph.TICK_PLACEMENT[Dygraph.TEN_MINUTELY]={datefield:Dygraph.DATEFIELD_MM,step:10,spacing:6e5},Dygraph.TICK_PLACEMENT[Dygraph.THIRTY_MINUTELY]={datefield:Dygraph.DATEFIELD_MM,step:30,spacing:18e5},Dygraph.TICK_PLACEMENT[Dygraph.HOURLY]={datefield:Dygraph.DATEFIELD_HH,step:1,spacing:36e5},Dygraph.TICK_PLACEMENT[Dygraph.TWO_HOURLY]={datefield:Dygraph.DATEFIELD_HH,step:2,spacing:72e5},Dygraph.TICK_PLACEMENT[Dygraph.SIX_HOURLY]={datefield:Dygraph.DATEFIELD_HH,step:6,spacing:216e5},Dygraph.TICK_PLACEMENT[Dygraph.DAILY]={datefield:Dygraph.DATEFIELD_D,step:1,spacing:864e5},Dygraph.TICK_PLACEMENT[Dygraph.TWO_DAILY]={datefield:Dygraph.DATEFIELD_D,step:2,spacing:1728e5},Dygraph.TICK_PLACEMENT[Dygraph.WEEKLY]={datefield:Dygraph.DATEFIELD_D,step:7,spacing:6048e5},Dygraph.TICK_PLACEMENT[Dygraph.MONTHLY]={datefield:Dygraph.DATEFIELD_M,step:1,spacing:2629817280},Dygraph.TICK_PLACEMENT[Dygraph.QUARTERLY]={datefield:Dygraph.DATEFIELD_M,step:3,spacing:216e5*365.2524},Dygraph.TICK_PLACEMENT[Dygraph.BIANNUAL]={datefield:Dygraph.DATEFIELD_M,step:6,spacing:432e5*365.2524},Dygraph.TICK_PLACEMENT[Dygraph.ANNUAL]={datefield:Dygraph.DATEFIELD_Y,step:1,spacing:864e5*365.2524},Dygraph.TICK_PLACEMENT[Dygraph.DECADAL]={datefield:Dygraph.DATEFIELD_Y,step:10,spacing:315578073600},Dygraph.TICK_PLACEMENT[Dygraph.CENTENNIAL]={datefield:Dygraph.DATEFIELD_Y,step:100,spacing:3155780736e3},Dygraph.PREFERRED_LOG_TICK_VALUES=function(){for(var t=[],e=-39;39>=e;e++)for(var a=Math.pow(10,e),i=1;9>=i;i++){var r=a*i;t.push(r)}return t}(),Dygraph.pickDateTickGranularity=function(t,e,a,i){for(var r=i("pixelsPerLabel"),n=0;n=r)return n}return-1},Dygraph.numDateTicks=function(t,e,a){var i=Dygraph.TICK_PLACEMENT[a].spacing;return Math.round(1*(e-t)/i)},Dygraph.getDateAxis=function(t,e,a,i,r){var n=i("axisLabelFormatter"),o=i("labelsUTC"),s=o?Dygraph.DateAccessorsUTC:Dygraph.DateAccessorsLocal,l=Dygraph.TICK_PLACEMENT[a].datefield,h=Dygraph.TICK_PLACEMENT[a].step,p=Dygraph.TICK_PLACEMENT[a].spacing,g=new Date(t),d=[];d[Dygraph.DATEFIELD_Y]=s.getFullYear(g),d[Dygraph.DATEFIELD_M]=s.getMonth(g),d[Dygraph.DATEFIELD_D]=s.getDate(g),d[Dygraph.DATEFIELD_HH]=s.getHours(g),d[Dygraph.DATEFIELD_MM]=s.getMinutes(g),d[Dygraph.DATEFIELD_SS]=s.getSeconds(g),d[Dygraph.DATEFIELD_MS]=s.getMilliseconds(g);var u=d[l]%h;a==Dygraph.WEEKLY&&(u=s.getDay(g)),d[l]-=u;for(var c=l+1;cv&&(v+=p,_=new Date(v));e>=v;)y.push({v:v,label:n.call(r,_,a,i,r)}),v+=p,_=new Date(v);else for(t>v&&(d[l]+=h,_=s.makeDate.apply(null,d),v=_.getTime());e>=v;)(a>=Dygraph.DAILY||s.getHours(_)%h===0)&&y.push({v:v,label:n.call(r,_,a,i,r)}),d[l]+=h,_=s.makeDate.apply(null,d),v=_.getTime();return y},Dygraph&&Dygraph.DEFAULT_ATTRS&&Dygraph.DEFAULT_ATTRS.axes&&Dygraph.DEFAULT_ATTRS.axes.x&&Dygraph.DEFAULT_ATTRS.axes.y&&Dygraph.DEFAULT_ATTRS.axes.y2&&(Dygraph.DEFAULT_ATTRS.axes.x.ticker=Dygraph.dateTicker,Dygraph.DEFAULT_ATTRS.axes.y.ticker=Dygraph.numericTicks,Dygraph.DEFAULT_ATTRS.axes.y2.ticker=Dygraph.numericTicks)}(),Dygraph.Plugins={},Dygraph.Plugins.Annotations=function(){"use strict";var t=function(){this.annotations_=[]};return t.prototype.toString=function(){return"Annotations Plugin"},t.prototype.activate=function(t){return{clearChart:this.clearChart,didDrawChart:this.didDrawChart}},t.prototype.detachLabels=function(){for(var t=0;to.x+o.w||h.canvasyo.y+o.h)){var p=h.annotation,g=6;p.hasOwnProperty("tickHeight")&&(g=p.tickHeight);var d=document.createElement("div");for(var u in r)r.hasOwnProperty(u)&&(d.style[u]=r[u]);p.hasOwnProperty("icon")||(d.className="dygraphDefaultAnnotation"),p.hasOwnProperty("cssClass")&&(d.className+=" "+p.cssClass);var c=p.hasOwnProperty("width")?p.width:16,y=p.hasOwnProperty("height")?p.height:16;if(p.hasOwnProperty("icon")){var _=document.createElement("img");_.src=p.icon,_.width=c,_.height=y,d.appendChild(_)}else h.annotation.hasOwnProperty("shortText")&&d.appendChild(document.createTextNode(h.annotation.shortText));var v=h.canvasx-c/2;d.style.left=v+"px";var f=0;if(p.attachAtBottom){var x=o.y+o.h-y-g;s[v]?x-=s[v]:s[v]=0,s[v]+=g+y,f=x}else f=h.canvasy-y-g;d.style.top=f+"px",d.style.width=c+"px",d.style.height=y+"px",d.title=h.annotation.text,d.style.color=e.colorsMap_[h.name],d.style.borderColor=e.colorsMap_[h.name],p.div=d,e.addAndTrackEvent(d,"click",n("clickHandler","annotationClickHandler",h,this)),e.addAndTrackEvent(d,"mouseover",n("mouseOverHandler","annotationMouseOverHandler",h,this)),e.addAndTrackEvent(d,"mouseout",n("mouseOutHandler","annotationMouseOutHandler",h,this)),e.addAndTrackEvent(d,"dblclick",n("dblClickHandler","annotationDblClickHandler",h,this)),i.appendChild(d),this.annotations_.push(d);var m=t.drawingContext;if(m.save(),m.strokeStyle=e.colorsMap_[h.name],m.beginPath(),p.attachAtBottom){var x=f+y;m.moveTo(h.canvasx,x),m.lineTo(h.canvasx,x+g)}else m.moveTo(h.canvasx,h.canvasy),m.lineTo(h.canvasx,h.canvasy-2-g);m.closePath(),m.stroke(),m.restore()}}},t.prototype.destroy=function(){this.detachLabels()},t}(),Dygraph.Plugins.Axes=function(){"use strict";var t=function(){this.xlabels_=[],this.ylabels_=[]};return t.prototype.toString=function(){return"Axes Plugin"},t.prototype.activate=function(t){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}},t.prototype.layout=function(t){var e=t.dygraph;if(e.getOptionForAxis("drawAxis","y")){var a=e.getOptionForAxis("axisLabelWidth","y")+2*e.getOptionForAxis("axisTickSize","y");t.reserveSpaceLeft(a)}if(e.getOptionForAxis("drawAxis","x")){var i;i=e.getOption("xAxisHeight")?e.getOption("xAxisHeight"):e.getOptionForAxis("axisLabelFontSize","x")+2*e.getOptionForAxis("axisTickSize","x"),t.reserveSpaceBottom(i)}if(2==e.numAxes()){if(e.getOptionForAxis("drawAxis","y2")){var a=e.getOptionForAxis("axisLabelWidth","y2")+2*e.getOptionForAxis("axisTickSize","y2");t.reserveSpaceRight(a)}}else e.numAxes()>2&&e.error("Only two y-axes are supported at this time. (Trying to use "+e.numAxes()+")")},t.prototype.detachLabels=function(){function t(t){for(var e=0;e0){var x=i.numAxes(),m=[f("y"),f("y2")];for(l=0;l<_.yticks.length;l++){if(s=_.yticks[l],"function"==typeof s)return;n=v.x;var D=1,w="y1",A=m[0];1==s[0]&&(n=v.x+v.w,D=-1,w="y2",A=m[1]);var b=A("axisLabelFontSize");o=v.y+s[1]*v.h,r=y(s[2],"y",2==x?w:null);var T=o-b/2;0>T&&(T=0),T+b+3>d?r.style.bottom="0":r.style.top=T+"px",0===s[0]?(r.style.left=v.x-A("axisLabelWidth")-A("axisTickSize")+"px",r.style.textAlign="right"):1==s[0]&&(r.style.left=v.x+v.w+A("axisTickSize")+"px",r.style.textAlign="left"),r.style.width=A("axisLabelWidth")+"px",p.appendChild(r),this.ylabels_.push(r)}var E=this.ylabels_[0],b=i.getOptionForAxis("axisLabelFontSize","y"),C=parseInt(E.style.top,10)+b;C>d-b&&(E.style.top=parseInt(E.style.top,10)-b/2+"px")}var L;if(i.getOption("drawAxesAtZero")){var P=i.toPercentXCoord(0);(P>1||0>P||isNaN(P))&&(P=0),L=e(v.x+P*v.w)}else L=e(v.x);h.strokeStyle=i.getOptionForAxis("axisLineColor","y"),h.lineWidth=i.getOptionForAxis("axisLineWidth","y"),h.beginPath(),h.moveTo(L,a(v.y)),h.lineTo(L,a(v.y+v.h)),h.closePath(),h.stroke(),2==i.numAxes()&&(h.strokeStyle=i.getOptionForAxis("axisLineColor","y2"),h.lineWidth=i.getOptionForAxis("axisLineWidth","y2"),h.beginPath(),h.moveTo(a(v.x+v.w),a(v.y)),h.lineTo(a(v.x+v.w),a(v.y+v.h)),h.closePath(),h.stroke())}if(i.getOptionForAxis("drawAxis","x")){if(_.xticks){var A=f("x");for(l=0;l<_.xticks.length;l++){s=_.xticks[l],n=v.x+s[0]*v.w,o=v.y+v.h,r=y(s[1],"x"),r.style.textAlign="center",r.style.top=o+A("axisTickSize")+"px";var S=n-A("axisLabelWidth")/2;S+A("axisLabelWidth")>g&&(S=g-A("axisLabelWidth"),r.style.textAlign="right"),0>S&&(S=0,r.style.textAlign="left"),r.style.left=S+"px",r.style.width=A("axisLabelWidth")+"px",
5 | p.appendChild(r),this.xlabels_.push(r)}}h.strokeStyle=i.getOptionForAxis("axisLineColor","x"),h.lineWidth=i.getOptionForAxis("axisLineWidth","x"),h.beginPath();var O;if(i.getOption("drawAxesAtZero")){var P=i.toPercentYCoord(0,0);(P>1||0>P)&&(P=1),O=a(v.y+P*v.h)}else O=a(v.y+v.h);h.moveTo(e(v.x),O),h.lineTo(e(v.x+v.w),O),h.closePath(),h.stroke()}h.restore()}},t}(),Dygraph.Plugins.ChartLabels=function(){"use strict";var t=function(){this.title_div_=null,this.xlabel_div_=null,this.ylabel_div_=null,this.y2label_div_=null};t.prototype.toString=function(){return"ChartLabels Plugin"},t.prototype.activate=function(t){return{layout:this.layout,didDrawChart:this.didDrawChart}};var e=function(t){var e=document.createElement("div");return e.style.position="absolute",e.style.left=t.x+"px",e.style.top=t.y+"px",e.style.width=t.w+"px",e.style.height=t.h+"px",e};t.prototype.detachLabels_=function(){for(var t=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_],e=0;e=2);for(o=h.yticks,l.save(),n=0;n=2;for(y&&l.installPattern(_),l.strokeStyle=s.getOptionForAxis("gridLineColor","x"),l.lineWidth=s.getOptionForAxis("gridLineWidth","x"),n=0;n/g,">")};return t.prototype.select=function(e){var a=e.selectedX,i=e.selectedPoints,r=e.selectedRow,n=e.dygraph.getOption("legend");if("never"===n)return void(this.legend_div_.style.display="none");if("follow"===n){var o=e.dygraph.plotter_.area,s=e.dygraph.getOption("labelsDivWidth"),l=e.dygraph.getOptionForAxis("axisLabelWidth","y"),h=i[0].x*o.w+20,p=i[0].y*o.h-20;h+s+1>window.scrollX+window.innerWidth&&(h=h-40-s-(l-o.x)),e.dygraph.graphDiv.appendChild(this.legend_div_),this.legend_div_.style.left=l+h+"px",this.legend_div_.style.top=p+"px"}var g=t.generateLegendHTML(e.dygraph,a,i,this.one_em_width_,r);this.legend_div_.innerHTML=g,this.legend_div_.style.display=""},t.prototype.deselect=function(e){var i=e.dygraph.getOption("legend");"always"!==i&&(this.legend_div_.style.display="none");var r=a(this.legend_div_);this.one_em_width_=r;var n=t.generateLegendHTML(e.dygraph,void 0,void 0,r,null);this.legend_div_.innerHTML=n},t.prototype.didDrawChart=function(t){this.deselect(t)},t.prototype.predraw=function(t){if(this.is_generated_div_){t.dygraph.graphDiv.appendChild(this.legend_div_);var e=t.dygraph.plotter_.area,a=t.dygraph.getOption("labelsDivWidth");this.legend_div_.style.left=e.x+e.w-a-1+"px",this.legend_div_.style.top=e.y+"px",this.legend_div_.style.width=a+"px"}},t.prototype.destroy=function(){this.legend_div_=null},t.generateLegendHTML=function(t,a,r,n,o){if(t.getOption("showLabelsOnHighlight")!==!0)return"";var s,l,h,p,g,d=t.getLabels();if("undefined"==typeof a){if("always"!=t.getOption("legend"))return"";for(l=t.getOption("labelsSeparateLines"),s="",h=1;h":" "),g=t.getOption("strokePattern",d[h]),p=e(g,u.color,n),s+=""+p+" "+i(d[h])+"")}return s}var c=t.optionsViewForAxis_("x"),y=c("valueFormatter");s=y.call(t,a,c,d[0],t,o,0),""!==s&&(s+=":");var _=[],v=t.numAxes();for(h=0;v>h;h++)_[h]=t.optionsViewForAxis_("y"+(h?1+h:""));var f=t.getOption("labelsShowZeroValues");l=t.getOption("labelsSeparateLines");var x=t.getHighlightSeries();for(h=0;h");var u=t.getPropertiesForSeries(m.name),D=_[u.axis-1],w=D("valueFormatter"),A=w.call(t,m.yval,D,m.name,t,o,d.indexOf(m.name)),b=m.name==x?" class='highlight'":"";s+=" "+i(m.name)+": "+A+""}}return s},e=function(t,e,a){var i=/MSIE/.test(navigator.userAgent)&&!window.opera;if(i)return"—";if(!t||t.length<=1)return'';var r,n,o,s,l,h=0,p=0,g=[];for(r=0;r<=t.length;r++)h+=t[r%t.length];if(l=Math.floor(a/(h-t[0])),l>1){for(r=0;rn;n++)for(r=0;p>r;r+=2)o=g[r%g.length],s=r';return d},t}(),Dygraph.Plugins.RangeSelector=function(){"use strict";var t=function(){this.isIE_=/MSIE/.test(navigator.userAgent)&&!window.opera,this.hasTouchInterface_="undefined"!=typeof TouchEvent,this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion),this.interfaceCreated_=!1};return t.prototype.toString=function(){return"RangeSelector Plugin"},t.prototype.activate=function(t){return this.dygraph_=t,this.isUsingExcanvas_=t.isUsingExcanvas_,this.getOption_("showRangeSelector")&&this.createInterface_(),{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}},t.prototype.destroy=function(){this.bgcanvas_=null,this.fgcanvas_=null,this.leftZoomHandle_=null,this.rightZoomHandle_=null,this.iePanOverlay_=null},t.prototype.getOption_=function(t,e){return this.dygraph_.getOption(t,e)},t.prototype.setDefaultOption_=function(t,e){this.dygraph_.attrs_[t]=e},t.prototype.createInterface_=function(){this.createCanvases_(),this.isUsingExcanvas_&&this.createIEPanOverlay_(),this.createZoomHandles_(),this.initInteraction_(),this.getOption_("animatedZooms")&&(console.warn("Animated zooms and range selector are not compatible; disabling animatedZooms."),this.dygraph_.updateOptions({animatedZooms:!1},!0)),this.interfaceCreated_=!0,this.addToGraph_()},t.prototype.addToGraph_=function(){var t=this.graphDiv_=this.dygraph_.graphDiv;t.appendChild(this.bgcanvas_),t.appendChild(this.fgcanvas_),t.appendChild(this.leftZoomHandle_),t.appendChild(this.rightZoomHandle_)},t.prototype.removeFromGraph_=function(){var t=this.graphDiv_;t.removeChild(this.bgcanvas_),t.removeChild(this.fgcanvas_),t.removeChild(this.leftZoomHandle_),t.removeChild(this.rightZoomHandle_),this.graphDiv_=null},t.prototype.reserveSpace_=function(t){this.getOption_("showRangeSelector")&&t.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)},t.prototype.renderStaticLayer_=function(){this.updateVisibility_()&&(this.resize_(),this.drawStaticLayer_())},t.prototype.renderInteractiveLayer_=function(){this.updateVisibility_()&&!this.isChangingRange_&&(this.placeZoomHandles_(),this.drawInteractiveLayer_())},t.prototype.updateVisibility_=function(){var t=this.getOption_("showRangeSelector");if(t)this.interfaceCreated_?this.graphDiv_&&this.graphDiv_.parentNode||this.addToGraph_():this.createInterface_();else if(this.graphDiv_){this.removeFromGraph_();var e=this.dygraph_;setTimeout(function(){e.width_=0,e.resize()},1)}return t},t.prototype.resize_=function(){function t(t,e,a){var i=Dygraph.getContextPixelRatio(e);t.style.top=a.y+"px",t.style.left=a.x+"px",t.width=a.w*i,t.height=a.h*i,t.style.width=a.w+"px",t.style.height=a.h+"px",1!=i&&e.scale(i,i)}var e=this.dygraph_.layout_.getPlotArea(),a=0;this.dygraph_.getOptionForAxis("drawAxis","x")&&(a=this.getOption_("xAxisHeight")||this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize")),this.canvasRect_={x:e.x,y:e.y+e.h+a+4,w:e.w,h:this.getOption_("rangeSelectorHeight")},t(this.bgcanvas_,this.bgcanvas_ctx_,this.canvasRect_),t(this.fgcanvas_,this.fgcanvas_ctx_,this.canvasRect_)},t.prototype.createCanvases_=function(){this.bgcanvas_=Dygraph.createCanvas(),this.bgcanvas_.className="dygraph-rangesel-bgcanvas",this.bgcanvas_.style.position="absolute",this.bgcanvas_.style.zIndex=9,this.bgcanvas_ctx_=Dygraph.getContext(this.bgcanvas_),this.fgcanvas_=Dygraph.createCanvas(),this.fgcanvas_.className="dygraph-rangesel-fgcanvas",this.fgcanvas_.style.position="absolute",this.fgcanvas_.style.zIndex=9,this.fgcanvas_.style.cursor="default",this.fgcanvas_ctx_=Dygraph.getContext(this.fgcanvas_)},t.prototype.createIEPanOverlay_=function(){this.iePanOverlay_=document.createElement("div"),this.iePanOverlay_.style.position="absolute",this.iePanOverlay_.style.backgroundColor="white",this.iePanOverlay_.style.filter="alpha(opacity=0)",this.iePanOverlay_.style.display="none",this.iePanOverlay_.style.cursor="move",this.fgcanvas_.appendChild(this.iePanOverlay_)},t.prototype.createZoomHandles_=function(){var t=new Image;t.className="dygraph-rangesel-zoomhandle",t.style.position="absolute",t.style.zIndex=10,t.style.visibility="hidden",t.style.cursor="col-resize",/MSIE 7/.test(navigator.userAgent)?(t.width=7,t.height=14,t.style.backgroundColor="white",t.style.border="1px solid #333333"):(t.width=9,t.height=16,t.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII="),this.isMobileDevice_&&(t.width*=2,t.height*=2),this.leftZoomHandle_=t,this.rightZoomHandle_=t.cloneNode(!1)},t.prototype.initInteraction_=function(){var t,e,a,i,r,n,o,s,l,h,p,g,d,u,c=this,y=document,_=0,v=null,f=!1,x=!1,m=!this.isMobileDevice_&&!this.isUsingExcanvas_,D=new Dygraph.IFrameTarp;t=function(t){var e=c.dygraph_.xAxisExtremes(),a=(e[1]-e[0])/c.canvasRect_.w,i=e[0]+(t.leftHandlePos-c.canvasRect_.x)*a,r=e[0]+(t.rightHandlePos-c.canvasRect_.x)*a;return[i,r]},e=function(t){return Dygraph.cancelEvent(t),f=!0,_=t.clientX,v=t.target?t.target:t.srcElement,("mousedown"===t.type||"dragstart"===t.type)&&(Dygraph.addEvent(y,"mousemove",a),Dygraph.addEvent(y,"mouseup",i)),c.fgcanvas_.style.cursor="col-resize",D.cover(),!0},a=function(t){if(!f)return!1;Dygraph.cancelEvent(t);var e=t.clientX-_;if(Math.abs(e)<4)return!0;_=t.clientX;var a,i=c.getZoomHandleStatus_();v==c.leftZoomHandle_?(a=i.leftHandlePos+e,a=Math.min(a,i.rightHandlePos-v.width-3),a=Math.max(a,c.canvasRect_.x)):(a=i.rightHandlePos+e,a=Math.min(a,c.canvasRect_.x+c.canvasRect_.w),a=Math.max(a,i.leftHandlePos+v.width+3));var n=v.width/2;return v.style.left=a-n+"px",c.drawInteractiveLayer_(),m&&r(),!0},i=function(t){return f?(f=!1,D.uncover(),Dygraph.removeEvent(y,"mousemove",a),Dygraph.removeEvent(y,"mouseup",i),c.fgcanvas_.style.cursor="default",m||r(),!0):!1},r=function(){try{var e=c.getZoomHandleStatus_();if(c.isChangingRange_=!0,e.isZoomed){var a=t(e);c.dygraph_.doZoomXDates_(a[0],a[1])}else c.dygraph_.resetZoom()}finally{c.isChangingRange_=!1}},n=function(t){if(c.isUsingExcanvas_)return t.srcElement==c.iePanOverlay_;var e=c.leftZoomHandle_.getBoundingClientRect(),a=e.left+e.width/2;e=c.rightZoomHandle_.getBoundingClientRect();var i=e.left+e.width/2;return t.clientX>a&&t.clientX=c.canvasRect_.x+c.canvasRect_.w?(r=c.canvasRect_.x+c.canvasRect_.w,i=r-n):(i+=e,r+=e);var o=c.leftZoomHandle_.width/2;return c.leftZoomHandle_.style.left=i-o+"px",c.rightZoomHandle_.style.left=r-o+"px",c.drawInteractiveLayer_(),m&&h(),!0},l=function(t){return x?(x=!1,Dygraph.removeEvent(y,"mousemove",s),Dygraph.removeEvent(y,"mouseup",l),m||h(),!0):!1},h=function(){try{c.isChangingRange_=!0,c.dygraph_.dateWindow_=t(c.getZoomHandleStatus_()),c.dygraph_.drawGraph_(!1)}finally{c.isChangingRange_=!1}},p=function(t){if(!f&&!x){var e=n(t)?"move":"default";e!=c.fgcanvas_.style.cursor&&(c.fgcanvas_.style.cursor=e)}},g=function(t){"touchstart"==t.type&&1==t.targetTouches.length?e(t.targetTouches[0])&&Dygraph.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?a(t.targetTouches[0])&&Dygraph.cancelEvent(t):i(t)},d=function(t){"touchstart"==t.type&&1==t.targetTouches.length?o(t.targetTouches[0])&&Dygraph.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?s(t.targetTouches[0])&&Dygraph.cancelEvent(t):l(t)},u=function(t,e){for(var a=["touchstart","touchend","touchmove","touchcancel"],i=0;it;t++){var s=this.getOption_("showInRangeSelector",r[t]);n[t]=s,null!==s&&(o=!0)}if(!o)for(t=0;t1&&(g=h.rollingAverage(g,e.rollPeriod(),p)),l.push(g)}var d=[];for(t=0;t0)&&(v=Math.min(v,x),f=Math.max(f,x))}var m=.25;if(a)for(f=Dygraph.log10(f),f+=f*m,v=Dygraph.log10(v),t=0;tthis.canvasRect_.x||a+10&&t[r][0]>o;)i--,r--}return i>=a?[a,i]:[0,t.length-1]},t.parseFloat=function(t){return null===t?0/0:t}}(),function(){"use strict";Dygraph.DataHandlers.DefaultHandler=function(){};var t=Dygraph.DataHandlers.DefaultHandler;t.prototype=new Dygraph.DataHandler,t.prototype.extractSeries=function(t,e,a){for(var i=[],r=a.get("logscale"),n=0;n=s&&(s=null),i.push([o,s])}return i},t.prototype.rollingAverage=function(t,e,a){e=Math.min(e,t.length);var i,r,n,o,s,l=[];if(1==e)return t;for(i=0;ir;r++)n=t[r][1],null===n||isNaN(n)||(s++,o+=t[r][1]);s?l[i]=[t[i][0],o/s]:l[i]=[t[i][0],null]}return l},t.prototype.getExtremeYValues=function(t,e,a){for(var i,r=null,n=null,o=0,s=t.length-1,l=o;s>=l;l++)i=t[l][1],null===i||isNaN(i)||((null===n||i>n)&&(n=i),(null===r||r>i)&&(r=i));return[r,n]}}(),function(){"use strict";Dygraph.DataHandlers.DefaultFractionHandler=function(){};var t=Dygraph.DataHandlers.DefaultFractionHandler;t.prototype=new Dygraph.DataHandlers.DefaultHandler,t.prototype.extractSeries=function(t,e,a){for(var i,r,n,o,s,l,h=[],p=100,g=a.get("logscale"),d=0;d=0&&(n-=t[i-e][2][0],o-=t[i-e][2][1]);var l=t[i][0],h=o?n/o:0;r[i]=[l,s*h]}return r}}(),function(){"use strict";Dygraph.DataHandlers.BarsHandler=function(){Dygraph.DataHandler.call(this)},Dygraph.DataHandlers.BarsHandler.prototype=new Dygraph.DataHandler;var t=Dygraph.DataHandlers.BarsHandler;t.prototype.extractSeries=function(t,e,a){},t.prototype.rollingAverage=function(t,e,a){},t.prototype.onPointsCreated_=function(t,e){for(var a=0;a=l;l++)if(i=t[l][1],null!==i&&!isNaN(i)){var h=t[l][2][0],p=t[l][2][1];h>i&&(h=i),i>p&&(p=i),(null===n||p>n)&&(n=p),(null===r||r>h)&&(r=h)}return[r,n]},t.prototype.onLineEvaluated=function(t,e,a){for(var i,r=0;r=0){var g=t[l-e];null===g[1]||isNaN(g[1])||(r-=g[2][0],o-=g[1],n-=g[2][1],s-=1)}s?p[l]=[t[l][0],1*o/s,[1*r/s,1*n/s]]:p[l]=[t[l][0],null,[null,null]]}return p}}(),function(){"use strict";Dygraph.DataHandlers.ErrorBarsHandler=function(){};var t=Dygraph.DataHandlers.ErrorBarsHandler;t.prototype=new Dygraph.DataHandlers.BarsHandler,t.prototype.extractSeries=function(t,e,a){for(var i,r,n,o,s=[],l=a.get("sigma"),h=a.get("logscale"),p=0;pr;r++)n=t[r][1],null===n||isNaN(n)||(l++,s+=n,p+=Math.pow(t[r][2][2],2));l?(h=Math.sqrt(p)/l,g=s/l,d[i]=[t[i][0],g,[g-u*h,g+u*h]]):(o=1==e?t[i][1]:null,d[i]=[t[i][0],o,[o,o]])}return d}}(),function(){"use strict";Dygraph.DataHandlers.FractionsBarsHandler=function(){};var t=Dygraph.DataHandlers.FractionsBarsHandler;t.prototype=new Dygraph.DataHandlers.BarsHandler,t.prototype.extractSeries=function(t,e,a){for(var i,r,n,o,s,l,h,p,g=[],d=100,u=a.get("sigma"),c=a.get("logscale"),y=0;y=0&&(p-=t[n-e][2][2],g-=t[n-e][2][3]);var u=t[n][0],c=g?p/g:0;if(h)if(g){var y=0>c?0:c,_=g,v=l*Math.sqrt(y*(1-y)/_+l*l/(4*_*_)),f=1+l*l/g;i=(y+l*l/(2*g)-v)/f,r=(y+l*l/(2*g)+v)/f,s[n]=[u,y*d,[i*d,r*d]]}else s[n]=[u,0,[0,0]];else o=g?l*Math.sqrt(c*(1-c)/g):1,s[n]=[u,d*c,[d*(c-o),d*(c+o)]]}return s}}();
6 | //# sourceMappingURL=dygraph-combined.js.map
--------------------------------------------------------------------------------
/codemap/ui/interaction.js:
--------------------------------------------------------------------------------
1 | // Code for a variety of interaction models. Used in interaction.html, but split out from
2 | // that file so they can be tested in isolation.
3 | //
4 | function downV3(event, g, context) {
5 | context.initializeMouseDown(event, g, context);
6 | if (event.altKey || event.shiftKey) {
7 | Dygraph.startZoom(event, g, context);
8 | } else {
9 | Dygraph.startPan(event, g, context);
10 | }
11 | }
12 |
13 | function moveV3(event, g, context) {
14 | if (context.isPanning) {
15 | Dygraph.movePan(event, g, context);
16 | } else if (context.isZooming) {
17 | Dygraph.moveZoom(event, g, context);
18 | }
19 | }
20 |
21 | function upV3(event, g, context) {
22 | if (context.isPanning) {
23 | Dygraph.endPan(event, g, context);
24 | } else if (context.isZooming) {
25 | Dygraph.endZoom(event, g, context);
26 | }
27 | }
28 |
29 | // Take the offset of a mouse event on the dygraph canvas and
30 | // convert it to a pair of percentages from the bottom left.
31 | // (Not top left, bottom is where the lower value is.)
32 | function offsetToPercentage(g, offsetX, offsetY) {
33 | // This is calculating the pixel offset of the leftmost date.
34 | var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
35 | var yar0 = g.yAxisRange(0);
36 |
37 | // This is calculating the pixel of the higest value. (Top pixel)
38 | var yOffset = g.toDomCoords(null, yar0[1])[1];
39 |
40 | // x y w and h are relative to the corner of the drawing area,
41 | // so that the upper corner of the drawing area is (0, 0).
42 | var x = offsetX - xOffset;
43 | var y = offsetY - yOffset;
44 |
45 | // This is computing the rightmost pixel, effectively defining the
46 | // width.
47 | var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
48 |
49 | // This is computing the lowest pixel, effectively defining the height.
50 | var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
51 |
52 | // Percentage from the left.
53 | var xPct = w == 0 ? 0 : (x / w);
54 | // Percentage from the top.
55 | var yPct = h == 0 ? 0 : (y / h);
56 |
57 | // The (1-) part below changes it from "% distance down from the top"
58 | // to "% distance up from the bottom".
59 | return [xPct, (1-yPct)];
60 | }
61 |
62 | function dblClickV3(event, g, context) {
63 | // Reducing by 20% makes it 80% the original size, which means
64 | // to restore to original size it must grow by 25%
65 |
66 | if (!(event.offsetX && event.offsetY)){
67 | event.offsetX = event.layerX - event.target.offsetLeft;
68 | event.offsetY = event.layerY - event.target.offsetTop;
69 | }
70 |
71 | var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
72 | var xPct = percentages[0];
73 | var yPct = percentages[1];
74 |
75 | if (event.ctrlKey) {
76 | zoom(g, -.25, xPct, yPct);
77 | } else {
78 | zoom(g, +.2, xPct, yPct);
79 | }
80 | }
81 |
82 | var lastClickedGraph = null;
83 |
84 | function clickV3(event, g, context) {
85 | lastClickedGraph = g;
86 | Dygraph.cancelEvent(event);
87 | }
88 |
89 | function scrollV3(event, g, context) {
90 | if (lastClickedGraph != g) {
91 | return;
92 | }
93 | var normal = event.detail ? event.detail * -1 : event.wheelDelta / 40;
94 | // For me the normalized value shows 0.075 for one click. If I took
95 | // that verbatim, it would be a 7.5%.
96 | var percentage = normal / 50;
97 |
98 | if (!(event.offsetX && event.offsetY)){
99 | event.offsetX = event.layerX - event.target.offsetLeft;
100 | event.offsetY = event.layerY - event.target.offsetTop;
101 | }
102 |
103 | var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
104 | var xPct = percentages[0];
105 | var yPct = percentages[1];
106 |
107 | zoom(g, percentage, xPct, yPct);
108 | Dygraph.cancelEvent(event);
109 | }
110 |
111 | // Adjusts [x, y] toward each other by zoomInPercentage%
112 | // Split it so the left/bottom axis gets xBias/yBias of that change and
113 | // tight/top gets (1-xBias)/(1-yBias) of that change.
114 | //
115 | // If a bias is missing it splits it down the middle.
116 | function zoom(g, zoomInPercentage, xBias, yBias) {
117 | xBias = xBias || 0.5;
118 | yBias = yBias || 0.5;
119 | function adjustAxis(axis, zoomInPercentage, bias) {
120 | var delta = axis[1] - axis[0];
121 | var increment = delta * zoomInPercentage;
122 | var foo = [increment * bias, increment * (1-bias)];
123 | return [ axis[0] + foo[0], axis[1] - foo[1] ];
124 | }
125 | var yAxes = g.yAxisRanges();
126 | var newYAxes = [];
127 | for (var i = 0; i < yAxes.length; i++) {
128 | newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
129 | }
130 |
131 | g.updateOptions({
132 | dateWindow: adjustAxis(g.xAxisRange(), zoomInPercentage, xBias),
133 | valueRange: newYAxes[0]
134 | });
135 | }
136 |
137 | var v4Active = false;
138 | var v4Canvas = null;
139 |
140 | function downV4(event, g, context) {
141 | context.initializeMouseDown(event, g, context);
142 | v4Active = true;
143 | moveV4(event, g, context); // in case the mouse went down on a data point.
144 | }
145 |
146 | var processed = [];
147 |
148 | function moveV4(event, g, context) {
149 | var RANGE = 7;
150 |
151 | if (v4Active) {
152 | var graphPos = Dygraph.findPos(g.graphDiv);
153 | var canvasx = Dygraph.pageX(event) - graphPos.x;
154 | var canvasy = Dygraph.pageY(event) - graphPos.y;
155 |
156 | var rows = g.numRows();
157 | // Row layout:
158 | // [date, [val1, stdev1], [val2, stdev2]]
159 | for (var row = 0; row < rows; row++) {
160 | var date = g.getValue(row, 0);
161 | var x = g.toDomCoords(date, null)[0];
162 | var diff = Math.abs(canvasx - x);
163 | if (diff < RANGE) {
164 | for (var col = 1; col < 3; col++) {
165 | // TODO(konigsberg): these will throw exceptions as data is removed.
166 | var vals = g.getValue(row, col);
167 | if (vals == null) { continue; }
168 | var val = vals[0];
169 | var y = g.toDomCoords(null, val)[1];
170 | var diff2 = Math.abs(canvasy - y);
171 | if (diff2 < RANGE) {
172 | var found = false;
173 | for (var i in processed) {
174 | var stored = processed[i];
175 | if(stored[0] == row && stored[1] == col) {
176 | found = true;
177 | break;
178 | }
179 | }
180 | if (!found) {
181 | processed.push([row, col]);
182 | drawV4(x, y);
183 | }
184 | return;
185 | }
186 | }
187 | }
188 | }
189 | }
190 | }
191 |
192 | function upV4(event, g, context) {
193 | if (v4Active) {
194 | v4Active = false;
195 | }
196 | }
197 |
198 | function dblClickV4(event, g, context) {
199 | restorePositioning(g);
200 | }
201 |
202 | function drawV4(x, y) {
203 | var ctx = v4Canvas;
204 |
205 | ctx.strokeStyle = "#000000";
206 | ctx.fillStyle = "#FFFF00";
207 | ctx.beginPath();
208 | ctx.arc(x,y,5,0,Math.PI*2,true);
209 | ctx.closePath();
210 | ctx.stroke();
211 | ctx.fill();
212 | }
213 |
214 | function captureCanvas(canvas, area, g) {
215 | v4Canvas = canvas;
216 | }
217 |
218 | function restorePositioning(g) {
219 | g.updateOptions({
220 | dateWindow: null,
221 | valueRange: null
222 | });
223 | }
--------------------------------------------------------------------------------
/codemap/ui/skel.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 | Codemap - IDA plugin for runtrace visualization.
4 |
5 |
6 |
7 |
8 |
9 |
40 |
41 |
42 |
43 | / runtime module base : --BASEADDR-- / input IDA static module base :
44 |
45 | / pause Codemap to see accurate trace result.
46 |
47 | Move cursor to execution flow graph.
48 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/idapythonrc.py:
--------------------------------------------------------------------------------
1 | # C:\Users\[USERNAME]\AppData\Roaming\Hex-Rays\IDA Pro\idapythonrc.py
2 | from idaapi import *
3 | import idc
4 | import zlib
5 | import traceback
6 | import webbrowser
7 | import datetime
8 | from codemap import codemap
9 |
10 | __version__ = '2.0'
11 |
12 | codemap = codemap.Codemap()
13 |
14 |
15 | def IDA_State():
16 | if get_root_filename() is None:
17 | return 'empty'
18 | try:
19 | a = idc.GetRegValue('esp')
20 | return 'running'
21 | except:
22 | return 'static'
23 |
24 |
25 | # batch function break point script - daehee
26 | def Functions(start=None, end=None):
27 | if not start:
28 | start = cvar.inf.minEA
29 | if not end:
30 | end = cvar.inf.maxEA
31 | chunk = get_fchunk(start)
32 | if not chunk:
33 | chunk = get_next_fchunk(start)
34 | while chunk and chunk.startEA < end and (chunk.flags & FUNC_TAIL) != 0:
35 | chunk = get_next_fchunk(chunk.startEA)
36 | func = chunk
37 | while func and func.startEA < end:
38 | yield (func)
39 | func = get_next_func(func.startEA)
40 |
41 |
42 | def FuncItems(start):
43 | func = get_func(start)
44 | if not func:
45 | return
46 | fii = func_item_iterator_t()
47 | ok = fii.set(func)
48 | while ok:
49 | yield fii.current()
50 | ok = fii.next_code()
51 |
52 | '''
53 | Returns a list of module objects with name,size,base and the rebase_to
54 | '''
55 | def Modules():
56 | mod = idaapi.module_info_t()
57 | result = idaapi.get_first_module(mod)
58 | while result:
59 | yield idaapi.object_t(name=mod.name, size=mod.size, base=mod.base, rebase_to=mod.rebase_to)
60 | result = idaapi.get_next_module(mod)
61 |
62 |
63 | # print slows IDA down
64 | class IDAHook(DBG_Hooks):
65 | global codemap
66 |
67 | def dbg_process_exit(self, pid, tid, ea, code):
68 | codemap.db_insert()
69 | codemap.init_codemap()
70 | print("Process exited pid=%d tid=%d ea=0x%x code=%d" %
71 | (pid, tid, ea, code))
72 |
73 | def dbg_bpt(self, tid, ea):
74 | if codemap.pause is True:
75 | return 0 # stop visualizing
76 |
77 | codemap.set_data()
78 | codemap.db_insert_queue()
79 | continue_process() # continue
80 | return 0 # no warning
81 |
82 |
83 | def hook_ida():
84 | global debughook
85 | # Remove an existing debug hook
86 | try:
87 | if debughook:
88 | print("Removing previous hook ...")
89 | debughook.unhook()
90 | except:
91 | pass
92 | # Install the debug hook
93 | debughook = IDAHook()
94 | debughook.hook()
95 | debughook.steps = 0
96 |
97 | '''
98 | - SetRangeBP -
99 | Get address range from user and setup bp to all instruction in that range.
100 | '''
101 | def SetRangeBP():
102 | if IDA_State() == 'empty':
103 | print 'no program loaded'
104 | return
105 | start_addr = AskStr('', 'Start Addr? (e.g. 0x8000) : ')
106 | end_addr = AskStr('', 'End Addr? (e.g. 0xC000) : ')
107 |
108 | start_addr = int(start_addr.replace('0x', ''), 16)
109 | end_addr = int(end_addr.replace('0x', ''), 16)
110 |
111 | for e in Heads(start_addr, end_addr):
112 | if get_bpt(e, bpt_t()) is False:
113 | add_bpt(e, 0, BPT_SOFT)
114 | else:
115 | del_bpt(e)
116 |
117 |
118 | '''
119 | - SetFunctionBP -
120 | put cursor inside the IDA-recognized function then call this.
121 | bp will be set to all instructions of function
122 | '''
123 | def SetFunctionBP():
124 | if IDA_State() == 'empty':
125 | print 'no program loaded'
126 | return
127 | ea = ScreenEA()
128 | target = 0
129 | for e in Functions():
130 | if e.startEA <= ea and ea <= e.endEA:
131 | target = e.startEA
132 |
133 | if target != 0:
134 | for e in FuncItems(target):
135 | if get_bpt(e, bpt_t()) is False:
136 | add_bpt(e, 0, BPT_SOFT)
137 | else:
138 | del_bpt(e)
139 | else:
140 | Warning('put cursor in the function body')
141 |
142 |
143 | '''
144 | - Start Trace -
145 | setup all bp's you want(maybe using GETBPLIST or SetFunctionBP or manually)
146 | then execute this script in order to create dygraph trace.
147 | '''
148 | def StartTracing():
149 | global codemap
150 | if codemap.start is False and IDA_State() != 'running':
151 | print 'IDA debugger not running'
152 | return
153 |
154 | if codemap.start is True:
155 | codemap.pause = not codemap.pause
156 | print 'Codemap Paused? : ', codemap.pause
157 | if codemap.pause is False: # resume tracing
158 | continue_process()
159 | else:
160 | codemap.db_insert()
161 | suspend_process()
162 | return
163 |
164 | # set current uid. if there is existing codemap instance, save it to prev_uids
165 | if codemap.uid != None:
166 | codemap.prev_uids.append( codemap.uid )
167 |
168 | codemap.uid = datetime.datetime.fromtimestamp(
169 | time.time()).strftime('%Y%m%d%H%M%S')
170 |
171 |
172 | codemap.init_arch()
173 | hook_ida()
174 |
175 | print 'hook ida done.'
176 | print 'homedir : ', codemap.homedir
177 | print 'making table...'
178 | # initialize sqlite3 db
179 | print codemap.db_create()
180 |
181 | # set default SQL
182 | if codemap.arch.name == 'x86':
183 | codemap.query = "select eip from trace{0}".format(codemap.uid)
184 | if codemap.arch.name == 'x64':
185 | codemap.query = "select rip from trace{0}".format(codemap.uid)
186 |
187 | print 'start HTTP server'
188 | # start HTTP server
189 | codemap.start_webserver()
190 |
191 | # fire up chrome!
192 | result = 'http://{0}:{1}/{2}'.format(codemap.server,
193 | codemap.port,
194 | codemap.uid)
195 | webbrowser.open(result)
196 | print 'start tracing...'
197 | codemap.start = True
198 | continue_process()
199 |
200 |
201 | '''
202 | - SaveModuleBP -
203 | open a dll file with IDA and execute this script after IDA analysis is done.
204 | the function offset information of dll will be saved to file inside Codemap directory
205 | '''
206 | def SaveModuleBP():
207 | global codemap
208 | try:
209 | modname = AskStr('', 'module name : ')
210 | bpo = ''
211 | for e in Functions():
212 | func = e.startEA
213 | length = e.endEA - e.startEA
214 | if length < codemap.func_min_size:
215 | continue
216 | offset = func - get_imagebase()
217 | bpo += str(offset) + '\n'
218 | print 'bp offset generation complete! ' + str(len(bpo))
219 | payload = bpo
220 | with open(codemap.homedir + modname + '.bpo', 'wb') as f:
221 | f.write(zlib.compress(payload))
222 | except:
223 | traceback.print_exc(file=sys.stdout)
224 |
225 | '''
226 | - LoadModuleBP -
227 | while debugging the target app, put cursor somewhere inside the target module code.
228 | execute the script and bp will be set for all functions specified in .bpo file
229 | '''
230 | def LoadModuleBP():
231 | global codemap
232 | try:
233 | # get current cursor
234 | cur = get_screen_ea()
235 | baseaddr = 0
236 | modname = ''
237 | # what module is my cursor pointing?
238 | for i in Modules():
239 | if cur > i.base and cur < i.base + i.size:
240 | modname = i.name.split('\x00')[0]
241 | modname = modname.split('\\')[-1:][0]
242 | baseaddr = i.base
243 |
244 | codemap.base = baseaddr # this is needed.
245 | modname = AskStr('', 'module name : ')
246 | payload = ''
247 | with open(codemap.homedir + modname + '.bpo', 'rb') as f:
248 | payload = zlib.decompress(f.read())
249 | bps = payload.split()
250 | code = bytearray()
251 | for bp in bps:
252 | code += 'add_bpt({0}, 0, BPT_SOFT);'.format(baseaddr + int(bp))
253 | print 'setting breakpoints...'
254 | # set bp!
255 | exec(str(code))
256 | except:
257 | traceback.print_exc(file=sys.stdout)
258 |
259 | def SetModuleBP():
260 | global codemap
261 | if codemap.start is False and IDA_State() is 'static':
262 | SaveModuleBP()
263 | if codemap.start is False and IDA_State() is 'running':
264 | LoadModuleBP()
265 |
266 | def ListenCodemap():
267 | global codemap
268 | codemap.start_websocketserver()
269 | print "Listning to codemap connection..."
270 |
271 | CompileLine('static key_1() { RunPythonStatement("StartTracing()"); }')
272 | CompileLine('static key_2() { RunPythonStatement("SetFunctionBP()"); }')
273 | CompileLine('static key_3() { RunPythonStatement("SetRangeBP()"); }')
274 | CompileLine('static key_4() { RunPythonStatement("SetModuleBP()"); }')
275 | CompileLine('static key_5() { RunPythonStatement("ListenCodemap()"); }')
276 |
277 | AddHotkey('Alt-1', 'key_1')
278 | AddHotkey('Alt-2', 'key_2')
279 | AddHotkey('Alt-3', 'key_3')
280 | AddHotkey('Alt-4', 'key_4')
281 | AddHotkey('Alt-5', 'key_5')
282 |
283 | print 'ALT-1 : Start(Resume)/Pause Codemap'
284 | print 'ALT-2 : Set Function BP'
285 | print 'ALT-3 : Set Range BP'
286 | print 'ALT-4 : Create/Setup Module BP'
287 | print 'ALT-5 : Connect Codemap Graph with IDA'
288 | print 'Codemap Python Plugin is ready. enjoy. - by daehee'
289 |
--------------------------------------------------------------------------------
/install.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | import platform
4 |
5 | sysname = platform.system()
6 | g_idadir = ''
7 | g_homedir = ''
8 |
9 | if sysname == 'Darwin' or sysname == 'Linux':
10 | g_idadir = '%s/.idapro' % os.environ['HOME']
11 | g_homedir = '%s/.idapro/codemap/' % os.environ['HOME']
12 | elif sysname == 'Windows':
13 | g_idadir = '%s\\Hex-Rays\\IDA Pro\\' % os.environ['APPDATA']
14 | g_homedir = '%s\\Hex-Rays\\IDA Pro\\codemap\\' % os.environ['APPDATA']
15 |
16 | if not os.path.exists(g_idadir):
17 | print 'IDA Pro is not installed!'
18 | os._exit(0)
19 |
20 | if os.path.exists(g_homedir):
21 | print 'Codemap is already installed. Do you want to reinstall it? (y/n)'
22 | cmd = raw_input()
23 | if cmd is not 'y':
24 | os._exit(0)
25 | else:
26 | try:
27 | shutil.rmtree(g_homedir)
28 | except:
29 | print 'cannot remove existing files!! maybe Codemap files are locked by some application..'
30 |
31 | if os.path.exists(g_idadir + 'idapythonrc.py'):
32 | print 'Codemap uses idapythonrc.py to place its code. it seems there already is idapythonrc.py'
33 | print 'Do you want to overwrite this? (y/n)'
34 | cmd = raw_input()
35 | if cmd is not 'y':
36 | os._exit(0)
37 |
38 | shutil.copytree('codemap', g_homedir)
39 | shutil.copy2('idapythonrc.py', g_idadir)
40 |
41 | print 'Codemap install complete! run IDA Pro now.'
42 |
--------------------------------------------------------------------------------