# c3 Visualization Library
# Layers for XY Plots

###################################################################
# XY Plot Chart Layers
###################################################################

# The root abstract class of layers for the {c3.Plot c3 XY Plot Chart}
#
# ## Internal Interface
# The internal interface that plot layers can implement essentially match the {c3.Base} internal abstract interface:
# * **{c3.base#init init()}**
# * **{c3.base#size size()}**
# * **{c3.base#update update()}**
# * **{c3.base#draw draw()}**
# * **{c3.base#style style()}**
#
# An additional method is added:
# * **zoom()** - Called when the chart is zoomed or panned.
#
# ## Extensibility
# Each layer has the following members added:
# * **chart** - Reference to the {c3.Plot XY Plot Chart} this layer belongs to
# * **content** - A {c3.Selection selection} of the layer content
# * **g** - A {https://github.com/mbostock/d3/wiki/Selections D3 selection} for the SVG g node for this layer
# * **width** - width of the layer
# * **height** - height of the layer
#
# @method #on(event, handler)
#   Register an event handler to catch events fired by the visualization.
#   @param event [String] The name of the event to handle.  _See the Exetensibility and Events section for {c3.base}._
#   @param handler [Function] Callback function called with the event.  The arguments passed to the function are event-specific.
#
# Items should be positioned on the the layer using the layer's `h` and `v` scales.
# As a performance optimization some layers may create a g node with the `scaled` class.
# When the plot is zoomed then this group will have a transform applied to reflect the zoom so
# individual elements do not need to be adjusted.  Please use the `chart.orig_h` scale in this case.
# Not that this approach does not work for many circumstances as it affects text aspect ratio,
# stroke widths, rounding errors, etc.
# @abstract
# @author Douglas Armstrong
class c3.Plot.Layer
    @version: 0.1
    c3.Layer = this # Shortcut for accessing plot layers.
    type: 'layer'
    @_next_uid: 0

    # [Array] Data for this layer  _This can be set for each individual layer or a default for the entire chart._
    data: undefined
    # [String] User name for this layer.  This is used in legends, for example.
    name: undefined
    # [String] CSS class to assign to this layer for user style sheets to customize
    class: undefined
    # [Boolean] If true this layer is considered to have "static" data and will not update when {c3.Base#redraw redraw()} is called.
    static_data: false
    # [{https://github.com/mbostock/d3/wiki/Scales d3.scale}] Scale for the _vertical_ Y axis for this layer.
    # Please set the _domain()_, c3 will set the _range()_.
    # _The vertical scale may be set for the entire chart instead of for each layer._
    h: undefined
    # [{https://github.com/mbostock/d3/wiki/Scales d3.scale}] Scale for the _vertical_ Y axis for this layer.
    # Please set the _domain()_, c3 will set the _range()_.
    # _The vertical scale may be set for the entire chart instead of for each layer._
    v: undefined
    # [Function] An accessor function to get the X value from a data item.
    # _This can be set for each individual layer or a default for the entire chart._
    # Some plots support calling this accessor with the index of the data as well as the datum itself.
    x: undefined
    # [Function] An accessor function to get the Y value from a data item.
    # _This can be set for each individual layer or a default for the entire chart._
    # Some plots support calling this accessor with the index of the data as well as the datum itself.
    y: undefined
    # [{c3.Selection.Options}] Options to set the **class**, **classes**, **styles**,
    # **events**, and **title** for this layer.
    options: undefined
    # [Object] An object to setup event handlers to catch events triggered by this c3 layer.
    # The keys represent event names and the values are the cooresponding handlers.
    handlers: undefined

    constructor: (opt)->
        c3.util.extend this, new c3.Dispatch
        c3.util.extend this, opt
        @uid = c3.Plot.Layer._next_uid++

    # Internal function for the Plot to prepare the layer.
    init: (@chart, @g)=>
        @trigger 'render_start'
        @data ?= @chart.data
        @h ?= @chart.h
        @v ?= @chart.v
        @x ?= @chart.x
        @y ?= @chart.y
        if @class? then @g.classed @class, true
        if @handlers? then @on event, handler for event, handler of @handlers
        @content = c3.select(@g)

        # Apply classes to layer g nodes based on the `type` of the layer object hierarchy
        prototype = Object.getPrototypeOf(@)
        while prototype
            if prototype.type? then @g.classed prototype.type, true
            prototype = Object.getPrototypeOf prototype

        @_init?()
        @trigger 'render'

    # Resize the layer, but _doesn't_ update the rendering, `resize()` should be used for that.
    size: (@width, @height)=>
        @trigger 'resize_start'
        c3.d3.set_range @h, [0, @width]
        c3.d3.set_range @v, [@height, 0]
        @_size?()
        @trigger 'resize'

    # Update the DOM bindings based on the new or modified data set
    update: (origin)=>
        if not @chart? then throw Error "Attempt to redraw uninitialized plot layer, please use render() when modifying set of layers."
        @trigger 'redraw_start', origin
        @_update?(origin)

    # Position the DOM elements based on current scales.
    draw: (origin)=>
        if not (@static_data and origin is 'redraw')
            @trigger 'redraw_start', origin if origin is 'resize'
            @_draw?(origin)
            @trigger 'redraw', origin

    # Restyle existing items in the layer
    style: (style_new)=>
        @trigger 'restyle_start', style_new
        @_style?(style_new)
        @trigger 'restyle', style_new
        @trigger 'rendered' if not @rendered
        return this

    # Called when a layer needs to update from a zoom, decimated layers overload this
    zoom: =>
        @draw?('zoom')
        @style?(true)

    # Redraw just this layer
    redraw: (origin='redraw')=>
        @update(origin)
        @draw(origin)
        @style(true)
        return this

    # Method to restyle this layer
    restyle: Layer::style

    # Adjust domains for layer scales for any automatic domains.
    # For layer-specific automatic domains the layer needs its own scale defined,
    # it cannot update the chart's shared scale.
    # @note Needs to happen after update() so that stacked layers are computed
    scale: =>
        refresh = false
        if @h_domain?
            if @h==@chart.h then throw Error "Layer cannot scale shared h scale, please define just h or both h and h_domain for layers"
            h_domain = if typeof @h_domain is 'function' then @h_domain.call(this) else @h_domain
            if h_domain[0] is 'auto' then h_domain[0] = @min_x()
            if h_domain[1] is 'auto' then h_domain[1] = @max_x()
            if h_domain[0]!=@h.domain()[0] or h_domain[1]!=@h.domain()[1]
                @h.domain h_domain
                refresh = true
        if @v_domain?
            if @v==@chart.v then throw Error "Layer cannot scale shared v scale, please define just v or both v and v_domain for layers"
            v_domain = if typeof @v_domain is 'function' then @v_domain.call(this) else @v_domain
            if v_domain[0] is 'auto' then v_domain[0] = @min_y()
            if v_domain[1] is 'auto' then v_domain[1] = @max_y()
            if v_domain[0]!=@v.domain()[0] or v_domain[1]!=@v.domain()[1]
                @v.domain v_domain
                refresh = true
        return refresh

    min_x: => d3.min @data, @x
    max_x: => d3.max @data, @x
    min_y: => d3.min @data, @y
    max_y: => d3.max @data, @y


###################################################################
# XY Plot Stackable Layers
###################################################################

