geo_pattern 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ test.rb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in geopatterns.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jason Long
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.
@@ -0,0 +1,123 @@
1
+ # GeoPattern
2
+
3
+ Generate beautiful tilng SVG patterns from a string. The string is converted into a SHA and a color and pattern are determined based on the values in the hash. The color is determined by shifting the hue and saturation from a default (or passed in) base color. One of 16 patterns is used (or you can specify one) and the sizing of the pattern elements is also determined by the hash values.
4
+
5
+ You can use the generated pattern as the `background-image` for a container. Using the `base64` representation of the pattern still results in SVG rendering, so it looks great on retina displays.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'geo_pattern'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install geo_pattern
20
+
21
+ ## Usage
22
+
23
+ Make a new pattern:
24
+
25
+ pattern = GeoPattern.generate("Mastering Markdown")
26
+
27
+ To specify a base background color:
28
+
29
+ pattern = GeoPattern.generate("Mastering Markdown", {:base_color => "#fc0"})
30
+
31
+ To use a specific [pattern generator](#available-patterns):
32
+
33
+ pattern = GeoPattern.generate("Mastering Markdown", {:generator => "sine_waves"})
34
+
35
+ Get the SVG string:
36
+
37
+ puts pattern.svg_string
38
+ # => <svg xmlns="http://www.w3.org/2000/svg" ...
39
+
40
+ Get the Base64 encoded string:
41
+
42
+ puts pattern.base64_string
43
+ # => PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC...
44
+
45
+ You can then use this string to set the background:
46
+
47
+ <div style="background-image:<%=pattern.base64_string%>"></div>
48
+
49
+
50
+ ## Available patterns
51
+
52
+ ### octogons
53
+
54
+ ![](http://jasonlong.github.io/geo_pattern/examples/octogons.png)
55
+
56
+ ### overlapping_circles
57
+
58
+ ![](http://jasonlong.github.io/geo_pattern/examples/overlapping_circles.png)
59
+
60
+ ### plus_signs
61
+
62
+ ![](http://jasonlong.github.io/geo_pattern/examples/plus_signs.png)
63
+
64
+ ### xes
65
+
66
+ ![](http://jasonlong.github.io/geo_pattern/examples/xes.png)
67
+
68
+ ### sine_waves
69
+
70
+ ![](http://jasonlong.github.io/geo_pattern/examples/sine_waves.png)
71
+
72
+ ### hexagons
73
+
74
+ ![](http://jasonlong.github.io/geo_pattern/examples/hexagons.png)
75
+
76
+ ### overlapping_rings
77
+
78
+ ![](http://jasonlong.github.io/geo_pattern/examples/overlapping_rings.png)
79
+
80
+ ### plaid
81
+
82
+ ![](http://jasonlong.github.io/geo_pattern/examples/plaid.png)
83
+
84
+ ### triangles
85
+
86
+ ![](http://jasonlong.github.io/geo_pattern/examples/triangles.png)
87
+
88
+ ### triangles_rotated
89
+
90
+ ![](http://jasonlong.github.io/geo_pattern/examples/triangles_rotated.png)
91
+
92
+ ### squares
93
+
94
+ ![](http://jasonlong.github.io/geo_pattern/examples/squares.png)
95
+
96
+ ### nested_squares
97
+
98
+ ![](http://jasonlong.github.io/geo_pattern/examples/nested_squares.png)
99
+
100
+ ### mosaic_squares
101
+
102
+ ![](http://jasonlong.github.io/geo_pattern/examples/mosaic_squares.png)
103
+
104
+ ### concentric_circles
105
+
106
+ ![](http://jasonlong.github.io/geo_pattern/examples/concentric_circles.png)
107
+
108
+ ### diamonds
109
+
110
+ ![](http://jasonlong.github.io/geo_pattern/examples/diamonds.png)
111
+
112
+ ### tessellation
113
+
114
+ ![](http://jasonlong.github.io/geo_pattern/examples/tessellation.png)
115
+
116
+
117
+ ## Contributing
118
+
119
+ 1. Fork it ( http://github.com/jasonlong/geo_patterns/fork )
120
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
121
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
122
+ 4. Push to the branch (`git push origin my-new-feature`)
123
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'geo_pattern/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "geo_pattern"
8
+ spec.version = GeoPattern::VERSION
9
+ spec.authors = ["Jason Long"]
10
+ spec.email = ["jlong@github.com"]
11
+ spec.summary = %q{Generate SVG beautiful patterns}
12
+ spec.description = %q{Generate SVG beautiful patterns}
13
+ spec.homepage = "https://github.com/jasonlong/geopatterns"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "color", "~> 1.5"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.5"
24
+ spec.add_development_dependency "rake"
25
+ end
@@ -0,0 +1,9 @@
1
+ require "geo_pattern/version"
2
+ require "geo_pattern/svg"
3
+ require "geo_pattern/pattern"
4
+
5
+ module GeoPattern
6
+ def self.generate(string=Time.now.to_s, opts={})
7
+ GeoPattern::Pattern.new(string, opts)
8
+ end
9
+ end
@@ -0,0 +1,934 @@
1
+ require 'base64'
2
+ require 'digest/sha1'
3
+ require 'color'
4
+
5
+ module GeoPattern
6
+ class Pattern
7
+ DEFAULTS = {
8
+ :base_color => '#933c3c'
9
+ }
10
+
11
+ PATTERNS = [
12
+ :octogons,
13
+ :overlapping_circles,
14
+ :plus_signs,
15
+ :xes,
16
+ :sine_waves,
17
+ :hexagons,
18
+ :overlapping_rings,
19
+ :plaid,
20
+ :triangles,
21
+ :squares,
22
+ :concentric_circles,
23
+ :diamonds,
24
+ :tessellation,
25
+ :nested_squares,
26
+ :mosaic_squares,
27
+ :triangles_rotated,
28
+ :chevrons
29
+ ]
30
+
31
+ FILL_COLOR_DARK = "#222"
32
+ FILL_COLOR_LIGHT = "#ddd"
33
+ STROKE_COLOR = "#000"
34
+ STROKE_OPACITY = 0.02
35
+ OPACITY_MIN = 0.02
36
+ OPACITY_MAX = 0.15
37
+
38
+ attr_reader :opts, :hash, :svg
39
+
40
+ def initialize(string, opts={})
41
+ @opts = DEFAULTS.merge(opts)
42
+ @hash = Digest::SHA1.hexdigest string
43
+ @svg = SVG.new
44
+ generate_background
45
+ generate_pattern
46
+ end
47
+
48
+ def svg_string
49
+ svg.to_s
50
+ end
51
+
52
+ def to_s
53
+ svg_string
54
+ end
55
+
56
+ def base64_string
57
+ Base64.strict_encode64(svg.to_s)
58
+ end
59
+
60
+ def generate_background
61
+ hue_offset = map(hex_val(14, 3), 0, 4095, 0, 359)
62
+ sat_offset = hex_val(17, 1)
63
+ base_color = Color::RGB.from_html(opts[:base_color]).to_hsl
64
+ base_color.hue = base_color.hue - hue_offset;
65
+
66
+ if (sat_offset % 2 == 0)
67
+ base_color.saturation = base_color.saturation + sat_offset
68
+ else
69
+ base_color.saturation = base_color.saturation - sat_offset
70
+ end
71
+ rgb = base_color.to_rgb
72
+ r = (rgb.r * 255).round
73
+ g = (rgb.g * 255).round
74
+ b = (rgb.b * 255).round
75
+ svg.rect(0, 0, "100%", "100%", {"fill" => "rgb(#{r}, #{g}, #{b})"})
76
+ end
77
+
78
+ def generate_pattern
79
+ if opts[:generator]
80
+ begin
81
+ send("geo_#{opts[:generator]}")
82
+ rescue
83
+ abort("Error: the requested generator is invalid.")
84
+ end
85
+ else
86
+ pattern = hex_val(20, 1)
87
+ send("geo_#{PATTERNS[pattern]}")
88
+ end
89
+ end
90
+
91
+ def geo_hexagons
92
+ scale = hex_val(0, 1)
93
+ side_length = map(scale, 0, 15, 8, 60)
94
+ hex_height = side_length * Math.sqrt(3)
95
+ hex_width = side_length * 2
96
+ hex = build_hexagon_shape(side_length)
97
+
98
+ svg.set_width((hex_width * 3) + (side_length * 3))
99
+ svg.set_height(hex_height * 6)
100
+
101
+ i = 0
102
+ for y in 0..5
103
+ for x in 0..5
104
+ val = hex_val(i, 1)
105
+ dy = x % 2 == 0 ? y*hex_height : y*hex_height + hex_height/2
106
+ opacity = opacity(val)
107
+ fill = fill_color(val)
108
+
109
+ styles = {
110
+ "fill" => fill,
111
+ "fill-opacity" => opacity,
112
+ "stroke" => STROKE_COLOR,
113
+ "stroke-opacity" => STROKE_OPACITY
114
+ }
115
+
116
+ svg.polyline(hex, styles.merge({"transform" => "translate(#{x*side_length*1.5 - hex_width/2}, #{dy - hex_height/2})"}))
117
+
118
+ # Add an extra one at top-right, for tiling.
119
+ if (x == 0)
120
+ svg.polyline(hex, styles.merge({"transform" => "translate(#{6*side_length*1.5 - hex_width/2}, #{dy - hex_height/2})"}))
121
+ end
122
+
123
+ # Add an extra row at the end that matches the first row, for tiling.
124
+ if (y == 0)
125
+ dy = x % 2 == 0 ? 6*hex_height : 6*hex_height + hex_height/2;
126
+ svg.polyline(hex, styles.merge({"transform" => "translate(#{x*side_length*1.5 - hex_width/2}, #{dy - hex_height/2})"}))
127
+ end
128
+
129
+ # Add an extra one at bottom-right, for tiling.
130
+ if (x == 0 && y == 0)
131
+ svg.polyline(hex, styles.merge({"transform" => "translate(#{6*side_length*1.5 - hex_width/2}, #{5*hex_height + hex_height/2})"}))
132
+ end
133
+ i += 1
134
+ end
135
+ end
136
+ end
137
+
138
+ def geo_sine_waves
139
+ period = map(hex_val(0, 1), 0, 15, 100, 400).floor
140
+ amplitude = map(hex_val(1, 1), 0, 15, 30, 100).floor
141
+ wave_width = map(hex_val(2, 1), 0, 15, 3, 30).floor
142
+
143
+ svg.set_width(period)
144
+ svg.set_height(wave_width * 36)
145
+
146
+ for i in 0..35
147
+ val = hex_val(i, 1)
148
+ opacity = opacity(val)
149
+ fill = fill_color(val)
150
+ x_offset = period / 4 * 0.7
151
+
152
+ styles = {
153
+ "fill" => "none",
154
+ "stroke" => fill,
155
+ "style" => {
156
+ "opacity" => opacity,
157
+ "stroke-width" => "#{wave_width}px"
158
+ }
159
+ }
160
+
161
+ str = "M0 "+amplitude.to_s+
162
+ " C "+x_offset.to_s+" 0, "+(period/2 - x_offset).to_s+" 0, "+(period/2).to_s+" "+amplitude.to_s+
163
+ " S "+(period-x_offset).to_s+" "+(amplitude*2).to_s+", "+period.to_s+" "+amplitude.to_s+
164
+ " S "+(period*1.5-x_offset).to_s+" 0, "+(period*1.5).to_s+", "+amplitude.to_s;
165
+
166
+ svg.path(str, styles.merge({"transform" => "translate(-#{period/4}, #{wave_width*i-amplitude*1.5})"}))
167
+ svg.path(str, styles.merge({"transform" => "translate(-#{period/4}, #{wave_width*i-amplitude*1.5 + wave_width*36})"}))
168
+ end
169
+ end
170
+
171
+ def geo_chevrons
172
+ chevron_width = map(hex_val(0, 1), 0, 15, 30, 80)
173
+ chevron_height = map(hex_val(0, 1), 0, 15, 30, 80)
174
+ chevron = build_chevron_shape(chevron_width, chevron_height)
175
+
176
+ svg.set_width(chevron_width * 6)
177
+ svg.set_height(chevron_height * 6 * 0.66)
178
+
179
+ i = 0
180
+ for y in 0..5
181
+ for x in 0..5
182
+ val = hex_val(i, 1)
183
+ opacity = opacity(val)
184
+ fill = fill_color(val)
185
+
186
+ styles = {
187
+ "stroke" => STROKE_COLOR,
188
+ "stroke-opacity" => STROKE_OPACITY,
189
+ "fill" => fill,
190
+ "fill-opacity" => opacity,
191
+ "stroke-width" => 1
192
+ }
193
+
194
+ svg.group(chevron, styles.merge({"transform" => "translate(#{x*chevron_width},#{y*chevron_height*0.66 - chevron_height/2})"}))
195
+
196
+ # Add an extra row at the end that matches the first row, for tiling.
197
+ if (y == 0)
198
+ svg.group(chevron, styles.merge({"transform" => "translate(#{x*chevron_width},#{6*chevron_height*0.66 - chevron_height/2})"}))
199
+ end
200
+ i += 1
201
+ end
202
+ end
203
+ end
204
+
205
+ def geo_plus_signs
206
+ square_size = map(hex_val(0, 1), 0, 15, 10, 25)
207
+ plus_size = square_size * 3
208
+ plus_shape = build_plus_shape(square_size)
209
+
210
+ svg.set_width(square_size * 12)
211
+ svg.set_height(square_size * 12)
212
+
213
+ i = 0
214
+ for y in 0..5
215
+ for x in 0..5
216
+ val = hex_val(i, 1)
217
+ opacity = opacity(val)
218
+ fill = fill_color(val)
219
+ dx = (y % 2 == 0) ? 0 : 1
220
+
221
+ styles = {
222
+ "fill" => fill,
223
+ "stroke" => STROKE_COLOR,
224
+ "stroke-opacity" => STROKE_OPACITY,
225
+ "style" => {
226
+ "fill-opacity" => opacity
227
+ }
228
+ }
229
+
230
+ svg.group(plus_shape, styles.merge({
231
+ "transform" => "translate(#{x*plus_size - x*square_size + dx*square_size - square_size},#{y*plus_size - y*square_size - plus_size/2})"}))
232
+
233
+ # Add an extra column on the right for tiling.
234
+ if (x == 0)
235
+ svg.group(plus_shape, styles.merge({
236
+ "transform" => "translate(#{4*plus_size - x*square_size + dx*square_size - square_size},#{y*plus_size - y*square_size - plus_size/2})"}))
237
+ end
238
+
239
+ # Add an extra row on the bottom that matches the first row, for tiling.
240
+ if (y == 0)
241
+ svg.group(plus_shape, styles.merge({
242
+ "transform" => "translate(#{x*plus_size - x*square_size + dx*square_size - square_size},#{4*plus_size - y*square_size - plus_size/2})"}))
243
+ end
244
+
245
+ # Add an extra one at top-right and bottom-right, for tiling.
246
+ if (x == 0 && y == 0)
247
+ svg.group(plus_shape, styles.merge({
248
+ "transform" => "translate(#{4*plus_size - x*square_size + dx*square_size - square_size},#{4*plus_size - y*square_size - plus_size/2})"}))
249
+ end
250
+ i += 1
251
+ end
252
+ end
253
+ end
254
+
255
+ def geo_xes
256
+ square_size = map(hex_val(0, 1), 0, 15, 10, 25)
257
+ x_shape = build_plus_shape(square_size) # rotated later
258
+ x_size = square_size * 3 * 0.943
259
+
260
+ svg.set_width(x_size * 3)
261
+ svg.set_height(x_size * 3)
262
+
263
+ i = 0
264
+ for y in 0..5
265
+ for x in 0..5
266
+ val = hex_val(i, 1)
267
+ opacity = opacity(val)
268
+ dy = x % 2 == 0 ? y*x_size - x_size*0.5 : y*x_size - x_size*0.5 + x_size/4
269
+ fill = fill_color(val)
270
+
271
+ styles = {
272
+ "fill" => fill,
273
+ "style" => {
274
+ "opacity" => opacity
275
+ }
276
+ }
277
+
278
+ svg.group(x_shape, styles.merge({
279
+ "transform" => "translate(#{x*x_size/2 - x_size/2},#{dy - y*x_size/2}) rotate(45, #{x_size/2}, #{x_size/2})"}))
280
+
281
+ # Add an extra column on the right for tiling.
282
+ if (x == 0)
283
+ svg.group(x_shape, styles.merge({
284
+ "transform" => "translate(#{6*x_size/2 - x_size/2},#{dy - y*x_size/2}) rotate(45, #{x_size/2}, #{x_size/2})"}))
285
+ end
286
+
287
+ # Add an extra row on the bottom that matches the first row, for tiling.
288
+ if (y == 0)
289
+ dy = x % 2 == 0 ? 6*x_size - x_size/2 : 6*x_size - x_size/2 + x_size/4;
290
+ svg.group(x_shape, styles.merge({
291
+ "transform" => "translate(#{x*x_size/2 - x_size/2},#{dy - 6*x_size/2}) rotate(45, #{x_size/2}, #{x_size/2})"}))
292
+ end
293
+
294
+ # These can hang off the bottom, so put a row at the top for tiling.
295
+ if (y == 5)
296
+ svg.group(x_shape, styles.merge({
297
+ "transform" => "translate(#{x*x_size/2 - x_size/2},#{dy - 11*x_size/2}) rotate(45, #{x_size/2}, #{x_size/2})"}))
298
+ end
299
+
300
+ # Add an extra one at top-right and bottom-right, for tiling.
301
+ if (x == 0 && y == 0)
302
+ svg.group(x_shape, styles.merge({
303
+ "transform" => "translate(#{6*x_size/2 - x_size/2},#{dy - 6*x_size/2}) rotate(45, #{x_size/2}, #{x_size/2})"}))
304
+ end
305
+ i += 1
306
+ end
307
+ end
308
+ end
309
+
310
+ def geo_overlapping_circles
311
+ scale = hex_val(0, 1)
312
+ diameter = map(scale, 0, 15, 25, 200)
313
+ radius = diameter/2;
314
+
315
+ svg.set_width(radius * 6)
316
+ svg.set_height(radius * 6)
317
+
318
+ i = 0
319
+ for y in 0..5
320
+ for x in 0..5
321
+ val = hex_val(i, 1)
322
+ opacity = opacity(val)
323
+ fill = fill_color(val)
324
+
325
+ styles = {
326
+ "fill" => fill,
327
+ "style" => {
328
+ "opacity" => opacity
329
+ }
330
+ }
331
+
332
+ svg.circle(x*radius, y*radius, radius, styles)
333
+
334
+ # Add an extra one at top-right, for tiling.
335
+ if (x == 0)
336
+ svg.circle(6*radius, y*radius, radius, styles)
337
+ end
338
+
339
+ # Add an extra row at the end that matches the first row, for tiling.
340
+ if (y == 0)
341
+ svg.circle(x*radius, 6*radius, radius, styles)
342
+ end
343
+
344
+ # Add an extra one at bottom-right, for tiling.
345
+ if (x == 0 and y == 0)
346
+ svg.circle(6*radius, 6*radius, radius, styles)
347
+ end
348
+ i += 1
349
+ end
350
+ end
351
+ end
352
+
353
+ def geo_octogons
354
+ square_size = map(hex_val(0, 1), 0, 15, 10, 60)
355
+ tile = build_octogon_shape(square_size)
356
+
357
+ svg.set_width(square_size * 6)
358
+ svg.set_height(square_size * 6)
359
+
360
+ i = 0
361
+ for y in 0..5
362
+ for x in 0..5
363
+ val = hex_val(i, 1)
364
+ opacity = opacity(val)
365
+ fill = fill_color(val)
366
+
367
+ svg.polyline(tile, {
368
+ "fill" => fill,
369
+ "fill-opacity" => opacity,
370
+ "stroke" => STROKE_COLOR,
371
+ "stroke-opacity" => STROKE_OPACITY,
372
+ "transform" => "translate(#{x*square_size}, #{y*square_size})"
373
+ })
374
+ i += 1
375
+ end
376
+ end
377
+ end
378
+
379
+ def geo_squares
380
+ square_size = map(hex_val(0, 1), 0, 15, 10, 60)
381
+
382
+ svg.set_width(square_size * 6)
383
+ svg.set_height(square_size * 6)
384
+
385
+ i = 0
386
+ for y in 0..5
387
+ for x in 0..5
388
+ val = hex_val(i, 1)
389
+ opacity = opacity(val)
390
+ fill = fill_color(val)
391
+
392
+ svg.rect(x*square_size, y*square_size, square_size, square_size, {
393
+ "fill" => fill,
394
+ "fill-opacity" => opacity,
395
+ "stroke" => STROKE_COLOR,
396
+ "stroke-opacity" => STROKE_OPACITY
397
+ })
398
+ i += 1
399
+ end
400
+ end
401
+ end
402
+
403
+ def geo_concentric_circles
404
+ scale = hex_val(0, 1)
405
+ ring_size = map(scale, 0, 15, 10, 60)
406
+ stroke_width = ring_size / 5
407
+
408
+ svg.set_width((ring_size + stroke_width) * 6)
409
+ svg.set_height((ring_size + stroke_width) * 6)
410
+
411
+ i = 0
412
+ for y in 0..5
413
+ for x in 0..5
414
+ val = hex_val(i, 1)
415
+ opacity = opacity(val)
416
+ fill = fill_color(val)
417
+
418
+ svg.circle(
419
+ x*ring_size + x*stroke_width + (ring_size + stroke_width)/2,
420
+ y*ring_size + y*stroke_width + (ring_size + stroke_width)/2,
421
+ ring_size/2, {
422
+ "fill" => "none",
423
+ "stroke" => fill,
424
+ "style" => {
425
+ "opacity" => opacity,
426
+ "stroke-width" => "#{stroke_width}px"
427
+ }
428
+ })
429
+
430
+ val = hex_val(40-i, 1)
431
+ opacity = opacity(val)
432
+ fill = fill_color(val)
433
+
434
+ svg.circle(
435
+ x*ring_size + x*stroke_width + (ring_size + stroke_width)/2,
436
+ y*ring_size + y*stroke_width + (ring_size + stroke_width)/2,
437
+ ring_size/4, {
438
+ "fill" => fill,
439
+ "fill-opacity" => opacity
440
+ })
441
+
442
+ i += 1
443
+ end
444
+ end
445
+ end
446
+
447
+ def geo_overlapping_rings
448
+ scale = hex_val(0, 1)
449
+ ring_size = map(scale, 0, 15, 10, 60)
450
+ stroke_width = ring_size / 4
451
+
452
+ svg.set_width(ring_size * 6)
453
+ svg.set_height(ring_size * 6)
454
+
455
+ i = 0
456
+ for y in 0..5
457
+ for x in 0..5
458
+ val = hex_val(i, 1)
459
+ opacity = opacity(val)
460
+ fill = fill_color(val)
461
+
462
+ styles = {
463
+ "fill" => "none",
464
+ "stroke" => fill,
465
+ "style" => {
466
+ "opacity" => opacity,
467
+ "stroke-width" => "#{stroke_width}px"
468
+ }
469
+ }
470
+
471
+ svg.circle(x*ring_size, y*ring_size, ring_size - stroke_width/2, styles)
472
+
473
+ # Add an extra one at top-right, for tiling.
474
+ if (x == 0)
475
+ svg.circle(6*ring_size, y*ring_size, ring_size - stroke_width/2, styles)
476
+ end
477
+
478
+ if (y == 0)
479
+ svg.circle(x*ring_size, 6*ring_size, ring_size - stroke_width/2, styles)
480
+ end
481
+
482
+ if (x == 0 and y == 0)
483
+ svg.circle(6*ring_size, 6*ring_size, ring_size - stroke_width/2, styles)
484
+ end
485
+ i += 1
486
+ end
487
+ end
488
+ end
489
+
490
+ def geo_triangles
491
+ scale = hex_val(0, 1)
492
+ side_length = map(scale, 0, 15, 15, 80)
493
+ triangle_height = side_length/2 * Math.sqrt(3)
494
+ triangle = build_triangle_shape(side_length, triangle_height)
495
+
496
+ svg.set_width(side_length * 3)
497
+ svg.set_height(triangle_height * 6)
498
+
499
+ i = 0
500
+ for y in 0..5
501
+ for x in 0..5
502
+ val = hex_val(i, 1)
503
+ opacity = opacity(val)
504
+ fill = fill_color(val)
505
+
506
+ styles = {
507
+ "fill" => fill,
508
+ "fill-opacity" => opacity,
509
+ "stroke" => STROKE_COLOR,
510
+ "stroke-opacity" => STROKE_OPACITY
511
+ }
512
+
513
+ rotation = ""
514
+ if y % 2 == 0
515
+ rotation = x % 2 == 0 ? 180 : 0
516
+ else
517
+ rotation = x % 2 != 0 ? 180 : 0
518
+ end
519
+
520
+ svg.polyline(triangle, styles.merge({
521
+ "transform" => "translate(#{x*side_length*0.5 - side_length/2}, #{triangle_height*y}) rotate(#{rotation}, #{side_length/2}, #{triangle_height/2})"}))
522
+
523
+ # Add an extra one at top-right, for tiling.
524
+ if (x == 0)
525
+ svg.polyline(triangle, styles.merge({
526
+ "transform" => "translate(#{6*side_length*0.5 - side_length/2}, #{triangle_height*y}) rotate(#{rotation}, #{side_length/2}, #{triangle_height/2})"}))
527
+ end
528
+ i += 1
529
+ end
530
+ end
531
+ end
532
+
533
+ def geo_triangles_rotated
534
+ scale = hex_val(0, 1)
535
+ side_length = map(scale, 0, 15, 15, 80)
536
+ triangle_width = side_length/2 * Math.sqrt(3)
537
+ triangle = build_rotated_triangle_shape(side_length, triangle_width)
538
+
539
+ svg.set_width(triangle_width * 6)
540
+ svg.set_height(side_length * 3)
541
+
542
+ i = 0
543
+ for y in 0..5
544
+ for x in 0..5
545
+ val = hex_val(i, 1)
546
+ opacity = opacity(val)
547
+ fill = fill_color(val)
548
+
549
+ styles = {
550
+ "fill" => fill,
551
+ "fill-opacity" => opacity,
552
+ "stroke" => STROKE_COLOR,
553
+ "stroke-opacity" => STROKE_OPACITY
554
+ }
555
+
556
+ rotation = ""
557
+ if y % 2 == 0
558
+ rotation = x % 2 == 0 ? 180 : 0
559
+ else
560
+ rotation = x % 2 != 0 ? 180 : 0
561
+ end
562
+
563
+ svg.polyline(triangle, styles.merge({
564
+ "transform" => "translate(#{triangle_width*x}, #{y*side_length*0.5 - side_length/2}) rotate(#{rotation}, #{triangle_width/2}, #{side_length/2})"}))
565
+
566
+ # Add an extra one at top-right, for tiling.
567
+ if (y == 0)
568
+ svg.polyline(triangle, styles.merge({
569
+ "transform" => "translate(#{triangle_width*x}, #{6*side_length*0.5 - side_length/2}) rotate(#{rotation}, #{triangle_width/2}, #{side_length/2})"}))
570
+
571
+ end
572
+ i += 1
573
+ end
574
+ end
575
+ end
576
+
577
+ def geo_diamonds
578
+ diamond_width = map(hex_val(0, 1), 0, 15, 10, 50)
579
+ diamond_height = map(hex_val(1, 1), 0, 15, 10, 50)
580
+ diamond = build_diamond_shape(diamond_width, diamond_height)
581
+
582
+ svg.set_width(diamond_width * 6)
583
+ svg.set_height(diamond_height * 3)
584
+
585
+ i = 0
586
+ for y in 0..5
587
+ for x in 0..5
588
+ val = hex_val(i, 1)
589
+ opacity = opacity(val)
590
+ fill = fill_color(val)
591
+
592
+ styles = {
593
+ "fill" => fill,
594
+ "fill-opacity" => opacity,
595
+ "stroke" => STROKE_COLOR,
596
+ "stroke-opacity" => STROKE_OPACITY
597
+ }
598
+
599
+ dx = (y % 2 == 0) ? 0 : diamond_width / 2
600
+
601
+ svg.polyline(diamond, styles.merge({
602
+ "transform" => "translate(#{x*diamond_width - diamond_width/2 + dx}, #{diamond_height/2*y - diamond_height/2})"}))
603
+
604
+ # Add an extra one at top-right, for tiling.
605
+ if (x == 0)
606
+ svg.polyline(diamond, styles.merge({
607
+ "transform" => "translate(#{6*diamond_width - diamond_width/2 + dx}, #{diamond_height/2*y - diamond_height/2})"}))
608
+ end
609
+
610
+ # Add an extra row at the end that matches the first row, for tiling.
611
+ if (y == 0)
612
+ svg.polyline(diamond, styles.merge({
613
+ "transform" => "translate(#{x*diamond_width - diamond_width/2 + dx}, #{diamond_height/2*6 - diamond_height/2})"}))
614
+ end
615
+
616
+ # Add an extra one at bottom-right, for tiling.
617
+ if (x == 0 and y == 0)
618
+ svg.polyline(diamond, styles.merge({
619
+ "transform" => "translate(#{6*diamond_width - diamond_width/2 + dx}, #{diamond_height/2*6 - diamond_height/2})"}))
620
+ end
621
+ i += 1
622
+ end
623
+ end
624
+ end
625
+
626
+ def geo_nested_squares
627
+ block_size = map(hex_val(0, 1), 0, 15, 4, 12)
628
+ square_size = block_size * 7
629
+
630
+ svg.set_width((square_size + block_size)*6 + block_size*6)
631
+ svg.set_height((square_size + block_size)*6 + block_size*6)
632
+
633
+ i = 0
634
+ for y in 0..5
635
+ for x in 0..5
636
+ val = hex_val(i, 1)
637
+ opacity = opacity(val)
638
+ fill = fill_color(val)
639
+
640
+ styles = {
641
+ "fill" => "none",
642
+ "stroke" => fill,
643
+ "style" => {
644
+ "opacity" => opacity,
645
+ "stroke-width" => "#{block_size}px"
646
+ }
647
+ }
648
+
649
+ svg.rect(x*square_size + x*block_size*2 + block_size/2,
650
+ y*square_size + y*block_size*2 + block_size/2,
651
+ square_size, square_size, styles)
652
+
653
+ val = hex_val(40-i, 1)
654
+ opacity = opacity(val)
655
+ fill = fill_color(val)
656
+
657
+ svg.rect(x*square_size + x*block_size*2 + block_size/2 + block_size*2,
658
+ y*square_size + y*block_size*2 + block_size/2 + block_size*2,
659
+ block_size * 3, block_size * 3, styles)
660
+ i += 1
661
+ end
662
+ end
663
+ end
664
+
665
+ def geo_mosaic_squares
666
+ triangle_size = map(hex_val(0, 1), 0, 15, 15, 50)
667
+
668
+ svg.set_width(triangle_size * 8)
669
+ svg.set_height(triangle_size * 8)
670
+
671
+ i = 0
672
+ for y in 0..3
673
+ for x in 0..3
674
+
675
+ if (x % 2 == 0)
676
+ if (y % 2 == 0)
677
+ draw_outer_mosaic_tile(x*triangle_size*2, y*triangle_size*2, triangle_size, hash[i])
678
+ else
679
+ draw_inner_mosaic_tile(x*triangle_size*2, y*triangle_size*2, triangle_size, hash[i..i+1])
680
+ end
681
+ else
682
+ if (y % 2 == 0)
683
+ draw_inner_mosaic_tile(x*triangle_size*2, y*triangle_size*2, triangle_size, hash[i..i+1])
684
+ else
685
+ draw_outer_mosaic_tile(x*triangle_size*2, y*triangle_size*2, triangle_size, hash[i])
686
+ end
687
+ end
688
+ i += 1
689
+ end
690
+ end
691
+ end
692
+
693
+ def geo_plaid
694
+ height = 0
695
+ width = 0
696
+
697
+ # horizontal stripes
698
+ i = 0
699
+ 18.times do
700
+ space = hex_val(i, 1)
701
+ height += space + 5
702
+
703
+ val = hex_val(i+1, 1)
704
+ opacity = opacity(val)
705
+ fill = fill_color(val)
706
+ stripe_height = val + 5
707
+
708
+ svg.rect(0, height, "100%", stripe_height, {
709
+ "opacity" => opacity,
710
+ "fill" => fill
711
+ })
712
+ height += stripe_height
713
+ i += 2
714
+ end
715
+
716
+ # vertical stripes
717
+ i = 0
718
+ 18.times do
719
+ space = hex_val(i, 1)
720
+ width += space + 5
721
+
722
+ val = hex_val(i+1, 1)
723
+ opacity = opacity(val)
724
+ fill = fill_color(val)
725
+ stripe_width = val + 5
726
+
727
+ svg.rect(width, 0, stripe_width, "100%", {
728
+ "opacity" => opacity,
729
+ "fill" => fill
730
+ })
731
+ width += stripe_width
732
+ i += 2
733
+ end
734
+
735
+ svg.set_width(width)
736
+ svg.set_height(height)
737
+ end
738
+
739
+ def geo_tessellation
740
+ # 3.4.6.4 semi-regular tessellation
741
+ side_length = map(hex_val(0, 1), 0, 15, 5, 40)
742
+ hex_height = side_length * Math.sqrt(3)
743
+ hex_width = side_length * 2
744
+ triangle_height = side_length/2 * Math.sqrt(3)
745
+ triangle = build_rotated_triangle_shape(side_length, triangle_height)
746
+ tile_width = side_length*3 + triangle_height*2
747
+ tile_height = (hex_height * 2) + (side_length * 2)
748
+
749
+ svg.set_width(tile_width)
750
+ svg.set_height(tile_height)
751
+
752
+ for i in 0..19
753
+ val = hex_val(i, 1)
754
+ opacity = opacity(val)
755
+ fill = fill_color(val)
756
+
757
+ styles = {
758
+ "stroke" => STROKE_COLOR,
759
+ "stroke-opacity" => STROKE_OPACITY,
760
+ "fill" => fill,
761
+ "fill-opacity" => opacity,
762
+ "stroke-width" => 1
763
+ }
764
+
765
+ case i
766
+ when 0 # all 4 corners
767
+ svg.rect(-side_length/2, -side_length/2, side_length, side_length, styles)
768
+ svg.rect(tile_width - side_length/2, -side_length/2, side_length, side_length, styles)
769
+ svg.rect(-side_length/2, tile_height-side_length/2, side_length, side_length, styles)
770
+ svg.rect(tile_width - side_length/2, tile_height-side_length/2, side_length, side_length, styles)
771
+ when 1 # center / top square
772
+ svg.rect(hex_width/2 + triangle_height, hex_height/2, side_length, side_length, styles)
773
+ when 2 # side squares
774
+ svg.rect(-side_length/2, tile_height/2-side_length/2, side_length, side_length, styles)
775
+ svg.rect(tile_width-side_length/2, tile_height/2-side_length/2, side_length, side_length, styles)
776
+ when 3 # center / bottom square
777
+ svg.rect(hex_width/2 + triangle_height, hex_height * 1.5 + side_length, side_length, side_length, styles)
778
+ when 4 # left top / bottom triangle
779
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{side_length/2}, #{-side_length/2}) rotate(0, #{side_length/2}, #{triangle_height/2})"}))
780
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{side_length/2}, #{tile_height--side_length/2}) rotate(0, #{side_length/2}, #{triangle_height/2}) scale(1, -1)"}))
781
+ when 5 # right top / bottom triangle
782
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{tile_width-side_length/2}, #{-side_length/2}) rotate(0, #{side_length/2}, #{triangle_height/2}) scale(-1, 1)"}))
783
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{tile_width-side_length/2}, #{tile_height+side_length/2}) rotate(0, #{side_length/2}, #{triangle_height/2}) scale(-1, -1)"}))
784
+ when 6 # center / top / right triangle
785
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{tile_width/2+side_length/2}, #{hex_height/2})"}))
786
+ when 7 # center / top / left triangle
787
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{tile_width-tile_width/2-side_length/2}, #{hex_height/2}) scale(-1, 1)"}))
788
+ when 8 # center / bottom / right triangle
789
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{tile_width/2+side_length/2}, #{tile_height-hex_height/2}) scale(1, -1)"}))
790
+ when 9 # center / bottom / left triangle
791
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{tile_width-tile_width/2-side_length/2}, #{tile_height-hex_height/2}) scale(-1, -1)"}))
792
+ when 10 # left / middle triangle
793
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{side_length/2}, #{tile_height/2 - side_length/2})"}))
794
+ when 11 # right / middle triangle
795
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{tile_width-side_length/2}, #{tile_height/2 - side_length/2}) scale(-1, 1)"}))
796
+ when 12 # left / top square
797
+ svg.rect(0, 0, side_length, side_length,
798
+ styles.merge({"transform" => "translate(#{side_length/2}, #{side_length/2}) rotate(-30, 0, 0)"}))
799
+ when 13 # right / top square
800
+ svg.rect(0, 0, side_length, side_length,
801
+ styles.merge({"transform" => "scale(-1, 1) translate(#{-tile_width+side_length/2}, #{side_length/2}) rotate(-30, 0, 0)" }))
802
+ when 14 # left / center-top square
803
+ svg.rect(0, 0, side_length, side_length,
804
+ styles.merge({"transform" => "translate(#{side_length/2}, #{tile_height/2-side_length/2-side_length}) rotate(30, 0, #{side_length})" }))
805
+ when 15 # right / center-top square
806
+ svg.rect(0, 0, side_length, side_length,
807
+ styles.merge({"transform" => "scale(-1, 1) translate(#{-tile_width+side_length/2}, #{tile_height/2-side_length/2-side_length}) rotate(30, 0, #{side_length})" }))
808
+ when 16 # left / center-top square
809
+ svg.rect(0, 0, side_length, side_length,
810
+ styles.merge({"transform" => "scale(1, -1) translate(#{side_length/2}, #{-tile_height+tile_height/2-side_length/2-side_length}) rotate(30, 0, #{side_length})" }))
811
+ when 17 # right / center-bottom square
812
+ svg.rect(0, 0, side_length, side_length,
813
+ styles.merge({"transform" => "scale(-1, -1) translate(#{-tile_width+side_length/2}, #{-tile_height+tile_height/2-side_length/2-side_length}) rotate(30, 0, #{side_length})" }))
814
+ when 18 # left / bottom square
815
+ svg.rect(0, 0, side_length, side_length,
816
+ styles.merge({"transform" => "scale(1, -1) translate(#{side_length/2}, #{-tile_height+side_length/2}) rotate(-30, 0, 0)"}))
817
+ when 19 # right / bottom square
818
+ svg.rect(0, 0, side_length, side_length,
819
+ styles.merge({"transform" => "scale(-1, -1) translate(#{-tile_width+side_length/2}, #{-tile_height+side_length/2}) rotate(-30, 0, 0)"}))
820
+ end
821
+ end
822
+ end
823
+
824
+ def build_chevron_shape(width, height)
825
+ e = height * 0.66
826
+ [
827
+ %Q{polyline("0,0,#{width/2},#{height-e},#{width/2},#{height},0,#{e},0,0")},
828
+ %Q{polyline("#{width/2},#{height-e},#{width},0,#{width},#{e},#{width/2},#{height},#{width/2},#{height-e}")}
829
+ ]
830
+ end
831
+
832
+ def build_octogon_shape(square_size)
833
+ s = square_size
834
+ c = s * 0.33
835
+ "#{c},0,#{s-c},0,#{s},#{c},#{s},#{s-c},#{s-c},#{s},#{c},#{s},0,#{s-c},0,#{c},#{c},0"
836
+ end
837
+
838
+ def build_hexagon_shape(sideLength)
839
+ c = sideLength
840
+ a = c/2
841
+ b = Math.sin(60 * Math::PI / 180)*c
842
+ "0,#{b},#{a},0,#{a+c},0,#{2*c},#{b},#{a+c},#{2*b},#{a},#{2*b},0,#{b}"
843
+ end
844
+
845
+ def build_plus_shape(square_size)
846
+ [
847
+ "rect(#{square_size},0,#{square_size},#{square_size * 3})",
848
+ "rect(0, #{square_size},#{square_size * 3},#{square_size})"
849
+ ]
850
+ end
851
+
852
+ def build_triangle_shape(side_length, height)
853
+ half_width = side_length / 2
854
+ "#{half_width}, 0, #{side_length}, #{height}, 0, #{height}, #{half_width}, 0"
855
+ end
856
+
857
+ def build_rotated_triangle_shape(side_length, width)
858
+ half_height = side_length / 2
859
+ "0, 0, #{width}, #{half_height}, 0, #{side_length}, 0, 0"
860
+ end
861
+
862
+ def build_right_triangle_shape(side_length)
863
+ "0, 0, #{side_length}, #{side_length}, 0, #{side_length}, 0, 0"
864
+ end
865
+
866
+ def build_diamond_shape(width, height)
867
+ "#{width/2}, 0, #{width}, #{height/2}, #{width/2}, #{height}, 0, #{height/2}"
868
+ end
869
+
870
+ def draw_inner_mosaic_tile(x, y, triangle_size, vals)
871
+ triangle = build_right_triangle_shape(triangle_size)
872
+ opacity = opacity(vals[0])
873
+ fill = fill_color(vals[0])
874
+ styles = {
875
+ "stroke" => STROKE_COLOR,
876
+ "stroke-opacity" => STROKE_OPACITY,
877
+ "fill-opacity" => opacity,
878
+ "fill" => fill
879
+ }
880
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{x+triangle_size}, #{y}) scale(-1, 1)"}))
881
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{x+triangle_size}, #{y+triangle_size*2}) scale(1, -1)"}))
882
+
883
+ opacity = opacity(vals[1])
884
+ fill = fill_color(vals[1])
885
+ styles = {
886
+ "stroke" => STROKE_COLOR,
887
+ "stroke-opacity" => STROKE_OPACITY,
888
+ "fill-opacity" => opacity,
889
+ "fill" => fill
890
+ }
891
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{x+triangle_size}, #{y+triangle_size*2}) scale(-1, -1)"}))
892
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{x+triangle_size}, #{y}) scale(1, 1)"}))
893
+ end
894
+
895
+ def draw_outer_mosaic_tile(x, y, triangle_size, val)
896
+ opacity = opacity(val)
897
+ fill = fill_color(val)
898
+ triangle = build_right_triangle_shape(triangle_size)
899
+ styles = {
900
+ "stroke" => STROKE_COLOR,
901
+ "stroke-opacity" => STROKE_OPACITY,
902
+ "fill-opacity" => opacity,
903
+ "fill" => fill
904
+ }
905
+
906
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{x}, #{y+triangle_size}) scale(1, -1)"}))
907
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{x+triangle_size*2}, #{y+triangle_size}) scale(-1, -1)"}))
908
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{x}, #{y+triangle_size}) scale(1, 1)"}))
909
+ svg.polyline(triangle, styles.merge({"transform" => "translate(#{x+triangle_size*2}, #{y+triangle_size}) scale(-1, 1)"}))
910
+ end
911
+
912
+ def hex_val(index, len)
913
+ hash[index, len || 1].to_i(16)
914
+ end
915
+
916
+ def fill_color(val)
917
+ (val % 2 == 0) ? FILL_COLOR_LIGHT : FILL_COLOR_DARK
918
+ end
919
+
920
+ def opacity(val)
921
+ map(val, 0, 15, OPACITY_MIN, OPACITY_MAX)
922
+ end
923
+
924
+ # Ruby implementation of Processing's map function
925
+ # http://processing.org/reference/map_.html
926
+ def map(value, v_min, v_max, d_min, d_max) # v for value, d for desired
927
+ v_value = value.to_f # so it returns float
928
+
929
+ v_range = v_max - v_min
930
+ d_range = d_max - d_min
931
+ (v_value - v_min) * d_range / v_range + d_min
932
+ end
933
+ end
934
+ end
@@ -0,0 +1,67 @@
1
+ module GeoPattern
2
+ class SVG
3
+ def initialize
4
+ @width = 100
5
+ @height = 100
6
+ @svg_string = ""
7
+ end
8
+
9
+ def set_width(width)
10
+ @width = width.floor
11
+ end
12
+
13
+ def set_height(height)
14
+ @height = height.floor
15
+ end
16
+
17
+ def svg_header
18
+ %Q{<svg xmlns="http://www.w3.org/2000/svg" width="#{@width}" height="#{@height}">}
19
+ end
20
+
21
+ def svg_closer
22
+ "</svg>"
23
+ end
24
+
25
+ def to_s
26
+ svg_header + @svg_string + svg_closer
27
+ end
28
+
29
+ def rect(x, y, width, height, args={})
30
+ @svg_string << %Q{<rect x="#{x}" y="#{y}" width="#{width}" height="#{height}" #{write_args(args)} />}
31
+ end
32
+
33
+ def circle(cx, cy, r, args={})
34
+ @svg_string << %Q{<circle cx="#{cx}" cy="#{cy}" r="#{r}" #{write_args(args)} />}
35
+ end
36
+
37
+ def path(str, args={})
38
+ @svg_string << %Q{<path d="#{str}" #{write_args(args)} />}
39
+ end
40
+
41
+ def polyline(str, args={})
42
+ @svg_string << %Q{<polyline points="#{str}" #{write_args(args)} />}
43
+ end
44
+
45
+ def group(elements, args={})
46
+ @svg_string << %Q{<g #{write_args(args)}>}
47
+ elements.each {|e| eval e}
48
+ @svg_string << %Q{</g>}
49
+ end
50
+
51
+ def write_args(args)
52
+ str = ""
53
+ args.each {|key, value|
54
+ if value.is_a?(Hash)
55
+ str << %Q{#{key}="}
56
+ value.each {|k, v|
57
+ str << %Q{#{k}:#{v};}
58
+ }
59
+ str << %Q{" }
60
+ else
61
+ str << %Q{#{key}="#{value}" }
62
+ end
63
+ }
64
+ str
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module GeoPattern
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geo_pattern
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jason Long
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: color
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.5'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.5'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.5'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.5'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Generate SVG beautiful patterns
63
+ email:
64
+ - jlong@github.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE.txt
72
+ - README.md
73
+ - Rakefile
74
+ - geopatterns.gemspec
75
+ - lib/geo_pattern.rb
76
+ - lib/geo_pattern/pattern.rb
77
+ - lib/geo_pattern/svg.rb
78
+ - lib/geo_pattern/version.rb
79
+ homepage: https://github.com/jasonlong/geopatterns
80
+ licenses:
81
+ - MIT
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 1.8.23
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Generate SVG beautiful patterns
104
+ test_files: []