foliage 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +9 -0
  4. data/app/assets/images/.keep +0 -0
  5. data/app/assets/images/map/marker/icon-2x.png +0 -0
  6. data/app/assets/images/map/marker/icon.png +0 -0
  7. data/app/assets/images/map/marker/icon.svg +67 -0
  8. data/app/assets/images/map/marker/shadow.png +0 -0
  9. data/app/assets/javascripts/core_ext.js.coffee +61 -0
  10. data/app/assets/javascripts/foliage.js.coffee +23 -0
  11. data/app/assets/javascripts/foliage/band.js.coffee +99 -0
  12. data/app/assets/javascripts/foliage/bubbles.js.coffee +77 -0
  13. data/app/assets/javascripts/foliage/categories.js.coffee +70 -0
  14. data/app/assets/javascripts/foliage/choropleth.js.coffee +51 -0
  15. data/app/assets/javascripts/foliage/color.js.coffee +39 -0
  16. data/app/assets/javascripts/foliage/gradient.js.coffee +72 -0
  17. data/app/assets/javascripts/foliage/heatmap.js.coffee +49 -0
  18. data/app/assets/javascripts/foliage/leaf.js.coffee +422 -0
  19. data/app/assets/javascripts/foliage/path.js.coffee +76 -0
  20. data/app/assets/javascripts/foliage/paths.js.coffee +131 -0
  21. data/app/assets/javascripts/foliage/point_group.js.coffee +83 -0
  22. data/app/assets/javascripts/foliage/points.js.coffee +79 -0
  23. data/app/assets/javascripts/foliage/simple.js.coffee +35 -0
  24. data/app/assets/javascripts/leaflet/geographic_util.js.coffee +23 -0
  25. data/app/assets/javascripts/leaflet/ghost_label.js.coffee +100 -0
  26. data/app/assets/javascripts/leaflet/ghost_label_cluster.js.coffee +192 -0
  27. data/app/assets/javascripts/leaflet/layers_scheduler.js.coffee +57 -0
  28. data/app/assets/javascripts/leaflet/reactive_measure.js.coffee +414 -0
  29. data/app/assets/stylesheets/all.scss +16 -0
  30. data/app/assets/stylesheets/application.css +15 -0
  31. data/app/assets/stylesheets/compass/reset.scss +3 -0
  32. data/app/assets/stylesheets/compass/reset/utilities.scss +142 -0
  33. data/app/assets/stylesheets/leaflet.scss +1093 -0
  34. data/app/assets/stylesheets/leaflet/label.scss +40 -0
  35. data/app/assets/stylesheets/leaflet/tooltip.scss +42 -0
  36. data/app/assets/stylesheets/mixins.scss +131 -0
  37. data/app/assets/stylesheets/reset.scss +89 -0
  38. data/app/assets/stylesheets/variables.scss +47 -0
  39. data/app/helpers/foliage_helper.rb +23 -0
  40. data/lib/foliage.rb +9 -0
  41. data/lib/foliage/leaf.rb +235 -0
  42. data/lib/foliage/rails.rb +2 -0
  43. data/lib/foliage/rails/engine.rb +7 -0
  44. data/lib/foliage/rails/integration.rb +8 -0
  45. data/lib/foliage/version.rb +3 -0
  46. data/vendor/assets/javascripts/.keep +0 -0
  47. data/vendor/assets/javascripts/autosize.js +211 -0
  48. data/vendor/assets/javascripts/geographiclib.js +3074 -0
  49. data/vendor/assets/javascripts/leaflet.js.erb +9175 -0
  50. data/vendor/assets/javascripts/leaflet/draw.js +3573 -0
  51. data/vendor/assets/javascripts/leaflet/easy-button.js +366 -0
  52. data/vendor/assets/javascripts/leaflet/fullscreen.js +162 -0
  53. data/vendor/assets/javascripts/leaflet/heatmap.js +142 -0
  54. data/vendor/assets/javascripts/leaflet/label.js +545 -0
  55. data/vendor/assets/javascripts/leaflet/measure.js +6966 -0
  56. data/vendor/assets/javascripts/leaflet/modal.js +364 -0
  57. data/vendor/assets/javascripts/leaflet/providers.js +479 -0
  58. data/vendor/assets/javascripts/rbush.js +621 -0
  59. data/vendor/assets/stylesheets/.keep +0 -0
  60. data/vendor/assets/stylesheets/bootstrap/mixins.scss +55 -0
  61. data/vendor/assets/stylesheets/bootstrap/variables.scss +10 -0
  62. data/vendor/assets/stylesheets/leaflet.scss +479 -0
  63. data/vendor/assets/stylesheets/leaflet/draw.scss +282 -0
  64. data/vendor/assets/stylesheets/leaflet/easy-button.scss +56 -0
  65. data/vendor/assets/stylesheets/leaflet/fullscreen.scss +2 -0
  66. data/vendor/assets/stylesheets/leaflet/measure.scss +168 -0
  67. data/vendor/assets/stylesheets/leaflet/modal.scss +85 -0
  68. metadata +171 -0