# An **abstract** class for layers that support stacking
# such as {c3.Plot.Layer.Path} and {c3.Plot.Layer.Bar}.
#
# **Stacking** allows the user to group their data into different _stacks_, which are stacked
# on top of each other in the chart.  Stacking is enabled when you define _either_ `stack_options.key`, `stacks`,
# or both options.  `stack_options` allows you to configure how stacking is performed from the layer's data,
# while `stacks` allows you to manually configure the exact set of stacks.
#
# This layer stacking is flexible to support several different ways of organizing the dataset into stacks:
# * For normalized datasets you can define a `stack_options.key()` accessor to provide a key that uniquely
#   identifies which stack an element belongs to.
# * Otherwise, you can manually define the set of `stacks` and the layer's `data` is copied into each.
# * * The layer `y` accessor will be called with the arguments (_datum_,_index_,_stack_)
#   for you to provide the value for that element for that stack.
# * * Or, you can define a `y` accessor for each stack to get the value for that element for that stack.
# * You can also directly define `data` in each stack specified in `stacks`.
#
# Please view the examples for more explanation on how to stack data.
# Remember, the set and order or stacks can always be programmatically constructed and dynamically updated.
#
# ## Extensibility
# The following {c3.Selection} members are made available if appropriate:
# * **groups** - An entry will exist for an svg:g node for each stack in the layer
#
# @see ../../../examples/doc/stack_example.html
# @abstract
# @author Douglas Armstrong
# @note If stacked, the input datasets may not have duplicate values in the same stack for the same X value.  There are other resitrictions if `safe` mode is not used.
# @note If you do not provide data elements for all stacks at all x values, then be prepared for your accessor callbacks to be called with _null_ objects.
class c3.Plot.Layer.Stackable extends c3.Plot.Layer
    @version: 0.2
    type: 'stackable'

    # [{c3.Selection.Options}] Enable stacking and specify stacking options for this layer.
    # This provides the normal {c3.Selection.Options} applied to each stack in the layer.  For callbacks,
    # the first argument is the stack object and the second argument is the index to the stack
    # In addition, the following options control stacking behaviour:
    # * **key** [Function] An accessor you can define to return a key that uniquely identifies which stack
    #   a data element belongs to.  If this is specified, then this callback is used to determine which stack
    #   each data element is assigned to.  Otherwise, the layer data array is used in full for each stack.
    # * **name** [Function] A callback you define to set the name of a stack that is passed the stack key as an argument.
    # * **offset** [String, Function] The name or a function for the stacking algorithm used to place the data.
    #   See {https://github.com/mbostock/d3/wiki/Stack-Layout#wiki-offset d3.stack.offset()} for details.
    # * * `zero` - The default for a zero baseline.
    # * * `expand` - Normalize all points to range from 0-1.
    # * **order** [String] Specify the mechanism to order the stacks.
    #   See {https://github.com/mbostock/d3/wiki/Stack-Layout#wiki-order d3.stack.order()} for details.
    stack_options: undefined

    # [Array<{c3.Plot.Layer.Stackable.Stack}>] An array of {c3.Plot.Layer.Stackable.Stack stack}
    # objects that can be used to manually specify the set of stacks.
    # Stack objects may contain:
    # * **key** [String] The key for this stack
    # * **y** [Function] A y accessor to use for this stack overriding the one provided by the chart or layer.
    # * **data** [Array] Manually specified dataset for this stack instead of using the layer's `data`.
    # * **name** [String] Name for the stack
    # * **options** [{c3.Selection.Options}] Options to manually set the **class**, **classes**,
    #   **styles**, **events**, and **title** of just this stack.
    stacks: undefined

    # [Boolean] Safe Mode.
    # Preform additional checks and fix up the data for situations such as:
    # * Data not sorted along X axis
    # * Remove data elements where X or Y values are undefined
    # * Pad missing values where stacks are not defined for all X values.
    # Note that this mode may cause the indexes passed to the accessors to match the
    # corrected data instead of the original data array.
    safe: true

    # Restack the data based on the **stack** and **stacks** options.
    _stack: => if @stack_options or @stacks?
        @stacks ?= []
        x_values_set = {}

        # Helper function to setup the current stack data and populate a shadow structure
        # to hold the x, y, and y0 positioning so we avoid modifying the user's data.
        add_value = (stack, datum, i)=>
            x = @x(datum,i); x_values_set[x] = x
            stack.y = stack.y ? @y ? throw Error "Y accessor must be defined in stack, layer, or chart"
            y = stack.y(datum,i,stack)
            stack.values.push { x:x, y:y, datum:datum }

        for stack in @stacks
            stack.name ?= @stack_options?.name? stack.key

            # Clear any previous stacking
            stack.values = [] # Shadow array to hold stack positioning

            # Data was provided manually in the stack definition
            if stack.data? then for datum, i in stack.data
                add_value stack, datum, i

        # Data has been provided in @current_data that we need to assign it to a stack
        if @current_data.length
            # Use stack_options.key() to assign data to stacks
            if @stack_options?.key?
                stack_map = {}
                for stack in @stacks # Refer to hard-coded stacks if defined
                    if stack_map[stack.key]? then throw Error "Stacks provided with duplicate keys: "+stack.key
                    stack_map[stack.key] = stack
                for datum, i in @current_data
                    key = @stack_options.key(datum)
                    if stack_map[key]?
                        stack = stack_map[key]
                    else
                        @stacks.push stack = stack_map[key] = {
                            key:key, name:@stack_options.name?(key), current_data:[], values:[] }
                    add_value stack, datum, i

            # Otherwise, assign all data to all stacks using each stack's y() accessor
            else if @stacks? then for stack in @stacks
                for datum, i in @current_data
                    add_value stack, datum, i

            else throw Error "Either stacks or stack_options.key must be defined to create the set of stacks."

        if @safe
            # Ensure everything is sorted
            # NOTE: We sort based on the @h scale in case of ordinal or other odd scales
            if @h.range()[0] is @h.range()[1] then @h.range [0,1]
            x_values = c3.array.sort_up (v for k,v of x_values_set), @h
            for stack in @stacks
                c3.array.sort_up stack.values, (v)=> @h v.x

            # Splice in missing data and remove undefined data (Important for D3's stack layout)
            i=0; while i<x_values.length
                undef = 0
                for stack in @stacks
                    # Check for missing values
                    # Compare using h scale to tolerate values such as Dates
                    stack_h = @h stack.values[i]?.x
                    layer_h = @h x_values[i]
                    if stack_h isnt layer_h
                        if stack_h < layer_h # Check for duplicate X values
                            if @h.domain()[0] is @h.domain()[1]
                                throw Error "Did you intend for an h scale with 0 domain?  Duplicate X values, invalid stacking, or bad h scale"
                            else throw Error "Multiple data elements with the same x value in the same stack, or invalid stacking"
                        stack.values.splice i, 0, { x:x_values[i], y:0, datum:null }
                        undef++
                    # Check for undefined y values
                    else if not stack.values[i].y?
                        stack.values[i].y = 0
                        undef++
                # If item is undefined for all stacks, then remove it completely
                if undef is @stacks.length
                    stack.values.splice(i,1) for stack in @stacks
                    x_values.splice(i,1)
                    i--
                i++

        # Prepare array of current data for each stack in case it is needed for binding (used by bar chart)
        for stack in @stacks
            stack.current_data = stack.values.map (v)->v.datum

        # Configure and run the D3 stack layout to generate y0 and y layout data for the elements.
        stacker = d3.layout.stack().values (stack)->stack.values
        if @stack_options?.offset? then stacker.offset @stack_options.offset
        if @stack_options?.order? then stacker.order @stack_options.order
        stacker @stacks

    _update: =>
        # Ensure data is sorted and skip elements that do not have a defined x or y value
        @current_data = if not @safe then @data else if @data?
            # TODO: we could try to use this values array later as an optimization and for more
            # stable i parameters passed to user accessors.  However, it will complicate the code.
            if @y? and not @stacks? and not @stack_options?
                values = ({x:@x(d,i), y:@y(d,i), datum:d} for d,i in @data)
                c3.array.sort_up values, (v)=> @h v.x # Use @h to accomodate date types
                (v.datum for v in values when v.x? and v.y?)
            else
                values = ({x:@x(d,i), datum:d} for d,i in @data)
                if @stacks? or @stack_options? # Sorting handled by _stack()
                    c3.array.sort_up values, (v)=> @h v.x
                (v.datum for v in values when v.x?)

        @_stack()
        @groups = @content.select('g.stack')
            .bind (@stacks ? [null]), if not @stacks?[0]?.key? then null else (stack)->stack.key
            .options @stack_options, (if @stacks?.some((stack)->stack.options?) then (stack)->stack.options)
            .update()

    _style: (style_new)=> @groups?.style(style_new)

    min_x: => if not @stacks? then super else d3.min @stacks[0]?.values, (v)-> v.x
    max_x: => if not @stacks? then super else d3.max @stacks[0]?.values, (v)-> v.x
    min_y: => if not @stacks? then super else 0
    max_y: => if not @stacks? then super else
        d3.max ((d3.sum @stacks, (s)->s.values[i].y) for i in [0..@stacks[0]?.values.length-1])


