├── .gitattributes ├── .gitignore ├── Defcon26 ├── config.py ├── db_test.py ├── netflow_collectorDB.py ├── openpimap_liteDB.py ├── static │ ├── iframe.html │ ├── justgage.js │ ├── raphael-2.1.4.min.js │ ├── sorttable.js │ ├── style.css │ └── tablesort.js └── templates │ └── index.html ├── README.md ├── config.py ├── map_detail.png ├── map_global.png ├── netflow_collector.py ├── openpimap.py └── openpimap_lite.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /Defcon26/config.py: -------------------------------------------------------------------------------- 1 | API_KEY = 'SHODAN_API_KEY' 2 | 3 | static_ip = "127.0.0.1" 4 | listening_port = 2303 5 | 6 | VirusTotalAPIKey = "VIRUS_TOTAL_API_KEY" 7 | 8 | # TWILIO Configuration Items 9 | # account_sid = "TWILIO SID" 10 | # auth_token = "TWILIO AUTH_TOKEN" 11 | # RECV NUMBER 12 | # TWILIO_NUMBER 13 | -------------------------------------------------------------------------------- /Defcon26/db_test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from config import * 3 | import sqlite3 4 | from flask import Flask, render_template, request 5 | app = Flask(__name__) 6 | 7 | # Edit database file to reflect the right name/location of the netflow DB 8 | conn = sqlite3.connect('netflow2.db') 9 | conn.row_factory = sqlite3.Row 10 | #conn.row_factory = sqlite3.Row 11 | c = conn.cursor() 12 | print("Deleting rows...") 13 | # Blacklist considers Google DNS malicious.. cuz.. bad guys use it... 14 | # Remove it from our database.. 15 | c.execute("DELETE FROM shodan WHERE dest=?", ("8.8.8.8",)) 16 | conn.commit() 17 | 18 | # Prints all of the junk in the shodan table.. for debugging. 19 | for i in c.execute("SELECT * FROM shodan"): 20 | for j in i: 21 | pass 22 | #print(j) 23 | c.execute("SELECT name FROM sqlite_master WHERE type='table';") 24 | #print(c.fetchone()) 25 | numSamples = 0 26 | ''' 27 | # Converts 28 | def dict_factory(cursor, row): 29 | d = {} 30 | for idx, col in enumerate(cursor.description): 31 | d[col[0]] = row[idx] 32 | return d 33 | ''' 34 | 35 | portNumber = "*" 36 | def getData(): 37 | global result1 38 | conn = sqlite3.connect('netflow2.db') 39 | #conn.row_factory = sqlite3.Row 40 | c = conn.cursor() 41 | #print(numSamples) 42 | c.execute("SELECT COUNT(*) FROM (SELECT DISTINCT dest, src FROM shodan LIMIT ?)", (numSamples,)) 43 | unique = c.fetchall() 44 | for row in unique: 45 | print("Total unique IP Addresses: " + str(row[0])) 46 | unique_IPs = str(row[0]) 47 | bytes = 0 48 | #c.execute("SELECT * FROM traffic WHERE src!=? ORDER BY time DESC LIMIT ?", (home, numSamples)) 49 | c.execute("SELECT * FROM shodan ORDER BY time DESC LIMIT ?", (numSamples,)) 50 | #print(result1) 51 | result1 = [i for i in c.fetchall()] 52 | #for i in result1: 53 | #print(i) 54 | #print(result1) 55 | print("Outbound: " + humansize(bytes)) 56 | outbound = humansize(bytes) 57 | bytes = 0 58 | print(portNumber) 59 | for row in c.execute("SELECT * FROM shodan WHERE dest!=? AND dport=? ORDER BY time DESC LIMIT ?", (home, portNumber, numSamples)): 60 | totalbytes = int(row[3]) * int(row[2]) 61 | bytes = bytes + totalbytes 62 | result2 = c.fetchall() 63 | #print(result2) 64 | result2 = {item[6]: item for item in result2} 65 | 66 | print("Inbound: " + humansize(bytes)) 67 | inbound = humansize(bytes) 68 | #print(inbound.split()[0]) 69 | total = float(inbound.split()[0]) + float(outbound.split()[0]) 70 | #print(str(total)) 71 | return unique_IPs, outbound.split()[0], outbound.split()[1], \ 72 | inbound.split()[0], inbound.split()[1], str(total) 73 | 74 | try: 75 | home = requests.get('http://ipquail.com/ip').text.strip("\n\r") 76 | print(home) 77 | except: 78 | home = static_ip 79 | 80 | suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] 81 | 82 | # Sweet function that converts bytes into readable form 83 | def humansize(nbytes): 84 | i = 0 85 | while nbytes >= 1024 and i < len(suffixes)-1: 86 | nbytes /= 1024. 87 | i += 1 88 | f = ('%.2f' % nbytes).rstrip('0').rstrip('.') 89 | return '%s %s' % (f, suffixes[i]) 90 | 91 | # DO MATH. 92 | def sqlStats(): 93 | #for row in c.execute("SELECT COUNT (DISTINCT src) FROM traffic"): 94 | for row in c.execute("SELECT COUNT(*) FROM (SELECT DISTINCT dest, src FROM shodan)"): 95 | print("Total unique IP Addresses: " + str(row[0])) 96 | unique_IPs = str(row[0]) 97 | bytes = 0 98 | 99 | for row in c.execute("SELECT * FROM shodan WHERE src=?", (home,)): 100 | totalbytes = int(row[3]) * int(row[2]) 101 | bytes = bytes + totalbytes 102 | print("Outbound: " + humansize(bytes)) 103 | outbound = humansize(bytes) 104 | bytes = 0 105 | for row in c.execute("SELECT * FROM shodan WHERE src!=?", (home,)): 106 | totalbytes = int(row[3]) * int(row[2]) 107 | bytes = bytes + totalbytes 108 | print("Inbound: " + humansize(bytes)) 109 | inbound = humansize(bytes) 110 | return unique_IPs, outbound, inbound 111 | 112 | #sqlStats() 113 | 114 | def maxRowsTable(): 115 | for row in c.execute("SELECT COUNT(time) FROM shodan"): 116 | maxNumberRows=row[0] 117 | return maxNumberRows 118 | 119 | ## Flask Web Page Handling 120 | @app.route("/") 121 | def index(): 122 | unique_IPs, outbound, outlabel, inbound, inlabel, total = getData() 123 | iframe = 'iframe.html' 124 | templateData = { 125 | 'unique' : unique_IPs, 126 | 'outbound' : outbound, 127 | 'inbound' : inbound, 128 | 'total' : total, 129 | 'outlabel' : outlabel, 130 | 'inlabel' : inlabel, 131 | 'result1': result1, 132 | 'iframe' : iframe 133 | } 134 | return render_template('index.html', **templateData) 135 | 136 | @app.route('/', methods=['POST']) 137 | def formPost(): 138 | global numSamples 139 | global portNumber 140 | global IPAddress 141 | 142 | try: 143 | portNumber = int(request.form['portNumber']) 144 | except: 145 | portNumber = 8888 146 | try: 147 | numSamples = int(request.form['numSamples']) 148 | except: 149 | numSamples = "1000000000" 150 | try: 151 | IPAddress = str(request.form['IPAddress']) 152 | except: 153 | IPAddress = "*" 154 | numMaxSamples = maxRowsTable() 155 | if (int(numSamples) > int(numMaxSamples)): 156 | numSamples = (int(numMaxSamples) -1) 157 | unique_IPs, outbound, outlabel, inbound, inlabel, total = getData() 158 | templateData = { 159 | 'unique': unique_IPs, 160 | 'outbound': outbound, 161 | 'inbound': inbound, 162 | 'total': total, 163 | 'outlabel': outlabel, 164 | 'inlabel': inlabel, 165 | 'result1': result1 166 | } 167 | return render_template('index.html', **templateData) 168 | 169 | if __name__ == "__main__": 170 | app.run(host="0.0.0.0", port=80, debug=False) 171 | -------------------------------------------------------------------------------- /Defcon26/netflow_collectorDB.py: -------------------------------------------------------------------------------- 1 | import socket, struct # Raw socket access 2 | import time # Time.. 3 | from config import listening_port # uses config file to save configurable data 4 | import sqlite3 # SQLlite database interaction 5 | from socket import inet_ntoa # For parsing packets 6 | import requests # Makes web requests to retrieve WAN IP address 7 | 8 | # Retrieve WAN IP Address for later parsing 9 | try: 10 | home = requests.get('http://ipquail.com/ip').text.strip("\n\r") 11 | print(home) 12 | with open("ip.txt", "w") as text_file: 13 | print(home, file=text_file) 14 | except: 15 | file = open("ip.txt","r") 16 | home = file.readline() 17 | print('Using old IP of ' + str(home)) 18 | 19 | WAN_IP = home 20 | 21 | conn = sqlite3.connect('netflow.db') 22 | conn.execute("CREATE TABLE IF NOT EXISTS traffic (src text, sport int, packet int, \ 23 | bytes int, dest text, dport int , time text)") 24 | 25 | SIZE_OF_HEADER = 24 26 | SIZE_OF_RECORD = 48 27 | 28 | print("Collector started at: ", time.strftime("%H:%M:%S %d-%m-%Y")) 29 | 30 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 31 | s.bind(('0.0.0.0', listening_port)) 32 | current_day = time.strftime("%d-%b-%Y") 33 | 34 | 35 | while True: 36 | buf, addr = s.recvfrom(1500) 37 | 38 | (version, count) = struct.unpack('!HH',buf[0:4]) 39 | if version != 5: 40 | print("Not NetFlow v5!") 41 | continue 42 | 43 | uptime = socket.ntohl(struct.unpack('I',buf[4:8])[0]) 44 | epochseconds = socket.ntohl(struct.unpack('I',buf[8:12])[0]) 45 | 46 | for i in range(0, count): 47 | try: 48 | base = SIZE_OF_HEADER+(i*SIZE_OF_RECORD) 49 | 50 | data = struct.unpack('!IIIIHH',buf[base+16:base+36]) 51 | nfdata = {} 52 | nfdata['saddr'] = inet_ntoa(buf[base + 0:base + 4]) 53 | nfdata['daddr'] = inet_ntoa(buf[base + 4:base + 8]) 54 | nfdata['pcount'] = data[0] 55 | nfdata['bcount'] = data[1] 56 | nfdata['stime'] = data[2] 57 | nfdata['etime'] = data[3] 58 | nfdata['sport'] = data[4] 59 | nfdata['dport'] = data[5] 60 | nfdata['protocol'] = inet_ntoa(buf[base + 39]) 61 | except: 62 | continue 63 | #print(nfdata) 64 | 65 | 66 | current_day = time.strftime("%H:%M:%S %d-%m-%Y") 67 | #print(current_day) 68 | sourceIP = nfdata['saddr'] 69 | destIP = nfdata['daddr'] 70 | 71 | if sourceIP == WAN_IP: 72 | sourceIP = 'HOME' 73 | else: 74 | destIP = 'HOME' 75 | print("%s:%s %s bytes -> %s:%s" % (sourceIP, nfdata['sport'], 76 | nfdata['bcount'], destIP, 77 | nfdata['dport'])) 78 | 79 | conn.execute("INSERT INTO traffic VALUES (?, ?, ?, ?, ?, ?, ?)", (sourceIP, nfdata['sport'], 80 | nfdata['pcount'], nfdata['bcount'], 81 | destIP, nfdata['dport'], 82 | current_day)) 83 | conn.commit() 84 | 85 | #print("db write..") 86 | #print("Wrote data to netflowData-" + current_day + ".csv at " + time.strftime("%H:%M:%S %d-%m-%Y")) -------------------------------------------------------------------------------- /Defcon26/openpimap_liteDB.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import geoip2.database # Used for mapping IP -> location. Requires offline GeoLite2 database 3 | import folium # Mapping application for data visualization 4 | import os # Checks for linux/windows for filepath options 5 | import time # For retrieving current date and checking script runtime 6 | import shodan # OSINT portion. Queries for banner information from Shodan.io 7 | import requests # Retrieves watchlists and makes web requests... 8 | from config import * # Includes sensitive API information in separate file for security purposes 9 | from folium.plugins import MarkerCluster # Marker portion for dots on the Folium map 10 | import sqlite3 # SQLlite interface for SQL database 11 | from ipaddress import ip_network # ip_address, IPv4Address, IPv4Network 12 | 13 | # Replace with the name of the SQL database created by the NetFlow collector service 14 | conn = sqlite3.connect('netflow2.db') 15 | conn.row_factory = lambda cursor, row: row[0] 16 | c = conn.cursor() 17 | 18 | # Creates all of the blacklists!! 19 | blacklist = [] 20 | bad = [] 21 | start_time = time.time() 22 | api = shodan.Shodan(API_KEY) 23 | TIME = [] 24 | firehol = "https://raw.githubusercontent.com/ktsaou/blocklist-ipsets/master/firehol_level4.netset" 25 | firehol_abusers30day = "https://iplists.firehol.org/files/firehol_abusers_30d.netset" 26 | try: 27 | firehol_list = requests.get(firehol, headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}).text.split('\n') 28 | #print(firehol_list) 29 | abuser_list = requests.get(firehol_abusers30day, headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}).text.split('\n') 30 | #print(abuser_list) 31 | #rawdata = firehol_list + abuser_list 32 | rawdata = firehol_list 33 | for i in rawdata: 34 | try: 35 | ip_network(i) 36 | #print(i) 37 | blacklist.append(i) 38 | except Exception as e: 39 | #print(e) 40 | pass 41 | except Exception as e: 42 | #print(e) 43 | pass 44 | 45 | #print(blacklist) 46 | 47 | # SLOW! ...like 3 queries a MINUTE! DO NOT USE unless you really want this data... 48 | def vtLookup(ip): 49 | apikey = VirusTotalAPIKey 50 | url = 'https://www.virustotal.com/vtapi/v2/ip-address/report' 51 | params = {'apikey': apikey, 'ip': ip} 52 | response = requests.get(url, params=params) 53 | data = response.json() 54 | sorted_date = sorted(data['resolutions'], 55 | key=lambda x: datetime.datetime.strptime(x['last_resolved'], '%Y-%m-%d %H:%M:%S'), 56 | reverse=True) 57 | url = sorted_date[0]['hostname'] 58 | print(ip + ": " + url) 59 | return url 60 | 61 | conn.execute("CREATE TABLE IF NOT EXISTS shodan (src text, sport int, packet int, \ 62 | bytes int, dest text, dport int , time text)") 63 | 64 | try: 65 | home = requests.get('http://ipquail.com/ip').text.strip("\n\r") 66 | print(home) 67 | except: 68 | home = static_ip 69 | 70 | #unique_IPs = c.execute("SELECT DISTINCT dest FROM traffic").fetchall() 71 | #print(len(unique_IPs)) 72 | unique_src = (c.execute("SELECT DISTINCT src FROM traffic").fetchall()) 73 | unique_dest = (c.execute("SELECT DISTINCT dest FROM traffic").fetchall()) 74 | #print(len(unique_src)) 75 | #print(len(unique_dest)) 76 | 77 | 78 | iplist=(sorted(set(unique_dest + unique_src))) 79 | 80 | print("Found a total of {} ip addresses.".format(len(iplist))) 81 | print("List of IP's: {}".format(iplist)) 82 | 83 | 84 | # Uhh... wat? 85 | # TODO: Fix this garbage... 86 | #reserved_networks = [x for x in [blacklist]] 87 | #print(reserved_networks) 88 | reserved_networks = blacklist 89 | reserved_networks = [x for x in reserved_networks if x !="8.8.8.8" ] 90 | for ip in iplist: 91 | if any((ip in net) for net in reserved_networks) == True: 92 | bad.append(ip) 93 | print("Found badguy at " + str(ip)) 94 | 95 | for ip in bad: 96 | try: 97 | time_var = conn.execute("SELECT time FROM shodan WHERE src=? OR dest=?", (ip,ip)) 98 | #print(time_var) 99 | time_var = c.fetchone() 100 | if time_var is None: 101 | print("Insterting " + ip + " into SQL database") 102 | conn.execute("INSERT INTO shodan SELECT * FROM traffic WHERE src=? OR dest=?", (ip, ip)) 103 | conn.commit() 104 | print(time_var) 105 | conn.execute("SELECT * FROM shodan WHERE src=? OR dest=? AND time=?",(ip,ip,time_var)) 106 | if c.fetchone() is None: 107 | print("Insterting " + ip + " into SQL database") 108 | conn.execute("INSERT INTO shodan SELECT * FROM traffic WHERE src=? OR dest=?", (ip,ip)) 109 | conn.commit() 110 | else: 111 | print("Already found value.. passing.") 112 | except Exception as e: 113 | pass 114 | #print(e) 115 | conn.execute("SELECT * FROM shodan") 116 | #print(c.fetchall()) 117 | 118 | # Geolocates the IP addresses using the GeoLite2-City.mmdb local database 119 | # Edit the file path to reflect the local database 120 | # Download the database at https://dev.maxmind.com/geoip/geoip2/geolite2/ 121 | def _geolocate(iplist): 122 | print("Starting geolocation...\n") 123 | # Update filepaths to match the location of the GeoLite2-City.mmdb database 124 | if os.name == 'nt': 125 | reader = geoip2.database.Reader('D:\PycharmProjects\openpimap\GeoLite2-City.mmdb') 126 | else: 127 | reader = geoip2.database.Reader('/root/GeoLite2/GeoLite2-City.mmdb') 128 | 129 | for ip in iplist: 130 | print(ip) 131 | try: 132 | response = reader.city(ip) 133 | country_name = response.country.name 134 | state = response.subdivisions.most_specific.name 135 | latitude = response.location.latitude 136 | longitude = response.location.longitude 137 | lat.append(latitude) 138 | long.append(longitude) 139 | country_array.append(country_name) 140 | state_array.append(state) 141 | hosts.append(ip) 142 | color.append('red') 143 | except Exception as e: 144 | print(e) 145 | 146 | # Maps everything in a pretty map. Major kudos to Folium for awesome designs out of the box. 147 | def _folium(outfile): 148 | print("Starting mapping...") 149 | #colors = list(zip(color)) 150 | locations = list(zip(lat, long)) 151 | popups = [] 152 | popup_list = list(zip(hosts, country_array, state_array)) 153 | for i in popup_list: 154 | print("Trying " + str(i)) 155 | shodan_data = [] 156 | try: 157 | host = api.host(i[0]) 158 | print(host) 159 | country = i[1] 160 | shodan_ip = host['ip_str'] 161 | shodan_org = str(host.get('org', 'n/a')) 162 | shodan_os = str(host.get('os', 'n/a')) 163 | hostname = str(host.get('hostnames', 'n/a')) 164 | print(hostname) 165 | for item in host['data']: 166 | shodan_ports = "Port: %s
Banner: %s
" % (item['port'], item['data']) 167 | s1 = shodan_ports.replace("\n", "
") 168 | s = s1.replace("\r", "
") 169 | shodan_data.append(s) 170 | print(shodan_data) 171 | 172 | except shodan.APIError as e: 173 | shodan_ip = i[0] 174 | country = i[1] 175 | shodan_org = "No Shodan Data Found" 176 | shodan_os = "No Shodan Data Found" 177 | shodan_data = "--No Shodan Data Found" 178 | hostname = "No hostname found" 179 | try: 180 | html = """ 181 |

IP: 182 | """ + shodan_ip + """ 183 |

184 |

URL: 185 | """ + hostname + """ 186 |

187 |

Country: 188 | """ + country + """ 189 |

190 |

Organization: 191 | """ + shodan_org + """ 192 |

193 |

OS: 194 | """ + shodan_os + """ 195 |

196 |

""" + str(shodan_data)[2:-2] + """

197 | """ 198 | except TypeError: 199 | html = """ 200 |

IP: 201 | """ + "none" + """ 202 |

203 |

Country: 204 | """ + "none" + """ 205 |

206 |

Organization: 207 | """ + "none" + """ 208 |

209 |

OS: 210 | """ + "none" + """ 211 |

