@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
package/src/utils/tip.ls
ADDED
|
@@ -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")
|
|
6
|
+
.pdl-cell(data-name="view")
|
|
7
|
+
.pdl-cell
|
|
8
|
+
.pdl-cell(data-name="xaxis")
|
|
9
|
+
.pdl-cell(data-name="legend")
|
|
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
|