# A _struct-type_ convention class to describe a stack when manually specifying the set of stacks
# to use for a stackable chart layer.
class c3.Plot.Layer.Stackable.Stack
    @version: 0.1

    # [String] The key for this stack
    key: undefined
    # [Function] A _y accessor_ to use for this stack, overriding the one provided by the chart or layer.
    y: undefined
    # [Array] An array of data elements to use for this stack instead of the layer or chart's `data`.
    data: undefined
    # [String] Name for the stack
    name: undefined
    # [{c3.Selection.Options}] Options to manually set the **class**, **classes**,
    # **styles**, **events**, and **title** of just this stack.
    options: undefined

    constructor: (opt)-> c3.util.extend this, opt


###################################################################
# XY Plot Line and Area Layers
###################################################################

# Abstract chart layer for the {c3.Plot XY Plot Chart}.
# Please instantiate a {c3.Plot.Layer.Line} or {c3.Plot.Layer.Area}
# @see c3.Plot.Layer.Line
# @see c3.Plot.Layer.Area
#
# Define an `r` or `a` to create circles at the various data points along the path
# with that associated radius or area.
#
# ## Extensibility
# The following {c3.Selection} members are made available if appropriate:
# * **paths** - There will be an element in paths for each stack in the layer
# * **circles** - Circles for each datapoint
# * **labels** - Labels for each datapoint
#
# @abstract
# @author Douglas Armstrong
class c3.Plot.Layer.Path extends c3.Plot.Layer.Stackable
    @version: 0.2
    type: 'path'

    # [Function] Factory to generate an SVG path string generator function.  _See {https://github.com/mbostock/d3/wiki/SVG-Shapes#path-data-generators} for details.
    path_generator_factory: undefined
    # [String] The type of D3 line interpolation to use.  _See {https://github.com/mbostock/d3/wiki/SVG-Shapes#area_interpolate d3.svg.area.interpolate} for options._  Some useful examples:
    # * _linear_ - Straight lines between data points
    # * _basis_ - Smooth curve based on a B-spline, the curve may not touch the data points
    # * _cardinal_ - Smooth curve that intersects all data points.
    # * _step-before_ - A step function where the horizontal segments are to the left of the data points
    # * _step-after_ - A step function where the horizontal segments are to the right of the data points
    interpolate: undefined
    # [Number] The tension value [0,1] for cardinal interpolation.  _See {https://github.com/mbostock/d3/wiki/SVG-Shapes#area_tension d3.svg.area.tension}._
    tension: undefined
    # [Function] Accessor function you can return true or false if the data point in data[] is defined or should be skipped.  _See {https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-area_defined d3.svg.area.defined}._
    # Note that this will cause disjoint paths on either side of the missing element,
    # it will not render a continuous path that skips the undefined element.  For that
    # behaviour simply enable `safe` mode and have the x or y accessor return undefined.
    defined: undefined
    # [Number, Function] Define to create circles at the data points along the path with this radius.
    r: undefined
    # [Number, Function] Define to create circles at the data points along the path with this area.
    # Takes precedence over r.
    a: undefined
    # [{c3.Selection.Options}] Options for the svg:path.  For example, to enable animations.
    path_options: undefined
    # [{c3.Selection.Options}] If circles are created at the data points via `r` or `a`, then this
    # defines options used to style or extend them.
    circle_options: undefined
    # [{c3.Selection.Options}] Create labels for each datapoint with these options
    label_options: undefined

    _init: =>
        if not @path_generator_factory? then throw Error "path_generator_factory must be defined for a path layer"
        @path_generator = @path_generator_factory()

    _update: (origin)=> if origin isnt 'zoom'
        super

        @paths = @groups.inherit('path.scaled').options(@path_options)

        # Bind the datapoint circles and labels
        if @r? or @a?
            @circles = @groups.select('circle').options(@circle_options).animate(origin is 'redraw')
                .bind(if @stacks? then (stack)->stack.current_data else @current_data).update()
        if @label_options?
            @labels = @groups.select('text').options(@label_options).animate(origin is 'redraw')
                .bind(if @stacks? then (stack)->stack.current_data else @current_data).update()

    _draw: (origin)=>
        # Only need to update the paths if the data has changed
        if origin isnt 'zoom'
            # Update the path generator based on the current settings
            if @interpolate? then @path_generator.interpolate @interpolate
            if @tension? then @path_generator.tension @tension
            if @defined? then @path_generator.defined @defined

            # Generate and render the paths.
            orig_h = @chart.orig_h ? @h # For rendering on the scaled layer
            @paths.animate(origin is 'redraw').position
                d: if @stacks? then (stack, stack_idx)=>
                        @path_generator
                            .x (d,i)=> orig_h stack.values[i].x
                            .y (d,i)=> @v (stack.values[i].y0 + stack.values[i].y)
                            .y0? if @baseline? and not stack_idx then (d,i)=> @v c3.functor(@baseline)(d,i) else
                                (d,i)=> @v(stack.values[i].y0)
                        @path_generator(stack.current_data) # Call the generator with this particular stack's data
                    else =>
                        @path_generator
                            .x (d,i)=> orig_h @x(d,i)
                            .y (d,i)=> @v @y(d,i)
                            .y0? if @baseline? then (d,i)=> @v c3.functor(@baseline)(d,i) else @height
                        @path_generator(@current_data)

        # Position the circles
        @circles?.animate(origin is 'redraw').position
            cx: (d,i,s)=> @h @x(d,i,s)
            cy:
                if @stacks? then (d,i,s)=> values = @stacks[s].values[i]; @v values.y+values.y0
                else (d,i)=> @v @y(d,i)
            r: if not @a? then @r else
                if typeof @a is 'function' then (d,i,s)=> Math.sqrt( @a(d,i,s) / Math.PI )
                else Math.sqrt( @a / Math.PI )

        # Set the labels
        @labels?.animate(origin is 'redraw').position
            transform: (d,i,s)=> 'translate('+(@h @x(d,i,s))+','+(@v @y(d,i,s))+')'

    _style: (style_new)=>
        super
        @paths.style(style_new)
        @circles?.style(style_new)
        @labels?.style(style_new)

    min_x: => if not @stacks? then (if @data.length then @x @data[0]) else @stacks[0]?.values[0]?.x
    max_x: => if not @stacks? then (if @data.length then @x @data.slice(-1)[0]) else @stacks[0]?.values.slice(-1)[0]?.x