212 | 213 | """ 214 | iframe = folium.IFrame(html=str(html), width=300, height=300) 215 | popups.append(iframe) 216 | time.sleep(0.75) 217 | 218 | m = folium.Map(location=[0, 0], tiles='cartodbdark_matter', zoom_start=3) 219 | m.add_child(MarkerCluster(locations=locations, popups=popups, icons=color)) 220 | m.save(outfile) 221 | 222 | 223 | # Initializes all of the variables.. 224 | # TODO: probably a much more efficient way to do this.. 225 | def _initialize(): 226 | print("Initializing all of the global variables and lists...\n") 227 | global external_ips, internal_ips, country_array, state_array, city_array, \ 228 | latitude_array, longitude_array, location_array, hosts, ports, geodata, \ 229 | lat, long, color, INBYTES, OUTBYTES, DATA, BYTES, md, oBL, CIA, noDNS, TITLES, \ 230 | compromise, daily_in, daily_out, message, eT 231 | message = [] 232 | daily_in = [] 233 | daily_out = [] 234 | ports = [] 235 | internal_ips = [] 236 | country_array = [] 237 | state_array = [] 238 | external_ips = [] 239 | geodata = [] 240 | lat = [] 241 | long = [] 242 | hosts = [] 243 | color = [] 244 | INBYTES = [] 245 | OUTBYTES = [] 246 | DATA = [] 247 | TITLES = [] 248 | compromise = [] 249 | 250 | _initialize() 251 | _geolocate(iplist=bad) 252 | _folium(outfile='index.html') 253 | 254 | print("time elapsed: {:.2f}s".format(time.time() - start_time)) 255 | -------------------------------------------------------------------------------- /Defcon26/static/justgage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JustGage - animated gauges using RaphaelJS 3 | * Check http://www.justgage.com for official releases 4 | * Licensed under MIT. 5 | * @author Bojan Djuricic (@Toorshia) 6 | **/ 7 | 8 | JustGage = function(config) { 9 | 10 | var obj = this; 11 | 12 | // Helps in case developer wants to debug it. unobtrusive 13 | if (config === null || config === undefined) { 14 | console.log('* justgage: Make sure to pass options to the constructor!'); 15 | return false; 16 | } 17 | 18 | var node; 19 | 20 | if (config.id !== null && config.id !== undefined) { 21 | node = document.getElementById(config.id); 22 | if (!node) { 23 | console.log('* justgage: No element with id : %s found', config.id); 24 | return false; 25 | } 26 | } else if (config.parentNode !== null && config.parentNode !== undefined) { 27 | node = config.parentNode; 28 | } else { 29 | console.log('* justgage: Make sure to pass the existing element id or parentNode to the constructor.'); 30 | return false; 31 | } 32 | 33 | var dataset = node.dataset ? node.dataset : {}; 34 | 35 | // check for defaults 36 | var defaults = (config.defaults !== null && config.defaults !== undefined) ? config.defaults : false; 37 | if (defaults !== false) { 38 | config = extend({}, config, defaults); 39 | delete config.defaults; 40 | } 41 | 42 | // configurable parameters 43 | obj.config = { 44 | // id : string 45 | // this is container element id 46 | id: config.id, 47 | 48 | // value : float 49 | // value gauge is showing 50 | value: kvLookup('value', config, dataset, 0, 'float'), 51 | 52 | // defaults : bool 53 | // defaults parameter to use 54 | defaults: kvLookup('defaults', config, dataset, 0, false), 55 | 56 | // parentNode : node object 57 | // this is container element 58 | parentNode: kvLookup('parentNode', config, dataset, null), 59 | 60 | // width : int 61 | // gauge width 62 | width: kvLookup('width', config, dataset, null), 63 | 64 | // height : int 65 | // gauge height 66 | height: kvLookup('height', config, dataset, null), 67 | 68 | // title : string 69 | // gauge title 70 | title: kvLookup('title', config, dataset, ""), 71 | 72 | // titleFontColor : string 73 | // color of gauge title 74 | titleFontColor: kvLookup('titleFontColor', config, dataset, "#999999"), 75 | 76 | // titleFontFamily : string 77 | // color of gauge title 78 | titleFontFamily: kvLookup('titleFontFamily', config, dataset, "sans-serif"), 79 | 80 | // titlePosition : string 81 | // 'above' or 'below' 82 | titlePosition: kvLookup('titlePosition', config, dataset, "above"), 83 | 84 | // valueFontColor : string 85 | // color of label showing current value 86 | valueFontColor: kvLookup('valueFontColor', config, dataset, "#010101"), 87 | 88 | // valueFontFamily : string 89 | // color of label showing current value 90 | valueFontFamily: kvLookup('valueFontFamily', config, dataset, "Arial"), 91 | 92 | // symbol : string 93 | // special symbol to show next to value 94 | symbol: kvLookup('symbol', config, dataset, ''), 95 | 96 | // min : float 97 | // min value 98 | min: kvLookup('min', config, dataset, 0, 'float'), 99 | 100 | // max : float 101 | // max value 102 | max: kvLookup('max', config, dataset, 100, 'float'), 103 | 104 | // reverse : bool 105 | // reverse min and max 106 | reverse: kvLookup('reverse', config, dataset, false), 107 | 108 | // humanFriendlyDecimal : int 109 | // number of decimal places for our human friendly number to contain 110 | humanFriendlyDecimal: kvLookup('humanFriendlyDecimal', config, dataset, 0), 111 | 112 | 113 | // textRenderer: func 114 | // function applied before rendering text 115 | textRenderer: kvLookup('textRenderer', config, dataset, null), 116 | 117 | // gaugeWidthScale : float 118 | // width of the gauge element 119 | gaugeWidthScale: kvLookup('gaugeWidthScale', config, dataset, 1.0), 120 | 121 | // gaugeColor : string 122 | // background color of gauge element 123 | gaugeColor: kvLookup('gaugeColor', config, dataset, "#edebeb"), 124 | 125 | // label : string 126 | // text to show below value 127 | label: kvLookup('label', config, dataset, ''), 128 | 129 | // labelFontColor : string 130 | // color of label showing label under value 131 | labelFontColor: kvLookup('labelFontColor', config, dataset, "#b3b3b3"), 132 | 133 | // shadowOpacity : int 134 | // 0 ~ 1 135 | shadowOpacity: kvLookup('shadowOpacity', config, dataset, 0.2), 136 | 137 | // shadowSize: int 138 | // inner shadow size 139 | shadowSize: kvLookup('shadowSize', config, dataset, 5), 140 | 141 | // shadowVerticalOffset : int 142 | // how much shadow is offset from top 143 | shadowVerticalOffset: kvLookup('shadowVerticalOffset', config, dataset, 3), 144 | 145 | // levelColors : string[] 146 | // colors of indicator, from lower to upper, in RGB format 147 | levelColors: kvLookup('levelColors', config, dataset, ["#a9d70b", "#f9c802", "#ff0000"], 'array', ','), 148 | 149 | // startAnimationTime : int 150 | // length of initial animation 151 | startAnimationTime: kvLookup('startAnimationTime', config, dataset, 700), 152 | 153 | // startAnimationType : string 154 | // type of initial animation (linear, >, <, <>, bounce) 155 | startAnimationType: kvLookup('startAnimationType', config, dataset, '>'), 156 | 157 | // refreshAnimationTime : int 158 | // length of refresh animation 159 | refreshAnimationTime: kvLookup('refreshAnimationTime', config, dataset, 700), 160 | 161 | // refreshAnimationType : string 162 | // type of refresh animation (linear, >, <, <>, bounce) 163 | refreshAnimationType: kvLookup('refreshAnimationType', config, dataset, '>'), 164 | 165 | // donutStartAngle : int 166 | // angle to start from when in donut mode 167 | donutStartAngle: kvLookup('donutStartAngle', config, dataset, 90), 168 | 169 | // valueMinFontSize : int 170 | // absolute minimum font size for the value 171 | valueMinFontSize: kvLookup('valueMinFontSize', config, dataset, 16), 172 | 173 | // titleMinFontSize 174 | // absolute minimum font size for the title 175 | titleMinFontSize: kvLookup('titleMinFontSize', config, dataset, 10), 176 | 177 | // labelMinFontSize 178 | // absolute minimum font size for the label 179 | labelMinFontSize: kvLookup('labelMinFontSize', config, dataset, 10), 180 | 181 | // minLabelMinFontSize 182 | // absolute minimum font size for the minimum label 183 | minLabelMinFontSize: kvLookup('minLabelMinFontSize', config, dataset, 10), 184 | 185 | // maxLabelMinFontSize 186 | // absolute minimum font size for the maximum label 187 | maxLabelMinFontSize: kvLookup('maxLabelMinFontSize', config, dataset, 10), 188 | 189 | // hideValue : bool 190 | // hide value text 191 | hideValue: kvLookup('hideValue', config, dataset, false), 192 | 193 | // hideMinMax : bool 194 | // hide min and max values 195 | hideMinMax: kvLookup('hideMinMax', config, dataset, false), 196 | 197 | // hideInnerShadow : bool 198 | // hide inner shadow 199 | hideInnerShadow: kvLookup('hideInnerShadow', config, dataset, false), 200 | 201 | // humanFriendly : bool 202 | // convert large numbers for min, max, value to human friendly (e.g. 1234567 -> 1.23M) 203 | humanFriendly: kvLookup('humanFriendly', config, dataset, false), 204 | 205 | // noGradient : bool 206 | // whether to use gradual color change for value, or sector-based 207 | noGradient: kvLookup('noGradient', config, dataset, false), 208 | 209 | // donut : bool 210 | // show full donut gauge 211 | donut: kvLookup('donut', config, dataset, false), 212 | 213 | // relativeGaugeSize : bool 214 | // whether gauge size should follow changes in container element size 215 | relativeGaugeSize: kvLookup('relativeGaugeSize', config, dataset, false), 216 | 217 | // counter : bool 218 | // animate level number change 219 | counter: kvLookup('counter', config, dataset, false), 220 | 221 | // decimals : int 222 | // number of digits after floating point 223 | decimals: kvLookup('decimals', config, dataset, 2), 224 | 225 | // customSectors : [] of objects 226 | // number of digits after floating point 227 | customSectors: kvLookup('customSectors', config, dataset, []), 228 | 229 | // formatNumber: boolean 230 | // formats numbers with commas where appropriate 231 | formatNumber: kvLookup('formatNumber', config, dataset, false), 232 | 233 | // pointer : bool 234 | // show value pointer 235 | pointer: kvLookup('pointer', config, dataset, false), 236 | 237 | // pointerOptions : object 238 | // define pointer look 239 | pointerOptions: kvLookup('pointerOptions', config, dataset, []) 240 | }; 241 | 242 | // variables 243 | var 244 | canvasW, 245 | canvasH, 246 | widgetW, 247 | widgetH, 248 | aspect, 249 | dx, 250 | dy, 251 | titleFontSize, 252 | titleX, 253 | titleY, 254 | valueFontSize, 255 | valueX, 256 | valueY, 257 | labelFontSize, 258 | labelX, 259 | labelY, 260 | minFontSize, 261 | minX, 262 | minY, 263 | maxFontSize, 264 | maxX, 265 | maxY; 266 | 267 | // overflow values 268 | if (obj.config.value > obj.config.max) obj.config.value = obj.config.max; 269 | if (obj.config.value < obj.config.min) obj.config.value = obj.config.min; 270 | obj.originalValue = kvLookup('value', config, dataset, -1, 'float'); 271 | 272 | // create canvas 273 | if (obj.config.id !== null && (document.getElementById(obj.config.id)) !== null) { 274 | obj.canvas = Raphael(obj.config.id, "100%", "100%"); 275 | } else if (obj.config.parentNode !== null) { 276 | obj.canvas = Raphael(obj.config.parentNode, "100%", "100%"); 277 | } 278 | 279 | if (obj.config.relativeGaugeSize === true) { 280 | obj.canvas.setViewBox(0, 0, 200, 150, true); 281 | } 282 | 283 | // canvas dimensions 284 | if (obj.config.relativeGaugeSize === true) { 285 | canvasW = 200; 286 | canvasH = 150; 287 | } else if (obj.config.width !== null && obj.config.height !== null) { 288 | canvasW = obj.config.width; 289 | canvasH = obj.config.height; 290 | } else if (obj.config.parentNode !== null) { 291 | obj.canvas.setViewBox(0, 0, 200, 150, true); 292 | canvasW = 200; 293 | canvasH = 150; 294 | } else { 295 | canvasW = getStyle(document.getElementById(obj.config.id), "width").slice(0, -2) * 1; 296 | canvasH = getStyle(document.getElementById(obj.config.id), "height").slice(0, -2) * 1; 297 | } 298 | 299 | // widget dimensions 300 | if (obj.config.donut === true) { 301 | 302 | // DONUT ******************************* 303 | 304 | // width more than height 305 | if (canvasW > canvasH) { 306 | widgetH = canvasH; 307 | widgetW = widgetH; 308 | // width less than height 309 | } else if (canvasW < canvasH) { 310 | widgetW = canvasW; 311 | widgetH = widgetW; 312 | // if height don't fit, rescale both 313 | if (widgetH > canvasH) { 314 | aspect = widgetH / canvasH; 315 | widgetH = widgetH / aspect; 316 | widgetW = widgetH / aspect; 317 | } 318 | // equal 319 | } else { 320 | widgetW = canvasW; 321 | widgetH = widgetW; 322 | } 323 | 324 | // delta 325 | dx = (canvasW - widgetW) / 2; 326 | dy = (canvasH - widgetH) / 2; 327 | 328 | // title 329 | titleFontSize = ((widgetH / 8) > 10) ? (widgetH / 10) : 10; 330 | titleX = dx + widgetW / 2; 331 | titleY = dy + widgetH / 11; 332 | 333 | // value 334 | valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 5.4) : 18; 335 | valueX = dx + widgetW / 2; 336 | if (obj.config.label !== '') { 337 | valueY = dy + widgetH / 1.85; 338 | } else { 339 | valueY = dy + widgetH / 1.7; 340 | } 341 | 342 | // label 343 | labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10; 344 | labelX = dx + widgetW / 2; 345 | labelY = valueY + labelFontSize; 346 | 347 | // min 348 | minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10; 349 | minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2; 350 | minY = labelY; 351 | 352 | // max 353 | maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10; 354 | maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2; 355 | maxY = labelY; 356 | 357 | } else { 358 | // HALF ******************************* 359 | 360 | // width more than height 361 | if (canvasW > canvasH) { 362 | widgetH = canvasH; 363 | widgetW = widgetH * 1.25; 364 | //if width doesn't fit, rescale both 365 | if (widgetW > canvasW) { 366 | aspect = widgetW / canvasW; 367 | widgetW = widgetW / aspect; 368 | widgetH = widgetH / aspect; 369 | } 370 | // width less than height 371 | } else if (canvasW < canvasH) { 372 | widgetW = canvasW; 373 | widgetH = widgetW / 1.25; 374 | // if height don't fit, rescale both 375 | if (widgetH > canvasH) { 376 | aspect = widgetH / canvasH; 377 | widgetH = widgetH / aspect; 378 | widgetW = widgetH / aspect; 379 | } 380 | // equal 381 | } else { 382 | widgetW = canvasW; 383 | widgetH = widgetW * 0.75; 384 | } 385 | 386 | // delta 387 | dx = (canvasW - widgetW) / 2; 388 | dy = (canvasH - widgetH) / 2; 389 | if (obj.config.titlePosition === 'below') { 390 | // shift whole thing down 391 | dy -= (widgetH / 6.4); 392 | } 393 | 394 | // title 395 | titleFontSize = ((widgetH / 8) > obj.config.titleMinFontSize) ? (widgetH / 10) : obj.config.titleMinFontSize; 396 | titleX = dx + widgetW / 2; 397 | titleY = dy + (obj.config.titlePosition === 'below' ? (widgetH * 1.07) : (widgetH / 6.4)); 398 | 399 | // value 400 | valueFontSize = ((widgetH / 6.5) > obj.config.valueMinFontSize) ? (widgetH / 6.5) : obj.config.valueMinFontSize; 401 | valueX = dx + widgetW / 2; 402 | valueY = dy + widgetH / 1.275; 403 | 404 | // label 405 | labelFontSize = ((widgetH / 16) > obj.config.labelMinFontSize) ? (widgetH / 16) : obj.config.labelMinFontSize; 406 | labelX = dx + widgetW / 2; 407 | labelY = valueY + valueFontSize / 2 + 5; 408 | 409 | // min 410 | minFontSize = ((widgetH / 16) > obj.config.minLabelMinFontSize) ? (widgetH / 16) : obj.config.minLabelMinFontSize; 411 | minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2; 412 | minY = labelY; 413 | 414 | // max 415 | maxFontSize = ((widgetH / 16) > obj.config.maxLabelMinFontSize) ? (widgetH / 16) : obj.config.maxLabelMinFontSize; 416 | maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2; 417 | maxY = labelY; 418 | } 419 | 420 | // parameters 421 | obj.params = { 422 | canvasW: canvasW, 423 | canvasH: canvasH, 424 | widgetW: widgetW, 425 | widgetH: widgetH, 426 | dx: dx, 427 | dy: dy, 428 | titleFontSize: titleFontSize, 429 | titleX: titleX, 430 | titleY: titleY, 431 | valueFontSize: valueFontSize, 432 | valueX: valueX, 433 | valueY: valueY, 434 | labelFontSize: labelFontSize, 435 | labelX: labelX, 436 | labelY: labelY, 437 | minFontSize: minFontSize, 438 | minX: minX, 439 | minY: minY, 440 | maxFontSize: maxFontSize, 441 | maxX: maxX, 442 | maxY: maxY 443 | }; 444 | 445 | // var clear 446 | canvasW, canvasH, widgetW, widgetH, aspect, dx, dy, titleFontSize, titleX, titleY, valueFontSize, valueX, valueY, labelFontSize, labelX, labelY, minFontSize, minX, minY, maxFontSize, maxX, maxY = null; 447 | 448 | // pki - custom attribute for generating gauge paths 449 | obj.canvas.customAttributes.pki = function(value, min, max, w, h, dx, dy, gws, donut, reverse) { 450 | 451 | var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path; 452 | 453 | if (donut) { 454 | alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI; 455 | Ro = w / 2 - w / 7; 456 | Ri = Ro - w / 6.666666666666667 * gws; 457 | 458 | Cx = w / 2 + dx; 459 | Cy = h / 1.95 + dy; 460 | 461 | Xo = w / 2 + dx + Ro * Math.cos(alpha); 462 | Yo = h - (h - Cy) - Ro * Math.sin(alpha); 463 | Xi = w / 2 + dx + Ri * Math.cos(alpha); 464 | Yi = h - (h - Cy) - Ri * Math.sin(alpha); 465 | 466 | path = "M" + (Cx - Ri) + "," + Cy + " "; 467 | path += "L" + (Cx - Ro) + "," + Cy + " "; 468 | if (value > ((max - min) / 2)) { 469 | path += "A" + Ro + "," + Ro + " 0 0 1 " + (Cx + Ro) + "," + Cy + " "; 470 | } 471 | path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " "; 472 | path += "L" + Xi + "," + Yi + " "; 473 | if (value > ((max - min) / 2)) { 474 | path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx + Ri) + "," + Cy + " "; 475 | } 476 | path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " "; 477 | path += "Z "; 478 | 479 | return { 480 | path: path 481 | }; 482 | 483 | } else { 484 | alpha = (1 - (value - min) / (max - min)) * Math.PI; 485 | Ro = w / 2 - w / 10; 486 | Ri = Ro - w / 6.666666666666667 * gws; 487 | 488 | Cx = w / 2 + dx; 489 | Cy = h / 1.25 + dy; 490 | 491 | Xo = w / 2 + dx + Ro * Math.cos(alpha); 492 | Yo = h - (h - Cy) - Ro * Math.sin(alpha); 493 | Xi = w / 2 + dx + Ri * Math.cos(alpha); 494 | Yi = h - (h - Cy) - Ri * Math.sin(alpha); 495 | 496 | path = "M" + (Cx - Ri) + "," + Cy + " "; 497 | path += "L" + (Cx - Ro) + "," + Cy + " "; 498 | path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " "; 499 | path += "L" + Xi + "," + Yi + " "; 500 | path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " "; 501 | path += "Z "; 502 | 503 | return { 504 | path: path 505 | }; 506 | } 507 | 508 | // var clear 509 | alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path = null; 510 | }; 511 | 512 | // ndl - custom attribute for generating needle path 513 | obj.canvas.customAttributes.ndl = function(value, min, max, w, h, dx, dy, gws, donut) { 514 | 515 | var dlt = w * 3.5 / 100; 516 | var dlb = w / 15; 517 | var dw = w / 100; 518 | 519 | if (obj.config.pointerOptions.toplength != null && obj.config.pointerOptions.toplength != undefined) dlt = obj.config.pointerOptions.toplength; 520 | if (obj.config.pointerOptions.bottomlength != null && obj.config.pointerOptions.bottomlength != undefined) dlb = obj.config.pointerOptions.bottomlength; 521 | if (obj.config.pointerOptions.bottomwidth != null && obj.config.pointerOptions.bottomwidth != undefined) dw = obj.config.pointerOptions.bottomwidth; 522 | 523 | var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, Xc, Yc, Xz, Yz, Xa, Ya, Xb, Yb, path; 524 | 525 | if (donut) { 526 | 527 | alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI; 528 | Ro = w / 2 - w / 7; 529 | Ri = Ro - w / 6.666666666666667 * gws; 530 | 531 | Cx = w / 2 + dx; 532 | Cy = h / 1.95 + dy; 533 | 534 | Xo = w / 2 + dx + Ro * Math.cos(alpha); 535 | Yo = h - (h - Cy) - Ro * Math.sin(alpha); 536 | Xi = w / 2 + dx + Ri * Math.cos(alpha); 537 | Yi = h - (h - Cy) - Ri * Math.sin(alpha); 538 | 539 | Xc = Xo + dlt * Math.cos(alpha); 540 | Yc = Yo - dlt * Math.sin(alpha); 541 | Xz = Xi - dlb * Math.cos(alpha); 542 | Yz = Yi + dlb * Math.sin(alpha); 543 | 544 | Xa = Xz + dw * Math.sin(alpha); 545 | Ya = Yz + dw * Math.cos(alpha); 546 | Xb = Xz - dw * Math.sin(alpha); 547 | Yb = Yz - dw * Math.cos(alpha); 548 | 549 | path = 'M' + Xa + ',' + Ya + ' '; 550 | path += 'L' + Xb + ',' + Yb + ' '; 551 | path += 'L' + Xc + ',' + Yc + ' '; 552 | path += 'Z '; 553 | 554 | return { 555 | path: path 556 | }; 557 | 558 | } else { 559 | alpha = (1 - (value - min) / (max - min)) * Math.PI; 560 | Ro = w / 2 - w / 10; 561 | Ri = Ro - w / 6.666666666666667 * gws; 562 | 563 | Cx = w / 2 + dx; 564 | Cy = h / 1.25 + dy; 565 | 566 | Xo = w / 2 + dx + Ro * Math.cos(alpha); 567 | Yo = h - (h - Cy) - Ro * Math.sin(alpha); 568 | Xi = w / 2 + dx + Ri * Math.cos(alpha); 569 | Yi = h - (h - Cy) - Ri * Math.sin(alpha); 570 | 571 | Xc = Xo + dlt * Math.cos(alpha); 572 | Yc = Yo - dlt * Math.sin(alpha); 573 | Xz = Xi - dlb * Math.cos(alpha); 574 | Yz = Yi + dlb * Math.sin(alpha); 575 | 576 | Xa = Xz + dw * Math.sin(alpha); 577 | Ya = Yz + dw * Math.cos(alpha); 578 | Xb = Xz - dw * Math.sin(alpha); 579 | Yb = Yz - dw * Math.cos(alpha); 580 | 581 | path = 'M' + Xa + ',' + Ya + ' '; 582 | path += 'L' + Xb + ',' + Yb + ' '; 583 | path += 'L' + Xc + ',' + Yc + ' '; 584 | path += 'Z '; 585 | 586 | return { 587 | path: path 588 | }; 589 | } 590 | 591 | // var clear 592 | alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, Xc, Yc, Xz, Yz, Xa, Ya, Xb, Yb, path = null; 593 | }; 594 | 595 | // gauge 596 | obj.gauge = obj.canvas.path().attr({ 597 | "stroke": "none", 598 | "fill": obj.config.gaugeColor, 599 | pki: [ 600 | obj.config.max, 601 | obj.config.min, 602 | obj.config.max, 603 | obj.params.widgetW, 604 | obj.params.widgetH, 605 | obj.params.dx, 606 | obj.params.dy, 607 | obj.config.gaugeWidthScale, 608 | obj.config.donut, 609 | obj.config.reverse 610 | ] 611 | }); 612 | 613 | // level 614 | obj.level = obj.canvas.path().attr({ 615 | "stroke": "none", 616 | "fill": getColor(obj.config.value, (obj.config.value - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors), 617 | pki: [ 618 | obj.config.min, 619 | obj.config.min, 620 | obj.config.max, 621 | obj.params.widgetW, 622 | obj.params.widgetH, 623 | obj.params.dx, 624 | obj.params.dy, 625 | obj.config.gaugeWidthScale, 626 | obj.config.donut, 627 | obj.config.reverse 628 | ] 629 | }); 630 | if (obj.config.donut) { 631 | obj.level.transform("r" + obj.config.donutStartAngle + ", " + (obj.params.widgetW / 2 + obj.params.dx) + ", " + (obj.params.widgetH / 1.95 + obj.params.dy)); 632 | } 633 | 634 | if (obj.config.pointer) { 635 | // needle 636 | obj.needle = obj.canvas.path().attr({ 637 | "stroke": (obj.config.pointerOptions.stroke !== null && obj.config.pointerOptions.stroke !== undefined) ? obj.config.pointerOptions.stroke : "none", 638 | "stroke-width": (obj.config.pointerOptions.stroke_width !== null && obj.config.pointerOptions.stroke_width !== undefined) ? obj.config.pointerOptions.stroke_width : 0, 639 | "stroke-linecap": (obj.config.pointerOptions.stroke_linecap !== null && obj.config.pointerOptions.stroke_linecap !== undefined) ? obj.config.pointerOptions.stroke_linecap : "square", 640 | "fill": (obj.config.pointerOptions.color !== null && obj.config.pointerOptions.color !== undefined) ? obj.config.pointerOptions.color : "#000000", 641 | ndl: [ 642 | obj.config.min, 643 | obj.config.min, 644 | obj.config.max, 645 | obj.params.widgetW, 646 | obj.params.widgetH, 647 | obj.params.dx, 648 | obj.params.dy, 649 | obj.config.gaugeWidthScale, 650 | obj.config.donut 651 | ] 652 | }); 653 | 654 | if (obj.config.donut) { 655 | obj.needle.transform("r" + obj.config.donutStartAngle + ", " + (obj.params.widgetW / 2 + obj.params.dx) + ", " + (obj.params.widgetH / 1.95 + obj.params.dy)); 656 | } 657 | 658 | } 659 | 660 | // title 661 | obj.txtTitle = obj.canvas.text(obj.params.titleX, obj.params.titleY, obj.config.title); 662 | obj.txtTitle.attr({ 663 | "font-size": obj.params.titleFontSize, 664 | "font-weight": "bold", 665 | "font-family": obj.config.titleFontFamily, 666 | "fill": obj.config.titleFontColor, 667 | "fill-opacity": "1" 668 | }); 669 | setDy(obj.txtTitle, obj.params.titleFontSize, obj.params.titleY); 670 | 671 | // value 672 | obj.txtValue = obj.canvas.text(obj.params.valueX, obj.params.valueY, 0); 673 | obj.txtValue.attr({ 674 | "font-size": obj.params.valueFontSize, 675 | "font-weight": "bold", 676 | "font-family": obj.config.valueFontFamily, 677 | "fill": obj.config.valueFontColor, 678 | "fill-opacity": "0" 679 | }); 680 | setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY); 681 | 682 | // label 683 | obj.txtLabel = obj.canvas.text(obj.params.labelX, obj.params.labelY, obj.config.label); 684 | obj.txtLabel.attr({ 685 | "font-size": obj.params.labelFontSize, 686 | "font-weight": "normal", 687 | "font-family": "Arial", 688 | "fill": obj.config.labelFontColor, 689 | "fill-opacity": "0" 690 | }); 691 | setDy(obj.txtLabel, obj.params.labelFontSize, obj.params.labelY); 692 | 693 | // min 694 | var min = obj.config.min; 695 | if (obj.config.reverse) { 696 | min = obj.config.max; 697 | } 698 | 699 | obj.txtMinimum = min; 700 | if (obj.config.humanFriendly) { 701 | obj.txtMinimum = humanFriendlyNumber(min, obj.config.humanFriendlyDecimal); 702 | } else if (obj.config.formatNumber) { 703 | obj.txtMinimum = formatNumber(min); 704 | } 705 | obj.txtMin = obj.canvas.text(obj.params.minX, obj.params.minY, obj.txtMinimum); 706 | obj.txtMin.attr({ 707 | "font-size": obj.params.minFontSize, 708 | "font-weight": "normal", 709 | "font-family": "Arial", 710 | "fill": obj.config.labelFontColor, 711 | "fill-opacity": (obj.config.hideMinMax || obj.config.donut) ? "0" : "1" 712 | }); 713 | setDy(obj.txtMin, obj.params.minFontSize, obj.params.minY); 714 | 715 | // max 716 | var max = obj.config.max; 717 | if (obj.config.reverse) { 718 | max = obj.config.min; 719 | } 720 | obj.txtMaximum = max; 721 | if (obj.config.humanFriendly) { 722 | obj.txtMaximum = humanFriendlyNumber(max, obj.config.humanFriendlyDecimal); 723 | } else if (obj.config.formatNumber) { 724 | obj.txtMaximum = formatNumber(max); 725 | } 726 | obj.txtMax = obj.canvas.text(obj.params.maxX, obj.params.maxY, obj.txtMaximum); 727 | obj.txtMax.attr({ 728 | "font-size": obj.params.maxFontSize, 729 | "font-weight": "normal", 730 | "font-family": "Arial", 731 | "fill": obj.config.labelFontColor, 732 | "fill-opacity": (obj.config.hideMinMax || obj.config.donut) ? "0" : "1" 733 | }); 734 | setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY); 735 | 736 | var defs = obj.canvas.canvas.childNodes[1]; 737 | var svg = "http://www.w3.org/2000/svg"; 738 | 739 | if (ie !== 'undefined' && ie < 9) { 740 | // VML mode - no SVG & SVG filter support 741 | } else if (ie !== 'undefined') { 742 | onCreateElementNsReady(function() { 743 | obj.generateShadow(svg, defs); 744 | }); 745 | } else { 746 | obj.generateShadow(svg, defs); 747 | } 748 | 749 | // var clear 750 | defs, svg = null; 751 | 752 | // set value to display 753 | if (obj.config.textRenderer) { 754 | obj.originalValue = obj.config.textRenderer(obj.originalValue); 755 | } else if (obj.config.humanFriendly) { 756 | obj.originalValue = humanFriendlyNumber(obj.originalValue, obj.config.humanFriendlyDecimal) + obj.config.symbol; 757 | } else if (obj.config.formatNumber) { 758 | obj.originalValue = formatNumber(obj.originalValue) + obj.config.symbol; 759 | } else { 760 | obj.originalValue = (obj.originalValue * 1).toFixed(obj.config.decimals) + obj.config.symbol; 761 | } 762 | 763 | if (obj.config.counter === true) { 764 | //on each animation frame 765 | eve.on("raphael.anim.frame." + (obj.level.id), function() { 766 | var currentValue = obj.level.attr("pki")[0]; 767 | if (obj.config.reverse) { 768 | currentValue = (obj.config.max * 1) + (obj.config.min * 1) - (obj.level.attr("pki")[0] * 1); 769 | } 770 | if (obj.config.textRenderer) { 771 | obj.txtValue.attr("text", obj.config.textRenderer(Math.floor(currentValue))); 772 | } else if (obj.config.humanFriendly) { 773 | obj.txtValue.attr("text", humanFriendlyNumber(Math.floor(currentValue), obj.config.humanFriendlyDecimal) + obj.config.symbol); 774 | } else if (obj.config.formatNumber) { 775 | obj.txtValue.attr("text", formatNumber(Math.floor(currentValue)) + obj.config.symbol); 776 | } else { 777 | obj.txtValue.attr("text", (currentValue * 1).toFixed(obj.config.decimals) + obj.config.symbol); 778 | } 779 | setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY); 780 | currentValue = null; 781 | }); 782 | //on animation end 783 | eve.on("raphael.anim.finish." + (obj.level.id), function() { 784 | obj.txtValue.attr({ 785 | "text": obj.originalValue 786 | }); 787 | setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY); 788 | }); 789 | } else { 790 | //on animation start 791 | eve.on("raphael.anim.start." + (obj.level.id), function() { 792 | obj.txtValue.attr({ 793 | "text": obj.originalValue 794 | }); 795 | setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY); 796 | }); 797 | } 798 | 799 | // animate gauge level, value & label 800 | var rvl = obj.config.value; 801 | if (obj.config.reverse) { 802 | rvl = (obj.config.max * 1) + (obj.config.min * 1) - (obj.config.value * 1); 803 | } 804 | obj.level.animate({ 805 | pki: [ 806 | rvl, 807 | obj.config.min, 808 | obj.config.max, 809 | obj.params.widgetW, 810 | obj.params.widgetH, 811 | obj.params.dx, 812 | obj.params.dy, 813 | obj.config.gaugeWidthScale, 814 | obj.config.donut, 815 | obj.config.reverse 816 | ] 817 | }, obj.config.startAnimationTime, obj.config.startAnimationType); 818 | 819 | if (obj.config.pointer) { 820 | obj.needle.animate({ 821 | ndl: [ 822 | rvl, 823 | obj.config.min, 824 | obj.config.max, 825 | obj.params.widgetW, 826 | obj.params.widgetH, 827 | obj.params.dx, 828 | obj.params.dy, 829 | obj.config.gaugeWidthScale, 830 | obj.config.donut 831 | ] 832 | }, obj.config.startAnimationTime, obj.config.startAnimationType); 833 | } 834 | 835 | obj.txtValue.animate({ 836 | "fill-opacity": (obj.config.hideValue) ? "0" : "1" 837 | }, obj.config.startAnimationTime, obj.config.startAnimationType); 838 | obj.txtLabel.animate({ 839 | "fill-opacity": "1" 840 | }, obj.config.startAnimationTime, obj.config.startAnimationType); 841 | }; 842 | 843 | /** Refresh gauge level */ 844 | JustGage.prototype.refresh = function(val, max) { 845 | 846 | var obj = this; 847 | var displayVal, color, max = max || null; 848 | 849 | // set new max 850 | if (max !== null) { 851 | obj.config.max = max; 852 | // TODO: update customSectors 853 | 854 | obj.txtMaximum = obj.config.max; 855 | if (obj.config.humanFriendly) { 856 | obj.txtMaximum = humanFriendlyNumber(obj.config.max, obj.config.humanFriendlyDecimal); 857 | } else if (obj.config.formatNumber) { 858 | obj.txtMaximum = formatNumber(obj.config.max); 859 | } 860 | if (!obj.config.reverse) { 861 | obj.txtMax.attr({ 862 | "text": obj.txtMaximum 863 | }); 864 | setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY); 865 | } else { 866 | obj.txtMin.attr({ 867 | "text": obj.txtMaximum 868 | }); 869 | setDy(obj.txtMin, obj.params.minFontSize, obj.params.minY); 870 | } 871 | } 872 | 873 | // overflow values 874 | displayVal = val; 875 | if ((val * 1) > (obj.config.max * 1)) { 876 | val = (obj.config.max * 1); 877 | } 878 | if ((val * 1) < (obj.config.min * 1)) { 879 | val = (obj.config.min * 1); 880 | } 881 | 882 | color = getColor(val, (val - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors); 883 | 884 | if (obj.config.textRenderer) { 885 | displayVal = obj.config.textRenderer(displayVal); 886 | } else if (obj.config.humanFriendly) { 887 | displayVal = humanFriendlyNumber(displayVal, obj.config.humanFriendlyDecimal) + obj.config.symbol; 888 | } else if (obj.config.formatNumber) { 889 | displayVal = formatNumber((displayVal * 1).toFixed(obj.config.decimals)) + obj.config.symbol; 890 | } else { 891 | displayVal = (displayVal * 1).toFixed(obj.config.decimals) + obj.config.symbol; 892 | } 893 | obj.originalValue = displayVal; 894 | obj.config.value = val * 1; 895 | 896 | if (!obj.config.counter) { 897 | obj.txtValue.attr({ 898 | "text": displayVal 899 | }); 900 | setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY); 901 | } 902 | 903 | var rvl = obj.config.value; 904 | if (obj.config.reverse) { 905 | rvl = (obj.config.max * 1) + (obj.config.min * 1) - (obj.config.value * 1); 906 | } 907 | obj.level.animate({ 908 | pki: [ 909 | rvl, 910 | obj.config.min, 911 | obj.config.max, 912 | obj.params.widgetW, 913 | obj.params.widgetH, 914 | obj.params.dx, 915 | obj.params.dy, 916 | obj.config.gaugeWidthScale, 917 | obj.config.donut, 918 | obj.config.reverse 919 | ], 920 | "fill": color 921 | }, obj.config.refreshAnimationTime, obj.config.refreshAnimationType); 922 | 923 | if (obj.config.pointer) { 924 | obj.needle.animate({ 925 | ndl: [ 926 | rvl, 927 | obj.config.min, 928 | obj.config.max, 929 | obj.params.widgetW, 930 | obj.params.widgetH, 931 | obj.params.dx, 932 | obj.params.dy, 933 | obj.config.gaugeWidthScale, 934 | obj.config.donut 935 | ] 936 | }, obj.config.refreshAnimationTime, obj.config.refreshAnimationType); 937 | } 938 | 939 | // var clear 940 | obj, displayVal, color, max = null; 941 | }; 942 | 943 | /** Generate shadow */ 944 | JustGage.prototype.generateShadow = function(svg, defs) { 945 | 946 | var obj = this; 947 | var sid = "inner-shadow-" + obj.config.id; 948 | var gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3; 949 | 950 | // FILTER 951 | gaussFilter = document.createElementNS(svg, "filter"); 952 | gaussFilter.setAttribute("id", sid); 953 | defs.appendChild(gaussFilter); 954 | 955 | // offset 956 | feOffset = document.createElementNS(svg, "feOffset"); 957 | feOffset.setAttribute("dx", 0); 958 | feOffset.setAttribute("dy", obj.config.shadowVerticalOffset); 959 | gaussFilter.appendChild(feOffset); 960 | 961 | // blur 962 | feGaussianBlur = document.createElementNS(svg, "feGaussianBlur"); 963 | feGaussianBlur.setAttribute("result", "offset-blur"); 964 | feGaussianBlur.setAttribute("stdDeviation", obj.config.shadowSize); 965 | gaussFilter.appendChild(feGaussianBlur); 966 | 967 | // composite 1 968 | feComposite1 = document.createElementNS(svg, "feComposite"); 969 | feComposite1.setAttribute("operator", "out"); 970 | feComposite1.setAttribute("in", "SourceGraphic"); 971 | feComposite1.setAttribute("in2", "offset-blur"); 972 | feComposite1.setAttribute("result", "inverse"); 973 | gaussFilter.appendChild(feComposite1); 974 | 975 | // flood 976 | feFlood = document.createElementNS(svg, "feFlood"); 977 | feFlood.setAttribute("flood-color", "black"); 978 | feFlood.setAttribute("flood-opacity", obj.config.shadowOpacity); 979 | feFlood.setAttribute("result", "color"); 980 | gaussFilter.appendChild(feFlood); 981 | 982 | // composite 2 983 | feComposite2 = document.createElementNS(svg, "feComposite"); 984 | feComposite2.setAttribute("operator", "in"); 985 | feComposite2.setAttribute("in", "color"); 986 | feComposite2.setAttribute("in2", "inverse"); 987 | feComposite2.setAttribute("result", "shadow"); 988 | gaussFilter.appendChild(feComposite2); 989 | 990 | // composite 3 991 | feComposite3 = document.createElementNS(svg, "feComposite"); 992 | feComposite3.setAttribute("operator", "over"); 993 | feComposite3.setAttribute("in", "shadow"); 994 | feComposite3.setAttribute("in2", "SourceGraphic"); 995 | gaussFilter.appendChild(feComposite3); 996 | 997 | // set shadow 998 | if (!obj.config.hideInnerShadow) { 999 | obj.canvas.canvas.childNodes[2].setAttribute("filter", "url(#" + sid + ")"); 1000 | obj.canvas.canvas.childNodes[3].setAttribute("filter", "url(#" + sid + ")"); 1001 | } 1002 | 1003 | // var clear 1004 | gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3 = null; 1005 | }; 1006 | 1007 | // 1008 | // tiny helper function to lookup value of a key from two hash tables 1009 | // if none found, return defaultvalue 1010 | // 1011 | // key: string 1012 | // tablea: object 1013 | // tableb: DOMStringMap|object 1014 | // defval: string|integer|float|null 1015 | // datatype: return datatype 1016 | // delimiter: delimiter to be used in conjunction with datatype formatting 1017 | // 1018 | function kvLookup(key, tablea, tableb, defval, datatype, delimiter) { 1019 | var val = defval; 1020 | var canConvert = false; 1021 | if (!(key === null || key === undefined)) { 1022 | if (tableb !== null && tableb !== undefined && typeof tableb === "object" && key in tableb) { 1023 | val = tableb[key]; 1024 | canConvert = true; 1025 | } else if (tablea !== null && tablea !== undefined && typeof tablea === "object" && key in tablea) { 1026 | val = tablea[key]; 1027 | canConvert = true; 1028 | } else { 1029 | val = defval; 1030 | } 1031 | if (canConvert === true) { 1032 | if (datatype !== null && datatype !== undefined) { 1033 | switch (datatype) { 1034 | case 'int': 1035 | val = parseInt(val, 10); 1036 | break; 1037 | case 'float': 1038 | val = parseFloat(val); 1039 | break; 1040 | default: 1041 | break; 1042 | } 1043 | } 1044 | } 1045 | } 1046 | return val; 1047 | }; 1048 | 1049 | /** Get color for value */ 1050 | function getColor(val, pct, col, noGradient, custSec) { 1051 | 1052 | var no, inc, colors, percentage, rval, gval, bval, lower, upper, range, rangePct, pctLower, pctUpper, color; 1053 | var noGradient = noGradient || custSec.length > 0; 1054 | 1055 | if (custSec.length > 0) { 1056 | for (var i = 0; i < custSec.length; i++) { 1057 | if (val > custSec[i].lo && val <= custSec[i].hi) { 1058 | return custSec[i].color; 1059 | } 1060 | } 1061 | } 1062 | 1063 | no = col.length; 1064 | if (no === 1) return col[0]; 1065 | inc = (noGradient) ? (1 / no) : (1 / (no - 1)); 1066 | colors = []; 1067 | for (i = 0; i < col.length; i++) { 1068 | percentage = (noGradient) ? (inc * (i + 1)) : (inc * i); 1069 | rval = parseInt((cutHex(col[i])).substring(0, 2), 16); 1070 | gval = parseInt((cutHex(col[i])).substring(2, 4), 16); 1071 | bval = parseInt((cutHex(col[i])).substring(4, 6), 16); 1072 | colors[i] = { 1073 | pct: percentage, 1074 | color: { 1075 | r: rval, 1076 | g: gval, 1077 | b: bval 1078 | } 1079 | }; 1080 | } 1081 | 1082 | if (pct === 0) { 1083 | return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')'; 1084 | } 1085 | 1086 | for (var j = 0; j < colors.length; j++) { 1087 | if (pct <= colors[j].pct) { 1088 | if (noGradient) { 1089 | return 'rgb(' + [colors[j].color.r, colors[j].color.g, colors[j].color.b].join(',') + ')'; 1090 | } else { 1091 | lower = colors[j - 1]; 1092 | upper = colors[j]; 1093 | range = upper.pct - lower.pct; 1094 | rangePct = (pct - lower.pct) / range; 1095 | pctLower = 1 - rangePct; 1096 | pctUpper = rangePct; 1097 | color = { 1098 | r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper), 1099 | g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper), 1100 | b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper) 1101 | }; 1102 | return 'rgb(' + [color.r, color.g, color.b].join(',') + ')'; 1103 | } 1104 | } 1105 | } 1106 | 1107 | } 1108 | 1109 | /** Fix Raphael display:none tspan dy attribute bug */ 1110 | function setDy(elem, fontSize, txtYpos) { 1111 | if ((!ie || ie > 9) && elem.node.firstChild.attributes.dy) { 1112 | elem.node.firstChild.attributes.dy.value = 0; 1113 | } 1114 | } 1115 | 1116 | /** Random integer */ 1117 | function getRandomInt(min, max) { 1118 | return Math.floor(Math.random() * (max - min + 1)) + min; 1119 | } 1120 | 1121 | /** Cut hex */ 1122 | function cutHex(str) { 1123 | return (str.charAt(0) == "#") ? str.substring(1, 7) : str; 1124 | } 1125 | 1126 | /** Human friendly number suffix - From: http://stackoverflow.com/questions/2692323/code-golf-friendly-number-abbreviator */ 1127 | function humanFriendlyNumber(n, d) { 1128 | var p, d2, i, s; 1129 | 1130 | p = Math.pow; 1131 | d2 = p(10, d); 1132 | i = 7; 1133 | while (i) { 1134 | s = p(10, i-- * 3); 1135 | if (s <= n) { 1136 | n = Math.round(n * d2 / s) / d2 + "KMGTPE" [i]; 1137 | } 1138 | } 1139 | return n; 1140 | } 1141 | 1142 | /** Format numbers with commas - From: http://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript */ 1143 | function formatNumber(x) { 1144 | var parts = x.toString().split("."); 1145 | parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); 1146 | return parts.join("."); 1147 | } 1148 | 1149 | /** Get style */ 1150 | function getStyle(oElm, strCssRule) { 1151 | var strValue = ""; 1152 | if (document.defaultView && document.defaultView.getComputedStyle) { 1153 | strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule); 1154 | } else if (oElm.currentStyle) { 1155 | strCssRule = strCssRule.replace(/\-(\w)/g, function(strMatch, p1) { 1156 | return p1.toUpperCase(); 1157 | }); 1158 | strValue = oElm.currentStyle[strCssRule]; 1159 | } 1160 | return strValue; 1161 | } 1162 | 1163 | /** Create Element NS Ready */ 1164 | function onCreateElementNsReady(func) { 1165 | if (document.createElementNS !== undefined) { 1166 | func(); 1167 | } else { 1168 | setTimeout(function() { 1169 | onCreateElementNsReady(func); 1170 | }, 100); 1171 | } 1172 | } 1173 | 1174 | /** Get IE version */ 1175 | // ---------------------------------------------------------- 1176 | // A short snippet for detecting versions of IE in JavaScript 1177 | // without resorting to user-agent sniffing 1178 | // ---------------------------------------------------------- 1179 | // If you're not in IE (or IE version is less than 5) then: 1180 | // ie === undefined 1181 | // If you're in IE (>=5) then you can determine which version: 1182 | // ie === 7; // IE7 1183 | // Thus, to detect IE: 1184 | // if (ie) {} 1185 | // And to detect the version: 1186 | // ie === 6 // IE6 1187 | // ie > 7 // IE8, IE9 ... 1188 | // ie < 9 // Anything less than IE9 1189 | // ---------------------------------------------------------- 1190 | // UPDATE: Now using Live NodeList idea from @jdalton 1191 | var ie = (function() { 1192 | 1193 | var undef, 1194 | v = 3, 1195 | div = document.createElement('div'), 1196 | all = div.getElementsByTagName('i'); 1197 | 1198 | while ( 1199 | div.innerHTML = '', 1200 | all[0] 1201 | ); 1202 | return v > 4 ? v : undef; 1203 | }()); 1204 | 1205 | // extend target object with second object 1206 | function extend(out) { 1207 | out = out || {}; 1208 | 1209 | for (var i = 1; i < arguments.length; i++) { 1210 | if (!arguments[i]) 1211 | continue; 1212 | 1213 | for (var key in arguments[i]) { 1214 | if (arguments[i].hasOwnProperty(key)) 1215 | out[key] = arguments[i][key]; 1216 | } 1217 | } 1218 | 1219 | return out; 1220 | }; 1221 | -------------------------------------------------------------------------------- /Defcon26/static/raphael-2.1.4.min.js: -------------------------------------------------------------------------------- 1 | // ┌────────────────────────────────────────────────────────────────────┐ \\ 2 | // │ Raphaël 2.1.4 - JavaScript Vector Library │ \\ 3 | // ├────────────────────────────────────────────────────────────────────┤ \\ 4 | // │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\ 5 | // │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\ 6 | // ├────────────────────────────────────────────────────────────────────┤ \\ 7 | // │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\ 8 | // └────────────────────────────────────────────────────────────────────┘ \\ 9 | !function(a,b){"function"==typeof define&&define.amd?define("eve",function(){return b()}):"object"==typeof exports?module.exports=b():a.eve=b()}(this,function(){var a,b,c="0.4.2",d="hasOwnProperty",e=/[\.\/]/,f="*",g=function(){},h=function(a,b){return a-b},i={n:{}},j=function(c,d){c=String(c);var e,f=b,g=Array.prototype.slice.call(arguments,2),i=j.listeners(c),k=0,l=[],m={},n=[],o=a;a=c,b=0;for(var p=0,q=i.length;q>p;p++)"zIndex"in i[p]&&(l.push(i[p].zIndex),i[p].zIndex<0&&(m[i[p].zIndex]=i[p]));for(l.sort(h);l[k]<0;)if(e=m[l[k++]],n.push(e.apply(d,g)),b)return b=f,n;for(p=0;q>p;p++)if(e=i[p],"zIndex"in e)if(e.zIndex==l[k]){if(n.push(e.apply(d,g)),b)break;do if(k++,e=m[l[k]],e&&n.push(e.apply(d,g)),b)break;while(e)}else m[e.zIndex]=e;else if(n.push(e.apply(d,g)),b)break;return b=f,a=o,n.length?n:null};return j._events=i,j.listeners=function(a){var b,c,d,g,h,j,k,l,m=a.split(e),n=i,o=[n],p=[];for(g=0,h=m.length;h>g;g++){for(l=[],j=0,k=o.length;k>j;j++)for(n=o[j].n,c=[n[m[g]],n[f]],d=2;d--;)b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}return p},j.on=function(a,b){if(a=String(a),"function"!=typeof b)return function(){};for(var c=a.split(e),d=i,f=0,h=c.length;h>f;f++)d=d.n,d=d.hasOwnProperty(c[f])&&d[c[f]]||(d[c[f]]={n:{}});for(d.f=d.f||[],f=0,h=d.f.length;h>f;f++)if(d.f[f]==b)return g;return d.f.push(b),function(a){+a==+a&&(b.zIndex=+a)}},j.f=function(a){var b=[].slice.call(arguments,1);return function(){j.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},j.stop=function(){b=1},j.nt=function(b){return b?new RegExp("(?:\\.|\\/|^)"+b+"(?:\\.|\\/|$)").test(a):a},j.nts=function(){return a.split(e)},j.off=j.unbind=function(a,b){if(!a)return void(j._events=i={n:{}});var c,g,h,k,l,m,n,o=a.split(e),p=[i];for(k=0,l=o.length;l>k;k++)for(m=0;mk;k++)for(c=p[k];c.n;){if(b){if(c.f){for(m=0,n=c.f.length;n>m;m++)if(c.f[m]==b){c.f.splice(m,1);break}!c.f.length&&delete c.f}for(g in c.n)if(c.n[d](g)&&c.n[g].f){var q=c.n[g].f;for(m=0,n=q.length;n>m;m++)if(q[m]==b){q.splice(m,1);break}!q.length&&delete c.n[g].f}}else{delete c.f;for(g in c.n)c.n[d](g)&&c.n[g].f&&delete c.n[g].f}c=c.n}},j.once=function(a,b){var c=function(){return j.unbind(a,c),b.apply(this,arguments)};return j.on(a,c)},j.version=c,j.toString=function(){return"You are running Eve "+c},j}),function(a,b){"function"==typeof define&&define.amd?define("raphael.core",["eve"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("eve")):a.Raphael=b(a.eve)}(this,function(a){function b(c){if(b.is(c,"function"))return t?c():a.on("raphael.DOMload",c);if(b.is(c,U))return b._engine.create[C](b,c.splice(0,3+b.is(c[0],S))).add(c);var d=Array.prototype.slice.call(arguments,0);if(b.is(d[d.length-1],"function")){var e=d.pop();return t?e.call(b._engine.create[C](b,d)):a.on("raphael.DOMload",function(){e.call(b._engine.create[C](b,d))})}return b._engine.create[C](b,arguments)}function c(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var d in a)a[y](d)&&(b[d]=c(a[d]));return b}function d(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function e(a,b,c){function e(){var f=Array.prototype.slice.call(arguments,0),g=f.join("␀"),h=e.cache=e.cache||{},i=e.count=e.count||[];return h[y](g)?(d(i,g),c?c(h[g]):h[g]):(i.length>=1e3&&delete h[i.shift()],i.push(g),h[g]=a[C](b,f),c?c(h[g]):h[g])}return e}function f(){return this.hex}function g(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function h(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function i(a,b,c,d,e,f,g,i,j){null==j&&(j=1),j=j>1?1:0>j?0:j;for(var k=j/2,l=12,m=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;l>p;p++){var q=k*m[p]+k,r=h(q,a,c,e,g),s=h(q,b,d,f,i),t=r*r+s*s;o+=n[p]*M.sqrt(t)}return k*o}function j(a,b,c,d,e,f,g,h,j){if(!(0>j||i(a,b,c,d,e,f,g,h)o;)m/=2,n+=(j>k?1:-1)*m,k=i(a,b,c,d,e,f,g,h,n);return n}}function k(a,b,c,d,e,f,g,h){if(!(N(a,c)N(e,g)||N(b,d)N(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+O(a,c).toFixed(2)||n>+N(a,c).toFixed(2)||n<+O(e,g).toFixed(2)||n>+N(e,g).toFixed(2)||o<+O(b,d).toFixed(2)||o>+N(b,d).toFixed(2)||o<+O(f,h).toFixed(2)||o>+N(f,h).toFixed(2)))return{x:l,y:m}}}}function l(a,c,d){var e=b.bezierBBox(a),f=b.bezierBBox(c);if(!b.isBBoxIntersect(e,f))return d?0:[];for(var g=i.apply(0,a),h=i.apply(0,c),j=N(~~(g/5),1),l=N(~~(h/5),1),m=[],n=[],o={},p=d?0:[],q=0;j+1>q;q++){var r=b.findDotsAtSegment.apply(b,a.concat(q/j));m.push({x:r.x,y:r.y,t:q/j})}for(q=0;l+1>q;q++)r=b.findDotsAtSegment.apply(b,c.concat(q/l)),n.push({x:r.x,y:r.y,t:q/l});for(q=0;j>q;q++)for(var s=0;l>s;s++){var t=m[q],u=m[q+1],v=n[s],w=n[s+1],x=P(u.x-t.x)<.001?"y":"x",y=P(w.x-v.x)<.001?"y":"x",z=k(t.x,t.y,u.x,u.y,v.x,v.y,w.x,w.y);if(z){if(o[z.x.toFixed(4)]==z.y.toFixed(4))continue;o[z.x.toFixed(4)]=z.y.toFixed(4);var A=t.t+P((z[x]-t[x])/(u[x]-t[x]))*(u.t-t.t),B=v.t+P((z[y]-v[y])/(w[y]-v[y]))*(w.t-v.t);A>=0&&1.001>=A&&B>=0&&1.001>=B&&(d?p++:p.push({x:z.x,y:z.y,t1:O(A,1),t2:O(B,1)}))}}return p}function m(a,c,d){a=b._path2curve(a),c=b._path2curve(c);for(var e,f,g,h,i,j,k,m,n,o,p=d?0:[],q=0,r=a.length;r>q;q++){var s=a[q];if("M"==s[0])e=i=s[1],f=j=s[2];else{"C"==s[0]?(n=[e,f].concat(s.slice(1)),e=n[6],f=n[7]):(n=[e,f,e,f,i,j,i,j],e=i,f=j);for(var t=0,u=c.length;u>t;t++){var v=c[t];if("M"==v[0])g=k=v[1],h=m=v[2];else{"C"==v[0]?(o=[g,h].concat(v.slice(1)),g=o[6],h=o[7]):(o=[g,h,g,h,k,m,k,m],g=k,h=m);var w=l(n,o,d);if(d)p+=w;else{for(var x=0,y=w.length;y>x;x++)w[x].segment1=q,w[x].segment2=t,w[x].bez1=n,w[x].bez2=o;p=p.concat(w)}}}}}return p}function n(a,b,c,d,e,f){null!=a?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function o(){return this.x+G+this.y+G+this.width+" × "+this.height}function p(a,b,c,d,e,f){function g(a){return((l*a+k)*a+j)*a}function h(a,b){var c=i(a,b);return((o*c+n)*c+m)*c}function i(a,b){var c,d,e,f,h,i;for(e=a,i=0;8>i;i++){if(f=g(e)-a,P(f)e)return c;if(e>d)return d;for(;d>c;){if(f=g(e),P(f-a)f?c=e:d=e,e=(d-c)/2+c}return e}var j=3*b,k=3*(d-b)-j,l=1-j-k,m=3*c,n=3*(e-c)-m,o=1-m-n;return h(a,1/(200*f))}function q(a,b){var c=[],d={};if(this.ms=b,this.times=1,a){for(var e in a)a[y](e)&&(d[$(e)]=a[e],c.push($(e)));c.sort(ka)}this.anim=d,this.top=c[c.length-1],this.percents=c}function r(c,d,e,f,g,h){e=$(e);var i,j,k,l,m,o,q=c.ms,r={},s={},t={};if(f)for(w=0,x=fb.length;x>w;w++){var u=fb[w];if(u.el.id==d.id&&u.anim==c){u.percent!=e?(fb.splice(w,1),k=1):j=u,d.attr(u.totalOrigin);break}}else f=+s;for(var w=0,x=c.percents.length;x>w;w++){if(c.percents[w]==e||c.percents[w]>f*c.top){e=c.percents[w],m=c.percents[w-1]||0,q=q/c.top*(e-m),l=c.percents[w+1],i=c.anim[e];break}f&&d.attr(c.anim[c.percents[w]])}if(i){if(j)j.initstatus=f,j.start=new Date-j.ms*f;else{for(var z in i)if(i[y](z)&&(ca[y](z)||d.paper.customAttributes[y](z)))switch(r[z]=d.attr(z),null==r[z]&&(r[z]=ba[z]),s[z]=i[z],ca[z]){case S:t[z]=(s[z]-r[z])/q;break;case"colour":r[z]=b.getRGB(r[z]);var A=b.getRGB(s[z]);t[z]={r:(A.r-r[z].r)/q,g:(A.g-r[z].g)/q,b:(A.b-r[z].b)/q};break;case"path":var B=Ia(r[z],s[z]),C=B[1];for(r[z]=B[0],t[z]=[],w=0,x=r[z].length;x>w;w++){t[z][w]=[0];for(var E=1,F=r[z][w].length;F>E;E++)t[z][w][E]=(C[w][E]-r[z][w][E])/q}break;case"transform":var G=d._,J=Na(G[z],s[z]);if(J)for(r[z]=J.from,s[z]=J.to,t[z]=[],t[z].real=!0,w=0,x=r[z].length;x>w;w++)for(t[z][w]=[r[z][w][0]],E=1,F=r[z][w].length;F>E;E++)t[z][w][E]=(s[z][w][E]-r[z][w][E])/q;else{var K=d.matrix||new n,L={_:{transform:G.transform},getBBox:function(){return d.getBBox(1)}};r[z]=[K.a,K.b,K.c,K.d,K.e,K.f],La(L,s[z]),s[z]=L._.transform,t[z]=[(L.matrix.a-K.a)/q,(L.matrix.b-K.b)/q,(L.matrix.c-K.c)/q,(L.matrix.d-K.d)/q,(L.matrix.e-K.e)/q,(L.matrix.f-K.f)/q]}break;case"csv":var M=H(i[z])[I](v),N=H(r[z])[I](v);if("clip-rect"==z)for(r[z]=N,t[z]=[],w=N.length;w--;)t[z][w]=(M[w]-r[z][w])/q;s[z]=M;break;default:for(M=[][D](i[z]),N=[][D](r[z]),t[z]=[],w=d.paper.customAttributes[z].length;w--;)t[z][w]=((M[w]||0)-(N[w]||0))/q}var O=i.easing,P=b.easing_formulas[O];if(!P)if(P=H(O).match(Y),P&&5==P.length){var Q=P;P=function(a){return p(a,+Q[1],+Q[2],+Q[3],+Q[4],q)}}else P=la;if(o=i.start||c.start||+new Date,u={anim:c,percent:e,timestamp:o,start:o+(c.del||0),status:0,initstatus:f||0,stop:!1,ms:q,easing:P,from:r,diff:t,to:s,el:d,callback:i.callback,prev:m,next:l,repeat:h||c.times,origin:d.attr(),totalOrigin:g},fb.push(u),f&&!j&&!k&&(u.stop=!0,u.start=new Date-q*f,1==fb.length))return hb();k&&(u.start=new Date-u.ms*f),1==fb.length&&gb(hb)}a("raphael.anim.start."+d.id,d,c)}}function s(a){for(var b=0;be;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a};if(b._g=z,b.type=z.win.SVGAngle||z.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML","VML"==b.type){var qa,ra=z.doc.createElement("div");if(ra.innerHTML='',qa=ra.firstChild,qa.style.behavior="url(#default#VML)",!qa||"object"!=typeof qa.adj)return b.type=F;ra=null}b.svg=!(b.vml="VML"==b.type),b._Paper=B,b.fn=u=B.prototype=b.prototype,b._id=0,b._oid=0,b.is=function(a,b){return b=L.call(b),"finite"==b?!X[y](+a):"array"==b?a instanceof Array:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||"array"==b&&Array.isArray&&Array.isArray(a)||V.call(a).slice(8,-1).toLowerCase()==b},b.angle=function(a,c,d,e,f,g){if(null==f){var h=a-d,i=c-e;return h||i?(180+180*M.atan2(-i,-h)/R+360)%360:0}return b.angle(a,c,f,g)-b.angle(d,e,f,g)},b.rad=function(a){return a%360*R/180},b.deg=function(a){return Math.round(180*a/R%360*1e3)/1e3},b.snapTo=function(a,c,d){if(d=b.is(d,"finite")?d:10,b.is(a,U)){for(var e=a.length;e--;)if(P(a[e]-c)<=d)return a[e]}else{a=+a;var f=c%a;if(d>f)return c-f;if(f>a-d)return c-f+a}return c};b.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=16*M.random()|0,c="x"==a?b:3&b|8;return c.toString(16)});b.setWindow=function(c){a("raphael.setWindow",b,z.win,c),z.win=c,z.doc=z.win.document,b._engine.initWin&&b._engine.initWin(z.win)};var sa=function(a){if(b.vml){var c,d=/^\s+|\s+$/g;try{var f=new ActiveXObject("htmlfile");f.write(""),f.close(),c=f.body}catch(g){c=createPopup().document.body}var h=c.createTextRange();sa=e(function(a){try{c.style.color=H(a).replace(d,F);var b=h.queryCommandValue("ForeColor");return b=(255&b)<<16|65280&b|(16711680&b)>>>16,"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=z.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",z.doc.body.appendChild(i),sa=e(function(a){return i.style.color=a,z.doc.defaultView.getComputedStyle(i,F).getPropertyValue("color")})}return sa(a)},ta=function(){return"hsb("+[this.h,this.s,this.b]+")"},ua=function(){return"hsl("+[this.h,this.s,this.l]+")"},va=function(){return this.hex},wa=function(a,c,d){if(null==c&&b.is(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,c=a.g,a=a.r),null==c&&b.is(a,T)){var e=b.getRGB(a);a=e.r,c=e.g,d=e.b}return(a>1||c>1||d>1)&&(a/=255,c/=255,d/=255),[a,c,d]},xa=function(a,c,d,e){a*=255,c*=255,d*=255;var f={r:a,g:c,b:d,hex:b.rgb(a,c,d),toString:va};return b.is(e,"finite")&&(f.opacity=e),f};b.color=function(a){var c;return b.is(a,"object")&&"h"in a&&"s"in a&&"b"in a?(c=b.hsb2rgb(a),a.r=c.r,a.g=c.g,a.b=c.b,a.hex=c.hex):b.is(a,"object")&&"h"in a&&"s"in a&&"l"in a?(c=b.hsl2rgb(a),a.r=c.r,a.g=c.g,a.b=c.b,a.hex=c.hex):(b.is(a,"string")&&(a=b.getRGB(a)),b.is(a,"object")&&"r"in a&&"g"in a&&"b"in a?(c=b.rgb2hsl(a),a.h=c.h,a.s=c.s,a.l=c.l,c=b.rgb2hsb(a),a.v=c.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1)),a.toString=va,a},b.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,d=a.o,a=a.h),a*=360;var e,f,g,h,i;return a=a%360/60,i=c*b,h=i*(1-P(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],xa(e,f,g,d)},b.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var e,f,g,h,i;return a=a%360/60,i=2*b*(.5>c?c:1-c),h=i*(1-P(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],xa(e,f,g,d)},b.rgb2hsb=function(a,b,c){c=wa(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=N(a,b,c),g=f-O(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:ta}},b.rgb2hsl=function(a,b,c){c=wa(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=N(a,b,c),h=O(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:ua}},b._path2string=function(){return this.join(",").replace(fa,"$1")};b._preload=function(a,b){var c=z.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,z.doc.body.removeChild(this)},c.onerror=function(){z.doc.body.removeChild(this)},z.doc.body.appendChild(c),c.src=a};b.getRGB=e(function(a){if(!a||(a=H(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:f};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:f};!(ea[y](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=sa(a));var c,d,e,g,h,i,j=a.match(W);return j?(j[2]&&(e=_(j[2].substring(5),16),d=_(j[2].substring(3,5),16),c=_(j[2].substring(1,3),16)),j[3]&&(e=_((h=j[3].charAt(3))+h,16),d=_((h=j[3].charAt(2))+h,16),c=_((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4][I](da),c=$(i[0]),"%"==i[0].slice(-1)&&(c*=2.55),d=$(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=$(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(g=$(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100)),j[5]?(i=j[5][I](da),c=$(i[0]),"%"==i[0].slice(-1)&&(c*=2.55),d=$(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=$(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(c/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(g=$(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),b.hsb2rgb(c,d,e,g)):j[6]?(i=j[6][I](da),c=$(i[0]),"%"==i[0].slice(-1)&&(c*=2.55),d=$(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=$(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(c/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(g=$(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),b.hsl2rgb(c,d,e,g)):(j={r:c,g:d,b:e,toString:f},j.hex="#"+(16777216|e|d<<8|c<<16).toString(16).slice(1),b.is(g,"finite")&&(j.opacity=g),j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:f}},b),b.hsb=e(function(a,c,d){return b.hsb2rgb(a,c,d).hex}),b.hsl=e(function(a,c,d){return b.hsl2rgb(a,c,d).hex}),b.rgb=e(function(a,b,c){function d(a){return a+.5|0}return"#"+(16777216|d(c)|d(b)<<8|d(a)<<16).toString(16).slice(1)}),b.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);return b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b})),c.hex},b.getColor.reset=function(){delete this.start},b.parsePathString=function(a){if(!a)return null;var c=ya(a);if(c.arr)return Aa(c.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];return b.is(a,U)&&b.is(a[0],U)&&(e=Aa(a)),e.length||H(a).replace(ga,function(a,b,c){var f=[],g=b.toLowerCase();if(c.replace(ia,function(a,b){b&&f.push(+b)}),"m"==g&&f.length>2&&(e.push([b][D](f.splice(0,2))),g="l",b="m"==b?"l":"L"),"r"==g)e.push([b][D](f));else for(;f.length>=d[g]&&(e.push([b][D](f.splice(0,d[g]))),d[g]););}),e.toString=b._path2string,c.arr=Aa(e),e},b.parseTransformString=e(function(a){if(!a)return null;var c=[];return b.is(a,U)&&b.is(a[0],U)&&(c=Aa(a)),c.length||H(a).replace(ha,function(a,b,d){{var e=[];L.call(b)}d.replace(ia,function(a,b){b&&e.push(+b)}),c.push([b][D](e))}),c.toString=b._path2string,c});var ya=function(a){var b=ya.ps=ya.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[y](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]};b.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=Q(j,3),l=Q(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*M.atan2(q-s,r-t)/R;return(q>s||t>r)&&(y+=180),{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}},b.bezierBBox=function(a,c,d,e,f,g,h,i){b.is(a,"array")||(a=[a,c,d,e,f,g,h,i]);var j=Ha.apply(null,a);return{x:j.min.x,y:j.min.y,x2:j.max.x,y2:j.max.y,width:j.max.x-j.min.x,height:j.max.y-j.min.y}},b.isPointInsideBBox=function(a,b,c){return b>=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},b.isBBoxIntersect=function(a,c){var d=b.isPointInsideBBox;return d(c,a.x,a.y)||d(c,a.x2,a.y)||d(c,a.x,a.y2)||d(c,a.x2,a.y2)||d(a,c.x,c.y)||d(a,c.x2,c.y)||d(a,c.x,c.y2)||d(a,c.x2,c.y2)||(a.xc.x||c.xa.x)&&(a.yc.y||c.ya.y)},b.pathIntersection=function(a,b){return m(a,b)},b.pathIntersectionNumber=function(a,b){return m(a,b,1)},b.isPointInsidePath=function(a,c,d){var e=b.pathBBox(a);return b.isPointInsideBBox(e,c,d)&&m(a,[["M",c,d],["H",e.x2+10]],1)%2==1},b._removedFactory=function(b){return function(){a("raphael.log",null,"Raphaël: you are calling to method “"+b+"” of removed object",b)}};var za=b.pathBBox=function(a){var b=ya(a);if(b.bbox)return c(b.bbox);if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=Ia(a);for(var d,e=0,f=0,g=[],h=[],i=0,j=a.length;j>i;i++)if(d=a[i],"M"==d[0])e=d[1],f=d[2],g.push(e),h.push(f);else{var k=Ha(e,f,d[1],d[2],d[3],d[4],d[5],d[6]);g=g[D](k.min.x,k.max.x),h=h[D](k.min.y,k.max.y),e=d[5],f=d[6]}var l=O[C](0,g),m=O[C](0,h),n=N[C](0,g),o=N[C](0,h),p=n-l,q=o-m,r={x:l,y:m,x2:n,y2:o,width:p,height:q,cx:l+p/2,cy:m+q/2};return b.bbox=c(r),r},Aa=function(a){var d=c(a);return d.toString=b._path2string,d},Ba=b._pathToRelative=function(a){var c=ya(a);if(c.rel)return Aa(c.rel);b.is(a,U)&&b.is(a&&a[0],U)||(a=b.parsePathString(a));var d=[],e=0,f=0,g=0,h=0,i=0;"M"==a[0][0]&&(e=a[0][1],f=a[0][2],g=e,h=f,i++,d.push(["M",e,f]));for(var j=i,k=a.length;k>j;j++){var l=d[j]=[],m=a[j];if(m[0]!=L.call(m[0]))switch(l[0]=L.call(m[0]),l[0]){case"a":l[1]=m[1],l[2]=m[2],l[3]=m[3],l[4]=m[4],l[5]=m[5],l[6]=+(m[6]-e).toFixed(3),l[7]=+(m[7]-f).toFixed(3);break;case"v":l[1]=+(m[1]-f).toFixed(3);break;case"m":g=m[1],h=m[2];default:for(var n=1,o=m.length;o>n;n++)l[n]=+(m[n]-(n%2?e:f)).toFixed(3)}else{l=d[j]=[],"m"==m[0]&&(g=m[1]+e,h=m[2]+f);for(var p=0,q=m.length;q>p;p++)d[j][p]=m[p]}var r=d[j].length;switch(d[j][0]){case"z":e=g,f=h;break;case"h":e+=+d[j][r-1];break;case"v":f+=+d[j][r-1];break;default:e+=+d[j][r-2],f+=+d[j][r-1]}}return d.toString=b._path2string,c.rel=Aa(d),d},Ca=b._pathToAbsolute=function(a){var c=ya(a);if(c.abs)return Aa(c.abs);if(b.is(a,U)&&b.is(a&&a[0],U)||(a=b.parsePathString(a)),!a||!a.length)return[["M",0,0]];var d=[],e=0,f=0,h=0,i=0,j=0;"M"==a[0][0]&&(e=+a[0][1],f=+a[0][2],h=e,i=f,j++,d[0]=["M",e,f]);for(var k,l,m=3==a.length&&"M"==a[0][0]&&"R"==a[1][0].toUpperCase()&&"Z"==a[2][0].toUpperCase(),n=j,o=a.length;o>n;n++){if(d.push(k=[]),l=a[n],l[0]!=aa.call(l[0]))switch(k[0]=aa.call(l[0]),k[0]){case"A":k[1]=l[1],k[2]=l[2],k[3]=l[3],k[4]=l[4],k[5]=l[5],k[6]=+(l[6]+e),k[7]=+(l[7]+f);break;case"V":k[1]=+l[1]+f;break;case"H":k[1]=+l[1]+e;break;case"R":for(var p=[e,f][D](l.slice(1)),q=2,r=p.length;r>q;q++)p[q]=+p[q]+e,p[++q]=+p[q]+f;d.pop(),d=d[D](g(p,m));break;case"M":h=+l[1]+e,i=+l[2]+f;default:for(q=1,r=l.length;r>q;q++)k[q]=+l[q]+(q%2?e:f)}else if("R"==l[0])p=[e,f][D](l.slice(1)),d.pop(),d=d[D](g(p,m)),k=["R"][D](l.slice(-2));else for(var s=0,t=l.length;t>s;s++)k[s]=l[s];switch(k[0]){case"Z":e=h,f=i;break;case"H":e=k[1];break;case"V":f=k[1];break;case"M":h=k[k.length-2],i=k[k.length-1];default:e=k[k.length-2],f=k[k.length-1]}}return d.toString=b._path2string,c.abs=Aa(d),d},Da=function(a,b,c,d){return[a,b,c,d,c,d]},Ea=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},Fa=function(a,b,c,d,f,g,h,i,j,k){var l,m=120*R/180,n=R/180*(+f||0),o=[],p=e(function(a,b,c){var d=a*M.cos(c)-b*M.sin(c),e=a*M.sin(c)+b*M.cos(c);return{x:d,y:e}});if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(a,b,-n),a=l.x,b=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(M.cos(R/180*f),M.sin(R/180*f),(a-i)/2),r=(b-j)/2,s=q*q/(c*c)+r*r/(d*d);s>1&&(s=M.sqrt(s),c=s*c,d=s*d);var t=c*c,u=d*d,v=(g==h?-1:1)*M.sqrt(P((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*c*r/d+(a+i)/2,x=v*-d*q/c+(b+j)/2,y=M.asin(((b-x)/d).toFixed(9)),z=M.asin(((j-x)/d).toFixed(9));y=w>a?R-y:y,z=w>i?R-z:z,0>y&&(y=2*R+y),0>z&&(z=2*R+z),h&&y>z&&(y-=2*R),!h&&z>y&&(z-=2*R)}var A=z-y;if(P(A)>m){var B=z,C=i,E=j;z=y+m*(h&&z>y?1:-1),i=w+c*M.cos(z),j=x+d*M.sin(z),o=Fa(i,j,c,d,f,0,h,C,E,[z,B,w,x])}A=z-y;var F=M.cos(y),G=M.sin(y),H=M.cos(z),J=M.sin(z),K=M.tan(A/4),L=4/3*c*K,N=4/3*d*K,O=[a,b],Q=[a+L*G,b-N*F],S=[i+L*J,j-N*H],T=[i,j];if(Q[0]=2*O[0]-Q[0],Q[1]=2*O[1]-Q[1],k)return[Q,S,T][D](o);o=[Q,S,T][D](o).join()[I](",");for(var U=[],V=0,W=o.length;W>V;V++)U[V]=V%2?p(o[V-1],o[V],n).y:p(o[V],o[V+1],n).x;return U},Ga=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:Q(j,3)*a+3*Q(j,2)*i*c+3*j*i*i*e+Q(i,3)*g,y:Q(j,3)*b+3*Q(j,2)*i*d+3*j*i*i*f+Q(i,3)*h}},Ha=e(function(a,b,c,d,e,f,g,h){var i,j=e-2*c+a-(g-2*e+c),k=2*(c-a)-2*(e-c),l=a-c,m=(-k+M.sqrt(k*k-4*j*l))/2/j,n=(-k-M.sqrt(k*k-4*j*l))/2/j,o=[b,h],p=[a,g];return P(m)>"1e12"&&(m=.5),P(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ga(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ga(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),j=f-2*d+b-(h-2*f+d),k=2*(d-b)-2*(f-d),l=b-d,m=(-k+M.sqrt(k*k-4*j*l))/2/j,n=(-k-M.sqrt(k*k-4*j*l))/2/j,P(m)>"1e12"&&(m=.5),P(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ga(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ga(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),{min:{x:O[C](0,p),y:O[C](0,o)},max:{x:N[C](0,p),y:N[C](0,o)}}}),Ia=b._path2curve=e(function(a,b){var c=!b&&ya(a);if(!b&&c.curve)return Aa(c.curve);for(var d=Ca(a),e=b&&Ca(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=(function(a,b,c){var d,e,f={T:1,Q:1};if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in f)&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][D](Fa[C](0,[b.x,b.y][D](a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e][D](a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"][D](Ea(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][D](Ea(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][D](Da(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][D](Da(b.x,b.y,a[1],b.y));break;case"V":a=["C"][D](Da(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][D](Da(b.x,b.y,b.X,b.Y))}return a}),i=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)k[b]="A",e&&(l[b]="A"),a.splice(b++,0,["C"][D](c.splice(0,6)));a.splice(b,1),p=N(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&"M"==a[g][0]&&"M"!=b[g][0]&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],p=N(d.length,e&&e.length||0))},k=[],l=[],m="",n="",o=0,p=N(d.length,e&&e.length||0);p>o;o++){d[o]&&(m=d[o][0]),"C"!=m&&(k[o]=m,o&&(n=k[o-1])),d[o]=h(d[o],f,n),"A"!=k[o]&&"C"==m&&(k[o]="C"),i(d,o),e&&(e[o]&&(m=e[o][0]),"C"!=m&&(l[o]=m,o&&(n=l[o-1])),e[o]=h(e[o],g,n),"A"!=l[o]&&"C"==m&&(l[o]="C"),i(e,o)),j(d,e,f,g,o),j(e,d,g,f,o);var q=d[o],r=e&&e[o],s=q.length,t=e&&r.length;f.x=q[s-2],f.y=q[s-1],f.bx=$(q[s-4])||f.x,f.by=$(q[s-3])||f.y,g.bx=e&&($(r[t-4])||g.x),g.by=e&&($(r[t-3])||g.y),g.x=e&&r[t-2],g.y=e&&r[t-1]}return e||(c.curve=Aa(d)),e?[d,e]:d},null,Aa),Ja=(b._parseDots=e(function(a){for(var c=[],d=0,e=a.length;e>d;d++){var f={},g=a[d].match(/^([^:]*):?([\d\.]*)/);if(f.color=b.getRGB(g[1]),f.color.error)return null;f.opacity=f.color.opacity,f.color=f.color.hex,g[2]&&(f.offset=g[2]+"%"),c.push(f)}for(d=1,e=c.length-1;e>d;d++)if(!c[d].offset){for(var h=$(c[d-1].offset||0),i=0,j=d+1;e>j;j++)if(c[j].offset){i=c[j].offset;break}i||(i=100,j=e),i=$(i);for(var k=(i-h)/(j-d+1);j>d;d++)h+=k,c[d].offset=h+"%"}return c}),b._tear=function(a,b){a==b.top&&(b.top=a.prev),a==b.bottom&&(b.bottom=a.next),a.next&&(a.next.prev=a.prev),a.prev&&(a.prev.next=a.next)}),Ka=(b._tofront=function(a,b){b.top!==a&&(Ja(a,b),a.next=null,a.prev=b.top,b.top.next=a,b.top=a)},b._toback=function(a,b){b.bottom!==a&&(Ja(a,b),a.next=b.bottom,a.prev=null,b.bottom.prev=a,b.bottom=a)},b._insertafter=function(a,b,c){Ja(a,c),b==c.top&&(c.top=a),b.next&&(b.next.prev=a),a.next=b.next,a.prev=b,b.next=a},b._insertbefore=function(a,b,c){Ja(a,c),b==c.bottom&&(c.bottom=a),b.prev&&(b.prev.next=a),a.prev=b.prev,b.prev=a,a.next=b},b.toMatrix=function(a,b){var c=za(a),d={_:{transform:F},getBBox:function(){return c}};return La(d,b),d.matrix}),La=(b.transformPath=function(a,b){return pa(a,Ka(a,b))},b._extractTransform=function(a,c){if(null==c)return a._.transform;c=H(c).replace(/\.{3}|\u2026/g,a._.transform||F);var d=b.parseTransformString(c),e=0,f=0,g=0,h=1,i=1,j=a._,k=new n;if(j.transform=d||[],d)for(var l=0,m=d.length;m>l;l++){var o,p,q,r,s,t=d[l],u=t.length,v=H(t[0]).toLowerCase(),w=t[0]!=v,x=w?k.invert():0;"t"==v&&3==u?w?(o=x.x(0,0),p=x.y(0,0),q=x.x(t[1],t[2]),r=x.y(t[1],t[2]),k.translate(q-o,r-p)):k.translate(t[1],t[2]):"r"==v?2==u?(s=s||a.getBBox(1),k.rotate(t[1],s.x+s.width/2,s.y+s.height/2),e+=t[1]):4==u&&(w?(q=x.x(t[2],t[3]),r=x.y(t[2],t[3]),k.rotate(t[1],q,r)):k.rotate(t[1],t[2],t[3]),e+=t[1]):"s"==v?2==u||3==u?(s=s||a.getBBox(1),k.scale(t[1],t[u-1],s.x+s.width/2,s.y+s.height/2),h*=t[1],i*=t[u-1]):5==u&&(w?(q=x.x(t[3],t[4]),r=x.y(t[3],t[4]),k.scale(t[1],t[2],q,r)):k.scale(t[1],t[2],t[3],t[4]),h*=t[1],i*=t[2]):"m"==v&&7==u&&k.add(t[1],t[2],t[3],t[4],t[5],t[6]),j.dirtyT=1,a.matrix=k}a.matrix=k,j.sx=h,j.sy=i,j.deg=e,j.dx=f=k.e,j.dy=g=k.f,1==h&&1==i&&!e&&j.bbox?(j.bbox.x+=+f,j.bbox.y+=+g):j.dirtyT=1}),Ma=function(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}},Na=b._equaliseTransform=function(a,c){ 10 | c=H(c).replace(/\.{3}|\u2026/g,a),a=b.parseTransformString(a)||[],c=b.parseTransformString(c)||[];for(var d,e,f,g,h=N(a.length,c.length),i=[],j=[],k=0;h>k;k++){if(f=a[k]||Ma(c[k]),g=c[k]||Ma(f),f[0]!=g[0]||"r"==f[0].toLowerCase()&&(f[2]!=g[2]||f[3]!=g[3])||"s"==f[0].toLowerCase()&&(f[3]!=g[3]||f[4]!=g[4]))return;for(i[k]=[],j[k]=[],d=0,e=N(f.length,g.length);e>d;d++)d in f&&(i[k][d]=f[d]),d in g&&(j[k][d]=g[d])}return{from:i,to:j}};b._getContainer=function(a,c,d,e){var f;return f=null!=e||b.is(a,"object")?a:z.doc.getElementById(a),null!=f?f.tagName?null==c?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:c,height:d}:{container:1,x:a,y:c,width:d,height:e}:void 0},b.pathToRelative=Ba,b._engine={},b.path2curve=Ia,b.matrix=function(a,b,c,d,e,f){return new n(a,b,c,d,e,f)},function(a){function c(a){return a[0]*a[0]+a[1]*a[1]}function d(a){var b=M.sqrt(c(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}a.add=function(a,b,c,d,e,f){var g,h,i,j,k=[[],[],[]],l=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],m=[[a,c,e],[b,d,f],[0,0,1]];for(a&&a instanceof n&&(m=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),g=0;3>g;g++)for(h=0;3>h;h++){for(j=0,i=0;3>i;i++)j+=l[g][i]*m[i][h];k[g][h]=j}this.a=k[0][0],this.b=k[1][0],this.c=k[0][1],this.d=k[1][1],this.e=k[0][2],this.f=k[1][2]},a.invert=function(){var a=this,b=a.a*a.d-a.b*a.c;return new n(a.d/b,-a.b/b,-a.c/b,a.a/b,(a.c*a.f-a.d*a.e)/b,(a.b*a.e-a.a*a.f)/b)},a.clone=function(){return new n(this.a,this.b,this.c,this.d,this.e,this.f)},a.translate=function(a,b){this.add(1,0,0,1,a,b)},a.scale=function(a,b,c,d){null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d)},a.rotate=function(a,c,d){a=b.rad(a),c=c||0,d=d||0;var e=+M.cos(a).toFixed(9),f=+M.sin(a).toFixed(9);this.add(e,f,-f,e,c,d),this.add(1,0,0,1,-c,-d)},a.x=function(a,b){return a*this.a+b*this.c+this.e},a.y=function(a,b){return a*this.b+b*this.d+this.f},a.get=function(a){return+this[H.fromCharCode(97+a)].toFixed(4)},a.toString=function(){return b.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},a.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},a.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},a.split=function(){var a={};a.dx=this.e,a.dy=this.f;var e=[[this.a,this.c],[this.b,this.d]];a.scalex=M.sqrt(c(e[0])),d(e[0]),a.shear=e[0][0]*e[1][0]+e[0][1]*e[1][1],e[1]=[e[1][0]-e[0][0]*a.shear,e[1][1]-e[0][1]*a.shear],a.scaley=M.sqrt(c(e[1])),d(e[1]),a.shear/=a.scaley;var f=-e[0][1],g=e[1][1];return 0>g?(a.rotate=b.deg(M.acos(g)),0>f&&(a.rotate=360-a.rotate)):a.rotate=b.deg(M.asin(f)),a.isSimple=!(+a.shear.toFixed(9)||a.scalex.toFixed(9)!=a.scaley.toFixed(9)&&a.rotate),a.isSuperSimple=!+a.shear.toFixed(9)&&a.scalex.toFixed(9)==a.scaley.toFixed(9)&&!a.rotate,a.noRotation=!+a.shear.toFixed(9)&&!a.rotate,a},a.toTransformString=function(a){var b=a||this[I]();return b.isSimple?(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[b.dx,b.dy]:F)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:F)+(b.rotate?"r"+[b.rotate,0,0]:F)):"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}(n.prototype);for(var Oa=function(){this.returnValue=!1},Pa=function(){return this.originalEvent.preventDefault()},Qa=function(){this.cancelBubble=!0},Ra=function(){return this.originalEvent.stopPropagation()},Sa=function(a){var b=z.doc.documentElement.scrollTop||z.doc.body.scrollTop,c=z.doc.documentElement.scrollLeft||z.doc.body.scrollLeft;return{x:a.clientX+c,y:a.clientY+b}},Ta=function(){return z.doc.addEventListener?function(a,b,c,d){var e=function(a){var b=Sa(a);return c.call(d,a,b.x,b.y)};if(a.addEventListener(b,e,!1),E&&K[b]){var f=function(b){for(var e=Sa(b),f=b,g=0,h=b.targetTouches&&b.targetTouches.length;h>g;g++)if(b.targetTouches[g].target==a){b=b.targetTouches[g],b.originalEvent=f,b.preventDefault=Pa,b.stopPropagation=Ra;break}return c.call(d,b,e.x,e.y)};a.addEventListener(K[b],f,!1)}return function(){return a.removeEventListener(b,e,!1),E&&K[b]&&a.removeEventListener(K[b],f,!1),!0}}:z.doc.attachEvent?function(a,b,c,d){var e=function(a){a=a||z.win.event;var b=z.doc.documentElement.scrollTop||z.doc.body.scrollTop,e=z.doc.documentElement.scrollLeft||z.doc.body.scrollLeft,f=a.clientX+e,g=a.clientY+b;return a.preventDefault=a.preventDefault||Oa,a.stopPropagation=a.stopPropagation||Qa,c.call(d,a,f,g)};a.attachEvent("on"+b,e);var f=function(){return a.detachEvent("on"+b,e),!0};return f}:void 0}(),Ua=[],Va=function(b){for(var c,d=b.clientX,e=b.clientY,f=z.doc.documentElement.scrollTop||z.doc.body.scrollTop,g=z.doc.documentElement.scrollLeft||z.doc.body.scrollLeft,h=Ua.length;h--;){if(c=Ua[h],E&&b.touches){for(var i,j=b.touches.length;j--;)if(i=b.touches[j],i.identifier==c.el._drag.id){d=i.clientX,e=i.clientY,(b.originalEvent?b.originalEvent:b).preventDefault();break}}else b.preventDefault();var k,l=c.el.node,m=l.nextSibling,n=l.parentNode,o=l.style.display;z.win.opera&&n.removeChild(l),l.style.display="none",k=c.el.paper.getElementByPoint(d,e),l.style.display=o,z.win.opera&&(m?n.insertBefore(l,m):n.appendChild(l)),k&&a("raphael.drag.over."+c.el.id,c.el,k),d+=g,e+=f,a("raphael.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,b)}},Wa=function(c){b.unmousemove(Va).unmouseup(Wa);for(var d,e=Ua.length;e--;)d=Ua[e],d.el._drag={},a("raphael.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,c);Ua=[]},Xa=b.el={},Ya=J.length;Ya--;)!function(a){b[a]=Xa[a]=function(c,d){return b.is(c,"function")&&(this.events=this.events||[],this.events.push({name:a,f:c,unbind:Ta(this.shape||this.node||z.doc,a,c,d||this)})),this},b["un"+a]=Xa["un"+a]=function(c){for(var d=this.events||[],e=d.length;e--;)d[e].name!=a||!b.is(c,"undefined")&&d[e].f!=c||(d[e].unbind(),d.splice(e,1),!d.length&&delete this.events);return this}}(J[Ya]);Xa.data=function(c,d){var e=ja[this.id]=ja[this.id]||{};if(0==arguments.length)return e;if(1==arguments.length){if(b.is(c,"object")){for(var f in c)c[y](f)&&this.data(f,c[f]);return this}return a("raphael.data.get."+this.id,this,e[c],c),e[c]}return e[c]=d,a("raphael.data.set."+this.id,this,d,c),this},Xa.removeData=function(a){return null==a?ja[this.id]={}:ja[this.id]&&delete ja[this.id][a],this},Xa.getData=function(){return c(ja[this.id]||{})},Xa.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},Xa.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var Za=[];Xa.drag=function(c,d,e,f,g,h){function i(i){(i.originalEvent||i).preventDefault();var j=i.clientX,k=i.clientY,l=z.doc.documentElement.scrollTop||z.doc.body.scrollTop,m=z.doc.documentElement.scrollLeft||z.doc.body.scrollLeft;if(this._drag.id=i.identifier,E&&i.touches)for(var n,o=i.touches.length;o--;)if(n=i.touches[o],this._drag.id=n.identifier,n.identifier==this._drag.id){j=n.clientX,k=n.clientY;break}this._drag.x=j+m,this._drag.y=k+l,!Ua.length&&b.mousemove(Va).mouseup(Wa),Ua.push({el:this,move_scope:f,start_scope:g,end_scope:h}),d&&a.on("raphael.drag.start."+this.id,d),c&&a.on("raphael.drag.move."+this.id,c),e&&a.on("raphael.drag.end."+this.id,e),a("raphael.drag.start."+this.id,g||f||this,i.clientX+m,i.clientY+l,i)}return this._drag={},Za.push({el:this,start:i}),this.mousedown(i),this},Xa.onDragOver=function(b){b?a.on("raphael.drag.over."+this.id,b):a.unbind("raphael.drag.over."+this.id)},Xa.undrag=function(){for(var c=Za.length;c--;)Za[c].el==this&&(this.unmousedown(Za[c].start),Za.splice(c,1),a.unbind("raphael.drag.*."+this.id));!Za.length&&b.unmousemove(Va).unmouseup(Wa),Ua=[]},u.circle=function(a,c,d){var e=b._engine.circle(this,a||0,c||0,d||0);return this.__set__&&this.__set__.push(e),e},u.rect=function(a,c,d,e,f){var g=b._engine.rect(this,a||0,c||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},u.ellipse=function(a,c,d,e){var f=b._engine.ellipse(this,a||0,c||0,d||0,e||0);return this.__set__&&this.__set__.push(f),f},u.path=function(a){a&&!b.is(a,T)&&!b.is(a[0],U)&&(a+=F);var c=b._engine.path(b.format[C](b,arguments),this);return this.__set__&&this.__set__.push(c),c},u.image=function(a,c,d,e,f){var g=b._engine.image(this,a||"about:blank",c||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},u.text=function(a,c,d){var e=b._engine.text(this,a||0,c||0,H(d));return this.__set__&&this.__set__.push(e),e},u.set=function(a){!b.is(a,"array")&&(a=Array.prototype.splice.call(arguments,0,arguments.length));var c=new jb(a);return this.__set__&&this.__set__.push(c),c.paper=this,c.type="set",c},u.setStart=function(a){this.__set__=a||this.set()},u.setFinish=function(a){var b=this.__set__;return delete this.__set__,b},u.getSize=function(){var a=this.canvas.parentNode;return{width:a.offsetWidth,height:a.offsetHeight}},u.setSize=function(a,c){return b._engine.setSize.call(this,a,c)},u.setViewBox=function(a,c,d,e,f){return b._engine.setViewBox.call(this,a,c,d,e,f)},u.top=u.bottom=null,u.raphael=b;var $a=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,g=e.clientLeft||d.clientLeft||0,h=b.top+(z.win.pageYOffset||e.scrollTop||d.scrollTop)-f,i=b.left+(z.win.pageXOffset||e.scrollLeft||d.scrollLeft)-g;return{y:h,x:i}};u.getElementByPoint=function(a,b){var c=this,d=c.canvas,e=z.doc.elementFromPoint(a,b);if(z.win.opera&&"svg"==e.tagName){var f=$a(d),g=d.createSVGRect();g.x=a-f.x,g.y=b-f.y,g.width=g.height=1;var h=d.getIntersectionList(g,null);h.length&&(e=h[h.length-1])}if(!e)return null;for(;e.parentNode&&e!=d.parentNode&&!e.raphael;)e=e.parentNode;return e==c.canvas.parentNode&&(e=d),e=e&&e.raphael?c.getById(e.raphaelid):null},u.getElementsByBBox=function(a){var c=this.set();return this.forEach(function(d){b.isBBoxIntersect(d.getBBox(),a)&&c.push(d)}),c},u.getById=function(a){for(var b=this.bottom;b;){if(b.id==a)return b;b=b.next}return null},u.forEach=function(a,b){for(var c=this.bottom;c;){if(a.call(b,c)===!1)return this;c=c.next}return this},u.getElementsByPoint=function(a,b){var c=this.set();return this.forEach(function(d){d.isPointInside(a,b)&&c.push(d)}),c},Xa.isPointInside=function(a,c){var d=this.realPath=oa[this.type](this);return this.attr("transform")&&this.attr("transform").length&&(d=b.transformPath(d,this.attr("transform"))),b.isPointInsidePath(d,a,c)},Xa.getBBox=function(a){if(this.removed)return{};var b=this._;return a?((b.dirty||!b.bboxwt)&&(this.realPath=oa[this.type](this),b.bboxwt=za(this.realPath),b.bboxwt.toString=o,b.dirty=0),b.bboxwt):((b.dirty||b.dirtyT||!b.bbox)&&((b.dirty||!this.realPath)&&(b.bboxwt=0,this.realPath=oa[this.type](this)),b.bbox=za(pa(this.realPath,this.matrix)),b.bbox.toString=o,b.dirty=b.dirtyT=0),b.bbox)},Xa.clone=function(){if(this.removed)return null;var a=this.paper[this.type]().attr(this.attr());return this.__set__&&this.__set__.push(a),a},Xa.glow=function(a){if("text"==this.type)return null;a=a||{};var b={width:(a.width||10)+(+this.attr("stroke-width")||1),fill:a.fill||!1,opacity:null==a.opacity?.5:a.opacity,offsetx:a.offsetx||0,offsety:a.offsety||0,color:a.color||"#000"},c=b.width/2,d=this.paper,e=d.set(),f=this.realPath||oa[this.type](this);f=this.matrix?pa(f,this.matrix):f;for(var g=1;c+1>g;g++)e.push(d.path(f).attr({stroke:b.color,fill:b.fill?b.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(b.width/c*g).toFixed(3),opacity:+(b.opacity/c).toFixed(3)}));return e.insertBefore(this).translate(b.offsetx,b.offsety)};var _a=function(a,c,d,e,f,g,h,k,l){return null==l?i(a,c,d,e,f,g,h,k):b.findDotsAtSegment(a,c,d,e,f,g,h,k,j(a,c,d,e,f,g,h,k,l))},ab=function(a,c){return function(d,e,f){d=Ia(d);for(var g,h,i,j,k,l="",m={},n=0,o=0,p=d.length;p>o;o++){if(i=d[o],"M"==i[0])g=+i[1],h=+i[2];else{if(j=_a(g,h,i[1],i[2],i[3],i[4],i[5],i[6]),n+j>e){if(c&&!m.start){if(k=_a(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),l+=["C"+k.start.x,k.start.y,k.m.x,k.m.y,k.x,k.y],f)return l;m.start=l,l=["M"+k.x,k.y+"C"+k.n.x,k.n.y,k.end.x,k.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!a&&!c)return k=_a(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),{x:k.x,y:k.y,alpha:k.alpha}}n+=j,g=+i[5],h=+i[6]}l+=i.shift()+i}return m.end=l,k=a?n:c?m:b.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),k.alpha&&(k={x:k.x,y:k.y,alpha:k.alpha}),k}},bb=ab(1),cb=ab(),db=ab(0,1);b.getTotalLength=bb,b.getPointAtLength=cb,b.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return db(a,b).end;var d=db(a,c,1);return b?db(d,b).end:d},Xa.getTotalLength=function(){var a=this.getPath();if(a)return this.node.getTotalLength?this.node.getTotalLength():bb(a)},Xa.getPointAtLength=function(a){var b=this.getPath();if(b)return cb(b,a)},Xa.getPath=function(){var a,c=b._getPath[this.type];if("text"!=this.type&&"set"!=this.type)return c&&(a=c(this)),a},Xa.getSubpath=function(a,c){var d=this.getPath();if(d)return b.getSubpath(d,a,c)};var eb=b.easing_formulas={linear:function(a){return a},"<":function(a){return Q(a,1.7)},">":function(a){return Q(a,.48)},"<>":function(a){var b=.48-a/1.04,c=M.sqrt(.1734+b*b),d=c-b,e=Q(P(d),1/3)*(0>d?-1:1),f=-c-b,g=Q(P(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){return a==!!a?a:Q(2,-10*a)*M.sin(2*(a-.075)*R/.3)+1},bounce:function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b}};eb.easeIn=eb["ease-in"]=eb["<"],eb.easeOut=eb["ease-out"]=eb[">"],eb.easeInOut=eb["ease-in-out"]=eb["<>"],eb["back-in"]=eb.backIn,eb["back-out"]=eb.backOut;var fb=[],gb=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},hb=function(){for(var c=+new Date,d=0;dh))if(i>h){var q=j(h/i);for(var s in k)if(k[y](s)){switch(ca[s]){case S:f=+k[s]+q*i*l[s];break;case"colour":f="rgb("+[ib(Z(k[s].r+q*i*l[s].r)),ib(Z(k[s].g+q*i*l[s].g)),ib(Z(k[s].b+q*i*l[s].b))].join(",")+")";break;case"path":f=[];for(var t=0,u=k[s].length;u>t;t++){f[t]=[k[s][t][0]];for(var v=1,w=k[s][t].length;w>v;v++)f[t][v]=+k[s][t][v]+q*i*l[s][t][v];f[t]=f[t].join(G)}f=f.join(G);break;case"transform":if(l[s].real)for(f=[],t=0,u=k[s].length;u>t;t++)for(f[t]=[k[s][t][0]],v=1,w=k[s][t].length;w>v;v++)f[t][v]=k[s][t][v]+q*i*l[s][t][v];else{var x=function(a){return+k[s][a]+q*i*l[s][a]};f=[["m",x(0),x(1),x(2),x(3),x(4),x(5)]]}break;case"csv":if("clip-rect"==s)for(f=[],t=4;t--;)f[t]=+k[s][t]+q*i*l[s][t];break;default:var z=[][D](k[s]);for(f=[],t=n.paper.customAttributes[s].length;t--;)f[t]=+z[t]+q*i*l[s][t]}o[s]=f}n.attr(o),function(b,c,d){setTimeout(function(){a("raphael.anim.frame."+b,c,d)})}(n.id,n,e.anim)}else{if(function(c,d,e){setTimeout(function(){a("raphael.anim.frame."+d.id,d,e),a("raphael.anim.finish."+d.id,d,e),b.is(c,"function")&&c.call(d)})}(e.callback,n,e.anim),n.attr(m),fb.splice(d--,1),e.repeat>1&&!e.next){for(g in m)m[y](g)&&(p[g]=e.totalOrigin[g]);e.el.attr(p),r(e.anim,e.el,e.anim.percents[0],null,e.totalOrigin,e.repeat-1)}e.next&&!e.stop&&r(e.anim,e.el,e.next,null,e.totalOrigin,e.repeat)}}}fb.length&&gb(hb)},ib=function(a){return a>255?255:0>a?0:a};Xa.animateWith=function(a,c,d,e,f,g){var h=this;if(h.removed)return g&&g.call(h),h;var i=d instanceof q?d:b.animation(d,e,f,g);r(i,h,i.percents[0],null,h.attr());for(var j=0,k=fb.length;k>j;j++)if(fb[j].anim==c&&fb[j].el==a){fb[k-1].start=fb[j].start;break}return h},Xa.onAnimation=function(b){return b?a.on("raphael.anim.frame."+this.id,b):a.unbind("raphael.anim.frame."+this.id),this},q.prototype.delay=function(a){var b=new q(this.anim,this.ms);return b.times=this.times,b.del=+a||0,b},q.prototype.repeat=function(a){var b=new q(this.anim,this.ms);return b.del=this.del,b.times=M.floor(N(a,0))||1,b},b.animation=function(a,c,d,e){if(a instanceof q)return a;(b.is(d,"function")||!d)&&(e=e||d||null,d=null),a=Object(a),c=+c||0;var f,g,h={};for(g in a)a[y](g)&&$(g)!=g&&$(g)+"%"!=g&&(f=!0,h[g]=a[g]);if(f)return d&&(h.easing=d),e&&(h.callback=e),new q({100:h},c);if(e){var i=0;for(var j in a){var k=_(j);a[y](j)&&k>i&&(i=k)}i+="%",!a[i].callback&&(a[i].callback=e)}return new q(a,c)},Xa.animate=function(a,c,d,e){var f=this;if(f.removed)return e&&e.call(f),f;var g=a instanceof q?a:b.animation(a,c,d,e);return r(g,f,g.percents[0],null,f.attr()),f},Xa.setTime=function(a,b){return a&&null!=b&&this.status(a,O(b,a.ms)/a.ms),this},Xa.status=function(a,b){var c,d,e=[],f=0;if(null!=b)return r(a,this,-1,O(b,1)),this;for(c=fb.length;c>f;f++)if(d=fb[f],d.el.id==this.id&&(!a||d.anim==a)){if(a)return d.status;e.push({anim:d.anim,status:d.status})}return a?0:e},Xa.pause=function(b){for(var c=0;cb;b++)!a[b]||a[b].constructor!=Xa.constructor&&a[b].constructor!=jb||(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},kb=jb.prototype;kb.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],!a||a.constructor!=Xa.constructor&&a.constructor!=jb||(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},kb.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},kb.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this};for(var lb in Xa)Xa[y](lb)&&(kb[lb]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a][C](c,b)})}}(lb));return kb.attr=function(a,c){if(a&&b.is(a,U)&&b.is(a[0],"object"))for(var d=0,e=a.length;e>d;d++)this.items[d].attr(a[d]);else for(var f=0,g=this.items.length;g>f;f++)this.items[f].attr(a,c);return this},kb.clear=function(){for(;this.length;)this.pop()},kb.splice=function(a,b,c){a=0>a?N(this.length+a,0):a,b=N(0,O(this.length-a,b));var d,e=[],f=[],g=[];for(d=2;dd;d++)f.push(this[a+d]);for(;dd?g[d]:e[d-h];for(d=this.items.length=this.length-=b-h;this[d];)delete this[d++];return new jb(f)},kb.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0},kb.animate=function(a,c,d,e){(b.is(d,"function")||!d)&&(e=d||null);var f,g,h=this.items.length,i=h,j=this;if(!h)return this;e&&(g=function(){!--h&&e.call(j)}),d=b.is(d,T)?d:g;var k=b.animation(a,c,d,g);for(f=this.items[--i].animate(k);i--;)this.items[i]&&!this.items[i].removed&&this.items[i].animateWith(f,k,k),this.items[i]&&!this.items[i].removed||h--;return this},kb.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},kb.getBBox=function(){for(var a=[],b=[],c=[],d=[],e=this.items.length;e--;)if(!this.items[e].removed){var f=this.items[e].getBBox();a.push(f.x),b.push(f.y),c.push(f.x+f.width),d.push(f.y+f.height)}return a=O[C](0,a),b=O[C](0,b),c=N[C](0,c),d=N[C](0,d),{x:a,y:b,x2:c,y2:d,width:c-a,height:d-b}},kb.clone=function(a){a=this.paper.set();for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},kb.toString=function(){return"Raphaël‘s set"},kb.glow=function(a){var b=this.paper.set();return this.forEach(function(c,d){var e=c.glow(a);null!=e&&e.forEach(function(a,c){b.push(a)})}),b},kb.isPointInside=function(a,b){var c=!1;return this.forEach(function(d){return d.isPointInside(a,b)?(c=!0,!1):void 0}),c},b.registerFont=function(a){if(!a.face)return a;this.fonts=this.fonts||{};var b={w:a.w,face:{},glyphs:{}},c=a.face["font-family"];for(var d in a.face)a.face[y](d)&&(b.face[d]=a.face[d]);if(this.fonts[c]?this.fonts[c].push(b):this.fonts[c]=[b],!a.svg){b.face["units-per-em"]=_(a.face["units-per-em"],10);for(var e in a.glyphs)if(a.glyphs[y](e)){var f=a.glyphs[e];if(b.glyphs[e]={w:f.w,k:{},d:f.d&&"M"+f.d.replace(/[mlcxtrv]/g,function(a){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[a]||"M"})+"z"},f.k)for(var g in f.k)f[y](g)&&(b.glyphs[e].k[g]=f.k[g])}}return a},u.getFont=function(a,c,d,e){if(e=e||"normal",d=d||"normal",c=+c||{normal:400,bold:700,lighter:300,bolder:800}[c]||400,b.fonts){var f=b.fonts[a];if(!f){var g=new RegExp("(^|\\s)"+a.replace(/[^\w\d\s+!~.:_-]/g,F)+"(\\s|$)","i");for(var h in b.fonts)if(b.fonts[y](h)&&g.test(h)){f=b.fonts[h];break}}var i;if(f)for(var j=0,k=f.length;k>j&&(i=f[j],i.face["font-weight"]!=c||i.face["font-style"]!=d&&i.face["font-style"]||i.face["font-stretch"]!=e);j++);return i}},u.print=function(a,c,d,e,f,g,h,i){g=g||"middle",h=N(O(h||0,1),-1),i=N(O(i||1,3),1);var j,k=H(d)[I](F),l=0,m=0,n=F;if(b.is(e,"string")&&(e=this.getFont(e)),e){j=(f||16)/e.face["units-per-em"];for(var o=e.face.bbox[I](v),p=+o[0],q=o[3]-o[1],r=0,s=+o[1]+("baseline"==g?q+ +e.face.descent:q/2),t=0,u=k.length;u>t;t++){if("\n"==k[t])l=0,x=0,m=0,r+=q*i;else{var w=m&&e.glyphs[k[t-1]]||{},x=e.glyphs[k[t]];l+=m?(w.w||e.w)+(w.k&&w.k[k[t]]||0)+e.w*h:0,m=1}x&&x.d&&(n+=b.transformPath(x.d,["t",l*j,r*j,"s",j,j,p,s,"t",(a-p)/j,(c-s)/j]))}}return this.path(n).attr({fill:"#000",stroke:"none"})},u.add=function(a){if(b.is(a,"array"))for(var c,d=this.set(),e=0,f=a.length;f>e;e++)c=a[e]||{},w[y](c.type)&&d.push(this[c.type]().attr(c));return d},b.format=function(a,c){var d=b.is(c,U)?[0][D](c):arguments;return a&&b.is(a,T)&&d.length-1&&(a=a.replace(x,function(a,b){return null==d[++b]?F:d[b]})),a||F},b.fullfill=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return String(b).replace(a,function(a,b){return c(a,b,d)})}}(),b.ninja=function(){return A.was?z.win.Raphael=A.is:delete Raphael,b},b.st=kb,a.on("raphael.DOMload",function(){t=!0}),function(a,c,d){function e(){/in/.test(a.readyState)?setTimeout(e,9):b.eve("raphael.DOMload")}null==a.readyState&&a.addEventListener&&(a.addEventListener(c,d=function(){a.removeEventListener(c,d,!1),a.readyState="complete"},!1),a.readyState="loading"),e()}(document,"DOMContentLoaded"),b}),function(a,b){"function"==typeof define&&define.amd?define("raphael.svg",["raphael.core"],function(a){return b(a)}):b("object"==typeof exports?require("./raphael.core"):a.Raphael)}(this,function(a){if(!a||a.svg){var b="hasOwnProperty",c=String,d=parseFloat,e=parseInt,f=Math,g=f.max,h=f.abs,i=f.pow,j=/[, ]+/,k=a.eve,l="",m=" ",n="http://www.w3.org/1999/xlink",o={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},p={};a.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var q=function(d,e){if(e){"string"==typeof d&&(d=q(d));for(var f in e)e[b](f)&&("xlink:"==f.substring(0,6)?d.setAttributeNS(n,f.substring(6),c(e[f])):d.setAttribute(f,c(e[f])))}else d=a._g.doc.createElementNS("http://www.w3.org/2000/svg",d),d.style&&(d.style.webkitTapHighlightColor="rgba(0,0,0,0)");return d},r=function(b,e){var j="linear",k=b.id+e,m=.5,n=.5,o=b.node,p=b.paper,r=o.style,s=a._g.doc.getElementById(k);if(!s){if(e=c(e).replace(a._radial_gradient,function(a,b,c){if(j="radial",b&&c){m=d(b),n=d(c);var e=2*(n>.5)-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&.5!=n&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/),"linear"==j){var t=e.shift();if(t=-d(t),isNaN(t))return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=a._parseDots(e);if(!w)return null;if(k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient),!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,"radial"==j?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;y>x;x++)s.appendChild(q("stop",{offset:w[x].offset?w[x].offset:x?"100%":"0%","stop-color":w[x].color||"#fff","stop-opacity":isFinite(w[x].opacity)?w[x].opacity:1}))}}return q(o,{fill:"url('"+document.location.origin+document.location.pathname+"#"+k+"')",opacity:1,"fill-opacity":1}),r.fill=l,r.opacity=1,r.fillOpacity=1,1},s=function(a){var b=a.getBBox(1);q(a.pattern,{patternTransform:a.matrix.invert()+" translate("+b.x+","+b.y+")"})},t=function(d,e,f){if("path"==d.type){for(var g,h,i,j,k,m=c(e).toLowerCase().split("-"),n=d.paper,r=f?"end":"start",s=d.node,t=d.attrs,u=t["stroke-width"],v=m.length,w="classic",x=3,y=3,z=5;v--;)switch(m[v]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":w=m[v];break;case"wide":y=5;break;case"narrow":y=2;break;case"long":x=5;break;case"short":x=2}if("open"==w?(x+=2,y+=2,z+=2,i=1,j=f?4:1,k={fill:"none",stroke:t.stroke}):(j=i=x/2,k={fill:t.stroke,stroke:"none"}),d._.arrows?f?(d._.arrows.endPath&&p[d._.arrows.endPath]--,d._.arrows.endMarker&&p[d._.arrows.endMarker]--):(d._.arrows.startPath&&p[d._.arrows.startPath]--,d._.arrows.startMarker&&p[d._.arrows.startMarker]--):d._.arrows={},"none"!=w){var A="raphael-marker-"+w,B="raphael-marker-"+r+w+x+y+"-obj"+d.id;a._g.doc.getElementById(A)?p[A]++:(n.defs.appendChild(q(q("path"),{"stroke-linecap":"round",d:o[w],id:A})),p[A]=1);var C,D=a._g.doc.getElementById(B);D?(p[B]++,C=D.getElementsByTagName("use")[0]):(D=q(q("marker"),{id:B,markerHeight:y,markerWidth:x,orient:"auto",refX:j,refY:y/2}),C=q(q("use"),{"xlink:href":"#"+A,transform:(f?"rotate(180 "+x/2+" "+y/2+") ":l)+"scale("+x/z+","+y/z+")","stroke-width":(1/((x/z+y/z)/2)).toFixed(4)}),D.appendChild(C),n.defs.appendChild(D),p[B]=1),q(C,k);var E=i*("diamond"!=w&&"oval"!=w);f?(g=d._.arrows.startdx*u||0,h=a.getTotalLength(t.path)-E*u):(g=E*u,h=a.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),k={},k["marker-"+r]="url(#"+B+")",(h||g)&&(k.d=a.getSubpath(t.path,g,h)),q(s,k),d._.arrows[r+"Path"]=A,d._.arrows[r+"Marker"]=B,d._.arrows[r+"dx"]=E,d._.arrows[r+"Type"]=w,d._.arrows[r+"String"]=e}else f?(g=d._.arrows.startdx*u||0,h=a.getTotalLength(t.path)-g):(g=0,h=a.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),d._.arrows[r+"Path"]&&q(s,{d:a.getSubpath(t.path,g,h)}),delete d._.arrows[r+"Path"],delete d._.arrows[r+"Marker"],delete d._.arrows[r+"dx"],delete d._.arrows[r+"Type"],delete d._.arrows[r+"String"];for(k in p)if(p[b](k)&&!p[k]){var F=a._g.doc.getElementById(k);F&&F.parentNode.removeChild(F)}}},u={"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},v=function(a,b,d){if(b=u[c(b).toLowerCase()]){for(var e=a.attrs["stroke-width"]||"1",f={round:e,square:e,butt:0}[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],h=b.length;h--;)g[h]=b[h]*e+(h%2?1:-1)*f;q(a.node,{"stroke-dasharray":g.join(",")})}else q(a.node,{"stroke-dasharray":"none"})},w=function(d,f){var i=d.node,k=d.attrs,m=i.style.visibility;i.style.visibility="hidden";for(var o in f)if(f[b](o)){if(!a._availableAttrs[b](o))continue;var p=f[o];switch(k[o]=p,o){case"blur":d.blur(p);break;case"title":var u=i.getElementsByTagName("title");if(u.length&&(u=u[0]))u.firstChild.nodeValue=p;else{u=q("title");var w=a._g.doc.createTextNode(p);u.appendChild(w),i.appendChild(u)}break;case"href":case"target":var x=i.parentNode;if("a"!=x.tagName.toLowerCase()){var z=q("a");x.insertBefore(z,i),z.appendChild(i),x=z}"target"==o?x.setAttributeNS(n,"show","blank"==p?"new":p):x.setAttributeNS(n,o,p);break;case"cursor":i.style.cursor=p;break;case"transform":d.transform(p);break;case"arrow-start":t(d,p);break;case"arrow-end":t(d,p,1);break;case"clip-rect":var A=c(p).split(j);if(4==A.length){d.clip&&d.clip.parentNode.parentNode.removeChild(d.clip.parentNode);var B=q("clipPath"),C=q("rect");B.id=a.createUUID(),q(C,{x:A[0],y:A[1],width:A[2],height:A[3]}),B.appendChild(C),d.paper.defs.appendChild(B),q(i,{"clip-path":"url(#"+B.id+")"}),d.clip=C}if(!p){var D=i.getAttribute("clip-path");if(D){var E=a._g.doc.getElementById(D.replace(/(^url\(#|\)$)/g,l));E&&E.parentNode.removeChild(E),q(i,{"clip-path":l}),delete d.clip}}break;case"path":"path"==d.type&&(q(i,{d:p?k.path=a._pathToAbsolute(p):"M0,0"}),d._.dirty=1,d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1)));break;case"width":if(i.setAttribute(o,p),d._.dirty=1,!k.fx)break;o="x",p=k.x;case"x":k.fx&&(p=-k.x-(k.width||0));case"rx":if("rx"==o&&"rect"==d.type)break;case"cx":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"height":if(i.setAttribute(o,p),d._.dirty=1,!k.fy)break;o="y",p=k.y;case"y":k.fy&&(p=-k.y-(k.height||0));case"ry":if("ry"==o&&"rect"==d.type)break;case"cy":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"r":"rect"==d.type?q(i,{rx:p,ry:p}):i.setAttribute(o,p),d._.dirty=1;break;case"src":"image"==d.type&&i.setAttributeNS(n,"href",p);break;case"stroke-width":(1!=d._.sx||1!=d._.sy)&&(p/=g(h(d._.sx),h(d._.sy))||1),i.setAttribute(o,p),k["stroke-dasharray"]&&v(d,k["stroke-dasharray"],f),d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"stroke-dasharray":v(d,p,f);break;case"fill":var F=c(p).match(a._ISURL);if(F){B=q("pattern");var G=q("image");B.id=a.createUUID(),q(B,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),q(G,{x:0,y:0,"xlink:href":F[1]}),B.appendChild(G),function(b){a._preload(F[1],function(){var a=this.offsetWidth,c=this.offsetHeight;q(b,{width:a,height:c}),q(G,{width:a,height:c})})}(B),d.paper.defs.appendChild(B),q(i,{fill:"url(#"+B.id+")"}),d.pattern=B,d.pattern&&s(d);break}var H=a.getRGB(p);if(H.error){if(("circle"==d.type||"ellipse"==d.type||"r"!=c(p).charAt())&&r(d,p)){if("opacity"in k||"fill-opacity"in k){var I=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l));if(I){var J=I.getElementsByTagName("stop");q(J[J.length-1],{"stop-opacity":("opacity"in k?k.opacity:1)*("fill-opacity"in k?k["fill-opacity"]:1)})}}k.gradient=p,k.fill="none";break}}else delete f.gradient,delete k.gradient,!a.is(k.opacity,"undefined")&&a.is(f.opacity,"undefined")&&q(i,{opacity:k.opacity}),!a.is(k["fill-opacity"],"undefined")&&a.is(f["fill-opacity"],"undefined")&&q(i,{"fill-opacity":k["fill-opacity"]});H[b]("opacity")&&q(i,{"fill-opacity":H.opacity>1?H.opacity/100:H.opacity});case"stroke":H=a.getRGB(p),i.setAttribute(o,H.hex),"stroke"==o&&H[b]("opacity")&&q(i,{"stroke-opacity":H.opacity>1?H.opacity/100:H.opacity}),"stroke"==o&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":("circle"==d.type||"ellipse"==d.type||"r"!=c(p).charAt())&&r(d,p); 11 | 12 | break;case"opacity":k.gradient&&!k[b]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){I=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),I&&(J=I.getElementsByTagName("stop"),q(J[J.length-1],{"stop-opacity":p}));break}default:"font-size"==o&&(p=e(p,10)+"px");var K=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[K]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if("text"==d.type&&(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){for(g.text=f.text;h.firstChild;)h.removeChild(h.firstChild);for(var j,k=c(f.text).split("\n"),m=[],n=0,o=k.length;o>n;n++)j=q("tspan"),n&&q(j,{dy:i*x,x:g.x}),j.appendChild(a._g.doc.createTextNode(k[n])),h.appendChild(j),m[n]=j}else for(m=h.getElementsByTagName("tspan"),n=0,o=m.length;o>n;n++)n?q(m[n],{dy:i*x,x:g.x}):q(m[0],{dy:0});q(h,{x:g.x,y:g.y}),d._.dirty=1;var p=d._getBBox(),r=g.y-(p.y+p.height/2);r&&a.is(r,"finite")&&q(m[0],{dy:r})}},z=function(a){return a.parentNode&&"a"===a.parentNode.tagName.toLowerCase()?a.parentNode:a},A=function(b,c){this[0]=this.node=b,b.raphael=!0,this.id=a._oid++,b.raphaelid=this.id,this.matrix=a.matrix(),this.realPath=null,this.paper=c,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!c.bottom&&(c.bottom=this),this.prev=c.top,c.top&&(c.top.next=this),c.top=this,this.next=null},B=a.el;A.prototype=B,B.constructor=A,a._engine.path=function(a,b){var c=q("path");b.canvas&&b.canvas.appendChild(c);var d=new A(c,b);return d.type="path",w(d,{fill:"none",stroke:"#000",path:a}),d},B.rotate=function(a,b,e){if(this.removed)return this;if(a=c(a).split(j),a.length-1&&(b=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(b=e),null==b||null==e){var f=this.getBBox(1);b=f.x+f.width/2,e=f.y+f.height/2}return this.transform(this._.transform.concat([["r",a,b,e]])),this},B.scale=function(a,b,e,f){if(this.removed)return this;if(a=c(a).split(j),a.length-1&&(b=d(a[1]),e=d(a[2]),f=d(a[3])),a=d(a[0]),null==b&&(b=a),null==f&&(e=f),null==e||null==f)var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,b,e,f]])),this},B.translate=function(a,b){return this.removed?this:(a=c(a).split(j),a.length-1&&(b=d(a[1])),a=d(a[0])||0,b=+b||0,this.transform(this._.transform.concat([["t",a,b]])),this)},B.transform=function(c){var d=this._;if(null==c)return d.transform;if(a._extractTransform(this,c),this.clip&&q(this.clip,{transform:this.matrix.invert()}),this.pattern&&s(this),this.node&&q(this.node,{transform:this.matrix}),1!=d.sx||1!=d.sy){var e=this.attrs[b]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":e})}return this},B.hide=function(){return this.removed||(this.node.style.display="none"),this},B.show=function(){return this.removed||(this.node.style.display=""),this},B.remove=function(){var b=z(this.node);if(!this.removed&&b.parentNode){var c=this.paper;c.__set__&&c.__set__.exclude(this),k.unbind("raphael.*.*."+this.id),this.gradient&&c.defs.removeChild(this.gradient),a._tear(this,c),b.parentNode.removeChild(b),this.removeData();for(var d in this)this[d]="function"==typeof this[d]?a._removedFactory(d):null;this.removed=!0}},B._getBBox=function(){if("none"==this.node.style.display){this.show();var a=!0}var b,c=!1;this.paper.canvas.parentElement?b=this.paper.canvas.parentElement.style:this.paper.canvas.parentNode&&(b=this.paper.canvas.parentNode.style),b&&"none"==b.display&&(c=!0,b.display="");var d={};try{d=this.node.getBBox()}catch(e){d={x:this.node.clientLeft,y:this.node.clientTop,width:this.node.clientWidth,height:this.node.clientHeight}}finally{d=d||{},c&&(b.display="none")}return a&&this.hide(),d},B.attr=function(c,d){if(this.removed)return this;if(null==c){var e={};for(var f in this.attrs)this.attrs[b](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}if(null==d&&a.is(c,"string")){if("fill"==c&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;if("transform"==c)return this._.transform;for(var g=c.split(j),h={},i=0,l=g.length;l>i;i++)c=g[i],c in this.attrs?h[c]=this.attrs[c]:a.is(this.paper.customAttributes[c],"function")?h[c]=this.paper.customAttributes[c].def:h[c]=a._availableAttrs[c];return l-1?h:h[g[0]]}if(null==d&&a.is(c,"array")){for(h={},i=0,l=c.length;l>i;i++)h[c[i]]=this.attr(c[i]);return h}if(null!=d){var m={};m[c]=d}else null!=c&&a.is(c,"object")&&(m=c);for(var n in m)k("raphael.attr."+n+"."+this.id,this,m[n]);for(n in this.paper.customAttributes)if(this.paper.customAttributes[b](n)&&m[b](n)&&a.is(this.paper.customAttributes[n],"function")){var o=this.paper.customAttributes[n].apply(this,[].concat(m[n]));this.attrs[n]=m[n];for(var p in o)o[b](p)&&(m[p]=o[p])}return w(this,m),this},B.toFront=function(){if(this.removed)return this;var b=z(this.node);b.parentNode.appendChild(b);var c=this.paper;return c.top!=this&&a._tofront(this,c),this},B.toBack=function(){if(this.removed)return this;var b=z(this.node),c=b.parentNode;c.insertBefore(b,c.firstChild),a._toback(this,this.paper);this.paper;return this},B.insertAfter=function(b){if(this.removed||!b)return this;var c=z(this.node),d=z(b.node||b[b.length-1].node);return d.nextSibling?d.parentNode.insertBefore(c,d.nextSibling):d.parentNode.appendChild(c),a._insertafter(this,b,this.paper),this},B.insertBefore=function(b){if(this.removed||!b)return this;var c=z(this.node),d=z(b.node||b[0].node);return d.parentNode.insertBefore(c,d),a._insertbefore(this,b,this.paper),this},B.blur=function(b){var c=this;if(0!==+b){var d=q("filter"),e=q("feGaussianBlur");c.attrs.blur=b,d.id=a.createUUID(),q(e,{stdDeviation:+b||1.5}),d.appendChild(e),c.paper.defs.appendChild(d),c._blur=d,q(c.node,{filter:"url(#"+d.id+")"})}else c._blur&&(c._blur.parentNode.removeChild(c._blur),delete c._blur,delete c.attrs.blur),c.node.removeAttribute("filter");return c},a._engine.circle=function(a,b,c,d){var e=q("circle");a.canvas&&a.canvas.appendChild(e);var f=new A(e,a);return f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"},f.type="circle",q(e,f.attrs),f},a._engine.rect=function(a,b,c,d,e,f){var g=q("rect");a.canvas&&a.canvas.appendChild(g);var h=new A(g,a);return h.attrs={x:b,y:c,width:d,height:e,rx:f||0,ry:f||0,fill:"none",stroke:"#000"},h.type="rect",q(g,h.attrs),h},a._engine.ellipse=function(a,b,c,d,e){var f=q("ellipse");a.canvas&&a.canvas.appendChild(f);var g=new A(f,a);return g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"},g.type="ellipse",q(f,g.attrs),g},a._engine.image=function(a,b,c,d,e,f){var g=q("image");q(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"}),g.setAttributeNS(n,"href",b),a.canvas&&a.canvas.appendChild(g);var h=new A(g,a);return h.attrs={x:c,y:d,width:e,height:f,src:b},h.type="image",h},a._engine.text=function(b,c,d,e){var f=q("text");b.canvas&&b.canvas.appendChild(f);var g=new A(f,b);return g.attrs={x:c,y:d,"text-anchor":"middle",text:e,"font-family":a._availableAttrs["font-family"],"font-size":a._availableAttrs["font-size"],stroke:"none",fill:"#000"},g.type="text",w(g,g.attrs),g},a._engine.setSize=function(a,b){return this.width=a||this.width,this.height=b||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox),this},a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b&&b.container,d=b.x,e=b.y,f=b.width,g=b.height;if(!c)throw new Error("SVG container not found.");var h,i=q("svg"),j="overflow:hidden;";return d=d||0,e=e||0,f=f||512,g=g||342,q(i,{height:g,version:1.1,width:f,xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink"}),1==c?(i.style.cssText=j+"position:absolute;left:"+d+"px;top:"+e+"px",a._g.doc.body.appendChild(i),h=1):(i.style.cssText=j+"position:relative",c.firstChild?c.insertBefore(i,c.firstChild):c.appendChild(i)),c=new a._Paper,c.width=f,c.height=g,c.canvas=i,c.clear(),c._left=c._top=0,h&&(c.renderfix=function(){}),c.renderfix(),c},a._engine.setViewBox=function(a,b,c,d,e){k("raphael.setViewBox",this,this._viewBox,[a,b,c,d,e]);var f,h,i=this.getSize(),j=g(c/i.width,d/i.height),l=this.top,n=e?"xMidYMid meet":"xMinYMin";for(null==a?(this._vbSize&&(j=1),delete this._vbSize,f="0 0 "+this.width+m+this.height):(this._vbSize=j,f=a+m+b+m+c+m+d),q(this.canvas,{viewBox:f,preserveAspectRatio:n});j&&l;)h="stroke-width"in l.attrs?l.attrs["stroke-width"]:1,l.attr({"stroke-width":h}),l._.dirty=1,l._.dirtyT=1,l=l.prev;return this._viewBox=[a,b,c,d,!!e],this},a.prototype.renderfix=function(){var a,b=this.canvas,c=b.style;try{a=b.getScreenCTM()||b.createSVGMatrix()}catch(d){a=b.createSVGMatrix()}var e=-a.e%1,f=-a.f%1;(e||f)&&(e&&(this._left=(this._left+e)%1,c.left=this._left+"px"),f&&(this._top=(this._top+f)%1,c.top=this._top+"px"))},a.prototype.clear=function(){a.eve("raphael.clear",this);for(var b=this.canvas;b.firstChild;)b.removeChild(b.firstChild);this.bottom=this.top=null,(this.desc=q("desc")).appendChild(a._g.doc.createTextNode("Created with Raphaël "+a.version)),b.appendChild(this.desc),b.appendChild(this.defs=q("defs"))},a.prototype.remove=function(){k("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]="function"==typeof this[b]?a._removedFactory(b):null};var C=a.st;for(var D in B)B[b](D)&&!C[b](D)&&(C[D]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(D))}}),function(a,b){"function"==typeof define&&define.amd?define("raphael.vml",["raphael.core"],function(a){return b(a)}):b("object"==typeof exports?require("./raphael.core"):a.Raphael)}(this,function(a){if(!a||a.vml){var b="hasOwnProperty",c=String,d=parseFloat,e=Math,f=e.round,g=e.max,h=e.min,i=e.abs,j="fill",k=/[, ]+/,l=a.eve,m=" progid:DXImageTransform.Microsoft",n=" ",o="",p={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},q=/([clmz]),?([^clmz]*)/gi,r=/ progid:\S+Blur\([^\)]+\)/g,s=/-?[^,\s-]+/g,t="position:absolute;left:0;top:0;width:1px;height:1px;behavior:url(#default#VML)",u=21600,v={path:1,rect:1,image:1},w={circle:1,ellipse:1},x=function(b){var d=/[ahqstv]/gi,e=a._pathToAbsolute;if(c(b).match(d)&&(e=a._path2curve),d=/[clmz]/g,e==a._pathToAbsolute&&!c(b).match(d)){var g=c(b).replace(q,function(a,b,c){var d=[],e="m"==b.toLowerCase(),g=p[b];return c.replace(s,function(a){e&&2==d.length&&(g+=d+p["m"==b?"l":"L"],d=[]),d.push(f(a*u))}),g+d});return g}var h,i,j=e(b);g=[];for(var k=0,l=j.length;l>k;k++){h=j[k],i=j[k][0].toLowerCase(),"z"==i&&(i="x");for(var m=1,r=h.length;r>m;m++)i+=f(h[m]*u)+(m!=r-1?",":o);g.push(i)}return g.join(n)},y=function(b,c,d){var e=a.matrix();return e.rotate(-b,.5,.5),{dx:e.x(c,d),dy:e.y(c,d)}},z=function(a,b,c,d,e,f){var g=a._,h=a.matrix,k=g.fillpos,l=a.node,m=l.style,o=1,p="",q=u/b,r=u/c;if(m.visibility="hidden",b&&c){if(l.coordsize=i(q)+n+i(r),m.rotation=f*(0>b*c?-1:1),f){var s=y(f,d,e);d=s.dx,e=s.dy}if(0>b&&(p+="x"),0>c&&(p+=" y")&&(o=-1),m.flip=p,l.coordorigin=d*-q+n+e*-r,k||g.fillsize){var t=l.getElementsByTagName(j);t=t&&t[0],l.removeChild(t),k&&(s=y(f,h.x(k[0],k[1]),h.y(k[0],k[1])),t.position=s.dx*o+n+s.dy*o),g.fillsize&&(t.size=g.fillsize[0]*i(b)+n+g.fillsize[1]*i(c)),l.appendChild(t)}m.visibility="visible"}};a.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var A=function(a,b,d){for(var e=c(b).toLowerCase().split("-"),f=d?"end":"start",g=e.length,h="classic",i="medium",j="medium";g--;)switch(e[g]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":h=e[g];break;case"wide":case"narrow":j=e[g];break;case"long":case"short":i=e[g]}var k=a.node.getElementsByTagName("stroke")[0];k[f+"arrow"]=h,k[f+"arrowlength"]=i,k[f+"arrowwidth"]=j},B=function(e,i){e.attrs=e.attrs||{};var l=e.node,m=e.attrs,p=l.style,q=v[e.type]&&(i.x!=m.x||i.y!=m.y||i.width!=m.width||i.height!=m.height||i.cx!=m.cx||i.cy!=m.cy||i.rx!=m.rx||i.ry!=m.ry||i.r!=m.r),r=w[e.type]&&(m.cx!=i.cx||m.cy!=i.cy||m.r!=i.r||m.rx!=i.rx||m.ry!=i.ry),s=e;for(var t in i)i[b](t)&&(m[t]=i[t]);if(q&&(m.path=a._getPath[e.type](e),e._.dirty=1),i.href&&(l.href=i.href),i.title&&(l.title=i.title),i.target&&(l.target=i.target),i.cursor&&(p.cursor=i.cursor),"blur"in i&&e.blur(i.blur),(i.path&&"path"==e.type||q)&&(l.path=x(~c(m.path).toLowerCase().indexOf("r")?a._pathToAbsolute(m.path):m.path),e._.dirty=1,"image"==e.type&&(e._.fillpos=[m.x,m.y],e._.fillsize=[m.width,m.height],z(e,1,1,0,0,0))),"transform"in i&&e.transform(i.transform),r){var y=+m.cx,B=+m.cy,D=+m.rx||+m.r||0,E=+m.ry||+m.r||0;l.path=a.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",f((y-D)*u),f((B-E)*u),f((y+D)*u),f((B+E)*u),f(y*u)),e._.dirty=1}if("clip-rect"in i){var G=c(i["clip-rect"]).split(k);if(4==G.length){G[2]=+G[2]+ +G[0],G[3]=+G[3]+ +G[1];var H=l.clipRect||a._g.doc.createElement("div"),I=H.style;I.clip=a.format("rect({1}px {2}px {3}px {0}px)",G),l.clipRect||(I.position="absolute",I.top=0,I.left=0,I.width=e.paper.width+"px",I.height=e.paper.height+"px",l.parentNode.insertBefore(H,l),H.appendChild(l),l.clipRect=H)}i["clip-rect"]||l.clipRect&&(l.clipRect.style.clip="auto")}if(e.textpath){var J=e.textpath.style;i.font&&(J.font=i.font),i["font-family"]&&(J.fontFamily='"'+i["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,o)+'"'),i["font-size"]&&(J.fontSize=i["font-size"]),i["font-weight"]&&(J.fontWeight=i["font-weight"]),i["font-style"]&&(J.fontStyle=i["font-style"])}if("arrow-start"in i&&A(s,i["arrow-start"]),"arrow-end"in i&&A(s,i["arrow-end"],1),null!=i.opacity||null!=i["stroke-width"]||null!=i.fill||null!=i.src||null!=i.stroke||null!=i["stroke-width"]||null!=i["stroke-opacity"]||null!=i["fill-opacity"]||null!=i["stroke-dasharray"]||null!=i["stroke-miterlimit"]||null!=i["stroke-linejoin"]||null!=i["stroke-linecap"]){var K=l.getElementsByTagName(j),L=!1;if(K=K&&K[0],!K&&(L=K=F(j)),"image"==e.type&&i.src&&(K.src=i.src),i.fill&&(K.on=!0),(null==K.on||"none"==i.fill||null===i.fill)&&(K.on=!1),K.on&&i.fill){var M=c(i.fill).match(a._ISURL);if(M){K.parentNode==l&&l.removeChild(K),K.rotate=!0,K.src=M[1],K.type="tile";var N=e.getBBox(1);K.position=N.x+n+N.y,e._.fillpos=[N.x,N.y],a._preload(M[1],function(){e._.fillsize=[this.offsetWidth,this.offsetHeight]})}else K.color=a.getRGB(i.fill).hex,K.src=o,K.type="solid",a.getRGB(i.fill).error&&(s.type in{circle:1,ellipse:1}||"r"!=c(i.fill).charAt())&&C(s,i.fill,K)&&(m.fill="none",m.gradient=i.fill,K.rotate=!1)}if("fill-opacity"in i||"opacity"in i){var O=((+m["fill-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+a.getRGB(i.fill).o+1||2)-1);O=h(g(O,0),1),K.opacity=O,K.src&&(K.color="none")}l.appendChild(K);var P=l.getElementsByTagName("stroke")&&l.getElementsByTagName("stroke")[0],Q=!1;!P&&(Q=P=F("stroke")),(i.stroke&&"none"!=i.stroke||i["stroke-width"]||null!=i["stroke-opacity"]||i["stroke-dasharray"]||i["stroke-miterlimit"]||i["stroke-linejoin"]||i["stroke-linecap"])&&(P.on=!0),("none"==i.stroke||null===i.stroke||null==P.on||0==i.stroke||0==i["stroke-width"])&&(P.on=!1);var R=a.getRGB(i.stroke);P.on&&i.stroke&&(P.color=R.hex),O=((+m["stroke-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+R.o+1||2)-1);var S=.75*(d(i["stroke-width"])||1);if(O=h(g(O,0),1),null==i["stroke-width"]&&(S=m["stroke-width"]),i["stroke-width"]&&(P.weight=S),S&&1>S&&(O*=S)&&(P.weight=1),P.opacity=O,i["stroke-linejoin"]&&(P.joinstyle=i["stroke-linejoin"]||"miter"),P.miterlimit=i["stroke-miterlimit"]||8,i["stroke-linecap"]&&(P.endcap="butt"==i["stroke-linecap"]?"flat":"square"==i["stroke-linecap"]?"square":"round"),"stroke-dasharray"in i){var T={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};P.dashstyle=T[b](i["stroke-dasharray"])?T[i["stroke-dasharray"]]:o}Q&&l.appendChild(P)}if("text"==s.type){s.paper.canvas.style.display=o;var U=s.paper.span,V=100,W=m.font&&m.font.match(/\d+(?:\.\d*)?(?=px)/);p=U.style,m.font&&(p.font=m.font),m["font-family"]&&(p.fontFamily=m["font-family"]),m["font-weight"]&&(p.fontWeight=m["font-weight"]),m["font-style"]&&(p.fontStyle=m["font-style"]),W=d(m["font-size"]||W&&W[0])||10,p.fontSize=W*V+"px",s.textpath.string&&(U.innerHTML=c(s.textpath.string).replace(/"));var X=U.getBoundingClientRect();s.W=m.w=(X.right-X.left)/V,s.H=m.h=(X.bottom-X.top)/V,s.X=m.x,s.Y=m.y+s.H/2,("x"in i||"y"in i)&&(s.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));for(var Y=["x","y","text","font","font-family","font-weight","font-style","font-size"],Z=0,$=Y.length;$>Z;Z++)if(Y[Z]in i){s._.dirty=1;break}switch(m["text-anchor"]){case"start":s.textpath.style["v-text-align"]="left",s.bbx=s.W/2;break;case"end":s.textpath.style["v-text-align"]="right",s.bbx=-s.W/2;break;default:s.textpath.style["v-text-align"]="center",s.bbx=0}s.textpath.style["v-text-kern"]=!0}},C=function(b,f,g){b.attrs=b.attrs||{};var h=(b.attrs,Math.pow),i="linear",j=".5 .5";if(b.attrs.gradient=f,f=c(f).replace(a._radial_gradient,function(a,b,c){return i="radial",b&&c&&(b=d(b),c=d(c),h(b-.5,2)+h(c-.5,2)>.25&&(c=e.sqrt(.25-h(b-.5,2))*(2*(c>.5)-1)+.5),j=b+n+c),o}),f=f.split(/\s*\-\s*/),"linear"==i){var k=f.shift();if(k=-d(k),isNaN(k))return null}var l=a._parseDots(f);if(!l)return null;if(b=b.shape||b.node,l.length){b.removeChild(g),g.on=!0,g.method="none",g.color=l[0].color,g.color2=l[l.length-1].color;for(var m=[],p=0,q=l.length;q>p;p++)l[p].offset&&m.push(l[p].offset+n+l[p].color);g.colors=m.length?m.join():"0% "+g.color,"radial"==i?(g.type="gradientTitle",g.focus="100%",g.focussize="0 0",g.focusposition=j,g.angle=0):(g.type="gradient",g.angle=(270-k)%360),b.appendChild(g)}return 1},D=function(b,c){this[0]=this.node=b,b.raphael=!0,this.id=a._oid++,b.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=c,this.matrix=a.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!c.bottom&&(c.bottom=this),this.prev=c.top,c.top&&(c.top.next=this),c.top=this,this.next=null},E=a.el;D.prototype=E,E.constructor=D,E.transform=function(b){if(null==b)return this._.transform;var d,e=this.paper._viewBoxShift,f=e?"s"+[e.scale,e.scale]+"-1-1t"+[e.dx,e.dy]:o;e&&(d=b=c(b).replace(/\.{3}|\u2026/g,this._.transform||o)),a._extractTransform(this,f+b);var g,h=this.matrix.clone(),i=this.skew,j=this.node,k=~c(this.attrs.fill).indexOf("-"),l=!c(this.attrs.fill).indexOf("url(");if(h.translate(1,1),l||k||"image"==this.type)if(i.matrix="1 0 0 1",i.offset="0 0",g=h.split(),k&&g.noRotation||!g.isSimple){j.style.filter=h.toFilter();var m=this.getBBox(),p=this.getBBox(1),q=m.x-p.x,r=m.y-p.y;j.coordorigin=q*-u+n+r*-u,z(this,1,1,q,r,0)}else j.style.filter=o,z(this,g.scalex,g.scaley,g.dx,g.dy,g.rotate);else j.style.filter=o,i.matrix=c(h),i.offset=h.offset();return null!==d&&(this._.transform=d,a._extractTransform(this,d)),this},E.rotate=function(a,b,e){if(this.removed)return this;if(null!=a){if(a=c(a).split(k),a.length-1&&(b=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(b=e),null==b||null==e){var f=this.getBBox(1);b=f.x+f.width/2,e=f.y+f.height/2}return this._.dirtyT=1,this.transform(this._.transform.concat([["r",a,b,e]])),this}},E.translate=function(a,b){return this.removed?this:(a=c(a).split(k),a.length-1&&(b=d(a[1])),a=d(a[0])||0,b=+b||0,this._.bbox&&(this._.bbox.x+=a,this._.bbox.y+=b),this.transform(this._.transform.concat([["t",a,b]])),this)},E.scale=function(a,b,e,f){if(this.removed)return this;if(a=c(a).split(k),a.length-1&&(b=d(a[1]),e=d(a[2]),f=d(a[3]),isNaN(e)&&(e=null),isNaN(f)&&(f=null)),a=d(a[0]),null==b&&(b=a),null==f&&(e=f),null==e||null==f)var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,b,e,f]])),this._.dirtyT=1,this},E.hide=function(){return!this.removed&&(this.node.style.display="none"),this},E.show=function(){return!this.removed&&(this.node.style.display=o),this},E.auxGetBBox=a.el.getBBox,E.getBBox=function(){var a=this.auxGetBBox();if(this.paper&&this.paper._viewBoxShift){var b={},c=1/this.paper._viewBoxShift.scale;return b.x=a.x-this.paper._viewBoxShift.dx,b.x*=c,b.y=a.y-this.paper._viewBoxShift.dy,b.y*=c,b.width=a.width*c,b.height=a.height*c,b.x2=b.x+b.width,b.y2=b.y+b.height,b}return a},E._getBBox=function(){return this.removed?{}:{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),a.eve.unbind("raphael.*.*."+this.id),a._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var b in this)this[b]="function"==typeof this[b]?a._removedFactory(b):null;this.removed=!0}},E.attr=function(c,d){if(this.removed)return this;if(null==c){var e={};for(var f in this.attrs)this.attrs[b](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}if(null==d&&a.is(c,"string")){if(c==j&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;for(var g=c.split(k),h={},i=0,m=g.length;m>i;i++)c=g[i],c in this.attrs?h[c]=this.attrs[c]:a.is(this.paper.customAttributes[c],"function")?h[c]=this.paper.customAttributes[c].def:h[c]=a._availableAttrs[c];return m-1?h:h[g[0]]}if(this.attrs&&null==d&&a.is(c,"array")){for(h={},i=0,m=c.length;m>i;i++)h[c[i]]=this.attr(c[i]);return h}var n;null!=d&&(n={},n[c]=d),null==d&&a.is(c,"object")&&(n=c);for(var o in n)l("raphael.attr."+o+"."+this.id,this,n[o]);if(n){for(o in this.paper.customAttributes)if(this.paper.customAttributes[b](o)&&n[b](o)&&a.is(this.paper.customAttributes[o],"function")){var p=this.paper.customAttributes[o].apply(this,[].concat(n[o]));this.attrs[o]=n[o];for(var q in p)p[b](q)&&(n[q]=p[q])}n.text&&"text"==this.type&&(this.textpath.string=n.text),B(this,n)}return this},E.toFront=function(){return!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&a._tofront(this,this.paper),this},E.toBack=function(){return this.removed?this:(this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),a._toback(this,this.paper)),this)},E.insertAfter=function(b){return this.removed?this:(b.constructor==a.st.constructor&&(b=b[b.length-1]),b.node.nextSibling?b.node.parentNode.insertBefore(this.node,b.node.nextSibling):b.node.parentNode.appendChild(this.node),a._insertafter(this,b,this.paper),this)},E.insertBefore=function(b){return this.removed?this:(b.constructor==a.st.constructor&&(b=b[0]),b.node.parentNode.insertBefore(this.node,b.node),a._insertbefore(this,b,this.paper),this)},E.blur=function(b){var c=this.node.runtimeStyle,d=c.filter;return d=d.replace(r,o),0!==+b?(this.attrs.blur=b,c.filter=d+n+m+".Blur(pixelradius="+(+b||1.5)+")",c.margin=a.format("-{0}px 0 0 -{0}px",f(+b||1.5))):(c.filter=d,c.margin=0,delete this.attrs.blur),this},a._engine.path=function(a,b){var c=F("shape");c.style.cssText=t,c.coordsize=u+n+u,c.coordorigin=b.coordorigin;var d=new D(c,b),e={fill:"none",stroke:"#000"};a&&(e.path=a),d.type="path",d.path=[],d.Path=o,B(d,e),b.canvas.appendChild(c);var f=F("skew");return f.on=!0,c.appendChild(f),d.skew=f,d.transform(o),d},a._engine.rect=function(b,c,d,e,f,g){var h=a._rectPath(c,d,e,f,g),i=b.path(h),j=i.attrs;return i.X=j.x=c,i.Y=j.y=d,i.W=j.width=e,i.H=j.height=f,j.r=g,j.path=h,i.type="rect",i},a._engine.ellipse=function(a,b,c,d,e){{var f=a.path();f.attrs}return f.X=b-d,f.Y=c-e,f.W=2*d,f.H=2*e,f.type="ellipse",B(f,{cx:b,cy:c,rx:d,ry:e}),f},a._engine.circle=function(a,b,c,d){{var e=a.path();e.attrs}return e.X=b-d,e.Y=c-d,e.W=e.H=2*d,e.type="circle",B(e,{cx:b,cy:c,r:d}),e},a._engine.image=function(b,c,d,e,f,g){var h=a._rectPath(d,e,f,g),i=b.path(h).attr({stroke:"none"}),k=i.attrs,l=i.node,m=l.getElementsByTagName(j)[0];return k.src=c,i.X=k.x=d,i.Y=k.y=e,i.W=k.width=f,i.H=k.height=g,k.path=h,i.type="image",m.parentNode==l&&l.removeChild(m),m.rotate=!0,m.src=c,m.type="tile",i._.fillpos=[d,e],i._.fillsize=[f,g],l.appendChild(m),z(i,1,1,0,0,0),i},a._engine.text=function(b,d,e,g){var h=F("shape"),i=F("path"),j=F("textpath");d=d||0,e=e||0,g=g||"",i.v=a.format("m{0},{1}l{2},{1}",f(d*u),f(e*u),f(d*u)+1),i.textpathok=!0,j.string=c(g),j.on=!0,h.style.cssText=t,h.coordsize=u+n+u,h.coordorigin="0 0";var k=new D(h,b),l={fill:"#000",stroke:"none",font:a._availableAttrs.font,text:g};k.shape=h,k.path=i,k.textpath=j,k.type="text",k.attrs.text=c(g),k.attrs.x=d,k.attrs.y=e,k.attrs.w=1,k.attrs.h=1,B(k,l),h.appendChild(j),h.appendChild(i),b.canvas.appendChild(h);var m=F("skew");return m.on=!0,h.appendChild(m),k.skew=m,k.transform(o),k},a._engine.setSize=function(b,c){var d=this.canvas.style;return this.width=b,this.height=c,b==+b&&(b+="px"),c==+c&&(c+="px"),d.width=b,d.height=c,d.clip="rect(0 "+b+" "+c+" 0)",this._viewBox&&a._engine.setViewBox.apply(this,this._viewBox),this},a._engine.setViewBox=function(b,c,d,e,f){a.eve("raphael.setViewBox",this,this._viewBox,[b,c,d,e,f]);var g,h,i=this.getSize(),j=i.width,k=i.height;return f&&(g=k/e,h=j/d,j>d*g&&(b-=(j-d*g)/2/g),k>e*h&&(c-=(k-e*h)/2/h)),this._viewBox=[b,c,d,e,!!f],this._viewBoxShift={dx:-b,dy:-c,scale:i},this.forEach(function(a){a.transform("...")}),this};var F;a._engine.initWin=function(a){var b=a.document;b.styleSheets.length<31?b.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)"):b.styleSheets[0].addRule(".rvml","behavior:url(#default#VML)");try{!b.namespaces.rvml&&b.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),F=function(a){return b.createElement("')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e=b.width,f=b.x,g=b.y;if(!c)throw new Error("VML container not found.");var h=new a._Paper,i=h.canvas=a._g.doc.createElement("div"),j=i.style;return f=f||0,g=g||0,e=e||512,d=d||342,h.width=e,h.height=d,e==+e&&(e+="px"),d==+d&&(d+="px"),h.coordsize=1e3*u+n+1e3*u,h.coordorigin="0 0",h.span=a._g.doc.createElement("span"),h.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",i.appendChild(h.span),j.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",e,d),1==c?(a._g.doc.body.appendChild(i),j.left=f+"px",j.top=g+"px",j.position="absolute"):c.firstChild?c.insertBefore(i,c.firstChild):c.appendChild(i),h.renderfix=function(){},h},a.prototype.clear=function(){a.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]="function"==typeof this[b]?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}}); -------------------------------------------------------------------------------- /Defcon26/static/sorttable.js: -------------------------------------------------------------------------------- 1 | /* 2 | SortTable 3 | version 2 4 | 7th April 2007 5 | Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/ 6 | 7 | Instructions: 8 | Download this file 9 | Add to your HTML 10 | Add class="sortable" to any table you'd like to make sortable 11 | Click on the headers to sort 12 | 13 | Thanks to many, many people for contributions and suggestions. 14 | Licenced as X11: http://www.kryogenix.org/code/browser/licence.html 15 | This basically means: do what you want with it. 16 | */ 17 | 18 | 19 | var stIsIE = /*@cc_on!@*/false; 20 | 21 | sorttable = { 22 | init: function() { 23 | // quit if this function has already been called 24 | if (arguments.callee.done) return; 25 | // flag this function so we don't do the same thing twice 26 | arguments.callee.done = true; 27 | // kill the timer 28 | if (_timer) clearInterval(_timer); 29 | 30 | if (!document.createElement || !document.getElementsByTagName) return; 31 | 32 | sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; 33 | 34 | forEach(document.getElementsByTagName('table'), function(table) { 35 | if (table.className.search(/\bsortable\b/) != -1) { 36 | sorttable.makeSortable(document.getElementById('table')); 37 | } 38 | }); 39 | 40 | }, 41 | 42 | makeSortable: function(table) { 43 | if (table.getElementsByTagName('thead').length == 0) { 44 | // table doesn't have a tHead. Since it should have, create one and 45 | // put the first table row in it. 46 | the = document.createElement('thead'); 47 | the.appendChild(table.rows[0]); 48 | table.insertBefore(the,table.firstChild); 49 | } 50 | // Safari doesn't support table.tHead, sigh 51 | if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0]; 52 | 53 | if (table.tHead.rows.length != 1) return; // can't cope with two header rows 54 | 55 | // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as 56 | // "total" rows, for example). This is B&R, since what you're supposed 57 | // to do is put them in a tfoot. So, if there are sortbottom rows, 58 | // for backwards compatibility, move them to tfoot (creating it if needed). 59 | sortbottomrows = []; 60 | for (var i=0; i5' : ' ▴'; 104 | this.appendChild(sortrevind); 105 | return; 106 | } 107 | if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) { 108 | // if we're already sorted by this column in reverse, just 109 | // re-reverse the table, which is quicker 110 | sorttable.reverse(this.sorttable_tbody); 111 | this.className = this.className.replace('sorttable_sorted_reverse', 112 | 'sorttable_sorted'); 113 | this.removeChild(document.getElementById('sorttable_sortrevind')); 114 | sortfwdind = document.createElement('span'); 115 | sortfwdind.id = "sorttable_sortfwdind"; 116 | sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; 117 | this.appendChild(sortfwdind); 118 | return; 119 | } 120 | 121 | // remove sorttable_sorted classes 122 | theadrow = this.parentNode; 123 | forEach(theadrow.childNodes, function(cell) { 124 | if (cell.nodeType == 1) { // an element 125 | cell.className = cell.className.replace('sorttable_sorted_reverse',''); 126 | cell.className = cell.className.replace('sorttable_sorted',''); 127 | } 128 | }); 129 | sortfwdind = document.getElementById('sorttable_sortfwdind'); 130 | if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); } 131 | sortrevind = document.getElementById('sorttable_sortrevind'); 132 | if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); } 133 | 134 | this.className += ' sorttable_sorted'; 135 | sortfwdind = document.createElement('span'); 136 | sortfwdind.id = "sorttable_sortfwdind"; 137 | sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; 138 | this.appendChild(sortfwdind); 139 | 140 | // build an array to sort. This is a Schwartzian transform thing, 141 | // i.e., we "decorate" each row with the actual sort key, 142 | // sort based on the sort keys, and then put the rows back in order 143 | // which is a lot faster because you only do getInnerText once per row 144 | row_array = []; 145 | col = this.sorttable_columnindex; 146 | rows = this.sorttable_tbody.rows; 147 | for (var j=0; j 12) { 184 | // definitely dd/mm 185 | return sorttable.sort_ddmm; 186 | } else if (second > 12) { 187 | return sorttable.sort_mmdd; 188 | } else { 189 | // looks like a date, but we can't tell which, so assume 190 | // that it's dd/mm (English imperialism!) and keep looking 191 | sortfn = sorttable.sort_ddmm; 192 | } 193 | } 194 | } 195 | } 196 | return sortfn; 197 | }, 198 | 199 | getInnerText: function(node) { 200 | // gets the text we want to use for sorting for a cell. 201 | // strips leading and trailing whitespace. 202 | // this is *not* a generic getInnerText function; it's special to sorttable. 203 | // for example, you can override the cell text with a customkey attribute. 204 | // it also gets .value for fields. 205 | 206 | if (!node) return ""; 207 | 208 | hasInputs = (typeof node.getElementsByTagName == 'function') && 209 | node.getElementsByTagName('input').length; 210 | 211 | if (node.getAttribute("sorttable_customkey") != null) { 212 | return node.getAttribute("sorttable_customkey"); 213 | } 214 | else if (typeof node.textContent != 'undefined' && !hasInputs) { 215 | return node.textContent.replace(/^\s+|\s+$/g, ''); 216 | } 217 | else if (typeof node.innerText != 'undefined' && !hasInputs) { 218 | return node.innerText.replace(/^\s+|\s+$/g, ''); 219 | } 220 | else if (typeof node.text != 'undefined' && !hasInputs) { 221 | return node.text.replace(/^\s+|\s+$/g, ''); 222 | } 223 | else { 224 | switch (node.nodeType) { 225 | case 3: 226 | if (node.nodeName.toLowerCase() == 'input') { 227 | return node.value.replace(/^\s+|\s+$/g, ''); 228 | } 229 | case 4: 230 | return node.nodeValue.replace(/^\s+|\s+$/g, ''); 231 | break; 232 | case 1: 233 | case 11: 234 | var innerText = ''; 235 | for (var i = 0; i < node.childNodes.length; i++) { 236 | innerText += sorttable.getInnerText(node.childNodes[i]); 237 | } 238 | return innerText.replace(/^\s+|\s+$/g, ''); 239 | break; 240 | default: 241 | return ''; 242 | } 243 | } 244 | }, 245 | 246 | reverse: function(tbody) { 247 | // reverse the rows in a tbody 248 | newrows = []; 249 | for (var i=0; i=0; i--) { 253 | tbody.appendChild(newrows[i]); 254 | } 255 | delete newrows; 256 | }, 257 | 258 | /* sort functions 259 | each sort function takes two parameters, a and b 260 | you are comparing a[0] and b[0] */ 261 | sort_numeric: function(a,b) { 262 | aa = parseFloat(a[0].replace(/[^0-9.-]/g,'')); 263 | if (isNaN(aa)) aa = 0; 264 | bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); 265 | if (isNaN(bb)) bb = 0; 266 | return aa-bb; 267 | }, 268 | sort_alpha: function(a,b) { 269 | if (a[0]==b[0]) return 0; 270 | if (a[0] 0 ) { 316 | var q = list[i]; list[i] = list[i+1]; list[i+1] = q; 317 | swap = true; 318 | } 319 | } // for 320 | t--; 321 | 322 | if (!swap) break; 323 | 324 | for(var i = t; i > b; --i) { 325 | if ( comp_func(list[i], list[i-1]) < 0 ) { 326 | var q = list[i]; list[i] = list[i-1]; list[i-1] = q; 327 | swap = true; 328 | } 329 | } // for 330 | b++; 331 | 332 | } // while(swap) 333 | } 334 | } 335 | 336 | /* ****************************************************************** 337 | Supporting functions: bundled here to avoid depending on a library 338 | ****************************************************************** */ 339 | 340 | // Dean Edwards/Matthias Miller/John Resig 341 | 342 | /* for Mozilla/Opera9 */ 343 | if (document.addEventListener) { 344 | document.addEventListener("DOMContentLoaded", sorttable.init, false); 345 | } 346 | 347 | /* for Internet Explorer */ 348 | /*@cc_on @*/ 349 | /*@if (@_win32) 350 | document.write(" 5 | 6 | 7 | 19 | 20 | 21 |

Traffic Statistics at a Glance

22 |
23 |

Inbound ==> {{ outbound }}

24 |

Outbound ==> {{ inbound }}

25 |
26 |

Unique IP Addresses: {{ unique }} ==> REFRESH

27 |
28 | 29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 37 |

HISTORICAL DATA

38 |

Enter number of samples to retrieve: 39 |

40 | Number of Samples: 41 | Port Number: 42 | IP Address: 43 | 44 | 45 |

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {% for item in result1 %} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {% endfor %} 72 | 73 |
Source IP AddressSource PortPacketsAvg Bytes per PacketDestination IP AddressDestination PortTimestamp
{{ item[0] }}{{ item[1] }}{{ item[2] }}{{ item[3] }}{{ item[4] }}{{ item[5] }}{{ item[6] }}
74 | 75 | 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenPiMap 2 | 3 | OpenPiMap is an open source NetFlow protocol analyzer written entirely in Python 3 designed to provide insights on botnet and network traffic analysis in a small business or home network. It was designed and tested on a Windows 10 computer and then ran continuously on a Raspberry Pi 1 Model B (and should work flawlessly on a Pi2 or Pi3). The netflow_collector.py is designed to be run in the background (as a cron job) continuously and will listen for NetFlow v5 traffic (generated by PfSense, Cisco, etc.). The openipmap.py script can be run daily, weekly, or as needed. There are lots of incomplete and commented features so be sure to follow the code to see what it is doing. The script is designed to follow this path: 4 | 5 | 1. Parse NetFlow data retrieved from the netflow_collector.py script. 6 | 2. Retrieve multiple IP blacklists and determine the public IP address of the host that it is running on. (Or can be hardcoded for running offline) 7 | 3. Correlate inbound and outbound IP addresses and potentially malicious IP addresses, ports, etc. to determine any kind of "bad" activity. 8 | 4. For every IP address that is identified as suspicious, it will query Shodan.io for current services, hostname, etc. This can be immensely useful in determining if the device is part of a botnet, an IoT device, or other infected system on the internet. All of this data can then be used for trend analysis, reporting, incident tracking, etc. 9 | 5. Finally, the script saves the map as an HTML file and pushes out an SMS message with the number of "bad" IPs that were detected. 10 | 11 | The script has built in functionality to do data consumption statistics, top ports/services charts, and 24 hour / weekly / monthly queries. However, Shodan.io has a rate limit on their free API usage!! Looking up hundreds or thousands of unique IPs will take a LONG time!! This doesn't break anything.. but be prepared to wait a few hours if you are running monthly reports. 12 | 13 | ![alt text](https://raw.githubusercontent.com/evilbotnet/openpimap/master/map_global.png) 14 | 15 | ![alt text](https://raw.githubusercontent.com/evilbotnet/openpimap/master/map_detail.png) 16 | 17 | 18 | # Notes 19 | The code isn't quite complete. However, it has been running for 6+ months on my Raspberry Pi 1 Model B without any hiccups. Be sure to check out the API keys required for Shodan and Twilio functions to work (all requests are well within the free API limits). You will also need a local copy of the GeoIP database for offline geolocation of IP addresses. 20 | 21 | Credit goes to Lalit Patel (http://codestacking.blogspot.com/2017/02/netflow-version-5-collector-in-python.html) for the NetFlow Collector. I made a few changes required to save the raw data to a CSV file for later processing and logging, but his collector was a great place to start. 22 | 23 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | API_KEY = 'SHODAN_API_KEY' 2 | static_ip = "127.0.0.1" 3 | 4 | 5 | # TWILIO Configuration Items 6 | # account_sid = "TWILIO SID" 7 | # auth_token = "TWILIO AUTH_TOKEN" 8 | # RECV NUMBER 9 | # TWILIO_NUMBER 10 | -------------------------------------------------------------------------------- /map_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilbotnet/OpenPiMap/c4b1d16de91a02900659dee40a37ebb0c789a2f5/map_detail.png -------------------------------------------------------------------------------- /map_global.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilbotnet/OpenPiMap/c4b1d16de91a02900659dee40a37ebb0c789a2f5/map_global.png -------------------------------------------------------------------------------- /netflow_collector.py: -------------------------------------------------------------------------------- 1 | import socket, struct 2 | import csv 3 | import time 4 | 5 | from socket import inet_ntoa 6 | 7 | SIZE_OF_HEADER = 24 8 | SIZE_OF_RECORD = 48 9 | 10 | print("Collector started at: ", time.strftime("%H:%M:%S %d-%m-%Y")) 11 | 12 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 13 | s.bind(('0.0.0.0', 2303)) 14 | current_day = time.strftime("%d-%b-%Y") 15 | 16 | with open("netflowData-" + current_day + ".csv", "a", encoding='utf8', newline='') as csv_file: 17 | writer = csv.writer(csv_file, delimiter=',') 18 | #line = "src, sport, packet, bytes, dst, dport, time" 19 | writer.writerow(["src", "sport", "packet", "bytes", "dst", "dport", "time"]) 20 | 21 | while True: 22 | buf, addr = s.recvfrom(1500) 23 | 24 | (version, count) = struct.unpack('!HH',buf[0:4]) 25 | if version != 5: 26 | print("Not NetFlow v5!") 27 | continue 28 | 29 | uptime = socket.ntohl(struct.unpack('I',buf[4:8])[0]) 30 | epochseconds = socket.ntohl(struct.unpack('I',buf[8:12])[0]) 31 | 32 | for i in range(0, count): 33 | try: 34 | base = SIZE_OF_HEADER+(i*SIZE_OF_RECORD) 35 | 36 | data = struct.unpack('!IIIIHH',buf[base+16:base+36]) 37 | nfdata = {} 38 | nfdata['saddr'] = inet_ntoa(buf[base + 0:base + 4]) 39 | nfdata['daddr'] = inet_ntoa(buf[base + 4:base + 8]) 40 | nfdata['pcount'] = data[0] 41 | nfdata['bcount'] = data[1] 42 | nfdata['stime'] = data[2] 43 | nfdata['etime'] = data[3] 44 | nfdata['sport'] = data[4] 45 | nfdata['dport'] = data[5] 46 | nfdata['protocol'] = inet_ntoa(buf[base + 39]) 47 | except: 48 | continue 49 | #print(nfdata) 50 | print("%s:%s %s bytes -> %s:%s" % (nfdata['saddr'], nfdata['sport'], 51 | nfdata['bcount'], nfdata['daddr'], 52 | nfdata['dport'])) 53 | current_day = time.strftime("%d-%b-%Y") 54 | #print(current_day) 55 | with open("netflowData-" + current_day + ".csv", "a", encoding='utf8', newline='') as csv_file: 56 | writer = csv.writer(csv_file, delimiter=',') 57 | line = (nfdata['saddr'], nfdata['sport'], 58 | nfdata['pcount'], nfdata['bcount'], 59 | nfdata['daddr'], nfdata['dport'], 60 | nfdata['stime'], time.strftime("%H")) 61 | writer.writerow(line) 62 | #print("Wrote data to netflowData-" + current_day + ".csv at " + time.strftime("%H:%M:%S %d-%m-%Y")) -------------------------------------------------------------------------------- /openpimap.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import geoip2.database 3 | import folium 4 | from folium.element import IFrame 5 | import os 6 | import time 7 | import shodan 8 | import requests 9 | import numpy as np 10 | from folium.plugins import MarkerCluster 11 | import matplotlib.pyplot as plt 12 | from collections import Counter 13 | from multiprocessing.dummy import Pool as ThreadPool, freeze_support 14 | from itertools import repeat 15 | from twilio.rest import TwilioRestClient 16 | start_time = time.time() 17 | API_KEY = 'INSERT SHODAN API KEY' 18 | api = shodan.Shodan(API_KEY) 19 | global external_ips, internal_ips, country_array, state_array, city_array, \ 20 | latitude_array, longitude_array, location_array, hosts, ports, geodata, \ 21 | lat, long, color, INBYTES, OUTBYTES, DATA, BYTES, md, oBL, CIA, noDNS, \ 22 | TITLES, daily_in, daily_out, TIME 23 | TIME = [] 24 | 25 | try: 26 | home = requests.get('http://ipquail.com/ip') 27 | except: 28 | pass 29 | 30 | malwaredomains_url = 'http://www.malwaredomainlist.com/hostslist/ip.txt' 31 | openBL_url = 'http://www.openbl.org/lists/base_30days.txt' 32 | CIArmy_url = 'http://cinsscore.com/list/ci-badguys.txt' 33 | nothinkDNS_url = 'http://www.nothink.org/blacklist/blacklist_malware_dns.txt' 34 | md = '' 35 | oBL = '' 36 | CIA = '' 37 | noDNS = '' 38 | try: 39 | malwaredomains = requests.get(malwaredomains_url, headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}) 40 | md = (malwaredomains.text).split('\r\n') 41 | # print(md) 42 | openBL = requests.get(openBL_url, headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}) 43 | oBL = (openBL.text).split('\n') 44 | # print(oBL) 45 | CIArmy = requests.get(CIArmy_url, headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}) 46 | CIA = (CIArmy.text).split('\n') 47 | # print(CIA) 48 | nothinkDNS = requests.get(nothinkDNS_url, headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}) 49 | noDNS = (nothinkDNS.text).split('\n') 50 | except: 51 | pass 52 | badguys = [] 53 | compromise = [] 54 | compromise_custom = [] 55 | biglist = md + oBL + noDNS + CIA 56 | 57 | def _csvParse(inputfile, home): 58 | f = open(inputfile) 59 | #badPorts = [21,22,23,2323,3306,3389,5358,7547] 60 | badPorts = [21,22,23,3306,3389] 61 | csv_file = csv.reader(f, delimiter=',') 62 | next(csv_file) 63 | inbytes = [] 64 | outbytes = [] 65 | for row in csv_file: 66 | if row: 67 | local_bytes = [] 68 | src = row[0] 69 | sport = row[1] 70 | packets = row[2] 71 | bytes = row[3] 72 | dst = row[4] 73 | dport = row[5] 74 | uptime = row[6] 75 | local_bytes.append(bytes) 76 | if src != home: 77 | external_ips.append(src) 78 | ports.append(dport) 79 | inbytes.append(int(bytes)) 80 | TIME.append(int(uptime)) 81 | if src == home: 82 | internal_ips.append(dst) 83 | outbytes.append(int(bytes)) 84 | TIME.append(int(uptime)) 85 | if (int(dport) in badPorts) and (src != home): 86 | hosts.append(src) 87 | compromise_custom.append(tuple([src, dport])) 88 | if (int(sport) in badPorts) and (src == home): 89 | hosts.append(dst) 90 | compromise_custom.append(tuple([dst, sport])) 91 | 92 | 93 | INBYTES.append(sum(inbytes)/(1024*1024)) 94 | OUTBYTES.append(sum(outbytes)/(1024*1024)) 95 | DATA.append(sum(inbytes + outbytes)) 96 | uniq_in_ips = set(internal_ips) 97 | uniq_ex_ips = set(external_ips) 98 | TITLES.append(inputfile[-15:-9]) 99 | daily_out.append(len(uniq_ex_ips)) 100 | daily_in.append(len(uniq_in_ips)) 101 | #print("Inbound: %s" % (len(uniq_ex_ips))) 102 | #print("Outbound: %s" % (len(uniq_in_ips))) 103 | 104 | def _geolocate(iplist): 105 | if os.name == 'nt': 106 | #print('Windows') 107 | reader = geoip2.database.Reader('C:\GeoLite2\GeoLite2-City.mmdb') 108 | else: 109 | #print('Linux') 110 | reader = geoip2.database.Reader('/root/GeoLite2/GeoLite2-City.mmdb') 111 | 112 | for ip in set(iplist): 113 | try: 114 | response = reader.city(ip) 115 | country_name = response.country.name 116 | state = response.subdivisions.most_specific.name 117 | city = response.city.name 118 | latitude = response.location.latitude 119 | longitude = response.location.longitude 120 | lat.append(latitude) 121 | long.append(longitude) 122 | country_array.append(country_name) 123 | state_array.append(state) 124 | hosts.append(ip) 125 | color.append('red') 126 | except: 127 | pass 128 | 129 | def _folium(outfile): 130 | colors = list(zip(color)) 131 | locations = list(zip(lat, long)) 132 | popups = [] 133 | popup_list = list(zip(hosts, country_array, state_array)) 134 | for i in popup_list: 135 | shodan_data = [] 136 | try: 137 | host = api.host(i[0]) 138 | country = i[1] 139 | shodan_ip = host['ip_str'] 140 | shodan_org = str(host.get('org', 'n/a')) 141 | shodan_os = str(host.get('os', 'n/a')) 142 | for item in host['data']: 143 | shodan_ports = "Port: %s
Banner: %s
" % (item['port'], item['data']) 144 | s1 = shodan_ports.replace("\n", "
") 145 | s = s1.replace("\r", "
") 146 | shodan_data.append(s) 147 | time.sleep(2) 148 | except shodan.APIError as e: 149 | print(e.value) 150 | shodan_ip = i[0] 151 | country = i[1] 152 | shodan_org = "No data available" 153 | shodan_os = "No data available" 154 | shodan_data = "--No data available--" 155 | 156 | html = """ 157 |

