1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
# Singelton class used to query CSS styles by way of reference elements.
# This allows canvas based visualizations to use the same styles as their
# SVG counterparts.
class QueryCSS
# Reference container id
REFERENCE_CONTAINER_ID = '_canvas_css_reference'
# Container Hash Attribute
CONTAINER_HASH_ATTR = 'data-epoch-container-id'
# Handles automatic container id generation
containerCount = 0
nextContainerId = -> "epoch-container-#{containerCount++}"
# Expression used to derive tag name, id, and class names from
# selectors given the the put method.
PUT_EXPR = /^([^#. ]+)?(#[^. ]+)?(\.[^# ]+)?$/
# Whether or not to log full selector lists
logging = false
# Converts selectors into actual dom elements (replaces put.js)
# Limited the functionality to what Epoch actually needs to
# operate correctly. We detect class names, ids, and element
# tag names.
put = (selector) ->
match = selector.match(PUT_EXPR)
return Epoch.error('Query CSS cannot match given selector: ' + selector) unless match?
[whole, tag, id, classNames] = match
tag = (tag ? 'div').toUpperCase()
element = document.createElement(tag)
element.id = id.substr(1) if id?
if classNames?
element.className = classNames.substr(1).replace(/\./g, ' ')
return element
# Lets the user set whether or not to log selector lists and resulting DOM trees.
# Useful for debugging QueryCSS itself.
@log: (b) ->
logging = b
# Key-Value cache for computed styles that we found using this class.
@cache = {}
# List of styles to pull from the full list of computed styles
@styleList = ['fill', 'stroke', 'stroke-width']
# The svg reference container
@container = null
# Purges the selector to style cache
@purge: ->
QueryCSS.cache = {}
# Gets the reference element container.
@getContainer: ->
return QueryCSS.container if QueryCSS.container?
container = document.createElement('DIV')
container.id = REFERENCE_CONTAINER_ID
document.body.appendChild(container)
QueryCSS.container = d3.select(container)
# @return [String] A unique identifier for the given container and selector.
# @param [String] selector Selector from which to derive the styles
# @param container The containing element for a chart.
@hash: (selector, container) ->
containerId = container.attr(CONTAINER_HASH_ATTR)
unless containerId?
containerId = nextContainerId()
container.attr(CONTAINER_HASH_ATTR, containerId)
return "#{containerId}__#{selector}"
# @return The computed styles for the given selector in the given container element.
# @param [String] selector Selector from which to derive the styles.
# @param container HTML containing element in which to place the reference SVG.
@getStyles: (selector, container) ->
# 0) Check for cached styles
cacheKey = QueryCSS.hash(selector, container)
cache = QueryCSS.cache[cacheKey]
return cache if cache?
# 1) Build a full reference tree (parents, container, and selector elements)
parents = []
parentNode = container.node().parentNode
while parentNode? and parentNode.nodeName.toLowerCase() != 'body'
parents.unshift parentNode
parentNode = parentNode.parentNode
parents.push container.node()
selectorList = []
for element in parents
sel = element.nodeName.toLowerCase()
if element.id? and element.id.length > 0
sel += '#' + element.id
if element.className? and element.className.length > 0
sel += '.' + Epoch.Util.trim(element.className).replace(/\s+/g, '.')
selectorList.push sel
selectorList.push('svg')
for subSelector in Epoch.Util.trim(selector).split(/\s+/)
selectorList.push(subSelector)
console.log(selectorList) if logging
parent = root = put(selectorList.shift())
while selectorList.length
el = put(selectorList.shift())
parent.appendChild el
parent = el
console.log(root) if logging
# 2) Place the reference tree and fetch styles given the selector
QueryCSS.getContainer().node().appendChild(root)
ref = d3.select('#' + REFERENCE_CONTAINER_ID + ' ' + selector)
styles = {}
for name in QueryCSS.styleList
styles[name] = ref.style(name)
QueryCSS.cache[cacheKey] = styles
# 3) Cleanup and return the styles
QueryCSS.getContainer().html('')
return styles
Epoch.QueryCSS = QueryCSS
|