# Line graph layer for the {c3.Plot XY Plot Chart}.  Please refer to {c3.Plot.Layer.Path} for documentation.
# @see c3.Plot.Layer.Path
# @author Douglas Armstrong
class c3.Plot.Layer.Line extends c3.Plot.Layer.Path
    type: 'line'
    path_generator_factory: d3.svg.line


# Area graph layer for the {c3.Plot XY Plot Chart}.  Please refer to {c3.Plot.Layer.Path} for documentation.
# @see c3.Plot.Layer.Path
# @author Douglas Armstrong
# @note The input data array should be sorted along the x axis.
class c3.Plot.Layer.Area extends c3.Plot.Layer.Path
    type: 'area'
    path_generator_factory: d3.svg.area
    # [Number, Function] Base value or accessor for the bottom of the area chart.
    # _Defaults to the bottom of the chart._
    baseline: undefined


###################################################################
# XY Plot Bar Layer
###################################################################

# Bar chart layer for the {c3.Plot XY Plot Chart}
#
# Bar charts may have positive or negative values unless they are stacked,
# then they must be positive.
#
# When an orinal scale is used this layer will adjust it so that it provides padding
# so the full bar on the left and right ends are fully visible.  With other types of scales
# the bars may have arbitrary x values from the user and may overlap.  In this case, it is up
# to the user to set the domain so bars are fully visible.
#
# ## Extensibility
# The following {c3.Selection} members are made available if appropriate:
# * **rects**
#
# @todo Support negative y values for bar layers
# @author Douglas Armstrong
class c3.Plot.Layer.Bar extends c3.Plot.Layer.Stackable
    @version: 0.2
    type: 'bar'

    # [Function] A callback to describe a unique key for each data element.
    # This is useful for animations during a redraw when updating the dataset.
    key: undefined
    # [Number, String, Function] Specify the width of the bars.
    # If this is a number it specifies the bar width in pixels.
    # If this is a string, such as `50%`, then it can specify the width of the bars as a
    # percentage of the available space for each bar based on proximity to each neighbor.
    # If this is a function it can set the width dynamically for each bar.
    bar_width: "50%"
    # [{c3.Selection.Options}] Options for the svg:rect's.
    # The callbacks are called with the user data for the rect as the first argument, the index of that
    # datum as the second argument, and the index of the stack for this rect as the third argument.
    # `stack.options` can be used instead to apply the same options to an entire stack.
    rect_options: undefined

    _update: =>
        super
        @rects = @groups.select('rect').options(@rect_options).animate('origin is redraw')
            .bind((if @stacks? then ((stack)->stack.current_data) else @current_data), @key).update()

    _draw: (origin)=>
        baseline = @v(0)

        # Set bar_width and bar_shift
        if typeof @bar_width is 'function'
            bar_width = @bar_width
            bar_shift = -> bar_width(arguments...) / 2
        else
            bar_width = +@bar_width
            if !isNaN bar_width # The user provided a simple number of pixels
                @h.rangeBands? @h.rangeExtent(), 1, 0.5 # Provide padding for an ordinal D3 scale
                bar_shift = bar_width/2
            else # The user provided a percentage
                if @bar_width.charAt?(@bar_width.length-1) is '%' # Use charAt to confirm this is a string
                    bar_ratio = +@bar_width[..-2] / 100
                    if isNaN bar_ratio then throw "Invalid bar_width percentage "+@bar_width[0..-2]
                    if @h.rangeBands? # Setup padding for an ordinal D3 scale
                        @h.rangeBands @h.rangeExtent(), 1-bar_ratio, 1-bar_ratio
                        bar_width = @h.rangeBand()
                        bar_shift = 0
                    else # Dynamically compute widths based on proximity to neighbors
                        bar_width = if @stacks? then (d,i,j)=>
                                values = @stacks[j].values
                                mid = @h values[i].x
                                left = @h if !i then (@chart.orig_h ? @h).domain()[0] else values[i-1].x
                                right = @h if i==values.length-1 then (@chart.orig_h ? @h).domain()[1] else values[i+1].x
                                width = Math.min((mid-left), (right-mid)) * bar_ratio
                                if width >= 0 then width else 0
                        else (d,i)=>
                                mid = @h @x(d,i)
                                left = @h if !i then (@chart.orig_h ? @h).domain()[0] else @x(@current_data[i-1],i-1)
                                right = @h if i==@current_data.length-1 then (@chart.orig_h ? @h).domain()[1] else @x(@current_data[i+1],i+1)
                                width = Math.min((mid-left), (right-mid)) * bar_ratio
                                if width >= 0 then width else 0
                        bar_shift = -> bar_width(arguments...) / 2
                else throw "Invalid bar_width "+@bar_width

        if @stacks?
            x = (d,i,j)=> @h( @stacks[j].values[i].x )
            y = (d,i,j)=> @v( @stacks[j].values[i].y0 + @stacks[j].values[i].y )
            height = (d,i,j)=> baseline - (@v @stacks[j].values[i].y)
        else
            x = (d,i)=> @h @x(d,i)
            y = (d,i)=> y=@y(d,i); if y>0 then @v(y) else baseline
            height = (d,i)=> Math.abs( baseline - (@v @y(d,i)) )

        @rects.animate(origin is 'redraw').position
            x: if not bar_shift then x
            else if typeof bar_shift isnt 'function' then -> x(arguments...) - bar_shift
            else -> x(arguments...) - bar_shift(arguments...)
            y: y
            height: height
            width: bar_width

    _style: (style_new)=>
        super
        @rects.style(style_new)


###################################################################
# XY Plot Straight Line Layers
###################################################################

