@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.
- package/CHANGELOG.md +11 -0
- package/README.md +50 -0
- package/build +39 -0
- package/dist/chart.css +8 -0
- package/dist/chart.js +603 -0
- package/dist/chart.min.css +1 -0
- package/dist/chart.min.js +1 -0
- package/dist/data/index.js +73 -0
- package/dist/utils/axis.js +357 -0
- package/dist/utils/axis.min.js +1 -0
- package/dist/utils/config.js +33 -0
- package/dist/utils/config.min.js +1 -0
- package/dist/utils/data.js +238 -0
- package/dist/utils/data.min.js +1 -0
- package/dist/utils/format.js +70 -0
- package/dist/utils/format.min.js +1 -0
- package/dist/utils/legend.css +40 -0
- package/dist/utils/legend.js +263 -0
- package/dist/utils/legend.min.css +1 -0
- package/dist/utils/legend.min.js +1 -0
- package/dist/utils/tint.js +138 -0
- package/dist/utils/tint.min.js +1 -0
- package/dist/utils/tip.js +148 -0
- package/dist/utils/tip.min.js +1 -0
- package/doc/charts.md +58 -0
- package/doc/config.md +44 -0
- package/doc/draft/chart.md +146 -0
- package/doc/draft/data.md +8 -0
- package/doc/draft/definition.md +0 -0
- package/doc/draft/events.md +0 -0
- package/doc/draft/filter.md +0 -0
- package/doc/draft/lifecycle.md +89 -0
- package/doc/spec.md +89 -0
- package/doc/todo.md +27 -0
- package/doc/utils/tip.md +84 -0
- package/package.json +83 -0
- package/src/chart.ls +320 -0
- package/src/chart.styl +6 -0
- package/src/data/en-US/category +20 -0
- package/src/data/en-US/fruit +39 -0
- package/src/data/en-US/movie +125 -0
- package/src/data/en-US/name +58 -0
- package/src/data/index.ls +34 -0
- package/src/data/zh-TW/category +20 -0
- package/src/data/zh-TW/fruits +38 -0
- package/src/data/zh-TW/movie +125 -0
- package/src/data/zh-TW/names +105 -0
- package/src/utils/axis.ls +229 -0
- package/src/utils/config.ls +11 -0
- package/src/utils/data.ls +104 -0
- package/src/utils/format.ls +30 -0
- package/src/utils/legend.ls +134 -0
- package/src/utils/legend.styl +37 -0
- package/src/utils/tint.ls +66 -0
- package/src/utils/tip.ls +97 -0
- package/web/src/pug/base.pug +19 -0
- package/web/src/pug/block/axis/0.0.1/index.ls +38 -0
- package/web/src/pug/block/axis/0.0.1/index.pug +1 -0
- package/web/src/pug/block/axis-bubble/0.0.1/index.ls +141 -0
- package/web/src/pug/block/axis-bubble/0.0.1/index.pug +1 -0
- package/web/src/pug/block/bar/0.0.1/index.ls +274 -0
- package/web/src/pug/block/bar/0.0.1/index.pug +35 -0
- package/web/src/pug/block/bar-list/0.0.1/index.ls +85 -0
- package/web/src/pug/block/bar-list/0.0.1/index.pug +30 -0
- package/web/src/pug/block/bar-race/0.0.1/index.ls +83 -0
- package/web/src/pug/block/bar-race/0.0.1/index.pug +21 -0
- package/web/src/pug/block/base/0.0.1/index.ls +67 -0
- package/web/src/pug/block/base/0.0.1/index.pug +5 -0
- package/web/src/pug/block/blank/0.0.1/index.ls +25 -0
- package/web/src/pug/block/blank/0.0.1/index.pug +1 -0
- package/web/src/pug/block/bubble/0.0.1/index.ls +220 -0
- package/web/src/pug/block/bubble/0.0.1/index.pug +28 -0
- package/web/src/pug/block/curve-bar/0.0.1/index.ls +89 -0
- package/web/src/pug/block/curve-bar/0.0.1/index.pug +16 -0
- package/web/src/pug/block/curve-bar/0.0.1/sample.js +338 -0
- package/web/src/pug/block/dumbbell/0.0.1/index.ls +90 -0
- package/web/src/pug/block/dumbbell/0.0.1/index.pug +1 -0
- package/web/src/pug/block/empty/0.0.1/index.ls +27 -0
- package/web/src/pug/block/empty/0.0.1/index.pug +2 -0
- package/web/src/pug/block/gauge/0.0.1/index.ls +100 -0
- package/web/src/pug/block/gauge/0.0.1/index.pug +31 -0
- package/web/src/pug/block/gauge/0.0.1/sample.js +338 -0
- package/web/src/pug/block/line/0.0.1/index.ls +265 -0
- package/web/src/pug/block/line/0.0.1/index.pug +33 -0
- package/web/src/pug/block/number/0.0.1/index.ls +69 -0
- package/web/src/pug/block/number/0.0.1/index.pug +20 -0
- package/web/src/pug/block/percent-list/0.0.1/index.ls +167 -0
- package/web/src/pug/block/percent-list/0.0.1/index.pug +34 -0
- package/web/src/pug/block/pie/0.0.1/index.ls +125 -0
- package/web/src/pug/block/pie/0.0.1/index.pug +19 -0
- package/web/src/pug/block/pie/0.0.1/sample.js +338 -0
- package/web/src/pug/block/pie-bubble/0.0.1/index.ls +185 -0
- package/web/src/pug/block/pie-bubble/0.0.1/index.pug +40 -0
- package/web/src/pug/block/radar/0.0.1/index.ls +113 -0
- package/web/src/pug/block/radar/0.0.1/index.pug +1 -0
- package/web/src/pug/block/radar/0.0.1/xx +285 -0
- package/web/src/pug/block/scatter/0.0.1/index.ls +128 -0
- package/web/src/pug/block/scatter/0.0.1/index.pug +30 -0
- package/web/src/pug/block/sheet/0.0.1/index.ls +38 -0
- package/web/src/pug/block/sheet/0.0.1/index.pug +5 -0
- package/web/src/pug/block/table/0.0.1/index.ls +40 -0
- package/web/src/pug/block/table/0.0.1/index.pug +3 -0
- package/web/src/pug/block/taiwantown/0.0.1/index.ls +48 -0
- package/web/src/pug/block/taiwantown/0.0.1/index.pug +52 -0
- package/web/src/pug/block/taiwanvillage/0.0.1/index.ls +53 -0
- package/web/src/pug/block/taiwanvillage/0.0.1/index.pug +52 -0
- package/web/src/pug/block/treemap/0.0.1/index.ls +172 -0
- package/web/src/pug/block/treemap/0.0.1/index.pug +42 -0
- package/web/src/pug/block/voronoi-treemap/0.0.1/index.ls +174 -0
- package/web/src/pug/block/voronoi-treemap/0.0.1/index.pug +17 -0
- package/web/src/pug/block/voronoi-treemap/0.0.1/sample.js +241 -0
- package/web/src/pug/block/wordcloud/0.0.1/index.ls +61 -0
- package/web/src/pug/block/wordcloud/0.0.1/index.pug +1 -0
- package/web/src/pug/block/worldmap/0.0.1/index.ls +83 -0
- package/web/src/pug/block/worldmap/0.0.1/index.pug +12 -0
- package/web/src/pug/index.ls +72 -0
- package/web/src/pug/index.pug +52 -0
- package/web/src/styl/index.styl +0 -0
- package/web/static/assets/block/cache.js +4 -0
- package/web/static/assets/custom-lib/d3/d3-array.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-axis.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-brush.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-color.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-dispatch.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-drag.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-ease.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-force-boundary.v0.0.1.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-force.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-format.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-hierarchy.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-interpolate.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-path.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-quadtree.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-random.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-scale-chromatic.v1.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-selection.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-shape.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-timer.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3-transition.v2.min.js +2 -0
- package/web/static/assets/custom-lib/d3/d3.v4.js +17178 -0
- package/web/static/assets/custom-lib/d3/d3.v4.min.js +2 -0
- package/web/static/assets/data/countrycode.json +1 -0
- package/web/static/assets/data/world-110m.json +1 -0
- package/web/static/assets/data/zh-to-alpha2.json +1 -0
- package/web/static/assets/img/fluid.svg +16 -0
- package/web/static/block/axis/0.0.1/index.html +79 -0
- package/web/static/block/axis-bubble/0.0.1/index.html +222 -0
- package/web/static/block/bar/0.0.1/index.html +581 -0
- package/web/static/block/bar-list/0.0.1/index.html +231 -0
- package/web/static/block/bar-race/0.0.1/index.html +184 -0
- package/web/static/block/base/0.0.1/index.html +197 -0
- package/web/static/block/blank/0.0.1/index.html +42 -0
- package/web/static/block/bubble/0.0.1/index.html +402 -0
- package/web/static/block/bubble/0.0.1/test.html +0 -0
- package/web/static/block/curve-bar/0.0.1/index.html +148 -0
- package/web/static/block/dumbbell/0.0.1/index.html +178 -0
- package/web/static/block/empty/0.0.1/index.html +76 -0
- package/web/static/block/gauge/0.0.1/index.html +150 -0
- package/web/static/block/line/0.0.1/index.html +540 -0
- package/web/static/block/number/0.0.1/index.html +152 -0
- package/web/static/block/percent-list/0.0.1/index.html +316 -0
- package/web/static/block/pie/0.0.1/index.html +281 -0
- package/web/static/block/pie-bubble/0.0.1/index.html +358 -0
- package/web/static/block/radar/0.0.1/index.html +149 -0
- package/web/static/block/scatter/0.0.1/index.html +251 -0
- package/web/static/block/sheet/0.0.1/index.html +103 -0
- package/web/static/block/table/0.0.1/index.html +73 -0
- package/web/static/block/taiwantown/0.0.1/index.html +210 -0
- package/web/static/block/taiwanvillage/0.0.1/index.html +220 -0
- package/web/static/block/treemap/0.0.1/index.html +360 -0
- package/web/static/block/voronoi-treemap/0.0.1/index.html +350 -0
- package/web/static/block/wordcloud/0.0.1/index.html +92 -0
- package/web/static/block/worldmap/0.0.1/index.html +207 -0
- package/web/static/css/index.css +0 -0
- package/web/static/css/index.min.css +0 -0
- package/web/static/favicon.ico +0 -0
- 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,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
|