IP: 158 | """ + shodan_ip + """ 159 |

160 |

Country: 161 | """ + country + """ 162 |

163 |

Organization: 164 | """ + shodan_org + """ 165 |

166 |

OS: 167 | """ + shodan_os + """ 168 |

169 |

""" + str(shodan_data)[2:-2] + """

170 | """ 171 | iframe = IFrame(html=html, width=300, height=300) 172 | popups.append(iframe) 173 | m = folium.Map(location=[np.mean(lat), np.mean(long)], tiles='cartodbdark_matter', zoom_start=2) 174 | m.add_child(MarkerCluster(locations=locations, popups=popups, icons=color)) 175 | m.save(outfile) 176 | 177 | def _blackList(hosts): 178 | global compromise 179 | 180 | print("Parsing through %s suspect IP addresses." % (len(set(biglist)))) 181 | for host in set(hosts): 182 | if host in set(biglist): 183 | # print("Bad Guy Found:") 184 | compromise.append(host) 185 | # print(host) 186 | if host in internal_ips: 187 | print("ALERT ALERT ALERT") 188 | print(host) 189 | compromise = compromise + compromise_custom 190 | print("Found %s bad guys" % len(set(compromise))) 191 | if not compromise: 192 | print("No malicious IPs were found") 193 | print("Checked against %s IPs." % len(biglist)) 194 | 195 | def _pieChart(variable, title, n, outfile): 196 | labels = [] 197 | sizes = [] 198 | legend = [] 199 | n_groups = n 200 | explode_array = [0, 0, 0.05, 0.05, 0.1, 0.1, 0.2, 0.3, 0.4, 0.6] 201 | explode = explode_array[:n_groups] 202 | portcount = Counter(variable) 203 | most_common = portcount.most_common(n=n_groups) 204 | fig = plt.figure(4, figsize=(4,5), facecolor="black") 205 | ax = fig.add_subplot(211) 206 | 207 | for x, y in most_common: 208 | #print(x, y) 209 | labels.append(x) 210 | sizes.append(y) 211 | legend.append(("%-7s: %-4s") % (x,y)) 212 | #plt.gca().axis("equal") 213 | ax.set_title(title, color="white") 214 | ax.axis("equal") 215 | pie = ax.pie(sizes, startangle=0, explode=explode) 216 | ax2 = fig.add_subplot(212) 217 | ax2.axis("off") 218 | ax2.legend(pie[0], legend, loc="center", fontsize=10, 219 | bbox_transform=plt.gcf().transFigure) 220 | plt.tight_layout() 221 | #plt.show() 222 | plt.savefig(outfile) 223 | plt.clf() 224 | 225 | def _barChart(yValues, xValues, outfile): 226 | fig = plt.figure(figsize=(15,5), facecolor="black", edgecolor="white") 227 | ax = fig.add_subplot(111) 228 | ax.tick_params(axis="y", colors="black") 229 | N = len(yValues) 230 | menMeans = yValues[:N] 231 | # menStd = [2,3,4,1,2] 232 | ind = np.arange(N) 233 | width = 0.75 234 | rects1 = ax.bar(ind, menMeans, width=width, color='black', error_kw=dict(elinewidth=2, ecolor='red')) 235 | ax.set_xlim(-width, len(ind) + width) 236 | # ax.set_ylim(0,45) 237 | ax.set_ylabel('MegaBytes') 238 | ax.set_xlabel('Date') 239 | ax.set_title('Megabytes over time') 240 | xTickMarks = xValues 241 | ax.set_xticks(ind) 242 | xtickNames = ax.set_xticklabels(xTickMarks) 243 | plt.setp(xtickNames, rotation=45, fontsize=10) 244 | #plt.show() 245 | plt.savefig(outfile) 246 | #plt.savefig() 247 | 248 | def _initialize(): 249 | global external_ips, internal_ips, country_array, state_array, city_array, \ 250 | latitude_array, longitude_array, location_array, hosts, ports, geodata, \ 251 | lat, long, color, INBYTES, OUTBYTES, DATA, BYTES, md, oBL, CIA, noDNS, TITLES, \ 252 | compromise, daily_in, daily_out, message 253 | message = [] 254 | daily_in = [] 255 | daily_out = [] 256 | ports = [] 257 | internal_ips = [] 258 | country_array = [] 259 | state_array = [] 260 | external_ips = [] 261 | geodata = [] 262 | lat = [] 263 | long = [] 264 | hosts = [] 265 | color = [] 266 | INBYTES = [] 267 | OUTBYTES = [] 268 | DATA = [] 269 | TITLES = [] 270 | compromise = [] 271 | 272 | directory = '.' 273 | files = sorted([f for f in os.listdir(directory) if f.startswith('netflowData-') and f.endswith('.csv')]) 274 | last24 = files[-2:-1] 275 | last72 = files[-4:-1] 276 | lastWeek = files[-8:-1] 277 | last30 = files[-31:-1] 278 | 279 | def _lastN(infiles, outfile): 280 | for item in infiles: 281 | #print(item) 282 | _csvParse(item, home) 283 | _blackList(hosts=set(internal_ips + external_ips)) 284 | _geolocate(compromise) 285 | print("TOTAL BAD GUYS: %s" % len(hosts)) 286 | #_pieChart(variable=ports, title='Top Ports', n=10,outfile=outfile[:-5] + '.jpg') 287 | #_pieChart(variable=external_ips, title='Top Ports', n=10,outfile=outfile[:-5] + '2.jpg') 288 | #_barChart(yValues=(DATA), xValues=sorted(TITLES), N=14,outfile=outfile[:-5] + '.jpg') 289 | #_folium(outfile=outfile) 290 | #_shodan(iplist=hosts, filename='shodan.txt') 291 | _initialize() 292 | 293 | 294 | def _shodan(iplist, filename): 295 | f = open(filename, "w+") 296 | for ip in iplist: 297 | try: 298 | host = api.host(ip) 299 | print("IP: %s\nOrganization: %s\nOperating System: %s\n" % (host['ip_str'], host.get('org', 'n/a'), host.get('os', 'n/a'))) 300 | f.write("IP: %s\nOrganization: %s\nOperating System: %s\n" % (host['ip_str'], host.get('org', 'n/a'), host.get('os', 'n/a'))) 301 | for item in host['data']: 302 | print("Port: %s\nBanner: %s\n" % (item['port'], item['data'])) 303 | f.write("Port: %s\nBanner: %s\n" % (item['port'], item['data'])) 304 | time.sleep(5) 305 | except shodan.exception.APIError as e: 306 | print(e.value) 307 | continue 308 | f.close() 309 | 310 | def _shodan2(ip): 311 | host = api.host(ip) 312 | shodan_data = [] 313 | shodan_host = "IP: %s\nOrganization: %s\nOperating System: %s\n" % (host['ip_str'], host.get('org', 'n/a'), host.get('os', 'n/a')) 314 | shodan_data.append(shodan_host) 315 | for item in host['data']: 316 | shodan_ports = "Port: %s\nBanner: %s\n" % (item['port'], item['data']) 317 | shodan_data.append(shodan_ports) 318 | return shodan_data 319 | 320 | def _lastHour(infiles, outfile): 321 | for item in infiles: 322 | _csvParse(item, home) 323 | print(TIME[-1]) 324 | hour = (TIME[-1] - 3600000) 325 | print(hour) 326 | i = 0 327 | index = 0 328 | for position, item in enumerate(TIME): 329 | if item > hour and i == 0: 330 | index = (int(position)) 331 | i = 1 332 | print(index) 333 | #_blackList(hosts=set(internal_ips[int(index):] + external_ips[int(index):])) 334 | _geolocate(iplist=set(internal_ips[int(index):] + external_ips[int(index):])) 335 | print(len(internal_ips)) 336 | print("TOTAL BAD GUYS: %s" % len(hosts)) 337 | #_folium(outfile=outfile) 338 | 339 | def _sendText(message): 340 | account_sid = "TWILIO SID" 341 | auth_token = "TWILIO AUTH_TOKEN" 342 | client = TwilioRestClient(account_sid, auth_token) 343 | message = client.messages.create(to='+RECV NUMBER', from_='+TWILIO NUMBER', body=message) 344 | 345 | def _multiThreadedTest(infiles): 346 | arg1 = [] 347 | arg2 = home 348 | for item in infiles: 349 | arg1.append(item) 350 | pool = ThreadPool(len(arg1)) 351 | pool.starmap(_csvParse, zip(arg1, repeat(arg2))) 352 | print("Parsed through %d IP addresses." % (len(set(internal_ips + external_ips)))) 353 | _blackList(hosts=set(internal_ips + external_ips)) 354 | _geolocate(hosts) 355 | #print(privateIP.text) 356 | #_initialize() 357 | #_multiThreadedTest(last30) 358 | #res_list = [x[0] for x in compromise] 359 | #_barChart(yValues=(DATA), xValues=sorted(TITLES),outfile="bar.png") 360 | #text_file = open("badguys.txt", "w") 361 | #for i in biglist: 362 | # text_file.write("%s\n" % (i)) 363 | #_pieChart(ports, "Top ports", 10, "topports.png") 364 | #_folium("test.html") 365 | biglist = [] 366 | with open ('badguys.txt', 'r') as myfile: 367 | for line in myfile: 368 | #print(line.strip('\n')) 369 | biglist.append(line.strip('\n')) 370 | 371 | 372 | #print(len(biglist)) 373 | _initialize() 374 | _multiThreadedTest(last30) 375 | _folium('lastMonth.html') 376 | _barChart(DATA, TITLES, 'bar.png') 377 | _pieChart(ports, "Top Ports", 10, "topports.png") 378 | freeze_support() 379 | print("time elapsed: {:.2f}s".format(time.time() - start_time)) -------------------------------------------------------------------------------- /openpimap_lite.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import geoip2.database 3 | import folium 4 | import os 5 | import time 6 | import shodan 7 | import requests 8 | from config import * 9 | from folium.plugins import MarkerCluster 10 | 11 | start_time = time.time() 12 | api = shodan.Shodan(API_KEY) 13 | TIME = [] 14 | 15 | try: 16 | home = requests.get('http://ipquail.com/ip').text.strip("\n\r") 17 | print(home) 18 | except: 19 | home = static_ip 20 | 21 | malwaredomains_url = 'http://www.malwaredomainlist.com/hostslist/ip.txt' 22 | openBL_url = 'http://www.openbl.org/lists/base_30days.txt' 23 | CIArmy_url = 'http://cinsscore.com/list/ci-badguys.txt' 24 | nothinkDNS_url = 'http://www.nothink.org/blacklist/blacklist_malware_dns.txt' 25 | 26 | try: 27 | malwaredomains = requests.get(malwaredomains_url, headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}) 28 | md = malwaredomains.text.split('\r\n') 29 | openBL = requests.get(openBL_url, headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}) 30 | oBL = openBL.text.split('\n') 31 | emergeThreat = requests.get('https://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt', 32 | headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}) 33 | eT = emergeThreat.text.split('\n') 34 | CIArmy = requests.get(CIArmy_url, headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}) 35 | CIA = CIArmy.text.split('\n') 36 | nothinkDNS = requests.get(nothinkDNS_url, headers={'User-agent': 'evilbotnet.com -- InfoSec Research'}) 37 | noDNS = nothinkDNS.text.split('\n') 38 | except: 39 | pass 40 | badguys = [] 41 | compromise = [] 42 | compromise_custom = [] 43 | biglist = md + oBL + noDNS + CIA + eT 44 | 45 | 46 | def _csvparse(inputfile, ip_address): 47 | f = open(inputfile) 48 | # badPorts = [21,22,23,2323,3306,3389,5358,7547] 49 | csv_file = csv.reader(f, delimiter=',') 50 | next(csv_file) 51 | inbytes = [] 52 | outbytes = [] 53 | for row in csv_file: 54 | if row: 55 | local_bytes = [] 56 | src = row[0] 57 | # sport = row[1] 58 | # packets = row[2] 59 | bytes = row[3] 60 | dst = row[4] 61 | dport = row[5] 62 | uptime = row[6] 63 | local_bytes.append(bytes) 64 | if src != ip_address: 65 | external_ips.append(src) 66 | ports.append(dport) 67 | inbytes.append(int(bytes)) 68 | TIME.append(int(uptime)) 69 | if src == ip_address: 70 | internal_ips.append(dst) 71 | outbytes.append(int(bytes)) 72 | TIME.append(int(uptime)) 73 | # if (int(dport) in badPorts) and (src != home): 74 | # hosts.append(src) 75 | # compromise_custom.append(tuple([src, dport])) 76 | # if (int(sport) in badPorts) and (src == home): 77 | # hosts.append(dst) 78 | # compromise_custom.append(tuple([dst, sport])) 79 | INBYTES.append(sum(inbytes) / (1024 * 1024)) 80 | OUTBYTES.append(sum(outbytes) / (1024 * 1024)) 81 | DATA.append(sum(inbytes + outbytes)) 82 | uniq_in_ips = set(internal_ips) 83 | uniq_ex_ips = set(external_ips) 84 | TITLES.append(inputfile[-15:-9]) 85 | daily_out.append(len(uniq_ex_ips)) 86 | daily_in.append(len(uniq_in_ips)) 87 | 88 | 89 | def _geolocate(iplist): 90 | if os.name == 'nt': 91 | reader = geoip2.database.Reader('D:\PycharmProjects\openpimap\GeoLite2-City.mmdb') 92 | else: 93 | reader = geoip2.database.Reader('/root/GeoLite2/GeoLite2-City.mmdb') 94 | 95 | for ip in set(iplist): 96 | try: 97 | response = reader.city(ip) 98 | country_name = response.country.name 99 | state = response.subdivisions.most_specific.name 100 | latitude = response.location.latitude 101 | longitude = response.location.longitude 102 | lat.append(latitude) 103 | long.append(longitude) 104 | country_array.append(country_name) 105 | state_array.append(state) 106 | hosts.append(ip) 107 | color.append('red') 108 | except Exception as e: 109 | pass 110 | 111 | 112 | def _folium(outfile): 113 | colors = list(zip(color)) 114 | locations = list(zip(lat, long)) 115 | popups = [] 116 | popup_list = list(zip(hosts, country_array, state_array)) 117 | for i in popup_list: 118 | shodan_data = [] 119 | try: 120 | host = api.host(i[0]) 121 | country = i[1] 122 | shodan_ip = host['ip_str'] 123 | shodan_org = str(host.get('org', 'n/a')) 124 | shodan_os = str(host.get('os', 'n/a')) 125 | for item in host['data']: 126 | shodan_ports = "Port: %s
Banner: %s
" % (item['port'], item['data']) 127 | s1 = shodan_ports.replace("\n", "
") 128 | s = s1.replace("\r", "
") 129 | shodan_data.append(s) 130 | except shodan.APIError as e: 131 | shodan_ip = i[0] 132 | country = i[1] 133 | shodan_org = "No Shodan Data Found" 134 | shodan_os = "No Shodan Data Found" 135 | shodan_data = "--No Shodan Data Found" 136 | try: 137 | html = """ 138 |