# Straight **horizontal** or **vertical** line layer for the
# {c3.Plot XY Plot Chart}.  This is an **abstract** layer, please instantiate a
# {c3.Plot.Layer.Line.Horizontal} or {c3.Plot.Layer.Line.Vertical}
# directly.
#
# A seperate line is drawn for each data element in the `data` array.
# _Set `label_options.text` to define a **label** for each line._
# Straight line layers are not _{c3.Plot.Layer.Stackable stackable}_.
#
# ## Extensibility
# The following {c3.Selection} members are made available if appropriate:
# * **vectors**
# * **lines**
# * **labels**
#
# ## Events
# * **dragstart**
# * **drag**
# * **dragend**
#
# @abstract
# @author Douglas Armstrong
class c3.Plot.Layer.Line.Straight extends c3.Plot.Layer
    @version: 0.1
    type: 'straight'

    # [Function] Optional accessor to identify data elements when changing the dataset
    key: undefined
    # [Function] Accessor to get the value for each data element.
    # _Defaults to the identity function._
    value: undefined
    # [Function] Accessor to determine if data elements are filtered in or not.
    filter: undefined
    # [Boolean] Enable lines to be draggable.
    # The drag event callbacks can be used to adjust the original data values
    draggable: false
    # [{c3.Selection.Options}] Options for the svg:g of the vector group nodes.
    # There is one node per data element.  Use this option for animating line movement.
    vector_options: undefined
    # [{c3.Selection.Options}] Options for the svg:line lines.
    line_options: undefined
    # [{c3.Selection.Options}] Options for the svg:line lines for hidden lines
    # behind each line that is wider and easier for users to interact with
    # e.g. for click or drag events.
    grab_line_options: undefined
    # [{c3.Selection.Options}] Define this to render labels.  Options for the svg:text labels.
    # This option also takes the following additional properties:
    # * **alignment** - [String] Alignment of label.
    # * * `left` or `right` for horizontal lines
    # * * `top` or `bottom` for vertical lines
    # * **dx** - [String] Relative placement for the label
    # * **dy** - [String] Relative placement for the label
    label_options: undefined

    _init: =>
        @value ?= (d)-> d

        # Draggable lines
        if @draggable
            # NOTE: Because vertical lines are rotated, we are always dragging `y`
            self = this
            @dragger = d3.behavior.drag()
            drag_value = undefined
            #drag.origin (d,i)=> { y: @scale @value(d,i) }
            @dragger.on 'dragstart', (d,i)=>
                d3.event.sourceEvent.stopPropagation() # Prevent panning in zoomable charts
                @trigger 'dragstart', d, i
            @dragger.on 'drag', (d,i)->
                domain = (self.chart.orig_h ? self.scale).domain()
                drag_value = Math.min(Math.max(self.scale.invert(d3.event.y), domain[0]), domain[1])
                d3.select(this).attr 'transform', 'translate(0,'+self.scale(drag_value)+')'
                self.trigger 'drag', drag_value, d, i
            @dragger.on 'dragend', (d,i)=>
                @trigger 'dragend', drag_value, d, i

    _size: =>
        @lines?.all?.attr 'x2', @line_length
        @grab_lines?.all?.attr 'x2', @line_length
        @labels?.all?.attr 'x', if @label_options.alignment is 'right' or @label_options.alignment is 'top' then @width else 0

    _update: (origin)=>
        @current_data = if @filter? then (d for d,i in @data when @filter(d,i)) else @data

        @vectors = @content.select('g.vector').options(@vector_options).animate(origin is 'redraw')
            .bind(@current_data, @key).update()
        @lines = @vectors.inherit('line').options(@line_options).update()

        if @label_options?
            @label_options.dx ?= '0.25em'
            @label_options.dy ?= '-0.25em'
            @labels = @vectors.inherit('text').options(@label_options).update()

        # Add extra width for grabbable line area
        if @draggable or @grab_line_options
            @grab_lines = @vectors.inherit('line.grab')
            if @grab_line_options then @grab_lines.options(@grab_line_options).update()

        if @draggable
            @vectors.new.call @dragger

    _draw: (origin)=>
        @vectors.animate(origin is 'redraw').position
            transform: (d,i)=> 'translate(0,' + (@scale @value(d,i)) + ')'

        @lines.new.attr 'x2', @line_length
        @grab_lines?.new.attr 'x2', @line_length

        if @labels?
            far_labels = @label_options.alignment is 'right' or @label_options.alignment is 'top'
            @g.style 'text-anchor', if far_labels then 'end' else 'start'
            @labels.position
                dx: if far_labels then '-'+@label_options.dx else @label_options.dx
                dy: @label_options.dy
                x: if far_labels then @line_length else 0

    _style: (style_new)=>
        @g.classed 'draggable', @draggable
        @vectors.style(style_new)
        @lines.style(style_new)
        @grab_lines?.style?(style_new)
        @labels?.style?(style_new)

# Horizontal line layer.  Please refer to {c3.Plot.Layer.Line.Straight} for documentation.
# @see c3.Plot.Layer.Line.Straight
class c3.Plot.Layer.Line.Horizontal extends c3.Plot.Layer.Line.Straight
    type: 'horizontal'

    _init: =>
        label_options?.alignment ?= 'left'
        super
        @scale = @v

    _size: =>
        @line_length = @width
        super

# Vertical line layer.  Please refer to {c3.Plot.Layer.Line.Straight} for documentation.
# @see c3.Plot.Layer.Line.Straight
class c3.Plot.Layer.Line.Vertical extends c3.Plot.Layer.Line.Straight
    type: 'vertical'

    _init: =>
        label_options?.alignment ?= 'top'
        super
        @scale = @h

    _size: =>
        @g.attr
            transform: 'rotate(-90) translate('+-@height+',0)'
        @line_length = @height
        super


###################################################################
# XY Plot Region Layers
###################################################################

# Render a rectangular region in an {c3.Plot XY Plot Chart}.
# @author Douglas Armstrong
# @todo Implement this layer type
class c3.Plot.Layer.Region extends c3.Plot.Layer
    type: 'region'


###################################################################
# XY Plot Scatter Layer
###################################################################

# Scatter plot layer for the {c3.Plot XY Plot Chart}
#
# Datapoints include a circle and an optional label.
# _Set `label_options.text` to define the label for each point._
#
# ## Extensibility
# The following {c3.Selection} members are made available if appropriate:
# * **points** - Representing svg:g nodes for each datapoint
# * **circles** - Representing svg:circle nodes for each datapoint
# * **labels** - Representing svg:text labels for each datapoint
# @author Douglas Armstrong
# @todo Only render datapoints within the current zoomed domain.
class c3.Plot.Layer.Scatter extends c3.Plot.Layer
    @version: 0.1
    type: 'scatter'

    # [Function] Accessor function to define a unique key to each data point.  This has performance implications.
    # _This is required to enable **animations**._
    key: undefined
    # [Function, Number] Accessor or value to set the value for each data point.
    #   This is used when limiting the number of elements.
    value: undefined
    # [Function, Number] Accessor or value to set the circle radius
    r: 1
    # [Function, Number] Accessor or value to set the circle area. _Takes precedence over r._
    a: undefined
    # [Boolean] Safe mode will not render data where a positioning accessor returns undefined.
    # _This may cause the index passed to accessors to not match the original data array._
    safe: true
    # [Function] Accessor to determine if the data point should be drawn or not
    # _This may cause the index passed to accessors to not match the original data array._
    filter: undefined
    # [Number] Limit the number of data points.
    # _This may cause the index passed to accessors to not match the original data array._
    limit_elements: undefined
    # [{c3.Selection.Options}] Options for svg:g nodes for each datapoint.
    point_options: undefined
    # [{c3.Selection.Options}] Options for the svg:circle of each datapoint
    circle_options: undefined
    # [{c3.Selection.Options}] Options for the svg:text lables of each datapoint.
    label_options: undefined

    _init: =>
        if not @x? then throw Error "x must be defined for a scatter plot layer"
        if not @y? then throw Error "y must be defined for a scatter plot layer"
        if not @h? then throw Error "h must be defined for a scatter plot layer"
        if not @v? then throw Error "v must be defined for a scatter plot layer"

    _update: (origin)=>
        if not @data then throw Error "Data must be defined for scatter layer."

        # Filter the data for safety
        @current_data = if @filter? and @key? then (d for d,i in @data when @filter(d,i)) else @data
        if @safe then @current_data = (d for d in @current_data when (
            @x(d)? and @y(d)? and (!@a? or typeof @a!='function' or @a(d)?) and (typeof @r!='function' or @r(d)?) ))

        # Limit the number of elements?
        if @limit_elements?
            if @value?
                @current_data = @current_data[..] # Copy the array to avoid messing up the user's order
                c3.array.sort_up @current_data, (d)=> -@value(d) # Sort by negative to avoid reversing array
                @current_data = @current_data[..@limit_elements]
            else @current_data = @current_data[..@limit_elements]

        # Bind and create the elements
        @points = @content.select('g.point').options(@point_options).animate(origin is 'redraw')
            .bind(@current_data, @key).update()

        # If there is no key, then hide the elements that are filtered
        if @filter? and not @key?
            @points.all.attr 'display', (d,i)=> if not @filter(d,i) then 'none'

        # Add circles to the data points
        @circles = @points.inherit('circle').options(@circle_options).animate(origin is 'redraw').update()

        # Add labels to the data points
        if @label_options?
            @labels = @points.inherit('text').options(@label_options).update()

    _draw: (origin)=>
        @points.animate(origin is 'redraw').position
            transform: (d,i)=> 'translate('+(@h @x(d,i))+','+(@v @y(d,i))+')'

        @circles.animate(origin is 'redraw').position
            r: if not @a? then @r else
                if typeof @a is 'function' then (d,i)=> Math.sqrt( @a(d,i) / Math.PI )
                else Math.sqrt( @a / Math.PI )

    _style: (style_new)=>
        @points.style(style_new)
        @circles.style(style_new)
        @labels?.style(style_new)


