epoch-charting
Version:
A general purpose real-time charting library for building beautiful, smooth, and high performance visualizations.
131 lines (103 loc) • 4.44 kB
text/coffeescript
# 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