iruby 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|