###################################################################
# XY Plot Swimlane Layers
###################################################################

# Base abstract class for {c3.Plot XY Plot} {c3.Plot.Layer layers} with horizontal swim lanes.
# Swimlanes are numbered based on the vertical scale domain for the layer.
# The first entry in the domain is the top swimlane and the last entry is the bottom swimlane plus 1.
# If the first and last domain values are equal, then there are no swimlanes rendered.
#
# ## Extensibility
# The following {c3.Selection} members are made available if appropriate:
# * **lanes** - svg:rect's for each swimlane
# * **tip** - HTML hover content
# @abstract
# @author Douglas Armstrong
class c3.Plot.Layer.Swimlane extends c3.Plot.Layer
    type: 'swimlane'

    # [Number] Height of a swimlane in pixels.
    # Chart height will be adjusted if number of swimlanes changes in a redraw()
    dy: undefined
    # [Function] Provide HTML content for a hover div when mousing over the layer
    # This callback will be called with the datum and index of the item being hovered over.
    # It will be called with null when hovering over the layer but not any data items.
    hover: undefined
    # [{c3.Selection.Options}] Options for the lane svg:rect nodes for swimlanes
    lane_options: undefined

    _init: =>
        if @lane_options? then @lanes = @content.select('rect.lane',':first-child').options(@lane_options)

        # Support html hover tooltips
        if @hover?
            anchor = d3.select(@chart.anchor)
            @tip = c3.select( anchor, 'div.c3.hover' ).singleton()
            layer = this
            mousemove = ->
                [layerX,layerY] = d3.mouse(this)
                # Get swimlane and ensure it is properly in range (mouse may be over last pixel)
                swimlane = Math.floor layer._v.invert layerY
                swimlane = Math.min swimlane, Math.max layer.v.domain()[0], layer.v.domain()[1]-1
                x = layer.h.invert layerX

                hover_html = (c3.functor layer.hover) layer._hover_datum(x, swimlane)..., swimlane
                if not hover_html
                    layer.tip.all.style 'display', 'none'
                else
                    layer.tip.all.html hover_html
                    layer.tip.all.style
                        display: 'block'
                        left: d3.event.clientX+'px'
                        top: d3.event.clientY+'px'
            # Manage tooltip event handlers, disable while zooming/panning
            @chart.content.all.on 'mouseleave.hover', => layer.tip.all.style 'display', 'none'
            @chart.content.all.on 'mousedown.hover', =>
                layer.tip.all.style 'display', 'none'
                @chart.content.all.on 'mousemove.hover', null
            @chart.content.all.on 'mouseup.hover', => @chart.content.all.on 'mousemove.hover', mousemove
            @chart.content.all.on 'mouseenter.hover', => if !d3.event.buttons then @chart.content.all.on 'mousemove.hover', mousemove
            @chart.content.all.on 'mousemove.hover', mousemove

    _size: =>
        # If @dy is not defined, we determine it based on the chart height
        if not @y? then @dy = @height
        else @dy ?= Math.round @height / (Math.abs(@v.domain()[1]-@v.domain()[0]))

    _update: =>
        # Support constant values and accessors
        @x = c3.functor @x
        @dx = c3.functor @dx

        @lanes?.bind([@v.domain()[0]...@v.domain()[1]]).update()

        # Update chart height to fit current number of swimlanes based on current v domain
        if @y? then @chart.size null, @dy*(Math.abs(@v.domain()[1]-@v.domain()[0])) + @chart.margins.top + @chart.margins.bottom

    _draw: (origin)=>
        # Swimlanes go from top to bottom.
        # The domain works because the end-point isn't inclusive which allows room for the swimlane width.
        # Don't mess with @v in case it is shared among layers
        @_v = @v.copy()
        c3.d3.set_range @_v, [0, @height]

        if origin is 'resize' or origin is 'render'
            @lanes?.position
                y: (lane)=> @_v lane+(if @invert_y then 1 else 0)
                width: @chart.orig_h.range()[1]
                height: @dy

    _style: =>
        @lanes?.style()


###################################################################
# XY Plot Segment Swimlane Layer
###################################################################

# A {c3.Plot.Layer.Swimlane swimlane layer} for drawing horizontal segments in each swimlane.
#
# _Set `label_options.text` to define labels for the segments._
# The following {c3.Selection} members are made available if appropriate:
# ## Extensibility
# The following {c3.Selection} members are made available if appropriate:
# * **rects** - svg:rect for each segment
# @todo Better threshold for determining which segments get labels.  _Currently it is any segment > 50 pixels wide._
# @author Douglas Armstrong
class c3.Plot.Layer.Swimlane.Segment extends c3.Plot.Layer.Swimlane
    @version: 0.1
    type: 'segment'

    # **REQUIRED** [Function] Accessor to get the width of the segment
    dx: null
    # [Function] Key accessor to uniquely identify the segment.
    #   Defining this improves performance for redraws.
    key: undefined
    # [Function] Accessor to determine if an element should be rendered
    filter: undefined
    # [Function] Value accessor for each segment.  Used when limiting the number of elements.  _Defaults to dx._
    value: undefined
    # [Number] Specifies the maximum number of segments this layer will draw.  Smaller segments are elided first.
    limit_elements: undefined
    # [{c3.Selection.Options}] Options for the svg:rect nodes for each segment
    rect_options: undefined
    # [{c3.Selection.Options}] Options for the label svg:text nodes for each segment
    label_options: undefined

    # IE10/11 doesn't support vector-effects: non-scaling-stroke, so avoid using scaled SVG.
    # This is a performance hit, because then we have to adjust the position of all rects for each redraw
    # TODO: Investigate if they added support for this in Edge.
    #scaled = !window.navigator.userAgent.match(/MSIE|Trident/) # MSIE==IE10, Trident==IE11, Edge==Edge
    # [3/18/2016] Disable the SVG scaled layer optimization completely for now.
    # If there are very large domains (e.g. a billion) then there is a floating-point precision problem
    # relying on SVG transformations to do the scaling/translation.
    # This doesn't seem to be a problem if we do the scaling ourselves in JavaScript.
    scaled = false

    _init: =>
        super
        @g.classed 'segment', true # Manually do this so inherited types also have this class
        if scaled then @scaled_g = @g.append('g').attr('class','scaled')
        @rects_group = c3.select((@scaled_g ? @g),'g.segments').singleton()
        if @label_options? then @labels_clip = c3.select(@g,'g.labels').singleton().select('svg')

    _hover_datum: (x, swimlane)=>
        right = @h.invert @h(x)+1 # Get the pixel width
        for datum,idx in @current_data
            if (!@y? or @y(datum)==swimlane) and (_x=@x(datum)) <= right and x <= _x+@dx(datum) then break
        return if idx==@current_data.length then [null,null] else [datum,idx]

    _update: =>
        super
        # Pull filtered data elements
        @current_data = if not @filter? then @data else (d for d,i in @data when @filter(d,i))

        # Pre-sort data by "value" for limiting to the most important elements
        if @limit_elements?
            if not @filter? then @current_data = @current_data[..]
            c3.array.sort_down @current_data, (@value ? @dx)

    _draw: (origin)=>
        super

        # Gather data for the current viewport domain
        [left_edge, right_edge] = @h.domain()
        half_pixel_width = (right_edge-left_edge) / ((@h.range()[1]-@h.range()[0]) || 1) / 2
        data = []
        for datum in @current_data when (x=@x(datum)) < right_edge and (x+(dx=@dx(datum))) > left_edge
            if dx < half_pixel_width
                if @limit_elements? then break else continue
            data.push datum
            if data.length == @limit_elements then break

        # Bind here because the current data set is dynamic based on zooming
        @rects = @rects_group.select('rect.segment').options(@rect_options).bind(data, @key).update()

        # Position the rects
        h = if @scaled_g? then (@chart.orig_h ? @h) else @h
        zero_pos = h(0)
        (if origin is 'resize' then @rects.all else @rects.new).attr 'height', @dy
        (if !scaled or !@key? or origin=='resize' or (origin=='redraw' and this instanceof c3.Plot.Layer.Swimlane.Flamechart)
        then @rects.all else @rects.new).attr
            x: (d)=> h @x(d)
            width: (d)=> (h @dx(d)) - zero_pos
            y: if not @y? then 0 else (d)=> @_v @y(d)

        # Bind and render lables here (not in _update() since the set is dynamic based on zooming and resizing)
        if @label_options?
            # Create labels in a nested SVG node so we can crop them based on the segment size.
            zero_pos = @h(0)
            current_labels = (datum for datum in data when (@h @dx datum)-zero_pos>50)
            @labels_clip.bind(current_labels, @key)
            @labels = @labels_clip.inherit('text').options(@label_options).update()

            (if origin is 'resize' then @labels_clip.all else @labels_clip.new).attr 'height', @dy
            @labels_clip.position
                x: (d)=> @h @x(d)
                y: if not @y? then 0 else (d,i)=> @_v @y(d,i)
                width: (d)=> (@h @dx(d)) - zero_pos
            self = this
            (if origin is 'resize' then @labels.all else @labels.new).attr 'y', self.dy/2
            @labels.position
                x: (d)->
                    x = self.x(d)
                    dx = self.dx(d)
                    left = Math.max x, self.h.domain()[0]
                    right = Math.min x+dx, self.h.domain()[1]
                    return self.h( (right-left)/2 + (left-x) ) - zero_pos
