@plotdb/chart 0.0.2

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 (177) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +50 -0
  3. package/build +39 -0
  4. package/dist/chart.css +8 -0
  5. package/dist/chart.js +603 -0
  6. package/dist/chart.min.css +1 -0
  7. package/dist/chart.min.js +1 -0
  8. package/dist/data/index.js +73 -0
  9. package/dist/utils/axis.js +357 -0
  10. package/dist/utils/axis.min.js +1 -0
  11. package/dist/utils/config.js +33 -0
  12. package/dist/utils/config.min.js +1 -0
  13. package/dist/utils/data.js +238 -0
  14. package/dist/utils/data.min.js +1 -0
  15. package/dist/utils/format.js +70 -0
  16. package/dist/utils/format.min.js +1 -0
  17. package/dist/utils/legend.css +40 -0
  18. package/dist/utils/legend.js +263 -0
  19. package/dist/utils/legend.min.css +1 -0
  20. package/dist/utils/legend.min.js +1 -0
  21. package/dist/utils/tint.js +138 -0
  22. package/dist/utils/tint.min.js +1 -0
  23. package/dist/utils/tip.js +148 -0
  24. package/dist/utils/tip.min.js +1 -0
  25. package/doc/charts.md +58 -0
  26. package/doc/config.md +44 -0
  27. package/doc/draft/chart.md +146 -0
  28. package/doc/draft/data.md +8 -0
  29. package/doc/draft/definition.md +0 -0
  30. package/doc/draft/events.md +0 -0
  31. package/doc/draft/filter.md +0 -0
  32. package/doc/draft/lifecycle.md +89 -0
  33. package/doc/spec.md +89 -0
  34. package/doc/todo.md +27 -0
  35. package/doc/utils/tip.md +84 -0
  36. package/package.json +83 -0
  37. package/src/chart.ls +320 -0
  38. package/src/chart.styl +6 -0
  39. package/src/data/en-US/category +20 -0
  40. package/src/data/en-US/fruit +39 -0
  41. package/src/data/en-US/movie +125 -0
  42. package/src/data/en-US/name +58 -0
  43. package/src/data/index.ls +34 -0
  44. package/src/data/zh-TW/category +20 -0
  45. package/src/data/zh-TW/fruits +38 -0
  46. package/src/data/zh-TW/movie +125 -0
  47. package/src/data/zh-TW/names +105 -0
  48. package/src/utils/axis.ls +229 -0
  49. package/src/utils/config.ls +11 -0
  50. package/src/utils/data.ls +104 -0
  51. package/src/utils/format.ls +30 -0
  52. package/src/utils/legend.ls +134 -0
  53. package/src/utils/legend.styl +37 -0
  54. package/src/utils/tint.ls +66 -0
  55. package/src/utils/tip.ls +97 -0
  56. package/web/src/pug/base.pug +19 -0
  57. package/web/src/pug/block/axis/0.0.1/index.ls +38 -0
  58. package/web/src/pug/block/axis/0.0.1/index.pug +1 -0
  59. package/web/src/pug/block/axis-bubble/0.0.1/index.ls +141 -0
  60. package/web/src/pug/block/axis-bubble/0.0.1/index.pug +1 -0
  61. package/web/src/pug/block/bar/0.0.1/index.ls +274 -0
  62. package/web/src/pug/block/bar/0.0.1/index.pug +35 -0
  63. package/web/src/pug/block/bar-list/0.0.1/index.ls +85 -0
  64. package/web/src/pug/block/bar-list/0.0.1/index.pug +30 -0
  65. package/web/src/pug/block/bar-race/0.0.1/index.ls +83 -0
  66. package/web/src/pug/block/bar-race/0.0.1/index.pug +21 -0
  67. package/web/src/pug/block/base/0.0.1/index.ls +67 -0
  68. package/web/src/pug/block/base/0.0.1/index.pug +5 -0
  69. package/web/src/pug/block/blank/0.0.1/index.ls +25 -0
  70. package/web/src/pug/block/blank/0.0.1/index.pug +1 -0
  71. package/web/src/pug/block/bubble/0.0.1/index.ls +220 -0
  72. package/web/src/pug/block/bubble/0.0.1/index.pug +28 -0
  73. package/web/src/pug/block/curve-bar/0.0.1/index.ls +89 -0
  74. package/web/src/pug/block/curve-bar/0.0.1/index.pug +16 -0
  75. package/web/src/pug/block/curve-bar/0.0.1/sample.js +338 -0
  76. package/web/src/pug/block/dumbbell/0.0.1/index.ls +90 -0
  77. package/web/src/pug/block/dumbbell/0.0.1/index.pug +1 -0
  78. package/web/src/pug/block/empty/0.0.1/index.ls +27 -0
  79. package/web/src/pug/block/empty/0.0.1/index.pug +2 -0
  80. package/web/src/pug/block/gauge/0.0.1/index.ls +100 -0
  81. package/web/src/pug/block/gauge/0.0.1/index.pug +31 -0
  82. package/web/src/pug/block/gauge/0.0.1/sample.js +338 -0
  83. package/web/src/pug/block/line/0.0.1/index.ls +265 -0
  84. package/web/src/pug/block/line/0.0.1/index.pug +33 -0
  85. package/web/src/pug/block/number/0.0.1/index.ls +69 -0
  86. package/web/src/pug/block/number/0.0.1/index.pug +20 -0
  87. package/web/src/pug/block/percent-list/0.0.1/index.ls +167 -0
  88. package/web/src/pug/block/percent-list/0.0.1/index.pug +34 -0
  89. package/web/src/pug/block/pie/0.0.1/index.ls +125 -0
  90. package/web/src/pug/block/pie/0.0.1/index.pug +19 -0
  91. package/web/src/pug/block/pie/0.0.1/sample.js +338 -0
  92. package/web/src/pug/block/pie-bubble/0.0.1/index.ls +185 -0
  93. package/web/src/pug/block/pie-bubble/0.0.1/index.pug +40 -0
  94. package/web/src/pug/block/radar/0.0.1/index.ls +113 -0
  95. package/web/src/pug/block/radar/0.0.1/index.pug +1 -0
  96. package/web/src/pug/block/radar/0.0.1/xx +285 -0
  97. package/web/src/pug/block/scatter/0.0.1/index.ls +128 -0
  98. package/web/src/pug/block/scatter/0.0.1/index.pug +30 -0
  99. package/web/src/pug/block/sheet/0.0.1/index.ls +38 -0
  100. package/web/src/pug/block/sheet/0.0.1/index.pug +5 -0
  101. package/web/src/pug/block/table/0.0.1/index.ls +40 -0
  102. package/web/src/pug/block/table/0.0.1/index.pug +3 -0
  103. package/web/src/pug/block/taiwantown/0.0.1/index.ls +48 -0
  104. package/web/src/pug/block/taiwantown/0.0.1/index.pug +52 -0
  105. package/web/src/pug/block/taiwanvillage/0.0.1/index.ls +53 -0
  106. package/web/src/pug/block/taiwanvillage/0.0.1/index.pug +52 -0
  107. package/web/src/pug/block/treemap/0.0.1/index.ls +172 -0
  108. package/web/src/pug/block/treemap/0.0.1/index.pug +42 -0
  109. package/web/src/pug/block/voronoi-treemap/0.0.1/index.ls +174 -0
  110. package/web/src/pug/block/voronoi-treemap/0.0.1/index.pug +17 -0
  111. package/web/src/pug/block/voronoi-treemap/0.0.1/sample.js +241 -0
  112. package/web/src/pug/block/wordcloud/0.0.1/index.ls +61 -0
  113. package/web/src/pug/block/wordcloud/0.0.1/index.pug +1 -0
  114. package/web/src/pug/block/worldmap/0.0.1/index.ls +83 -0
  115. package/web/src/pug/block/worldmap/0.0.1/index.pug +12 -0
  116. package/web/src/pug/index.ls +72 -0
  117. package/web/src/pug/index.pug +52 -0
  118. package/web/src/styl/index.styl +0 -0
  119. package/web/static/assets/block/cache.js +4 -0
  120. package/web/static/assets/custom-lib/d3/d3-array.v2.min.js +2 -0
  121. package/web/static/assets/custom-lib/d3/d3-axis.v2.min.js +2 -0
  122. package/web/static/assets/custom-lib/d3/d3-brush.v2.min.js +2 -0
  123. package/web/static/assets/custom-lib/d3/d3-color.v2.min.js +2 -0
  124. package/web/static/assets/custom-lib/d3/d3-dispatch.v2.min.js +2 -0
  125. package/web/static/assets/custom-lib/d3/d3-drag.v2.min.js +2 -0
  126. package/web/static/assets/custom-lib/d3/d3-ease.v2.min.js +2 -0
  127. package/web/static/assets/custom-lib/d3/d3-force-boundary.v0.0.1.min.js +2 -0
  128. package/web/static/assets/custom-lib/d3/d3-force.v2.min.js +2 -0
  129. package/web/static/assets/custom-lib/d3/d3-format.v2.min.js +2 -0
  130. package/web/static/assets/custom-lib/d3/d3-hierarchy.v2.min.js +2 -0
  131. package/web/static/assets/custom-lib/d3/d3-interpolate.v2.min.js +2 -0
  132. package/web/static/assets/custom-lib/d3/d3-path.v2.min.js +2 -0
  133. package/web/static/assets/custom-lib/d3/d3-quadtree.v2.min.js +2 -0
  134. package/web/static/assets/custom-lib/d3/d3-random.v2.min.js +2 -0
  135. package/web/static/assets/custom-lib/d3/d3-scale-chromatic.v1.min.js +2 -0
  136. package/web/static/assets/custom-lib/d3/d3-selection.v2.min.js +2 -0
  137. package/web/static/assets/custom-lib/d3/d3-shape.v2.min.js +2 -0
  138. package/web/static/assets/custom-lib/d3/d3-timer.v2.min.js +2 -0
  139. package/web/static/assets/custom-lib/d3/d3-transition.v2.min.js +2 -0
  140. package/web/static/assets/custom-lib/d3/d3.v4.js +17178 -0
  141. package/web/static/assets/custom-lib/d3/d3.v4.min.js +2 -0
  142. package/web/static/assets/data/countrycode.json +1 -0
  143. package/web/static/assets/data/world-110m.json +1 -0
  144. package/web/static/assets/data/zh-to-alpha2.json +1 -0
  145. package/web/static/assets/img/fluid.svg +16 -0
  146. package/web/static/block/axis/0.0.1/index.html +79 -0
  147. package/web/static/block/axis-bubble/0.0.1/index.html +222 -0
  148. package/web/static/block/bar/0.0.1/index.html +581 -0
  149. package/web/static/block/bar-list/0.0.1/index.html +231 -0
  150. package/web/static/block/bar-race/0.0.1/index.html +184 -0
  151. package/web/static/block/base/0.0.1/index.html +197 -0
  152. package/web/static/block/blank/0.0.1/index.html +42 -0
  153. package/web/static/block/bubble/0.0.1/index.html +402 -0
  154. package/web/static/block/bubble/0.0.1/test.html +0 -0
  155. package/web/static/block/curve-bar/0.0.1/index.html +148 -0
  156. package/web/static/block/dumbbell/0.0.1/index.html +178 -0
  157. package/web/static/block/empty/0.0.1/index.html +76 -0
  158. package/web/static/block/gauge/0.0.1/index.html +150 -0
  159. package/web/static/block/line/0.0.1/index.html +540 -0
  160. package/web/static/block/number/0.0.1/index.html +152 -0
  161. package/web/static/block/percent-list/0.0.1/index.html +316 -0
  162. package/web/static/block/pie/0.0.1/index.html +281 -0
  163. package/web/static/block/pie-bubble/0.0.1/index.html +358 -0
  164. package/web/static/block/radar/0.0.1/index.html +149 -0
  165. package/web/static/block/scatter/0.0.1/index.html +251 -0
  166. package/web/static/block/sheet/0.0.1/index.html +103 -0
  167. package/web/static/block/table/0.0.1/index.html +73 -0
  168. package/web/static/block/taiwantown/0.0.1/index.html +210 -0
  169. package/web/static/block/taiwanvillage/0.0.1/index.html +220 -0
  170. package/web/static/block/treemap/0.0.1/index.html +360 -0
  171. package/web/static/block/voronoi-treemap/0.0.1/index.html +350 -0
  172. package/web/static/block/wordcloud/0.0.1/index.html +92 -0
  173. package/web/static/block/worldmap/0.0.1/index.html +207 -0
  174. package/web/static/css/index.css +0 -0
  175. package/web/static/css/index.min.css +0 -0
  176. package/web/static/favicon.ico +0 -0
  177. package/web/static/index.html +153 -0
