iruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +70 -0
- data/Rakefile +13 -0
- data/Ruby Notebook.ipynb +180 -0
- data/attic/output/html.rb +217 -0
- data/bin/iruby +6 -0
- data/iruby.gemspec +26 -0
- data/lib/iruby.rb +11 -0
- data/lib/iruby/command.rb +103 -0
- data/lib/iruby/completer.rb +19 -0
- data/lib/iruby/display_hook.rb +29 -0
- data/lib/iruby/kernel.rb +193 -0
- data/lib/iruby/out_stream.rb +93 -0
- data/lib/iruby/session.rb +300 -0
- data/lib/iruby/version.rb +3 -0
- data/static/base/images/favicon.ico +0 -0
- data/static/base/images/ipynblogo.png +0 -0
- data/static/base/images/src/ipynblogo.svg +768 -0
- data/static/base/images/src/ruby.svg +948 -0
- data/static/custom/custom.css +40 -0
- data/static/custom/custom.js +2 -0
- data/test/helper.rb +5 -0
- data/test/html_test.rb +17 -0
- data/test/output_maps_test.rb +110 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 39e9bb6cc42415e3ab312341fd04724112d2952a
|
4
|
+
data.tar.gz: 7ffcf1e9550a71da16c554dada617e81e4fa7a41
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 699ae86c55615f002265cb48b9b53463c9ce228aff076a296b2f338a74423799b3781cdada02ad848b13ad307945da3d46be78e8cbc1274280836d4acbc1c08a
|
7
|
+
data.tar.gz: 34aa0376810e9e1a1f8f0f6d9dc68c10bd2d714ee690966dff6cd224ca6b3ab5068aee3009324aba7b566a2a7c8114223c2fafe9ba4e36658b3056f6634ee9db
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Damián Silvani
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# IRuby
|
2
|
+
|
3
|
+
This is a Ruby kernel for IPython.
|
4
|
+
|
5
|
+
It adds a special `iruby_profile` command for staging some customization
|
6
|
+
that enables the Ruby kernel by default, and sets syntax-highlighting in the notebook
|
7
|
+
to Ruby mode.
|
8
|
+
|
9
|
+
### Usage
|
10
|
+
|
11
|
+
Clone this repository and run `bin/iruby_profile` to create the profile, then
|
12
|
+
use IPython as usual:
|
13
|
+
|
14
|
+
```bash
|
15
|
+
git clone git://github.com/minrk/iruby
|
16
|
+
cd iruby
|
17
|
+
# build and install IRuby
|
18
|
+
gem build iruby.gemspec
|
19
|
+
$ gem install iruby-*.gem
|
20
|
+
# Create an IPython profile with default config
|
21
|
+
$ iruby_profile --create
|
22
|
+
$ ipython notebook --profile=ruby
|
23
|
+
```
|
24
|
+
|
25
|
+
|
26
|
+
## Background
|
27
|
+
|
28
|
+
### Building an in-browser REPL for Ruby (IRuby)
|
29
|
+
|
30
|
+
Hey, I'm Josh Adams. I'm a partner and CTO at isotope|eleven. We alo host
|
31
|
+
Birmingham, AL's Open Source Software meetup - BOSS.
|
32
|
+
|
33
|
+
At one of these sessions in early 2012, Tom Brander did a presentation and used
|
34
|
+
IPython in his browser to manage it (there was much code and it was executed
|
35
|
+
live). This was the first time I'd seen IPython in the browser where it
|
36
|
+
actually worked like it was supposed to, and I was extremely impressed.
|
37
|
+
|
38
|
+
If you've not seen IPython, it looks like this <* Insert Screenshot Here *> in
|
39
|
+
its web-browser mode. It also manages a lot of console-basd REPLs.
|
40
|
+
|
41
|
+
Anyway, it has notebooks that you can save out to execute later, and you can
|
42
|
+
pass them around as little code snippets for other people to check out. It's
|
43
|
+
very impressive.
|
44
|
+
|
45
|
+
But I'm primarily a Rubyist, and I'm happy that way :) I couldn't sit by while
|
46
|
+
Python had this awesome tool that we lacked. I looked around for a bit, and
|
47
|
+
there was nothing like IPython in our ecosystem. There were, however, quite a
|
48
|
+
few people asking about it. So I figured I'd do something about it.
|
49
|
+
|
50
|
+
#### The Architecture
|
51
|
+
|
52
|
+
So the IPython guys did a great job explaining their core architecture, both in
|
53
|
+
words and in pared-down code, in a blog post they wrote concerning it. In
|
54
|
+
general, it works like this <* Diagram *>
|
55
|
+
|
56
|
+
There's a kernel that runs in the background and gets connected to by a
|
57
|
+
frontend. They communicate using zeromq, and they send json formatted messages
|
58
|
+
back and forth. These messages are in a very well defined structure. Anyway,
|
59
|
+
this way the frontend of the repl is disconnected from the environment that's
|
60
|
+
running it.
|
61
|
+
|
62
|
+
So the code repository they linked to in their blog post included the kernel and
|
63
|
+
the frontend as small-ish python files - around 300 and 200 lines respectively.
|
64
|
+
We had a hack weekend at isotope|eleven where myself and Robby Clements got
|
65
|
+
together and (when we weren't playing Counterstrike1.6) did the closest thing to
|
66
|
+
a straight port that we could swing. Within about 2 hours of work, we had a
|
67
|
+
working proof of concept that was primarily a 1 to 1 port.
|
68
|
+
|
69
|
+
The next move was to build the web frontend. This just consists of a websocket
|
70
|
+
server and a fairly basic frontend webpage.
|
data/Rakefile
ADDED
data/Ruby Notebook.ipynb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
{
|
2
|
+
"metadata": {
|
3
|
+
"name": ""
|
4
|
+
},
|
5
|
+
"nbformat": 3,
|
6
|
+
"nbformat_minor": 0,
|
7
|
+
"worksheets": [
|
8
|
+
{
|
9
|
+
"cells": [
|
10
|
+
{
|
11
|
+
"cell_type": "code",
|
12
|
+
"collapsed": false,
|
13
|
+
"input": [
|
14
|
+
"puts \"hi\"\n",
|
15
|
+
"raise \"zofe\""
|
16
|
+
],
|
17
|
+
"language": "python",
|
18
|
+
"metadata": {},
|
19
|
+
"outputs": [
|
20
|
+
{
|
21
|
+
"output_type": "stream",
|
22
|
+
"stream": "stdout",
|
23
|
+
"text": [
|
24
|
+
"hi\n"
|
25
|
+
]
|
26
|
+
},
|
27
|
+
{
|
28
|
+
"ename": "RuntimeError",
|
29
|
+
"evalue": "zofe",
|
30
|
+
"output_type": "pyerr",
|
31
|
+
"traceback": [
|
32
|
+
"\u001b[31mRuntimeError\u001b[0m: zofe",
|
33
|
+
"\u001b[37m<main>:1:in `<main>'\u001b[0m",
|
34
|
+
"\u001b[37m/home/minad/code/git/iruby/lib/iruby/kernel.rb:116:in `eval'\u001b[0m",
|
35
|
+
"\u001b[37m/home/minad/code/git/iruby/lib/iruby/kernel.rb:116:in `execute_request'\u001b[0m",
|
36
|
+
"\u001b[37m/home/minad/code/git/iruby/lib/iruby/kernel.rb:176:in `start'\u001b[0m",
|
37
|
+
"\u001b[37m/home/minad/code/git/iruby/lib/iruby/command.rb:60:in `run_kernel'\u001b[0m",
|
38
|
+
"\u001b[37m/home/minad/code/git/iruby/lib/iruby/command.rb:15:in `run'\u001b[0m",
|
39
|
+
"\u001b[37m/home/minad/code/git/iruby/bin/iruby:6:in `<main>'\u001b[0m"
|
40
|
+
]
|
41
|
+
}
|
42
|
+
],
|
43
|
+
"prompt_number": 3
|
44
|
+
},
|
45
|
+
{
|
46
|
+
"cell_type": "markdown",
|
47
|
+
"metadata": {},
|
48
|
+
"source": [
|
49
|
+
"# This is an IPython notebook, backed by a ruby kernel.\n",
|
50
|
+
"\n",
|
51
|
+
"I wrote a kernel in Ruby that adheres to the [IPython](http://ipython.org/) messaging protocol. Then I modified IPython, with the help of minrk from #ipython, to instantiate my Ruby Kernel instead of its own Python kernel.\n",
|
52
|
+
"\n",
|
53
|
+
"The IPython KernelManager initializes the RubyKernel with popen, and from that point communication occurs over ZeroMQ, exactly as in IPython's kernel.\n",
|
54
|
+
"\n",
|
55
|
+
"Once that was done, it's easy to execute ruby against that kernel right in the browser."
|
56
|
+
]
|
57
|
+
},
|
58
|
+
{
|
59
|
+
"cell_type": "code",
|
60
|
+
"collapsed": false,
|
61
|
+
"input": [
|
62
|
+
"class Neat\n",
|
63
|
+
" def eh?\n",
|
64
|
+
" \"hell yes it is.\"\n",
|
65
|
+
" end\n",
|
66
|
+
"end\n",
|
67
|
+
"\n",
|
68
|
+
"Neat.new.eh?\n",
|
69
|
+
"\n",
|
70
|
+
"puts \"hi\""
|
71
|
+
],
|
72
|
+
"language": "python",
|
73
|
+
"metadata": {},
|
74
|
+
"outputs": [
|
75
|
+
{
|
76
|
+
"output_type": "stream",
|
77
|
+
"stream": "stdout",
|
78
|
+
"text": [
|
79
|
+
"hi\n"
|
80
|
+
]
|
81
|
+
}
|
82
|
+
],
|
83
|
+
"prompt_number": 2
|
84
|
+
},
|
85
|
+
{
|
86
|
+
"cell_type": "markdown",
|
87
|
+
"metadata": {},
|
88
|
+
"source": [
|
89
|
+
"## What does this give you?\n",
|
90
|
+
"\n",
|
91
|
+
"This gives us a very fancy web notebook interface for Ruby. It's a very good tool for programming presentations. Tom Brander's presentation to the BOSS meetup group made me realize exactly how far IPython had come since I last saw it, and I knew I wanted to have it for ruby.\n",
|
92
|
+
"\n",
|
93
|
+
"It's basically an in-browser REPL loop, with some extra goodies."
|
94
|
+
]
|
95
|
+
},
|
96
|
+
{
|
97
|
+
"cell_type": "code",
|
98
|
+
"collapsed": false,
|
99
|
+
"input": [
|
100
|
+
"class ThatsPrettyCool\n",
|
101
|
+
" def how_does_it_work?\n",
|
102
|
+
" puts \"I'm glad you asked!\"\n",
|
103
|
+
" :see_below\n",
|
104
|
+
" end\n",
|
105
|
+
"end\n",
|
106
|
+
"\n",
|
107
|
+
"ThatsPrettyCool.new.how_does_it_work?"
|
108
|
+
],
|
109
|
+
"language": "python",
|
110
|
+
"metadata": {},
|
111
|
+
"outputs": [
|
112
|
+
{
|
113
|
+
"output_type": "stream",
|
114
|
+
"stream": "stdout",
|
115
|
+
"text": [
|
116
|
+
"I'm glad you asked!\n"
|
117
|
+
]
|
118
|
+
},
|
119
|
+
{
|
120
|
+
"metadata": {},
|
121
|
+
"output_type": "pyout",
|
122
|
+
"prompt_number": 4,
|
123
|
+
"text": [
|
124
|
+
"see_below"
|
125
|
+
]
|
126
|
+
}
|
127
|
+
],
|
128
|
+
"prompt_number": 4
|
129
|
+
},
|
130
|
+
{
|
131
|
+
"cell_type": "markdown",
|
132
|
+
"metadata": {},
|
133
|
+
"source": [
|
134
|
+
"### Here's how it works\n",
|
135
|
+
"\n",
|
136
|
+
"Each notebook has its own kernel. When you open a notebook in the web interface, a kernel is started in the background by the IPython webserver. A websocket is then used to connect the frontend directly to the kernel, more or less, and they pass messages back and forth.\n",
|
137
|
+
"\n",
|
138
|
+
"All of the messages are in a JSON format. The actual information passed over the wire between the web server and the kernel is a little different, but it basically just includes some session/auth information, and then the components of the message serialized, too be unserialized on the Kernel into the full json message again.\n",
|
139
|
+
"\n",
|
140
|
+
"### What does a message look like?\n",
|
141
|
+
"\n",
|
142
|
+
"A given JSON message looks like this:\n",
|
143
|
+
"\n",
|
144
|
+
" {\"header\"=>\n",
|
145
|
+
" {\"username\"=>\"username\",\n",
|
146
|
+
" \"msg_id\"=>\"38C1E3299BB14F7C99D95504CEAE4856\",\n",
|
147
|
+
" \"msg_type\"=>\"execute_request\",\n",
|
148
|
+
" \"session\"=>\"BAC5EA31BC6F4C1D888DD5F8C46B6F9D\"},\n",
|
149
|
+
" \"msg_id\"=>\"msg_id\",\n",
|
150
|
+
" \"msg_type\"=>\"msg_type\",\n",
|
151
|
+
" \"parent_header\"=>{},\n",
|
152
|
+
" \"content\"=>\n",
|
153
|
+
" {\"user_variables\"=>[],\n",
|
154
|
+
" \"allow_stdin\"=>false,\n",
|
155
|
+
" \"code\"=>\n",
|
156
|
+
" \"class ThatsPrettyCool\\n def how_does_it_work?\\n puts \\\"I'm glad you asked!\\\"\\n end\\nend\\n\\nThatsPrettyCool.new.how_does_it_work?\",\n",
|
157
|
+
" \"silent\"=>false,\n",
|
158
|
+
" \"user_expressions\"=>{}},\n",
|
159
|
+
" \"buffers\"=>[]}"
|
160
|
+
]
|
161
|
+
},
|
162
|
+
{
|
163
|
+
"cell_type": "markdown",
|
164
|
+
"metadata": {},
|
165
|
+
"source": [
|
166
|
+
"### What remains to be done for this to be 'complete'?\n",
|
167
|
+
"\n",
|
168
|
+
"- Right now, I'm not passing back the 'return value of the last expression' like you'd expect a proper REPL to do. I'm not exactly sure how to get the analog of python's sys.displayhook working, but I welcome anyone to teach me how it's done :) I can probably figure it out from the pry source.\n",
|
169
|
+
"- backtraces are a little bit wonky right now and don't give you the proper backtrace into the executed code, but rather into the Kernel's source where the eval loop runs...not very helpful.\n",
|
170
|
+
"- There aren't any tests.\n",
|
171
|
+
"- It would be nice to support the fancy IPython graph and html-table gooies, I'll look into that.\n",
|
172
|
+
"- IPython supports tab-completion in the browser REPL, but my kernel doesn't know how to respond to those messages.\n",
|
173
|
+
"- There are some other message types I've not yet implemented, but I don't really see the problem at present :)"
|
174
|
+
]
|
175
|
+
}
|
176
|
+
],
|
177
|
+
"metadata": {}
|
178
|
+
}
|
179
|
+
]
|
180
|
+
}
|
@@ -0,0 +1,217 @@
|
|
1
|
+
module IRuby
|
2
|
+
module Output
|
3
|
+
module HTML
|
4
|
+
def self.table(data)
|
5
|
+
#
|
6
|
+
# data = {a: 1, b:2}
|
7
|
+
|
8
|
+
if data.respond_to?(:keys)
|
9
|
+
d = data
|
10
|
+
else
|
11
|
+
d = data
|
12
|
+
end
|
13
|
+
|
14
|
+
r = "<table>"
|
15
|
+
if d.respond_to?(:keys) # hash
|
16
|
+
columns = [0,1]
|
17
|
+
elsif d.first.respond_to?(:keys) # array of hashes
|
18
|
+
columns = d.first.keys
|
19
|
+
r << "<tr>#{columns.map{|c| "<th>#{c.to_s}</th>"}.join}</tr>"
|
20
|
+
else # array
|
21
|
+
columns = (0 .. d.first.length)
|
22
|
+
end
|
23
|
+
d.each{|row|
|
24
|
+
r << "<tr>"
|
25
|
+
columns.each{|column|
|
26
|
+
r << "<td>#{row[column].to_s}</td>"
|
27
|
+
}
|
28
|
+
r << "</tr>"
|
29
|
+
}
|
30
|
+
r << "</table>"
|
31
|
+
r
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.image(image)
|
35
|
+
data = image.respond_to?(:to_blob) ? image.to_blob : image
|
36
|
+
"<img src='data:image/png;base64,#{Base64.encode64(data)}'>"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.chart_pie(o)
|
40
|
+
data=o.delete(:data)
|
41
|
+
title=o.delete(:title)
|
42
|
+
size=o.delete(:size) || 300
|
43
|
+
g = Gruff::Pie.new(size)
|
44
|
+
g.title = title if title
|
45
|
+
data.each do |data|
|
46
|
+
label = data[0].strip
|
47
|
+
label = "?" if label == ''
|
48
|
+
g.data(label, data[1])
|
49
|
+
end
|
50
|
+
image(g.to_blob)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.chart_bar(o)
|
54
|
+
data=o.delete(:data)
|
55
|
+
title=o.delete(:title)
|
56
|
+
size=o.delete(:size) || 300
|
57
|
+
|
58
|
+
klass = o.delete(:stacked) ? Gruff::StackedBar : Gruff::Bar
|
59
|
+
g = klass.new(size)
|
60
|
+
|
61
|
+
if labels=o.delete(:labels)
|
62
|
+
if ! labels.respond_to?(:keys)
|
63
|
+
labels = Hash[labels.map.with_index{|v,k| [k,v]}]
|
64
|
+
end
|
65
|
+
g.labels = labels
|
66
|
+
end
|
67
|
+
|
68
|
+
g.title = title if title
|
69
|
+
data.each do |data|
|
70
|
+
g.data(data[0], data[1])
|
71
|
+
end
|
72
|
+
image(g.to_blob)
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
module Gmaps
|
77
|
+
def self.points2latlng(points)
|
78
|
+
"[" + points.reject{|p| not p.lat or not p.lon}.map{|p|
|
79
|
+
icon_url = nil
|
80
|
+
icon_url = p.icon_url if p.respond_to?(:icon_url)
|
81
|
+
icon_url = "http://www.google.com/intl/en_us/mapfiles/ms/micons/#{p.icon}-dot.png"if p.respond_to?(:icon)
|
82
|
+
"{" + [
|
83
|
+
"location: new google.maps.LatLng(#{p.lat.to_f}, #{p.lon.to_f})",
|
84
|
+
p.respond_to?(:weight) && p.weight && "weight: #{p.weight.to_i} ",
|
85
|
+
p.respond_to?(:label) && "label: #{p.label.to_s.to_json}",
|
86
|
+
p.respond_to?(:z_index) && "z_index: #{p.z_index.to_json}",
|
87
|
+
icon_url && "icon_url: #{icon_url.to_json}",
|
88
|
+
].reject{|x| ! x}
|
89
|
+
.join(",") + "}"
|
90
|
+
}.join(',') + "]"
|
91
|
+
end
|
92
|
+
def self.base_map(o)
|
93
|
+
zoom = o.delete(:zoom)
|
94
|
+
center = o.delete(:center)
|
95
|
+
map_type = o.delete(:map_type)
|
96
|
+
width = o.delete(:width) || "500px"
|
97
|
+
height = o.delete(:height) || "500px"
|
98
|
+
r = <<E
|
99
|
+
<div id='map-canvas' style='width: #{width}; height: #{height};'></div>
|
100
|
+
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&libraries=visualization&callback=initialize"></script>
|
101
|
+
|
102
|
+
<script>
|
103
|
+
function initialize() {
|
104
|
+
var latlngbounds = new google.maps.LatLngBounds();
|
105
|
+
var zoom = #{zoom.to_json};
|
106
|
+
var center = #{center.to_json};
|
107
|
+
var map_type = #{map_type.to_json} || google.maps.MapTypeId.SATELLITE;
|
108
|
+
|
109
|
+
var mapOptions = {
|
110
|
+
mapTypeId: map_type
|
111
|
+
};
|
112
|
+
|
113
|
+
if (zoom){
|
114
|
+
mapOptions.zoom = zoom
|
115
|
+
}
|
116
|
+
if (center){
|
117
|
+
mapOptions.center = new google.maps.LatLng(center.lat, center.lon)
|
118
|
+
}
|
119
|
+
|
120
|
+
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
|
121
|
+
|
122
|
+
#{yield}
|
123
|
+
}
|
124
|
+
</script>
|
125
|
+
E
|
126
|
+
r
|
127
|
+
|
128
|
+
end
|
129
|
+
def self.heatmap(o)
|
130
|
+
data = o.delete(:points)
|
131
|
+
points = points2latlng(data)
|
132
|
+
radius = o.delete(:radius)
|
133
|
+
raise "Missing :points parameter" if not data
|
134
|
+
base_map(o){<<E
|
135
|
+
var points = #{points};
|
136
|
+
if (! zoom){
|
137
|
+
for (var i = 0; i < points.length; i++) {
|
138
|
+
latlngbounds.extend(points[i].location);
|
139
|
+
}
|
140
|
+
map.fitBounds(latlngbounds);
|
141
|
+
}
|
142
|
+
|
143
|
+
|
144
|
+
var pointArray = new google.maps.MVCArray(points);
|
145
|
+
|
146
|
+
heatmap = new google.maps.visualization.HeatmapLayer({
|
147
|
+
radius: #{radius.to_json} || 10,
|
148
|
+
data: pointArray
|
149
|
+
});
|
150
|
+
|
151
|
+
heatmap.setMap(map);
|
152
|
+
E
|
153
|
+
}
|
154
|
+
|
155
|
+
end
|
156
|
+
def self.markers(o)
|
157
|
+
data = o.delete(:points)
|
158
|
+
points = points2latlng(data)
|
159
|
+
radius = o.delete(:radius)
|
160
|
+
raise "Missing :points parameter" if not data
|
161
|
+
base_map(o){<<E
|
162
|
+
var points = #{points};
|
163
|
+
if (! zoom){
|
164
|
+
for (var i = 0; i < points.length; i++) {
|
165
|
+
latlngbounds.extend(points[i].location);
|
166
|
+
}
|
167
|
+
map.fitBounds(latlngbounds);
|
168
|
+
}
|
169
|
+
|
170
|
+
for (var i=0; i<points.length; i++){
|
171
|
+
var marker = new google.maps.Marker({
|
172
|
+
position: points[i].location,
|
173
|
+
map: map,
|
174
|
+
icon: points[i].icon_url,
|
175
|
+
zIndex: points[i].z_index,
|
176
|
+
title: points[i].label
|
177
|
+
});
|
178
|
+
}
|
179
|
+
|
180
|
+
E
|
181
|
+
}
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
185
|
+
#stolen from https://github.com/Bantik/heatmap/blob/master/lib/heatmap.rb
|
186
|
+
module WordCloud
|
187
|
+
|
188
|
+
def self.wordcloud(histogram={})
|
189
|
+
html = %{<div class="wordcloud">}
|
190
|
+
histogram.keys.sort{|a,b| histogram[a] <=> histogram[b]}.reverse.each do |k|
|
191
|
+
next if histogram[k] < 1
|
192
|
+
_max = histogram_max(histogram) * 2
|
193
|
+
_size = element_size(histogram, k)
|
194
|
+
_heat = element_heat(histogram[k], _max)
|
195
|
+
html << %{
|
196
|
+
<span class="wordcloud_element" style="color: ##{_heat}#{_heat}#{_heat}; font-size: #{_size}px;">#{k}</span>
|
197
|
+
}
|
198
|
+
end
|
199
|
+
html << %{<br style="clear: both;" /></div>}
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.histogram_max(histogram)
|
203
|
+
histogram.map{|k,v| histogram[k]}.max
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.element_size(histogram, key)
|
207
|
+
(((histogram[key] / histogram.map{|k,v| histogram[k]}.reduce(&:+).to_f) * 100) + 5).to_i
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.element_heat(val, max)
|
211
|
+
sprintf("%02x" % (200 - ((200.0 / max) * val)))
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|