foliage 0.1.0

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.
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