IP: 139 | """ + shodan_ip + """ 140 |

141 |

Country: 142 | """ + country + """ 143 |

144 |

Organization: 145 | """ + shodan_org + """ 146 |

147 |

OS: 148 | """ + shodan_os + """ 149 |

150 |

""" + str(shodan_data)[2:-2] + """

151 | """ 152 | except TypeError: 153 | html = """ 154 |

IP: 155 | """ + "none" + """ 156 |

157 |

Country: 158 | """ + "none" + """ 159 |

160 |

Organization: 161 | """ + "none" + """ 162 |

163 |

OS: 164 | """ + "none" + """ 165 |

166 | 167 | """ 168 | iframe = folium.IFrame(html=str(html), width=300, height=300) 169 | popups.append(iframe) 170 | time.sleep(0.75) 171 | 172 | m = folium.Map(location=[0, 0], tiles='cartodbdark_matter', zoom_start=2) 173 | m.add_child(MarkerCluster(locations=locations, popups=popups, icons=color)) 174 | m.save(outfile) 175 | 176 | 177 | def _blacklist(suspects): 178 | global compromise 179 | for host in set(suspects): 180 | if host in set(biglist): 181 | compromise.append(host) 182 | 183 | 184 | def _initialize(): 185 | global external_ips, internal_ips, country_array, state_array, city_array, \ 186 | latitude_array, longitude_array, location_array, hosts, ports, geodata, \ 187 | lat, long, color, INBYTES, OUTBYTES, DATA, BYTES, md, oBL, CIA, noDNS, TITLES, \ 188 | compromise, daily_in, daily_out, message, eT 189 | message = [] 190 | daily_in = [] 191 | daily_out = [] 192 | ports = [] 193 | internal_ips = [] 194 | country_array = [] 195 | state_array = [] 196 | external_ips = [] 197 | geodata = [] 198 | lat = [] 199 | long = [] 200 | hosts = [] 201 | color = [] 202 | INBYTES = [] 203 | OUTBYTES = [] 204 | DATA = [] 205 | TITLES = [] 206 | compromise = [] 207 | 208 | directory = '.' 209 | files = sorted([f for f in os.listdir(directory) if f.startswith('netflowData-') and f.endswith('.csv')]) 210 | last24 = files[0:1] 211 | last72 = files[0:3] 212 | lastWeek = files[0:8] 213 | last30 = files[0:31] 214 | 215 | 216 | def _lastn(infiles, output): 217 | for item in infiles: 218 | _csvparse(item, home) 219 | _blacklist(set(internal_ips + external_ips)) 220 | _geolocate(compromise) 221 | _folium(outfile=output) 222 | _initialize() 223 | 224 | _initialize() 225 | _lastn(last24, output="last24.html") 226 | _lastn(last72, "last72.html") 227 | _lastn(lastWeek, "lastWeek.html") 228 | _lastn(last30, "lastMonth.html") 229 | print("time elapsed: {:.2f}s".format(time.time() - start_time)) 230 | --------------------------------------------------------------------------------