@@ -0,0 +1,40 @@
1
+ .leaflet-label {
2
+ color: black;
3
+ font-size: 14px;
4
+ display: block;
5
+ padding: 0.5*10px 10px;
6
+ position: absolute;
7
+ pointer-events: none;
8
+ white-space: nowrap;
9
+ z-index: 1;
10
+ text-shadow: 0 0 2px white;
11
+ text-shadow: 2px 0 0 #fff, -2px 0 0 #fff, 0 2px 0 #fff, 0 -2px 0 #fff, 1px 1px #fff, -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff;
12
+ &.leaflet-clickable {
13
+ cursor: pointer;
14
+ pointer-events: auto;
15
+ }
16
+
17
+ &:before, &:after {
18
+ border-top: 6px solid transparent;
19
+ border-bottom: 6px solid transparent;
20
+ content: none;
21
+ position: absolute;
22
+ top: 5px;
23
+ }
24
+
25
+ &:before {
26
+ border-right: 6px solid black;
27
+ border-right-color: inherit;
28
+ left: -10px;
29
+ }
30
+
31
+ &:after {
32
+ border-left: 6px solid black;
33
+ border-left-color: inherit;
34
+ right: -10px;
35
+ }
36
+ }
37
+
38
+ .leaflet-label-right:before, .leaflet-label-left:after {
39
+ content: "";
40
+ }
@@ -0,0 +1,42 @@
1
+ .leaflet-draw-tooltip-left {
2
+ background: rgb(54, 54, 54);
3
+ background: rgba(0, 0, 0, 0.5);
4
+ border: 1px solid transparent;
5
+ position: absolute;
6
+ visibility: hidden;
7
+ white-space: nowrap;
8
+ z-index: 6;
9
+ &:before {
10
+ border: none !important;
11
+ }
12
+ &:after {
13
+ border-left: 6px solid black;
14
+ border-left-color: rgba(0, 0, 0, 0.5);
15
+ border-top: 6px solid transparent;
16
+ border-bottom: 6px solid transparent;
17
+ content: "";
18
+ position: absolute;
19
+ top: 7px;
20
+ left: 100%;
21
+ }
22
+ span {
23
+ color: inherit;
24
+ }
25
+ }
26
+
27
+ .leaflet-draw-tooltip-measure {
28
+ &.perimeter {
29
+ }
30
+ &.area {
31
+ padding-left: 10px
32
+ }
33
+ }
34
+
35
+ .reactive-measure-control {
36
+ background: rgba(255,255,255, 0.6);
37
+ padding: 0 10px;
38
+
39
+ &.selection {
40
+ background: rgba(mix(white, red), 0.5);
41
+ }
42
+ }
@@ -0,0 +1,131 @@
1
+ @import "bootstrap/mixins";
2
+
3
+ @mixin calc($property, $expression) {
4
+ #{$property}: -webkit-calc(#{$expression});
5
+ #{$property}: -khtml-calc(#{$expression});
6
+ #{$property}: -moz-calc(#{$expression});
7
+ #{$property}: -ms-calc(#{$expression});
8
+ #{$property}: -o-calc(#{$expression});
9
+ #{$property}: calc(#{$expression});
10
+ }
11
+
12
+ @mixin prefixed-property($property, $value) {
13
+ -webkit-#{$property}: $value;
14
+ -khtml-#{$property}: $value;
15
+ -moz-#{$property}: $value;
16
+ -ms-#{$property}: $value;
17
+ -o-#{$property}: $value;
18
+ #{$property}: $value;
19
+ }
20
+
21
+ @mixin border-radius($radius) {
22
+ @include prefixed-property(border-radius, $radius);
23
+ }
24
+
25
+ @mixin inline-block($alignment: middle) {
26
+ display: -moz-inline-stack;
27
+ display: inline-block;
28
+ @if $alignment and $alignment != none {
29
+ vertical-align: $alignment;
30
+ }
31
+ }
32
+
33
+
34
+ @mixin basic-icon($font-size: null) {
35
+ font-family: Agric;
36
+ font-weight: normal;
37
+ font-style: normal;
38
+ text-decoration: inherit;
39
+ -webkit-font-smoothing: antialiased;
40
+ width: auto;
41
+ height: auto;
42
+ background-image: none;
43
+ background-position: 0% 0%;
44
+ background-repeat: repeat;
45
+ margin-top: 0;
46
+ @if $font-size != null {
47
+ font-size: $font-size;
48
+ }
49
+ }
50
+
51
+ @mixin is-icon($font-size: round(1.2 * $fs-normal)) {
52
+ @include basic-icon($font-size);
53
+ vertical-align: middle;
54
+ }
55
+
56
+ @mixin use-icon($name) {
57
+ &::before { content: icon-character($name); }
58
+ }
59
+
60
+
61
+ @mixin define-right-property($base-name, $value) {
62
+ html & { #{$base-name}-right: $value; }
63
+ html[dir="ltr"] & { #{$base-name}-right: $value; }
64
+ html[dir="rtl"] & { #{$base-name}-left: $value; }
65
+ }
66
+
67
+ @mixin define-left-property($base-name, $value) {
68
+ html & { #{$base-name}-left: $value; }
69
+ html[dir="ltr"] & { #{$base-name}-left: $value; }
70
+ html[dir="rtl"] & { #{$base-name}-right: $value; }
71
+ }
72
+
73
+ @mixin margin-right($value) { @include define-right-property(margin, $value); }
74
+ @mixin margin-left($value) { @include define-left-property(margin, $value); }
75
+
76
+ @mixin text-align($dir) {
77
+ @if $dir == right {
78
+ html & { text-align: right; }
79
+ html[dir="ltr"] & { text-align: right; }
80
+ html[dir="rtl"] & { text-align: left; }
81
+ } @else if $dir == left {
82
+ html & { text-align: left; }
83
+ html[dir="ltr"] & { text-align: left; }
84
+ html[dir="rtl"] & { text-align: right; }
85
+ } @else {
86
+ html &, html[dir="ltr"] &, html[dir="rtl"] & { text-align: $dir; }
87
+ }
88
+ }
89
+
90
+ @mixin float($dir) {
91
+ @if $dir == right {
92
+ html & { float: right; }
93
+ html[dir="ltr"] & { float: right; }
94
+ html[dir="rtl"] & { float: left; }
95
+ } @else if $dir == left {
96
+ html & { float: left; }
97
+ html[dir="ltr"] & { float: left; }
98
+ html[dir="rtl"] & { float: right; }
99
+ } @else {
100
+ html & html[dir="ltr"] &, html[dir="rtl"] & { float: $dir; }
101
+ }
102
+ }
103
+
104
+ @mixin menu-box {
105
+ background: $menu-background;
106
+ @include menu-shadow;
107
+ border: $menu-border;
108
+ }
109
+
110
+ @mixin menu-shadow {
111
+ @include box-shadow(0 0 ($fs-normal/4) rgba(0, 0, 0, 0.3));
112
+ }
113
+
114
+ @mixin menu {
115
+ @include menu-box;
116
+ &, li a {
117
+ font-weight: normal;
118
+ color: $text-color;
119
+ }
120
+ }
121
+
122
+ @mixin legend-label {
123
+ &, * {
124
+ color: $neutral-color;
125
+ text-transform: uppercase;
126
+ font-size: $fs-small;
127
+ line-height: $lh-small;
128
+ font-weight: bold;
129
+ }
130
+ }
131
+
@@ -0,0 +1,89 @@
1
+ @import "compass/reset";
2
+
3
+ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video, input, textarea, select, options, button {
4
+ margin: 0;
5
+ padding: 0;
6
+ font-family: $base-font-family;
7
+ vertical-align: middle;
8
+ font-size: $fs-normal;
9
+ line-height: $lh-normal;
10
+ color: $text-color;
11
+ }
12
+
13
+ html {
14
+ &, * {
15
+ @include text-align(left);
16
+ }
17
+ }
18
+
19
+ mark {
20
+ background-color: $highlight-color;
21
+ }
22
+
23
+ td, th, a, p, input, textarea, select, div {
24
+ font-size: $fs-normal;
25
+ }
26
+ small, sup, sub {
27
+ font-size: $fs-small;
28
+ }
29
+ a:link,
30
+ a:visited,
31
+ a:focus {
32
+ &, * {
33
+ color: $active-link-color;
34
+ text-decoration: none;
35
+ }
36
+ .label {
37
+ color: $base-font-reversed-color;
38
+ }
39
+ }
40
+
41
+ /*
42
+ Other input types are: color, date, datetime, datetime-local, email, month,
43
+ number, range, search, tel, time, url, week, button, checkbox, color, date,
44
+ datetime, datetime-local, email, file, hidden, image, month, number, password,
45
+ radio, range, reset, search, submit, tel, text, time, url, week and file
46
+ */
47
+
48
+ input[type="color"], input[type="date"], input[type="datetime"], input[type="datetime-local"], input[type="email"], input[type="image"], input[type="month"], input[type="number"], input[type="password"], input[type="range"], input[type="tel"], input[type="text"], input[type="time"], input[type="url"], input[type="week"], input[type="search"], textarea, select {
49
+ border: $menu-border;
50
+ padding: round($fs-normal * 0.2);
51
+ @include box-sizing(content-box);
52
+ @include border-radius($default-border-radius);
53
+ background: #FFF; //$desktop-background;
54
+ font-size: $fs-normal;
55
+ line-height: $lh-normal;
56
+ height: $lh-normal;
57
+ vertical-align: middle;
58
+ // @include box-shadow(0 2px round($fs-normal/2) rgba(black, 0.1) inset, 0 0 round($fs-normal*0.6) rgba(white, 0.6));
59
+ // @include box-shadow(0 2px round($fs-normal/2) rgba(black, 0.1) inset);
60
+ @include box-shadow(0 0 0 rgba(black, 0.1));
61
+ @include transition(box-shadow 0.2s, border-color 0.2s);
62
+ &:focus {
63
+ // background: #FFF;
64
+ border-color: $base-color;
65
+ @include box-shadow(0 0 round($fs-normal*0.6) rgba($base-color, 0.6));
66
+ }
67
+ &:disabled, &[disabled] {
68
+ background: #EEE;
69
+ color: #777;
70
+ cursor: not-allowed;
71
+ }
72
+ }
73
+ input[type="number"] {
74
+ @include text-align(right);
75
+ }
76
+ input[type="date"], input[type="datetime"] {
77
+ @include text-align(center);
78
+ }
79
+ input[type="date"] {
80
+ width: 12ex;
81
+ }
82
+ input[type="datetime"] {
83
+ width: 20ex;
84
+ }
85
+
86
+
87
+ textarea {
88
+ min-height: 3em;
89
+ }
@@ -0,0 +1,47 @@
1
+ // Basic colors
2
+ $white: #FFFFFF !default;
3
+ $black: #000000 !default;
4
+
5
+ // Font sizes
6
+ $fs-ratio: 1.2 !default;
7
+ $fs-small: 11px !default;
8
+ $fs-normal: 13px !default;
9
+
10
+ // Line heights
11
+ $lh-ratio: 1.61803398875 !default;
12
+ $lh-small: round($lh-ratio * $fs-small);
13
+ $lh-normal: round($lh-ratio * $fs-normal);
14
+
15
+ // Colors
16
+ $base-color: #688ED8 !default;
17
+ $highlight-color: #FFEE80 !default;
18
+ $neutral-color: mix($black, $white, 45%) !default;
19
+ $active-color: $base-color !default;
20
+ $active-link-color: darken($base-color, 10%) !default;
21
+ $title-color: darken($base-color, 40%) !default;
22
+ $text-color: darken($base-color, 40%) !default;
23
+ $base-font-reversed-color: $white !default;
24
+
25
+ $hover-transparency: 0.92 !default;
26
+ $hover-mask: transparentize($neutral-color, $hover-transparency);
27
+
28
+ $neutral-background: change-color($neutral-color, $lightness: 94%) !default;
29
+
30
+ // Menu colors
31
+ $menu-background: mix($neutral-background, $white, 55%) !default;
32
+ $menu-border-color: mix($menu-background, $black, 90%);
33
+ $menu-border-width: floor($fs-normal / 10);
34
+ $menu-border: $menu-border-width solid $menu-border-color;
35
+
36
+
37
+ $default-border-radius: round($fs-normal * 0.2);
38
+ $default-gap: round($fs-normal * 0.55);
39
+
40
+ // Map
41
+ $map-button-size: round(2 * $fs-normal);
42
+
43
+ // Font families
44
+ $sans-font-family: 'Open Sans', 'Droid Sans', 'Liberation Sans', Helvetica, sans-serif !default;
45
+ $base-font-family: $sans-font-family !default;
46
+
47
+ @import "bootstrap/variables"
@@ -0,0 +1,23 @@
1
+ require 'foliage'
2
+
3
+ module FoliageHelper
4
+ # Example of how to use in HAML view:
5
+ #
6
+ # = foliage do |leaf|
7
+ # - leaf.serie :data, <data>
8
+ # - leaf.background "openstreetmap.hot"
9
+ # - leaf.background "openweather.precipitations"
10
+ # - leaf.background "openweather.heat"
11
+ # - leaf.choropleth :<property>, :data
12
+ # - leaf.control :fullscreen
13
+ # - leaf.control :layer_selector
14
+ # - leaf.control :background_selector
15
+ # - leaf.control :search
16
+ #
17
+ def foliage(options = {}, html_options = {})
18
+ theme_colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']
19
+ leaf = Foliage::Leaf.new({ categories_colors: theme_colors }.merge(options))
20
+ yield leaf
21
+ content_tag(:div, nil, html_options.deep_merge(data: { leaf: leaf.to_json }))
22
+ end
23
+ end
data/lib/foliage.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'foliage/leaf'
2
+ require 'foliage/version'
3
+
4
+ module Foliage
5
+ autoload :VERSION, 'foliage/version'
6
+ autoload :Leaf, 'foliage/leaf'
7
+ end
8
+
9
+ require 'foliage/rails'
@@ -0,0 +1,235 @@
1
+ module Foliage
2
+ class Leaf
3
+ def initialize(config = {})
4
+ @config = config
5
+ @categories_colors = @config.delete(:categories_colors)
6
+ @config[:backgrounds] = map_backgrounds
7
+ end
8
+
9
+ def map_backgrounds
10
+ [
11
+ {
12
+ name: 'OpenStreetMap Hot',
13
+ url: 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
14
+ referenceName: 'open_street_map.hot',
15
+ attribution: '&copy; <a href=\'http://openstreetmap.org\'>OpenStreetMap</a> contributors, <a href=\'http://creativecommons.org/licenses/by-sa/2.0/\'>CC-BY-SA</a>, Tiles courtesy of <a href=\'http://hot.openstreetmap.org/\' target=\'_blank\'>Humanitarian OpenStreetMap Team</a>',
16
+ tms: false,
17
+ byDefault: false
18
+ },
19
+ {
20
+ name: 'Thunderforest Landscape',
21
+ url: 'https://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png',
22
+ referenceName: 'thunderforest.landscape',
23
+ attribution:
24
+ 'Maps © <a href=\'http://www.thunderforest.com\'>Thunderforest</a>, Data © <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap contributors</a>',
25
+ subdomains: 'abc',
26
+ tms: false,
27
+ byDefault: false
28
+ },
29
+ {
30
+ name: 'Stamen Watercolor',
31
+ url: 'http://{s}.tile.stamen.com/watercolor/{z}/{x}/{y}.jpg',
32
+ referenceName: 'stamen.watercolor',
33
+ attribution: 'Map tiles by <a href=\'http://stamen.com\'>Stamen Design</a>, <a href=\'http://creativecommons.org/licenses/by/3.0\'>CC BY 3.0</a> &mdash; Map data &copy; <a href=\'http://openstreetmap.org\'>OpenStreetMap</a> contributors, <a href=\'http://creativecommons.org/licenses/by-sa/2.0/\'>CC-BY-SA</a>',
34
+ subdomains: 'abcd',
35
+ minZoom: 3,
36
+ maxZoom: 15,
37
+ tms: false,
38
+ byDefault: true
39
+ },
40
+ {
41
+ name: 'Esri World imagery',
42
+ url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
43
+ referenceName: 'esri.world_imagery',
44
+ attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
45
+ tms: false,
46
+ byDefault: false
47
+ }
48
+ ]
49
+ end
50
+
51
+ def background(layer, options = {})
52
+ # allow to add custom background
53
+ options[:name] = layer.name if layer.name?
54
+ options[:url] = layer.url if layer.url?
55
+ options[:by_default] = layer.by_default if layer.by_default?
56
+ @config[:backgrounds] ||= []
57
+ @config[:backgrounds] << options
58
+ end
59
+
60
+ def overlay(name, provider_name)
61
+ @config[:overlays] ||= []
62
+ @config[:overlays] << { name: name, provider_name: provider_name }
63
+ end
64
+
65
+ # def layer(name, list = {})
66
+ # @config[:layers] ||= []
67
+ # @config[:layers] << {name: name, list: list}
68
+ # end
69
+
70
+ def layer(name, serie, options = {})
71
+ options[:label] = name.to_s.humanize unless options[:label]
72
+ name = name.to_s.parameterize.tr('-', '_') unless name.is_a?(Symbol)
73
+ @config[:layers] ||= []
74
+ @config[:layers] << { reference: name.to_s.camelcase(:lower) }.merge(options.merge(name: name, serie: serie.to_s.camelcase(:lower)))
75
+ end
76
+
77
+ def simple(name, serie, options = {})
78
+ layer(name, serie, options.merge(type: :simple))
79
+ end
80
+
81
+ def choropleth(name, serie, options = {})
82
+ layer(name, serie, options.merge(type: :choropleth))
83
+ end
84
+
85
+ def bubbles(name, serie, options = {})
86
+ layer(name, serie, options.merge(type: :bubbles))
87
+ end
88
+
89
+ def heatmap(name, serie, options = {})
90
+ layer(name, serie, options.merge(type: :heatmap))
91
+ end
92
+
93
+ def band(name, serie, options = {})
94
+ layer(name, serie, options.merge(type: :band))
95
+ end
96
+
97
+ def categories(name, serie, options = {})
98
+ layer(name, serie, { colors: @categories_colors }.merge(options.merge(type: :categories)))
99
+ end
100
+
101
+ def paths(name, serie, options = {})
102
+ layer(name, serie, { colors: @categories_colors }.merge(options.merge(type: :paths)))
103
+ end
104
+
105
+ def path(name, serie, options = {})
106
+ layer(name, serie, { colors: @categories_colors }.merge(options.merge(type: :path)))
107
+ end
108
+
109
+ def points(name, serie, options = {})
110
+ layer(name, serie, { colors: @categories_colors }.merge(options.merge(type: :points)))
111
+ end
112
+
113
+ def point_group(name, serie, options = {})
114
+ layer(name, serie, options.merge(type: :point_group))
115
+ end
116
+
117
+ # def multi_points(name, serie, options = {})
118
+ # layer(name, serie, options.merge(type: :multi_points))
119
+ # end
120
+
121
+ # Add a serie of geo data
122
+ def serie(name, data)
123
+ raise StandardError, 'data must be an array. Got: ' + data.class.name unless data.is_a? Array
124
+ @config[:series] ||= {}.with_indifferent_access
125
+ @config[:series][name] = data.compact.collect do |item|
126
+ next unless item[:shape]
127
+ item
128
+ .merge(shape: item[:shape])
129
+ .merge(item[:popup] ? { popup: compile_leaf_popup(item[:popup], item) } : {})
130
+ end.compact
131
+ end
132
+
133
+ # Add a control
134
+ def control(name, options = true)
135
+ @config[:controls] ||= {}.with_indifferent_access
136
+ @config[:controls][name.to_s.camelize(:lower)] = options
137
+ end
138
+
139
+ def to_json
140
+ @config.deep_transform_keys do |key|
141
+ key.to_s.camelize(:lower)
142
+ end.to_json
143
+ end
144
+
145
+ protected
146
+
147
+ # Build a data structure for popup building
148
+ def compile_leaf_popup(object, item)
149
+ if object.is_a?(TrueClass)
150
+ hash = { header: item[:name] }
151
+ for key, value in item
152
+ unless [:header, :footer, :name, :shape].include?(key)
153
+ hash[key] = value.to_s
154
+ end
155
+ end
156
+ compile_leaf_popup(hash, item)
157
+ elsif object.is_a?(String)
158
+ return [{ type: :content, content: object }]
159
+ elsif object.is_a?(Hash)
160
+ blocks = []
161
+ if header = object[:header]
162
+ blocks << compile_block(header, :header, content: item[:name])
163
+ end
164
+ if content = object[:content]
165
+ if content.is_a? String
166
+ blocks << { type: :content, content: content }
167
+ elsif content.is_a? Array
168
+ for value in content
169
+ block = {}
170
+ if value.is_a? String
171
+ block[:content] = value
172
+ elsif value.is_a? Hash
173
+ block.update(value)
174
+ else
175
+ raise "Not implemented array block for #{object.class}"
176
+ end
177
+ if block[:label].is_a?(TrueClass)
178
+ block[:label] = "attributes.#{attribute}".t(default: ["labels.#{attribute}".to_sym, attribute.to_s.humanize])
179
+ elsif !block[:label]
180
+ block.delete(:label)
181
+ end
182
+ blocks << block.merge(type: :content)
183
+ end
184
+ elsif content.is_a? Hash
185
+ for attribute, value in content
186
+ block = {}
187
+ if value.is_a? String
188
+ block[:content] = value
189
+ elsif value.is_a? Hash
190
+ block.update(value)
191
+ elsif value.is_a? TrueClass
192
+ block[:value] = item[attribute].to_s
193
+ block[:label] = true
194
+ end
195
+ if block[:label].is_a?(TrueClass)
196
+ block[:label] = attribute.to_s.humanize
197
+ elsif !block[:label]
198
+ block.delete(:label)
199
+ end
200
+ blocks << block.merge(type: :content)
201
+ end
202
+ else
203
+ raise "Not implemented content for #{content.class}"
204
+ end
205
+ end
206
+ if footer = object[:footer]
207
+ blocks << compile_block(footer, :footer, content: item[:name])
208
+ end
209
+ return blocks
210
+ else
211
+ raise "Not implemented for #{object.class}"
212
+ end
213
+ end
214
+
215
+ def compile_block(*args)
216
+ options = args.extract_options!
217
+ info = args.shift
218
+ type = args.shift || options[:type]
219
+ if info.is_a? String
220
+ block = { type: type, content: info }
221
+ elsif info.is_a? TrueClass
222
+ if options[:content]
223
+ block = { type: type, content: options[:content] }
224
+ else
225
+ raise StandardError, 'Option :content must be given when info is a TrueClass'
226
+ end
227
+ elsif info.is_a? Hash
228
+ block = info.merge(type: type)
229
+ else
230
+ raise StandardError, "Not implemented #{type} for #{object.class}"
231
+ end
232
+ block
233
+ end
234
+ end
235
+ end