#                x: (d)->
#                    x = self.x(d)
#                    dx = self.dx(d)
#                    left = Math.max x, self.h.domain()[0]
#                    right = Math.min x+dx, self.h.domain()[1]
#                    # NOTE: This is expensive.  Chrome was faster with offsetWidth, but Firefox and IE11 didn't support it
#                    text_width = this.offsetWidth ? this.getBBox().width
#                    if self.h(right-left)-zero_pos > text_width
#                        return self.h( (right-left)/2 + (left-x) ) - zero_pos - (text_width/2)
#                    else
#                        return if x < left then self.h(left-x)-zero_pos+1 else 1
        else c3.select(@g,'g.labels').all.remove()

        # Style any new elements we added by resizing larger that allowed new relevant elements to be drawn
        if origin is 'resize' and (not @rects.new.empty() or (@labels? and not @labels.new.empty()))
            @_style true

    _style: (style_new)=>
        super
        @rects.style(style_new)
        @labels?.style(style_new)


###################################################################
# Flamechart
###################################################################

# A {c3.Plot.Layer.Swimlane swimlane layer} for rendering flamecharts.
# You should not define `y`, but you must define a `dy`.
# A `key()` is required for this layer.
#
# @todo Remove requirement for `key()` accessor.
# @author Douglas Armstrong
class c3.Plot.Layer.Swimlane.Flamechart extends c3.Plot.Layer.Swimlane.Segment
    @version: 0.1
    type: 'flamechart'

    _init: =>
        super
        if not @key? then throw Error "`key()` accessor function is required for Flamechart layers"
        if not @dy? then throw Error "`dy` option is required for Flamechart layers"
        if @y? then throw Error "`y` option cannot be defined for Flamechart layers"
        @y = (d)=> @depths[@key d]
        @depths = {}

    _update: (origin)=>
        super

        # Compute depths for each data element
        data = @current_data[..]
        c3.array.sort_up data, @x
        max_depth = 0
        stack = []
        for datum in data
            frame =
                x: @x datum
                dx: @dx datum
            while stack.length and frame.x >= (_frame=stack[stack.length-1]).x + _frame.dx
                stack.length--
            stack.push frame
            max_depth = Math.max max_depth, stack.length # stack.length is starting from 0, so don't reduce by one.
            @depths[@key datum] = stack.length - 1

        # Set the vertical domain and resize chart based on maximum flamechart depth
        @v.domain [0, max_depth]
        c3.Plot.Layer.Swimlane::_update.call this, origin


###################################################################
# XY Plot Sampled Swimlane Layers
###################################################################

# A {c3.Plot.Layer.Swimlane swimlane layer} that will sample for each pixel in each swimlane.
# @abstract
# @author Douglas Armstrong
class c3.Plot.Layer.Swimlane.Sampled extends c3.Plot.Layer.Swimlane
    @version: 0.0
    type: 'sampled'

    # **REQUIRED** [Function] Accessor to get the width of the segment
    dx: null
    # [Function] Callback to determine if the data element should be rendered or not
    filter: undefined
    # [Boolean] If safe mode is off then it is assumed the data is sorted by the x-axis
    safe: true

    _hover_datum: (x, swimlane)=>
        data = @swimlane_data[swimlane]
        right = @h.invert @h(x)+1 # Get the pixel width
        idx = d3.bisector(@x).right(data, x) - 1
        return if idx<0 then [null,null]
        else if x < @x(datum=data[idx])+@dx(datum) then [datum,idx]
        else if ++idx<data.length and @x(datum=data[idx]) <= right then [datum,idx]
        else [null,null]

    _update: =>
        super
        # Arrange data by swimlane and remove filtered items
        @swimlane_data = []
        @swimlane_data[swimlane] = [] for swimlane in [@v.domain()[0]...@v.domain()[1]]
        [top_edge, bottom_edge] = @v.domain()
        for datum, i in @data when (!@filter? or @filter(datum,i))
            swimlane = @y datum, i
            if top_edge <= swimlane < bottom_edge then @swimlane_data[swimlane].push datum

        # Sort data in safe mode
        if @safe
            c3.array.sort_up(data,@x) for data in @swimlane_data

    _sample: (sample)=>
        # Sample data points for each pixel in each swimlane
        bisector = d3.bisector(@x).right
        for swimlane in [@v.domain()[0]...@v.domain()[1]]
            v = @_v swimlane
            data = @swimlane_data[swimlane]
            if not data.length then continue

            # Optimized to find the left starting point
            prev_idx = bisector(data, @h.domain()[0])
            if not prev_idx
                pixel = Math.round @h @x @data[prev_idx]
            else
                prev_idx--
                pixel = if @h(@x(@data[prev_idx])+@dx(@data[prev_idx])) > 0 then 0 else
                    Math.round @h @x data[prev_idx]

            # Iterate through each pixel in this swimlane
            while pixel < @width
                x = @h.invert(pixel)

                # Find the next data element for this pixel, or skip to the next pixel if there is a gap
                idx = prev_idx
                while idx < data.length
                    datum = data[idx]
                    prev_idx = idx
                    if (datum_x=@x(datum)) > x
                        pixel = Math.round @h datum_x
                        break
                    if x <= datum_x+@dx(datum) then break
                    idx++
                if idx==data.length then break

                sample pixel, v, datum
                pixel++
        return # avoid returning a comprehension