@@ -0,0 +1,229 @@
1
+ ceNS = -> document.createElementNS("http://www.w3.org/2000/svg", it)
2
+
3
+ axis = (opt={}) ->
4
+ @dir = {"bottom": 0, "left": 2}[opt.direction or 'bottom']
5
+ @name = opt.name or \axis
6
+ # unlike legend, we don't use n.layout to layout things, only use its primary size.
7
+ # we will update n.layout's secondary size, it won't affect how we layout things.
8
+ # so we don't need layout.update before rendering.
9
+ # layout.update is still needed after rendering for stuff outside axis to work.
10
+ # TODO still yaxis and xaxis may affect each other to cause a recursive update,
11
+ # so we have to be careful and have some constraint over this. (e.g., set max size of n.layout)
12
+ @layout = opt.layout or new layout root: @root
13
+ @_ticks = []
14
+ @_scale = opt.scale or (->it)
15
+ @_format = opt.format or if chart? => chart.utils.format.simple else (->it)
16
+ @cfg = {tick-size: 8} <<< opt.cfg
17
+ @_map = {} # tick value -> object{v,c,t,n,g}
18
+ @_n = n =
19
+ root: ceNS \g # root node, appended to render node
20
+ layout: @layout.get-node(@name) # decide render region size
21
+ render: @layout.get-group(@name) # render region node
22
+ wrap: null # for wrapping tick text
23
+ domain: ceNS \path # axis domain line
24
+ n.render.appendChild n.root
25
+ n.root.appendChild n.domain
26
+
27
+ n.label = ceNS \text
28
+ n.label.textContent = ''
29
+ n.label
30
+ ..setAttribute \dominant-baseline, \hanging
31
+ ..setAttribute \text-anchor, \middle
32
+ n.root.appendChild n.label
33
+ @wrapper!
34
+ @layout
35
+ ..on \update, ~> @update!
36
+ ..on \render, ~> @render!
37
+ @
38
+
39
+
40
+ axis.prototype = Object.create(Object.prototype) <<< do
41
+ # while we may only need to update `white-space` and `width`,
42
+ # strangely style changing only works if we replace node with a new one.
43
+ # this is a workaround before we find the root cause.
44
+ wrapper: ->
45
+ n = @_n
46
+ if n.wrap and n.wrap.parentNode => n.wrap.parentNode.removeChild n.wrap
47
+ n.wrap = document.createElement \div
48
+ n.wrap.style <<< {
49
+ visibility: \hidden
50
+ position: \absolute
51
+ top: 0
52
+ left: 0
53
+ pointerEvents: \none
54
+ userSelect: \none
55
+ zIndex: 0
56
+ wordBreak: "break-all"
57
+ lineHeight: \.95em
58
+ } <<< (if @dir < 2 and @cfg.textWidth? => {
59
+ whiteSpace: 'wrap'
60
+ width: "#{@cfg.textWidth >? 1}em"
61
+ } else {whiteSpace: \pre, width: \4em})
62
+ n.layout.appendChild n.wrap
63
+
64
+ config: ->
65
+ if !(it?) => return @cfg
66
+ @cfg <<< it
67
+ if @cfg.format => @format @cfg.format
68
+ @wrapper!
69
+
70
+ format: -> if it? => @_format = it else @_format
71
+ scale: -> if it? => @_scale = it else @_scale
72
+ caption: ->
73
+ if @cfg.show-caption? and !@cfg.show-caption => @_n.label.textContent = ''
74
+ else @_n.label.textContent = it
75
+ update: ->
76
+ ticks: (ticks) ->
77
+ /* (v)alue -> (c)oordinate -> (t)ext -> (n)odes: { g(group), p(path), t(text) } */
78
+ map = {}
79
+ size = @_n.layout.getBoundingClientRect![if @dir < 2 => \width else \height]
80
+ count = Math.round(size / 25)
81
+ itr = Math.round(ticks.length / (count >? 1)) >? 1
82
+ @_ticks = ticks
83
+ .filter (d,i) -> !(i % itr) or i == (ticks.length - 1)
84
+ .map (o) ~>
85
+ if typeof(o) != \object => o = {v: o}
86
+ map[o.v] = @_map{}[o.v] <<< o
87
+ for k,v of @_map => if !map[k] => v.n.g.parentNode.removeChild v.n.g
88
+ @_map = map
89
+ @_ticks
90
+ .filter (o) -> !o.n
91
+ .map (o) ~>
92
+ o.n = g: ceNS(\g), p: ceNS(\path), t: ceNS(\g)
93
+ o.n.g.appendChild o.n.p
94
+ o.n.g.appendChild o.n.t
95
+ @_n.root.appendChild o.n.g
96
+ if isNaN(+o.v) => o.t = o.v
97
+
98
+ render: ->
99
+ n = @_n
100
+ is-horizon = (@dir < 2)
101
+ wrap = n.wrap
102
+ ticks = @_ticks
103
+ if @cfg.enabled? and !@cfg.enabled =>
104
+ if is-horizon => n.layout.style <<< height: "0px", paddingTop: "0", paddingBottom: "0"
105
+ else n.layout.style <<< width: "0px", paddingLeft: "0", paddingRight: "0"
106
+ n.domain.style.display = 'none'
107
+ return
108
+ else
109
+ if is-horizon => n.layout.style <<< height: "", paddingTop: "", paddingBottom: ""
110
+ else n.layout.style <<< width: "", paddingLeft: "", paddingRight: ""
111
+ n.domain.style.display = ''
112
+ font-size = getComputedStyle(wrap).font-size
113
+ box = n.layout.getBoundingClientRect!
114
+ n.domain.style <<< strokeWidth: 1, stroke: \currentColor, fill: \none
115
+ ticks.map (o, idx) ~>
116
+ pos = if !idx => 0 else if idx == ticks.length - 1 => 2 else 1
117
+ o._c = if o.c? => o.c else @_scale(o.v) + (if @_scale.bandwidth => @_scale.bandwidth!/2 else 0)
118
+ o._t = if o.t? => o.t else @_format o.v
119
+ o.n.p.style <<< strokeWidth: 1, stroke: \currentColor, fill: \none
120
+ wrap.textContent = o._t
121
+ wrap.style.textAlign = if is-horizon => <[left center right]>[pos] else <[right left]>[@dir - 2]
122
+ o.n.tt = wrap-svg-text {node: wrap}
123
+ o.n.t.style.font-size = font-size
124
+ o.n.t.textContent = ''
125
+ o.n.t.appendChild o.n.tt
126
+ ticks.sort (a,b) -> a._c - b._c
127
+
128
+ if is-horizon =>
129
+ n.domain.setAttribute \d, "M0 0L#{box.width} 0Z"
130
+ n.label.setAttribute \x, box.width / 2
131
+ else
132
+ n.domain.setAttribute \d, "M0 0L0 #{box.height}Z"
133
+ n.label.setAttribute "transform", "translate(0,#{box.height/2}) rotate(90)"
134
+ ticks.map (o) -> o.box = o.n.t.getBBox!
135
+ @adjust!
136
+
137
+ adjust: ->
138
+ ticks = @_ticks
139
+ is-horizon = (@dir < 2)
140
+ insert = (idx, lv, lc) ~>
141
+ if lc.checked[idx] => return else lc.checked[idx] = true
142
+ tick = ticks[idx]
143
+ # 0: smallest 'screen coord' tick / 1: middle tick / 2: biggest 'screen coord' tick
144
+ # top(0) -> down(2) / left(0) -> right(0)
145
+ pos = if !idx => 0 else if idx == ticks.length - 1 => 2 else 1
146
+
147
+ [coord,text] = [tick._c, tick._t]
148
+ box = tick.box
149
+ offset = {x: -box.x, y: -box.y}
150
+ do-offset = if @_scale.bandwidth => false
151
+ else if @cfg.boundary-tick-offset? => !!@cfg.boundary-tick-offset
152
+ else if @dir < 2 => true else false
153
+ r = if do-offset => pos/2 else 1/2
154
+ if is-horizon => offset.x -= box.width * r # start: 0w, middle: -.5w, end: -w ( left to right )
155
+ else offset.y -= box.height * r # start: 0h, middle: .5h, end: -h ( top to bottom )
156
+ tick.n.tt.setAttribute \transform, "translate(#{offset.x},#{offset.y})"
157
+
158
+ box =
159
+ size: box[if is-horizon => \width else \height]
160
+ size-alt: box[if is-horizon => \height else \width]
161
+ lv: lv, tick: tick, idx: idx
162
+ box.coord = coord - (box.size * r) #(if is-horizon => pos else (2 - pos)) / 2)
163
+
164
+ if is-horizon =>
165
+ tick.n.g.setAttribute \transform, "translate(#{coord},0)"
166
+ tick.n.p.setAttribute \d, "M0 0L0 #{@cfg.tick-size}"
167
+ tick.n.t.setAttribute \transform, "translate(0,#{@cfg.tick-size})"
168
+ else
169
+ tick.n.g.setAttribute \transform, "translate(0,#{coord})"
170
+ tick.n.p.setAttribute \d, "M0 0L-#{@cfg.tick-size} 0"
171
+ tick.n.t.setAttribute \transform, "translate(#{-@cfg.tick-size - box.size-alt - 1},0)"
172
+
173
+ m = 2
174
+ [ip1, ip2] = [ box.coord, box.coord + box.size ]
175
+ [op1, op2] = [ ip1 - m, ip2 + m]
176
+
177
+ overlap = lc.picked
178
+ .filter (b) ->
179
+ [iq1, iq2] = [b.coord, b.coord + b.size]
180
+ [oq1, oq2] = [iq1 - m, iq2 + m]
181
+ return (ip1 > oq1 and ip1 < oq2) or (iq1 > op1 and iq1 < op2)
182
+ .length
183
+
184
+ tick.n.t.style.opacity = if overlap => 0 else 1
185
+ lc.picked.push box
186
+ return !overlap # return true if insert successfully
187
+
188
+ # align lv ( see below commented codes )
189
+ # try to only show ticks in the same level.
190
+ # yet current implementation doesn't work well
191
+ # since we may have different size in each interval even in the same level
192
+
193
+ bfs = (maxlv = 1) ~>
194
+ [lc,fifo] = [{checked:{}, picked: [], node: @_n.wrap}, []]
195
+ lc.font-size = getComputedStyle(lc.node).font-size
196
+ # align lv
197
+ ticks.map -> it.n.t.style.opacity = 0
198
+ if ticks.length =>
199
+ fifo.push [0, ticks.length - 1, 0]
200
+ [0, ticks.length - 1].map -> insert it, 0, lc
201
+ while fifo.length =>
202
+ [L, R, D] = fifo.splice 0, 1 .0
203
+ if D >= maxlv => continue
204
+ M = Math.floor(( L + R ) / 2)
205
+ # align lv
206
+ #if !insert(M, D, lc) => maxlv = D
207
+ insert(M, D, lc)
208
+ if M > L + 1 => fifo.push [L, M, D + 1]
209
+ if R > M + 1 => fifo.push [M, R, D + 1]
210
+
211
+ /* align lv
212
+ lc.picked.map -> if it.lv >= maxlv => it.tick.n.t.style.opacity = 0
213
+ lc.picked = lc.picked.filter -> it.lv < maxlv
214
+ */
215
+
216
+ size = Math.max.apply(Math, [0] ++ lc.picked.map(-> it.size-alt))
217
+ if @dir < 2 =>
218
+ @_n.label.setAttribute \y, size + @cfg.tick-size
219
+ @_n.layout.style.height = "#{(size + @cfg.tick-size + @_n.label.getBoundingClientRect!height)}px"
220
+ else
221
+ @_n.label.setAttribute \x, 0
222
+ @_n.label.setAttribute \y, 0
223
+ @_n.layout.style.width = "#{(size + @cfg.tick-size + @_n.label.getBoundingClientRect!width)}px"
224
+ box = @_n.layout.getBoundingClientRect!
225
+ @_n.label.setAttribute \transform, "translate(#{-(size + @cfg.tick-size)},#{box.height/2}) rotate(90)"
226
+ @_n.root.setAttribute \transform "translate(#{box.width},0)"
227
+ bfs 5
228
+
229
+ if chart? => chart.{}utils.axis = axis
@@ -0,0 +1,11 @@
1
+ config = preset:
2
+ default:
3
+ margin: type: \number
4
+ font:
5
+ size: type: \number, min: 1, max: 256, step: 1, default: 16
6
+ color: type: \color
7
+ palette: type: \palette
8
+ background: type: \color
9
+
10
+ if chart? => chart.{}utils.config = config
11
+
@@ -0,0 +1,104 @@
1
+ main =
2
+ number: (opt = {}) ->
3
+ data = opt.data
4
+ .filter -> !(!it or "#{it}".trim! == '')
5
+ len = data
6
+ .filter -> !isNaN(parseFloat(it))
7
+ .length
8
+ (len / data.length)
9
+
10
+ serial: (opt = {}) ->
11
+ data = [] ++ opt.data
12
+ data.sort (a,b) -> b - a
13
+ hash = {}
14
+ for i from 1 til data.length =>
15
+ if isNaN(data[i]) or isNaN(data[i - 1]) =>
16
+ hash[if data[i] > data[i - 1] => "#{data[i]}:#{data[i - 1]}" else "#{data[i - 1]}:#{data[i]}"] = true
17
+ else delta = data[i] - data[i - 1]
18
+ hash[delta] = true
19
+ return 1 / ([k for k of hash].filter(->it != \0).length or 2)
20
+
21
+ text: (opt={}) -> 1 - (@number opt)
22
+
23
+ category: (opt={}) ->
24
+ len = Array.from(new Set(opt.data)).length
25
+ maxlen = opt.data.length
26
+ ret = (1 - (((len - 2) >? 0) / maxlen) - (1 / maxlen)) >? 0 <? 1
27
+ if @number(opt) => ret = ret * 0.9
28
+ return ret
29
+
30
+ parse: (data) ->
31
+ if Array.isArray(data) =>
32
+ head = data.slice 0, 1 .0
33
+ body = data.slice 1
34
+ body = body.map (d,i) ->
35
+ ret = {}
36
+ head.map (h,j) -> ret[h] = d[j]
37
+ ret
38
+ return {head, body}
39
+ return data
40
+
41
+ autotype: (data = []) ->
42
+ head = [k for k of data.0 or {}]
43
+ type = []
44
+ for key in head =>
45
+ d = data.map -> it[key]
46
+ ret = do
47
+ key: key
48
+ types:
49
+ R: main.number({data: d})
50
+ O: main.serial({data: d})
51
+ N: main.text({data: d})
52
+ C: main.category({data: d})
53
+ list = <[R O N C]>.map -> [it, ret.types[it]]
54
+ list.sort (a,b) -> b.1 - a.1
55
+ ret.type = list.0.0
56
+ type.push ret
57
+
58
+ return type
59
+
60
+ autobind: (data = [], dimension = {}) ->
61
+ datatypes = @autotype data
62
+ dims = [{k,v} for k,v of dimension].filter -> !it.v.passive
63
+ dims.sort (a,b) ->
64
+ ret = (a.v.priority or 100) - (b.v.priority or 100)
65
+ if ret != 0 => return ret
66
+ [ma,mb] = [(a.v.type or \R), (b.v.type or \R)].map (t) ->
67
+ Math.min.apply Math, [0 til t.length].map (i) -> "CONR".indexOf(t[i])
68
+ return ma - mb
69
+ for dim in dims =>
70
+ dim.bind = null
71
+ ts = dim.v.type or \RNOC
72
+ for i from 0 til ts.length =>
73
+ t = ts[i]
74
+ datatypes.sort (a, b) ->
75
+ ret = (b.types[t] or 0) - (a.types[t] or 0)
76
+ if b.types[t] == a.types[t] and t == \R => return (a.types.O or 0) - (b.types.O or 0)
77
+ return ret
78
+ for i from 0 til datatypes.length =>
79
+ dt = datatypes[i]
80
+ if dt.types[t] < 0.5 or dt.used or (t == \C and dt.types.R > 0.5) => continue
81
+ dim.bind = if dim.v.multiple => [dt] else dt
82
+ dt.used = true
83
+ break
84
+ for dim in dims =>
85
+ if !dim.v.multiple => continue
86
+ ts = dim.v.type or \RNOC
87
+ for i from 0 til ts.length =>
88
+ t = ts[i]
89
+ datatypes.sort (a, b) ->
90
+ ret = (b[t] or 0) - (a[t] or 0)
91
+ if b[t] == a[t] and t == \R => return (a.O or 0) - (b.O or 0)
92
+ return ret
93
+ for i from 0 til datatypes.length =>
94
+ dt = datatypes[i]
95
+ if dt.types[t] < 0.5 or dt.used or (t == \C and dt.types.R > 0.5) => continue
96
+ dim.bind.push dt
97
+ dt.used = true
98
+ ret = {}
99
+ for dim in dims => if dim.bind =>
100
+ ret[dim.k] = dim.bind
101
+ (if Array.isArray(dim.bind) => dim.bind else [dim.bind]).map -> delete it.used
102
+ return ret
103
+
104
+ if chart? => chart.{}utils.data = main
@@ -0,0 +1,30 @@
1
+ format = {}
2
+ format.auto = (list = []) ->
3
+ return d3.format ".3s"
4
+ list = list.map(->+it).filter -> !isNaN(it)
5
+ if !list.length => return d3.format "d"
6
+ list.sort (a,b) -> a - b
7
+ s = Math.max.apply Math, list.map ->
8
+ s = 1
9
+ while it
10
+ if it % 10 => break
11
+ it = Math.floor(it / 10)
12
+ s++
13
+ Math.ceil((Math.log(it or 1) / Math.log(10)) - s) >? 0
14
+ int-rate = list.filter(-> Math.floor(it) == it).length / list.length
15
+ diffs = [Math.abs(list[i] - list[i - 1]) for i from 1 til list.length].map ->
16
+ Math.log(if it => it else 1) / Math.log(10)
17
+ diff-avg = diffs.reduce(((a,b) -> a + b),0) / list.length
18
+ type = if int-rate > 0.9 => "s" else "f"
19
+ size = (if diff-avg >= 0 => (s >? 0) else Math.ceil(Math.abs(diff-avg)))
20
+ return d3.format ".#size#type"
21
+
22
+ format.simple = (v) ->
23
+ if isNaN(v = +v) => return v
24
+ return if v >= 1000000000 => (v / 1000000000).toFixed(0) + "G"
25
+ else if v >= 1000000 => (v / 1000000).toFixed(0) + "M"
26
+ else if v >= 1000 => (v / 1000).toFixed(0) + "K"
27
+ else if Math.floor(v) == v => (v).toFixed(0)
28
+ else v.toFixed(2)
29
+
30
+ if chart? => chart.{}utils.format = format
@@ -0,0 +1,134 @@
1
+ svgns = "http://www.w3.org/2000/svg"
2
+
3
+ legend = (opt = {}) ->
4
+ @_data = opt.data or []
5
+ @name = opt.name or \legend
6
+ @cfg = opt.cfg or {}
7
+ @root = if typeof(opt.root) == \string => document.querySelector(opt.root) else opt.root
8
+ @dir = opt.direction or 'vertical'
9
+ @evt-handler = {}
10
+ @_picked = Object.fromEntries @_data.map -> [it.key, true]
11
+ @layout = opt.layout or new layout root: @root
12
+ @layout
13
+ ..on \update, ~> @update!
14
+ ..on \render, ~> @render!
15
+ @id = Math.random!toString(36)substring(2)
16
+ node-layout = @layout.get-node @name
17
+ node-render = @layout.get-group @name
18
+ if !node-layout.querySelector('[ld-each=legend]') =>
19
+ node-layout.innerHTML = """
20
+ <div class="pdlg #{@dir}">
21
+ <div ld-each="legend">
22
+ <div class="pdl-cell" ld="shape" data-only></div>
23
+ <div class="pdl-cell" ld="text" data-only></div>
24
+ </div>
25
+ </div>
26
+ """
27
+ if !node-render.querySelector('[ld-each=legend]') =>
28
+ gl = document.createElementNS(svgns, "g")
29
+ gs = document.createElementNS(svgns, "g")
30
+ gt = document.createElementNS(svgns, "g")
31
+ gl.classList.add \pdlg-g
32
+ gl.setAttribute \ld-each, \legend
33
+ gs.setAttribute \ld, \shape
34
+ gt.setAttribute \ld, \text
35
+ gl.appendChild gs
36
+ gl.appendChild gt
37
+ node-render.appendChild gl
38
+
39
+ @_func = [
40
+ {key: "all", type: "func", text: "全選"}
41
+ {key: "none", type: "func", text: "全不選"}
42
+ ]
43
+
44
+ @view =
45
+ layout: new ldview do
46
+ root: @layout.get-node @name
47
+ init-render: false
48
+ handler:
49
+ legend:
50
+ list: ~> @_data ++ (if @cfg.selectable => @_func else [])
51
+ key: -> it.key
52
+ view:
53
+ handler:
54
+ shape: ({node, ctx}) ~>
55
+ node.setAttribute \data-name, @_name ctx, "shape"
56
+ text: ({node, ctx}) ~>
57
+ node.setAttribute \data-name, @_name ctx, "text"
58
+ node.innerText = ctx.text
59
+
60
+
61
+ render: new ldview do
62
+ root: @layout.get-group @name
63
+ init-render: false
64
+ handler:
65
+ legend:
66
+ list: ~> @_data ++ (if @cfg.selectable => @_func else [])
67
+ key: -> it.key
68
+ view:
69
+ action: click: "@": ({node, ctx}) ~>
70
+ if !@cfg.selectable => return
71
+ if ctx.type == \func =>
72
+ for k of @_picked => @_picked[k] = if ctx.key == \all => true else false
73
+ else @_picked[ctx.key] = !@_picked[ctx.key]
74
+ @view.render.render \legend
75
+ @fire \select, @_picked
76
+ handler:
77
+ "@": ({node, ctx}) ~>
78
+ node.style.opacity = if !@cfg.selectable or ctx.type == \func or (@_picked[ctx.key]) => 1 else 0.3
79
+ "shape": ({node, ctx}) ~>
80
+ if ctx.type == \func =>
81
+ node.style.display = \none
82
+ return
83
+ if !node.querySelector \circle => node.appendChild document.createElementNS(svgns, \circle)
84
+ n = node.querySelector \circle
85
+ box = @layout.get-box @_name(ctx, \shape)
86
+ rbox = @layout.get-box @name
87
+ n.setAttribute \cx, ".5em"
88
+ n.setAttribute \cy, ".5em"
89
+ n.setAttribute \r, ".5em"
90
+ n.setAttribute \transform, "translate(#{box.x - rbox.x}, #{box.y - rbox.y})"
91
+ if typeof(opt.shape) == \function => opt.shape.call n, ctx
92
+
93
+ "text": ({node,ctx}) ~>
94
+ n = @layout.get-node(@_name ctx, \text)
95
+ nbox = @layout.get-box(@_name ctx, \text)
96
+ rbox = @layout.get-box(@name)
97
+ node.setAttribute \transform, "translate(#{nbox.x - rbox.x}, #{nbox.y - rbox.y})"
98
+ node.textContent = ""
99
+ ret = wrap-svg-text node: n, use-range: true
100
+ node.appendChild ret
101
+ @update!
102
+ @
103
+
104
+ legend.prototype = Object.create(Object.prototype) <<< do
105
+ _name: (ctx,type="s") -> "pdlg-#{type}-#{@id}-#{ctx.type or ''}-#{ctx.key}"
106
+ enabled: -> @_data and @_data.length and (!(@cfg.enabled?) or @cfg.enabled)
107
+ update: ->
108
+ val = if @enabled! => '' else '0px'
109
+ @layout.get-node \legend .style <<< {width: val, height: val, padding: val, margin: val}
110
+ @view.layout.render!
111
+ render: ->
112
+ enabled = @enabled!
113
+ g = @layout.get-group(@name)
114
+ g.style.display = if enabled => '' else 'none'
115
+ if enabled => @view.render.render!
116
+ config: (cfg) ->
117
+ if !(cfg?) => return @cfg
118
+ @cfg = cfg
119
+ @dir = if @cfg.position == \bottom => \horizontal else \vertical
120
+ node = @layout.get-node(@name).querySelector \.pdlg
121
+ <[horizontal vertical]>.map ~> node.classList.toggle it, @dir == it
122
+ data: (data = []) ->
123
+ hash = {}
124
+ data.map (d,i) -> hash[d.key] = i
125
+ @_data = data.filter (d,i) -> hash[d.key] == i
126
+ @_picked = Object.fromEntries @_data.map (d) -> [d.key, true]
127
+ @update!
128
+ @layout.update false
129
+ @view.render.render!
130
+ on: (n, cb) -> (if Array.isArray(n) => n else [n]).map (n) ~> @evt-handler.[][n].push cb
131
+ fire: (n, ...v) -> for cb in (@evt-handler[n] or []) => cb.apply @, v
132
+ is-selected: (key) -> return !!@_picked[key]
133
+
134
+ if chart? => chart.{}utils.legend = legend
@@ -0,0 +1,37 @@
1
+ .pdlg
2
+ height: 100%
3
+ width: 100%
4
+ display: flex
5
+ align-items: start
6
+ justify-content: center
7
+
8
+ .pdlg > div
9
+ display: flex
10
+ align-items: center
11
+ line-height: 1em
12
+ [ld=shape]
13
+ display: inline-block
14
+ width: 1em
15
+ height: 1em
16
+ margin-right: .5em
17
+ background: #999
18
+ flex: 0 0 auto
19
+ line-height: 1em
20
+ [ld=text]
21
+ color: #999
22
+ padding-right: .25em
23
+ line-height: 1.2em
24
+
25
+ .pdlg.vertical
26
+ flex-direction: column
27
+ align-items: start
28
+ & > div
29
+ margin-bottom: .5em
30
+
31
+ .pdlg.horizontal
32
+ & > div
33
+ margin-right: .5em
34
+
35
+ g.pdlg-g
36
+ cursor: pointer
37
+ transition: opacity .15s ease-in-out
@@ -0,0 +1,66 @@
1
+ tint = (opt = {}) ->
2
+ @is-unique = if opt.unique? => opt.unique else true
3
+ @map = {} # calculated name to index map
4
+ @nmap = [] # predefined name to index map
5
+ @wm = new WeakMap!
6
+ @count = 0
7
+ @set opt.palette or opt.colors
8
+ @
9
+
10
+ tint.default = <[#e15c64 #f7b26a #f3ed9d #a4bd81 #a0d7d2 #8db1da #daaedb]>
11
+
12
+ tint.prototype = Object.create(Object.prototype) <<< do
13
+ set: (pal = [], reset = false) ->
14
+ @colors = if Array.isArray(pal) and pal.length => pal
15
+ else if pal.colors and Array.isArray(pal.colors) => pal.colors
16
+ else tint.default
17
+ @nmap = pal.maps or []
18
+ @colors = @colors.map -> it.value or it
19
+ if @colors.length == 1 => @colors.push ldcolor.complement(@colors.0)
20
+ @colors = @colors.map -> ldcolor.hsl(it)
21
+ @len = @colors.length >? 2
22
+ if reset => @reset!
23
+ else @nmap.map (d,i) ~> (if Array.isArray(d) => d else [d]).map ~> @get it, 0, i
24
+
25
+ unique: -> return if it? => @is-unique = !!it else @is-unique
26
+ reset: ->
27
+ @map = {}
28
+ @wm = new WeakMap!
29
+ @count = 0
30
+ (@nmap or []).map (d,i) ~> (if Array.isArray(d) => d else [d]).map ~> @get it, 0, i
31
+ get: (data, variant = 0, idx) ->
32
+ if idx? =>
33
+ if @count < idx => @count = idx + 1
34
+ val = idx % @len
35
+ if typeof(data) == \object => @wm.set data, val
36
+ else @map[data] = val
37
+ else
38
+ if typeof(data) == \object =>
39
+ if !(val = @wm.get data)? => @wm.set data, (val = @count++)
40
+ else if !(val = @map[data])? => @map[data] = (val = @count++)
41
+ if !@is-unique => val = val % @len
42
+ if val >= @len =>
43
+ seg = val % ( @len - 1 )
44
+ pos = Math.floor(val / (@len - 1))
45
+ [fz,fm] = [0,1]
46
+ while pos > 0
47
+ [fz,fm] = [fz * 2, fm * 2]
48
+ if pos % 2 => fz += 1
49
+ pos = pos .>>. 1
50
+ pos = fz/fm
51
+ [c1,c2] = [@colors[seg], @colors[seg + 1]]
52
+ [h,s,l] = <[h s l]>.map (k) ->
53
+ pos * (c2[k] - c1[k]) + c1[k]
54
+ ret = {h,s,l}
55
+ else ret = @colors[val]
56
+ if variant > 0 => ret = ldcolor.lighter(ret, variant)
57
+ else if variant < 0 => ret = ldcolor.darker(ret, -variant)
58
+ return ldcolor.web(ret)
59
+
60
+ text: (v, opt = {}) ->
61
+ hcl = ldcolor.hcl(@get v)
62
+ return ldcolor.web(if hcl.l > 50 => (opt.text-dark or \#000) else (opt.text-light or \#fff))
63
+
64
+
65
+
66
+ if chart? => chart.{}utils.tint = tint