@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,97 @@
1
+ tip = (opt = {}) ->
2
+ @root = if typeof(opt.root) == \string => document.querySelector(opt.root) else opt.root
3
+ @tip = if typeof(opt.node) == \string => @root.querySelector(opt.node) else opt.node
4
+ @margin = if opt.margin? => opt.margin else 10
5
+ @type = if opt.type == \over => \over else \move
6
+ @accessor = opt.accessor
7
+ @range = opt.range
8
+ @zmgr = opt.zmgr
9
+ @_z = 0
10
+ @enabled = true
11
+ if !@tip =>
12
+ @tip = document.createElement \div
13
+ @tip.innerHTML = """
14
+ <div ld="name" style="font-size:.85em"></div>
15
+ <div ld="value" style="font-weight:bold;font-size:1.1em;line-height:1.2em"></div>
16
+ """
17
+ @root.appendChild @tip
18
+ @tip.style <<<
19
+ padding: '.4em .5em'
20
+ background: \#13171a
21
+ color: \#fff
22
+ borderRadius: \3px
23
+ boxShadow: '0 2px 4px rgba(0,0,0,.3)'
24
+ lineHeight: \1.2em
25
+ fontSize: \.9em
26
+ maxWidth: "10em"
27
+ wordBreak: "break-all"
28
+
29
+ @tip.style <<<
30
+ position: \absolute
31
+ z-index: 1
32
+ top: 0
33
+ left: 0
34
+ pointerEvents: \none
35
+ userSelect: \none
36
+ opacity: 0
37
+ transition: 'opacity .15s ease-in-out' + (if @type == \over => ', transform .15s ease-in-out' else '')
38
+
39
+ @view = new ldview do
40
+ root: @tip
41
+ text: opt.view or {
42
+ name: ({ctx}) -> if ctx => ctx.name else ''
43
+ value: ({ctx}) -> if ctx => ctx.value else ''
44
+ }
45
+ @hide = debounce 3000, ->
46
+ @tip.style <<< opacity: 0
47
+ if @zmgr => @zmgr.remove @_z
48
+ @_evthdr = {}
49
+ if @accessor =>
50
+ @root.addEventListener (if @type == \over => \mouseover else \mousemove), @_evthdr.over = (e) ~>
51
+ if !(e.target and data = @accessor {evt: e}) => return
52
+ @render {data, node: e.target, evt: e}
53
+ @root.addEventListener \mouseout, @_evthdr.out = ~> @hide!now!
54
+
55
+ @
56
+
57
+ tip.prototype = Object.create(Object.prototype) <<<
58
+ destroy: ->
59
+ if @_evthdr.over => @root.removeEventListener (if @type == \over => \mouseover else \mousemove), @_evthdr.over
60
+ if @_evthdr.out => @root.removeEventListener \mouseout, @_evthdr.out
61
+ toggle: -> return if !(it?) => @enabled else @enabled = it
62
+ render: (opt = {}) ->
63
+ if !@enabled => return
64
+ @view.set-ctx opt.data
65
+ @view.render!
66
+ rbox = @root.getBoundingClientRect!
67
+ nbox = if opt.node? =>
68
+ ret = opt.node.getBoundingClientRect!
69
+ ret <<< {x: ret.x - rbox.x, y: ret.y - rbox.y}
70
+ else if opt.box? => opt.box
71
+ else {x: opt.x, y: opt.y, width: opt.width, height: opt.height}
72
+ tbox = @tip.getBoundingClientRect!
73
+ [mx, my] = [opt.evt.clientX - rbox.x, opt.evt.clientY - rbox.y] # point relative to root origin
74
+ m = @margin # margin
75
+ [nx, ny] = [ # centroid of target element
76
+ nbox.x + (if nbox.width? => nbox.width/2 else 0)
77
+ nbox.y + (if nbox.height? => nbox.height/2 else 0)
78
+ ]
79
+ # expected tip offset: by default, centered at mouse position
80
+ [tx, ty] = [mx - tbox.width/2, my - tbox.height/2]
81
+ [tx, ty] = [ # then, offset to direction against centroid
82
+ tx + (if nx < mx => 1 else -1 ) * (m + tbox.width / 2),
83
+ ty + (if ny < my => 1 else -1 ) * (m + tbox.height / 2)
84
+ ]
85
+ range-box = if @range => range-box = @range! else rbox
86
+
87
+ [tx, ty] = [ # last, bound in @root
88
+ (((tx >? range-box.x - rbox.x) + tbox.width) <? range-box.width + range-box.x - rbox.x) - tbox.width,
89
+ (((ty >? range-box.y - rbox.y) + tbox.height) <? range-box.height + range-box.y - rbox.y) - tbox.height
90
+ ]
91
+ @tip.style <<<
92
+ transform: "translate(#{tx}px,#{ty}px)"
93
+ opacity: 1
94
+ z-index: @_z = (if !@zmgr => 10 else @zmgr.add 10)
95
+ @hide!
96
+
97
+ if chart? => chart.{}utils.tip = tip
@@ -0,0 +1,19 @@
1
+ //- view
2
+ doctype html
3
+ html
4
+ include @/@loadingio/bootstrap.ext/dist/index.pug
5
+ include @/ldview/dist/index.pug
6
+ block vars
7
+ head
8
+ block prehead
9
+ +css("/assets/lib/bootstrap/main/css/bootstrap.min.css")
10
+ +css("/assets/lib/bootstrap.ldui/main/bootstrap.ldui.min.css")
11
+ +css("/assets/lib/@loadingio/loading.css/main/loading.min.css")
12
+ +css("/css/index.css")
13
+ block head
14
+ body
15
+ block body
16
+ +script("https://polyfill.io/v3/polyfill.min.js?features=ResizeObserver")
17
+ +script("/assets/lib/@loadingio/ldquery/main/ldq.min.js")
18
+ +script("/assets/lib/ldview/main/index.min.js")
19
+ block script
@@ -0,0 +1,38 @@
1
+ <-(->it!) _
2
+
3
+ block-factory =
4
+ pkg:
5
+ name: 'axis', version: '0.0.1'
6
+ extend: {name: "base", version: "0.0.1"}
7
+ dependencies: []
8
+ init: ({root, context, pubsub}) ->
9
+ pubsub.fire \init, mod: mod {context} .then ~> it.0
10
+
11
+ mod = ({context}) ->
12
+ {d3,forceBoundary,ldcolor,repeatString$,infinite} = context
13
+ sample: ->
14
+ raw: [0 to 7].map (val) ~> {val: val, name: val}
15
+ binding:
16
+ name: {key: \name}
17
+ value: {key: \val}
18
+ config: {}
19
+ dimension:
20
+ value: {type: \R, name: "value"}
21
+ name: {type: \N, name: "name"}
22
+ init: ->
23
+ @ab = d3.axisBottom!
24
+ @scale = x: d3.scaleLinear!
25
+ @g = d3.select @svg .append \g
26
+
27
+ parse: ->
28
+
29
+ resize: ->
30
+ @scale.x.domain [0, 20] .range [20, @box.width - 20]
31
+ @ab.scale @scale.x .tickValues [0 to 20]
32
+
33
+ render: ->
34
+ @g.call @ab
35
+
36
+
37
+
38
+ return block-factory
@@ -0,0 +1 @@
1
+ div: script(type="@plotdb/block"): include:lsc index.ls
@@ -0,0 +1,141 @@
1
+ <- (->it!) _
2
+
3
+ block-factory =
4
+ pkg:
5
+ name: 'axis-bubble', version: '0.0.1'
6
+ extend: {name: "base", version: "0.0.1"}
7
+ dependencies: []
8
+ init: ({root, context, pubsub}) ->
9
+ pubsub.fire \init, mod: mod {context} .then ~> it.0
10
+
11
+ mod = ({context}) ->
12
+ {d3, forceBoundary, ldcolor, repeatString$} = context
13
+ sample: ->
14
+ raw: [0 to 200].map (val) ~>
15
+ {val: Math.random! * 50 + 10, x: Math.random!, cat: Math.ceil(5 * Math.random!)}
16
+ binding: do
17
+ color: {key: \cat}
18
+ radius: {key: \val}
19
+ xpos: {key: \x}
20
+ config: {}
21
+ dimension:
22
+ color: {type: \R, name: "顏色"}
23
+ radius: {type: \R, name: "半徑"}
24
+ xpos: {type: \R, name: "X座標"}
25
+ init: ->
26
+ @svg.addEventListener \click, ~>
27
+ @type = !@type
28
+ @resize!
29
+ @render!
30
+ @sim.stop!
31
+ @sim.alpha 0.9
32
+ @svg.appendChild t = document.createElementNS('http://www.w3.org/2000/svg', 'text')
33
+ @text = d3.select(t)
34
+ @text
35
+ .attr \x, 20
36
+ .attr \y, 20
37
+ parse: ->
38
+ @xextent = d3.extent(@data.map -> it.xpos)
39
+ @rextent = d3.extent @data.map -> it.radius
40
+ @cextent = d3.extent @data.map -> it.color
41
+ resize: ->
42
+ rng = d3.randomUniform.source(d3.randomLcg(root.seed))(0, 1)
43
+ @scale-x = d3.scaleLinear!domain @xextent .range [0, @box.width]
44
+ if @type =>
45
+ @scale-y = d3.scaleLinear!domain @cextent .range [0, @box.height]
46
+ else
47
+ @scale-y = ~> @box.height / 2
48
+ @data.map (v) ~>
49
+ v <<< {
50
+ x: v.x or @scale-x(v.xpos)
51
+ y: v.y or @box.height / 2
52
+ ty: @scale-y(v.color)
53
+ val: v.radius
54
+ r: v.radius
55
+ c: v.color
56
+ }
57
+ @area = @box.width * @box.height
58
+ @rate = 0.5
59
+ render: ->
60
+ rate = @rate
61
+ @scale = do
62
+ color: d3.interpolateTurbo
63
+ r: d3.scaleLinear!domain(@rextent).range([0,1])
64
+ c: d3.scaleLinear!domain(@cextent).range([0,1])
65
+ pal = if @cfg.palette => @cfg.palette.colors.map -> ldcolor.web(it.value or it) else <[#f00 #0f0 #00f #f90 #9f0 #0f9]>
66
+ int-color = d3.interpolateDiscrete pal
67
+ @scale.color = ~> int-color(@scale.c it)
68
+
69
+ d3.select @svg .selectAll \circle.bubble .data @data
70
+ ..exit!remove!
71
+ ..enter!append \circle
72
+ .attr \class, \bubble
73
+ .attr \r, (d,i) ~> 0
74
+ .attr \fill, (d,i) ~> @scale.color d.c
75
+
76
+ d3.select @svg .selectAll \circle.bubble
77
+ .attr \cx, (d,i) -> d.x
78
+ .attr \cy, (d,i) -> d.y
79
+ .attr \fill, (d,i) ~> @scale.color d.c #@scale.r(d.c)
80
+ .attr \r, (d,i) ~> (d.r >? 2) * @rate
81
+
82
+ d3.select @svg .selectAll \g.label .data @data
83
+ ..exit!remove!
84
+ ..enter!append \g
85
+ .attr \class, \label
86
+ .each (d,i) ->
87
+ [0] #[0,1]
88
+ .map ~> d3.select(@).append \text
89
+ .map ->
90
+ it
91
+ .attr \dy, \-.28em
92
+ .attr \text-anchor, \middle
93
+ .attr \font-size, \.7em
94
+ .attr \font-family, \Rubik
95
+ .style \pointer-event, \none
96
+ d3.select @svg .selectAll \g.label
97
+ .attr \transform, (d,i) -> "translate(#{d.x},#{d.y})"
98
+ .each (d,i) ->
99
+ d3.select(@).selectAll \text
100
+ .attr \opacity, (if ( d.r * 2 * rate ) < "#{(d.val).toFixed(2)}".length * 7 => 0 else 1)
101
+ .attr \dy, (e,i) -> \.38em #if i == 0 => '-.28em' else '.88em'
102
+ .text (e,i) ->
103
+ if i == 0 =>
104
+ if d.val > 1000000 => (d.val/1000000).toFixed(2) + "M"
105
+ else if d.val > 1000 => (d.val/1000).toFixed(2) + "K"
106
+ else (d.val).toFixed(2)
107
+ else return d._idx
108
+
109
+ tick: ->
110
+ if !@sim =>
111
+ kickoff = true
112
+ @fc = fc = d3.forceCollide!
113
+ @sim = d3.forceSimulation!
114
+ .force \center, @fg = d3.forceCenter @box.width/2, @box.height/2
115
+ .force \x, @fx = d3.forceX(@box.width / 2).strength(0.15)
116
+ .force \y, @fy = d3.forceY((@box.height) / 2).strength(0.1)
117
+ .force \b, @fb = forceBoundary((->it.r), (->it.r), (~>@box.width - it.r), (~>@box.height - it.r))
118
+ .force \collide, fc
119
+ @sim.stop!
120
+ @sim.alpha 0.9
121
+ @sim.nodes(@data)
122
+ @fc.strength 1.0
123
+ @fc.radius ~> @rate * it.r + 2
124
+ @fg.x @box.width/2
125
+ @fg.y @box.height/2
126
+ @fx.strength 0.15
127
+ @fy.strength 0.01
128
+ @sim.tick if kickoff => 20 else 1
129
+ alpha = @sim.alpha!
130
+ @text.text alpha
131
+ if alpha < 0.001 => @stop!
132
+ @data.map -> it.y = it.y + (it.ty - it.y) * alpha * 0.1
133
+
134
+ d3.select @svg .selectAll \circle.bubble
135
+ .attr \cx, (d,i) -> d.x
136
+ .attr \cy, (d,i) -> d.y
137
+ d3.select @svg .selectAll \g.label
138
+ .attr \transform, (d,i) -> "translate(#{d.x},#{d.y})"
139
+ @render!
140
+
141
+ return block-factory
@@ -0,0 +1 @@
1
+ div: script(type="@plotdb/block"): include:lsc index.ls
@@ -0,0 +1,274 @@
1
+ <-(->it!) _
2
+
3
+ block-factory =
4
+ pkg:
5
+ name: 'bar', version: '0.0.1'
6
+ extend: {name: "base", version: "0.0.1"}
7
+ dependencies: []
8
+ i18n:
9
+ "zh-TW":
10
+ "size": "長度"
11
+ init: ({root, context, t, pubsub}) ->
12
+ pubsub.fire \init, mod: mod({context, t})
13
+
14
+ mod = ({context, t}) ->
15
+ {chart,d3,debounce} = context
16
+ sample: ->
17
+ raw: [0 to 10].map (val) ~>
18
+ ret = {name: val}
19
+ for i from 1 to 4 => ret["val#i"] = Math.random!
20
+ return ret
21
+ binding:
22
+ size: [1 to 4].map -> {key: "val#it", unit: "amount"}
23
+ name: {key: "name"}
24
+ config: {} <<< chart.utils.config.preset.default <<<
25
+ sort:
26
+ enabled: type: \boolean
27
+ dir: type: \choice, values: <[asc desc none]>
28
+ type: type: \choice, values: <[bar column]>, default: \column
29
+ dimension:
30
+ size: {type: \R, multiple: true, name: "size"}
31
+ name: {type: \NO, name: "name"}
32
+ init: ->
33
+ @tint = tint = new chart.utils.tint!
34
+ [[\multiple, \stack], [\percent, false], [\dancing, true]].map ([k,v]) ~>
35
+ if !(@cfg[k]?) => @cfg[k] = v
36
+ @svg.addEventListener \click, ~>
37
+ if !@cfg.dancing or @cfg.percent => return
38
+ @align-idx = ((@align-idx or 0) + 1) % (@binding.size.length + 1)
39
+ @resize!
40
+ @render!
41
+ @brush = d3.brushX!
42
+ @g = Object.fromEntries <[view xaxis yaxis legend]>.map ~> [it, d3.select(@layout.get-group it)]
43
+ @range = "null"
44
+ brush = debounce 150, (e) ~>
45
+ if @cfg.{}brush.enabled? and !@cfg.brush.enabled => return
46
+ range = e.selection
47
+ if (_range = JSON.stringify(range)) == @range => return else @range = _range
48
+ if !range => @filter {name: undefined}, true
49
+ else
50
+ domain = @scale.x.domain!
51
+ bandwidth = @scale.x.bandwidth!
52
+ range = range.map -> Math.floor(it / bandwidth) >? 0 <? (domain.length - 1)
53
+ range = [range.0 to range.1].map -> domain[it]
54
+ @filter {name: {type: 'index', value: range}}, true
55
+ @brush.on \end, brush
56
+ @brush.on \brush, brush
57
+ @scale = scale = {}
58
+ @yaxis = new chart.utils.axis layout: @layout, name: \yaxis, direction: \left
59
+ @xaxis = new chart.utils.axis layout: @layout, name: \xaxis, direction: \bottom
60
+ @legend = new chart.utils.legend do
61
+ layout: @layout
62
+ name: \legend
63
+ root: @root
64
+ shape: (d) -> d3.select(@).attr \fill, tint.get d.key
65
+ # d3.brush covers the whole svg element. need to support by calculation. TODO
66
+ /*
67
+ @tip = new chart.utils.tip {
68
+ root: @root
69
+ accessor: ({evt}) ~>
70
+ if !(evt.target and data = d3.select(evt.target).datum!) => return null
71
+ v = if isNaN(data.size) => '-' else "#{data.size.toFixed(2)}#{data.unit or ''}"
72
+ return {name: "#{data.group or ''} / #{data.name or ''}", value: v}
73
+ range: ~> @layout.get-node \view .getBoundingClientRect!
74
+ }
75
+ */
76
+
77
+ destroy: -> if @tip => @tip.destroy!
78
+
79
+ filter: (filters, internal = false) ->
80
+ @render!
81
+ if internal => return
82
+ @brush.move @g.view, if filters.name => filters.name.map ~> @scale.x(it) else null
83
+ parse: ->
84
+ @data.map (d) -> d.size = d.size.map -> if isNaN(it) => 0 else it
85
+ @align-idx = undefined
86
+ resize: ->
87
+ @type = @cfg.type or \column
88
+ #@tip.toggle(if @cfg.{}tip.enabled? => @cfg.tip.enabled else true)
89
+ @legend.config @cfg.legend
90
+ @legend.update!
91
+ @layout.update false
92
+
93
+ @max-per-group = @binding.size.map (d,i) ~> Math.max.apply(Math, @data.map -> it.size[i])
94
+
95
+ # alternative align base. required for dancing stack
96
+ if @cfg.align-idx and !@align-idx => @align-idx = @cfg.align-idx
97
+ align-idx = (if @cfg.percent => 0 else (@align-idx or 0)) <? @binding.size.length
98
+ @base-offset = @data.map (d) -> [0 til align-idx].reduce(((a,b) -> a + d.size[b]),0) or 0
99
+ @delta = delta = Math.max.apply(Math, @base-offset)
100
+ # stack bar
101
+ if @cfg.multiple == \stack
102
+ # 100% stack
103
+ if @cfg.percent => max = 1
104
+ else
105
+ max = Math.max.apply(Math,
106
+ @data.map (d) ~> [align-idx til d.size.length].reduce(((a,b) -> a + d.size[b]),0)
107
+ ) + (delta or 0)
108
+ # group bar
109
+ else if @cfg.multiple == \group
110
+ max = Math.max.apply(Math, @data.map (d) ~> Math.max.apply(Math, d.size))
111
+ # split bars
112
+ else if @cfg.multiple == \split
113
+ max = @max-per-group.reduce(((a,b) -> a + b),0)
114
+ @order = @data.map (d,i) -> {key: d._idx, idx: i, name: d.name}
115
+ @data.map (d,i) -> d.sum = d.size.reduce(((a,b) -> a + b),0)
116
+ if @cfg.{}sort.enabled =>
117
+ @order.sort (a,b) ~> (if @cfg.sort.dir == \asc => 1 else -1) * (@data[a.idx].sum - @data[b.idx].sum)
118
+ console.log @cfg
119
+ if @cfg.palette => @tint.set(@cfg.palette.colors.map -> it.value or it)
120
+ # order reversed to make it visually aligned with bar.
121
+ @legend.data [{
122
+ key: i, text: @binding.size[i].name or @binding.size[i].key
123
+ } for i from @binding.size.length - 1 to 0 by -1 ]
124
+ @layout.update false
125
+
126
+ box = @layout.get-box \view
127
+
128
+ if @type == \column =>
129
+ @scale <<<
130
+ y: d3.scaleLinear!domain([0,max]).range [box.height, 0]
131
+ x: d3.scaleBand!domain @order.map(->it.name or it.key) .range [0, box.width]
132
+ max-tick = Math.ceil(@layout.get-box \yaxis .height / 40) >? 2
133
+ yticks = if @cfg.bump => [1 to ((@parsed.length or 1) >? 1)]
134
+ else @scale.y.ticks((@cfg.{}yaxis.tick-count or 4) <? max-tick)
135
+ @yaxis.config @cfg.yaxis
136
+ v = @binding.size.0 or {}
137
+ if @binding.size.length > 1 => @yaxis.caption( if v.unit => "#{v.unit}" else '')
138
+ else @yaxis.caption( (v.name or v.key or '') + if v.unit => "(#{v.unit})" else '')
139
+ @yaxis.ticks yticks
140
+ @yaxis.scale @scale.y
141
+ @xaxis.config @cfg.{}xaxis
142
+ @xaxis.ticks @data.map -> it.name
143
+ @xaxis.scale @scale.x
144
+ @xaxis.caption(
145
+ (@binding.name.name or @binding.name.key) +
146
+ if @binding.name.unit => "(#{@binding.name.unit})" else ''
147
+ )
148
+ else
149
+ @scale <<<
150
+ x: d3.scaleLinear!domain([0,max]).range [box.width, 0]
151
+ y: d3.scaleBand!domain @order.map(->it.name or it.key) .range [0, box.height]
152
+ max-tick = Math.ceil(@layout.get-box \xaxis .width / 80) >? 2
153
+ xticks = if @cfg.bump => [1 to ((@parsed.length or 1) >? 1)]
154
+ else @scale.x.ticks((@cfg.{}xaxis.tick-count or 4) <? max-tick)
155
+ @xaxis.config @cfg.xaxis
156
+ v = @binding.size.0 or {}
157
+ if @binding.size.length > 1 => @xaxis.caption( if v.unit => "#{v.unit}" else '')
158
+ else @xaxis.caption( (v.name or v.key or '') + if v.unit => "(#{v.unit})" else '')
159
+ @xaxis.ticks xticks
160
+ @xaxis.scale @scale.x
161
+ @yaxis.config @cfg.{}yaxis
162
+ @yaxis.ticks @data.map -> it.name
163
+ @yaxis.scale @scale.y
164
+ @yaxis.caption(
165
+ (@binding.name.name or @binding.name.key) +
166
+ if @binding.name.unit => "(#{@binding.name.unit})" else ''
167
+ )
168
+
169
+ # xaxis and yaxis affect each other and they all affect view.
170
+ # we may need to update them until stable but for now we simply layout twice.
171
+ for i from 0 til 2 =>
172
+ @layout.update false
173
+ box = @layout.get-box('view')
174
+ [w,h] = [box.width, box.height]
175
+ @tint.set @cfg.palette
176
+ r = @cfg.dot-size or 3
177
+ @scale.x.range [0, w - r]
178
+ @scale.y.range [h, r]
179
+ @xaxis.render!
180
+ @yaxis.render!
181
+
182
+ box = @layout.get-box \view
183
+ @brush.extent [[0, 1],[box.width, box.height - 1]]
184
+
185
+ render: ->
186
+ {binding, scale, range, delta, base-offset, max-per-group, cfg, tint, data, type} = @
187
+ @g.view.selectAll \g.bar .data @data
188
+ ..exit!remove!
189
+ ..enter!append \g .attr \class, \bar
190
+ @g.view.selectAll \g.bar
191
+ .attr \transform, (d,i) ~>
192
+ if @type == \column => "translate(#{scale.x(d.name)},0)"
193
+ else "translate(0,#{scale.y(d.name)})"
194
+ .each (item,j) ->
195
+ [bars,offset] = [[],0]
196
+ # alternative align base. required for dancing stack
197
+ offset = (delta - base-offset[j])
198
+
199
+ # use to tweak output size / offset. 1 = no modification
200
+ sum = 1
201
+ # and set sum to this for 100% stack bar
202
+ if cfg.percent == true => sum = item.size.reduce(((a,b) -> a + b),0) >? 1
203
+
204
+ for idx from 0 til item.size.length =>
205
+ size = item.size[idx]
206
+ key = idx
207
+ bars.push {
208
+ size: size / sum
209
+ offset: offset / sum
210
+ key: key
211
+ group: item.name
212
+ name: binding.size[key].name or binding.size[key].key
213
+ unit: binding.size[key].unit or ''
214
+ }
215
+ # multiple bars
216
+ if cfg.multiple == \split => offset += max-per-group[idx]
217
+ # stack bar
218
+ else if cfg.multiple == \stack => offset += size
219
+
220
+
221
+ d3.select @ .selectAll \rect .data(bars, (d,i) -> d.key)
222
+ ..exit!remove!
223
+ ..enter!append \rect
224
+ ..attr(\width, (d,i) -> (scale.x.bandwidth! - 2) >? 1) if type == \column
225
+ ..attr(\y, (d,i) -> scale.y(0)) if type == \column
226
+ ..attr(\height, (d,i) -> (scale.y.bandwidth! - 2) >? 1) if type != \column
227
+ ..attr(\x, (d,i) -> scale.x(0)) if type != \column
228
+
229
+ tran = d3.select @ .selectAll \rect
230
+ .transition!duration 350
231
+ # group bar
232
+ .call (sel) ->
233
+ if cfg.multiple != \group => return
234
+ sel
235
+ ..attr(\x, (d,i) -> 1 + i * (scale.x.bandwidth! - 2) / bars.length) if type == \column
236
+ ..attr(\width, (d,i) -> (scale.x.bandwidth! - 2) / bars.length) if type == \column
237
+ ..attr(\y, (d,i) -> Math.round(Math.min(scale.y(0), scale.y(d.size)))) if type == \column
238
+
239
+ ..attr(\y, (d,i) -> 1 + i * (scale.y.bandwidth! - 2) / bars.length) if type != \column
240
+ ..attr(\height, (d,i) -> (scale.y.bandwidth! - 2) / bars.length) if type != \column
241
+ ..attr(\x, (d,i) -> Math.round(Math.min(scale.x(0), scale.x(d.size)))) if type != \column
242
+
243
+ # stack bar
244
+ .call (sel) ->
245
+ if cfg.multiple != \stack => return
246
+ sel
247
+ ..attr(\x, 1) if type == \column
248
+ ..attr(\width, (d,i) -> scale.x.bandwidth! - 2) if type == \column
249
+ ..attr( \y, (d,i) -> Math.round(Math.min(scale.y(d.offset), scale.y(d.offset + d.size)))) if type == \column
250
+ ..attr(\y, 1) if type != \column
251
+ ..attr(\height, (d,i) -> scale.y.bandwidth! - 2) if type != \column
252
+ ..attr( \x, (d,i) -> Math.round(Math.min(scale.x(d.offset), scale.x(d.offset + d.size)))) if type != \column
253
+
254
+ if type == \column =>
255
+ tran.attr \height, (d,i) -> Math.round(Math.abs(scale.y(0) - scale.y(d.size)))
256
+ else
257
+ tran.attr \width, (d,i) -> Math.round(Math.abs(scale.x(0) - scale.x(d.size)))
258
+ tran
259
+ .attr \fill, (d,i) ~> tint.get i, (if cfg.color-variant => ((j/data.length) - 0.5) else 0)
260
+ .attr \fill-opacity, (d,i) ~>
261
+ if !(binding.name and filter = binding.name.filter) => return 1
262
+ if !(range = filter.value) => return 1
263
+ if item.name in range => 1 else 0.2
264
+
265
+ if !(@cfg.{}brush.enabled? and !@cfg.brush.enabled) => @g.view.call @brush
266
+ @g.view.selectAll \rect.selection
267
+ .attr \shape-rendering, \auto
268
+ @legend.render!
269
+ @yaxis.render!
270
+ @xaxis.render!
271
+
272
+ tick: ->
273
+
274
+ return block-factory
@@ -0,0 +1,35 @@
1
+ div
2
+ div(plug="layout")
3
+ .pdl-layout
4
+ div(data-type="layout")
5
+ .pdl-cell(data-name="yaxis") &nbsp;
6
+ .pdl-cell(data-name="view")
7
+ .pdl-cell
8
+ .pdl-cell(data-name="xaxis") &nbsp;
9
+ .pdl-cell(data-name="legend") &nbsp;
10
+ div(data-type="render")
11
+
12
+ style(type="text/css"): :stylus
13
+ [data-name=xaxis] { font-size: 0.8em }
14
+ [data-name=yaxis] { font-size: 0.8em }
15
+ .pdl-layout > div[data-type=layout]
16
+ display: grid
17
+ grid-template-columns: fit-content(2em) 1fr fit-content(10em)
18
+ grid-template-rows: 1fr fit-content(1em)
19
+ /* 0.5em for axis label at the boundary */
20
+ padding: 0 0.5em
21
+ & > .pdl-cell[data-name=legend]
22
+ grid-column: 3
23
+ grid-row: 1 / span 2
24
+ padding-left: 1em
25
+
26
+ .pdl-layout.legend-below > div[data-type=layout]
27
+ grid-template-columns: 2em 1fr
28
+ grid-template-rows: 1fr fit-content(2em) fit-content(3em)
29
+ .pdl-cell[data-name=legend]
30
+ padding-top: 1em
31
+ grid-column: 1 / span 2
32
+ grid-row: 3
33
+
34
+
35
+ script(type="@plotdb/block"): include:lsc index.ls