# A {c3.Plot.Layer.Swimlane.Sampled sampled swimlane layer} implemented via SVG lines
# ## Extensibility
# The following {c3.Selection} members are made available if appropriate:
# * **lines** - svg:rect's for each swimlane
# @todo Optimize by generating pixel data array once in _size() and reusing it in _draw()
class c3.Plot.Layer.Swimlane.Sampled.SVG extends c3.Plot.Layer.Swimlane.Sampled
    @version: 0.0
    type: 'svg'

    # [{c3.Selection.Options}] Options for the svg:line's in each swimlane
    line_options: undefined

    _draw: (origin)=>
        super

        # Gather sampled pixels to bind to SVG linex
        current_data = []
        pixels = []
        @_sample (x,y,datum)->
            current_data.push datum
            pixels.push { x:x, y:y }

        # Bind data in _draw without a key because it is based on pixel sampling
        @lines = c3.select(@g,'line').options(@line_options).bind(current_data).update()
        @lines.position
            x1: (d,i)-> pixels[i].x + 0.5 # Center line on pixels to avoid anti-aliasing
            x2: (d,i)-> pixels[i].x + 0.5
            y1: if not @y? then 0 else (d,i)-> pixels[i].y - 0.5
            y2: if not @y? then @height else (d,i)=> pixels[i].y + @dy - 0.5

    _style: =>
        super
        @lines.style()


# A {c3.Plot.Layer.Swimlane.Sampled sampled swimlane layer} implemented via HTML5 Canvas
# This layer supports `line_options.styles.stroke` and HTML `hover` "tooltips".
class c3.Plot.Layer.Swimlane.Sampled.Canvas extends c3.Plot.Layer.Swimlane.Sampled
    @version: 0.0
    type: 'canvas'

    # [{c3.Selection.Options}] Options for the svg:line's in each swimlane
    line_options: undefined

    _init: =>
        super
        foreignObject = c3.select(@g,'foreignObject').singleton().position({height:'100%',width:'100%'})
        @canvas = foreignObject.select('xhtml|canvas').singleton()
        #@canvas = document.createElement('canvas')
        #@image = c3.select(@g,'svg|image').singleton()

    _size: =>
        super
        @canvas.position
            height: @height
            width: @width

    __draw: =>
        context = @canvas.node().getContext('2d')
        context.clearRect 0,0, @width,@height

        # Translate by 0.5 so lines are centered on pixels to avoid anti-aliasing which causes transparency
        context.translate 0.5, 0.5

        # Sample pixels to render onto canvas
        stroke = c3.functor @line_options?.styles?.stroke
        @_sample (x,y,datum)=>
            context.beginPath()
            context.moveTo x, y
            context.lineTo x, y+@dy
            context.strokeStyle = stroke datum
            context.stroke()

        context.translate -0.5, -0.5

        #@image.all.attr('href',@canvas.toDataURL('image/png'))
        #@image.all.node().setAttributeNS('http://www.w3.org/1999/xlink','xlink:href',@canvas.toDataURL('image/png'))

    _draw: (origin)=> super; @__draw(origin)

    _style: (style_new)=> super; if not style_new then @__draw('restyle')

    # For the sampled layer, draw and style are the same.  By default zoom does both, so just do one.
    zoom: => @__draw('zoom')


####################################################################
## XY Plot Decimated Layer
####################################################################
#
## A decimated layer may be created to assist certain layer types to manage large datasets.
## When using a decimated layer pass into the constructor an array of the data at different
## detail granularities as well as a layer object instance that will be used as a "prototype" to
## construct a different layer for each detail level of data.  This layer will only show one
## of the levels at a time and will automatically transition between them as the user zooms in and out.
##
## _When using a decimated layer, the **data** option does not need to be set for the layer prototype._
## @example Creating a decimated layer
##    mychart = new c3.Plot.horiz_zoom {
##       anchor: '#chart_div'
##       h: d3.scale.linear().domain [0, 1]
##       v: d3.scale.linear().domain [0, 100]
##       x: (d)-> d.x
##       layers: [
##           new c3.Layer.decimated mylevels, new c3.Layer.area {
##               y: (d)-> d.y
##               interpolate: 'basis'
##           }
##       ]
##    }
##    mychart.render()
## @todo Should this be implemented as a mix-in instead?
## @todo A built-in helper for users to construct decimated groups using CrossFilter
## @author Douglas Armstrong
#class c3.Plot.Layer.Decimated extends c3.Plot.Layer
#    @version: 0.1
#    type: 'decimated'
#
#    # [Number] The maximum number of data elements to render at a given time when preparing sections for smooth panning.
#    renderport_elements: 8000
#    # [Number] If a decimated element spans more than this number of pixels after zooming then switch to the next level of detail.
#    pixels_per_bucket_limit: 2
#
#    # @param levels [Array] An Array of detail levels.  Each entry in the array should be an array of data elements.
#    # Each level should also add a **granulairty** property which specified how many X domain units are combined into a single element for this level of detail.
#    # @param proto_layer [c3.Plot.Layer] A layer instance to use as a prototype to make layers for each level of detail.
#    constructor: (@levels, @proto_layer)->
##        @type += ' '+@proto_layer.type
#        for level, i in @levels
#            level.index = i
#            level.renderport = [0,0]
#
#    _init: =>
#        for level, i in @levels
#            level.layer = new @proto_layer.constructor()
#            c3.util.defaults level.layer, @proto_layer
#            level.layer.data = level
#            level.layer.g = @g.append('g').attr('class', 'level _'+i+' layer')
#            level.layer.init @chart
#
#    _size: =>
#        for level in @levels
#            level.layer.size @width, @height
#
#    _update: =>
#        # Invalidate the non-visible levels
#        for level in @levels when level isnt @current_level
#            level.renderport = [0,0]
#        @current_level?.layer.update()
#
#    _draw: =>
#        if not @current_level? then @zoom()
#        else @current_level.layer.draw()
#
#    zoom: =>
#        # Find the optimal decimation level for this viewport
#        view_width = @chart.h.domain()[1] - @chart.h.domain()[0]
#        for level in @levels
#            visible_buckets = view_width / level.granularity
#            if visible_buckets*@pixels_per_bucket_limit > @width
#                new_level = level
#                break
#        if !new_level? then new_level = @levels[@levels.length-1]
#
#        # Did we change decimation levels?
#        if @current_level != new_level
#            @current_level = new_level
#            @g.selectAll('g.level').style('display','none')
#            @g.select('g.level[class~=_'+@current_level.index+']').style('display',null)
#
#        # Determine if current viewport is outside current renderport and we need to redraw
#        if @chart.h.domain()[0] < @current_level.renderport[0] or
#           @chart.h.domain()[1] > @current_level.renderport[1]
#
#            # Limit number of elements to render, centered on the current viewport
#            center = (@chart.h.domain()[0]+@chart.h.domain()[1]) / 2
#            bisector = d3.bisector (d)->d.key
#            center_element = bisector.left @current_level, center
#            element_domain = []
#            element_domain[0] = center_element - @renderport_elements/2 - 1
#            element_domain[1] = center_element + @renderport_elements/2
#            if element_domain[0]<0 then element_domain[0] = 0
#            if element_domain[1]>@current_level.length-1 then element_domain[1] = @current_level.length-1
#
#            @current_level.renderport = (@current_level[i].key for i in element_domain)
#
#            # Swap data for the new renderport and redraw
#            @current_level.layer.data = @current_level[element_domain[0]..element_domain[1]]
#            @current_level.layer.redraw()
