Full Code of Netflix/ember-nf-graph for AI

master 851b63edc969 cached
290 files
2.4 MB
655.8k tokens
147 symbols
1 requests
Download .txt
Showing preview only (2,621K chars total). Download the full file or copy to clipboard to get everything.
Repository: Netflix/ember-nf-graph
Branch: master
Commit: 851b63edc969
Files: 290
Total size: 2.4 MB

Directory structure:
gitextract_jjd60v_k/

├── .editorconfig
├── .ember-cli
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── .watchmanconfig
├── CHANGELOG.md
├── LICENSE.md
├── OSSMETADATA
├── README.md
├── addon/
│   ├── .gitkeep
│   ├── components/
│   │   ├── .gitignore
│   │   ├── nf-area-stack.js
│   │   ├── nf-area.js
│   │   ├── nf-bars-group.js
│   │   ├── nf-bars.js
│   │   ├── nf-brush-selection.js
│   │   ├── nf-component.js
│   │   ├── nf-crosshairs.js
│   │   ├── nf-dot.js
│   │   ├── nf-graph-content.js
│   │   ├── nf-graph-yieldables.js
│   │   ├── nf-graph.js
│   │   ├── nf-group.js
│   │   ├── nf-horizontal-line.js
│   │   ├── nf-line.js
│   │   ├── nf-plot.js
│   │   ├── nf-plots.js
│   │   ├── nf-range-marker.js
│   │   ├── nf-range-markers.js
│   │   ├── nf-right-tick.js
│   │   ├── nf-selection-box.js
│   │   ├── nf-svg-image.js
│   │   ├── nf-svg-line.js
│   │   ├── nf-svg-path.js
│   │   ├── nf-svg-rect.js
│   │   ├── nf-tick-label.js
│   │   ├── nf-tracker.js
│   │   ├── nf-vertical-line.js
│   │   ├── nf-x-axis.js
│   │   ├── nf-y-axis.js
│   │   └── nf-y-diff.js
│   ├── mixins/
│   │   ├── .gitignore
│   │   ├── graph-area-utils.js
│   │   ├── graph-data-graphic.js
│   │   ├── graph-graphic-with-tracking-dot.js
│   │   ├── graph-line-utils.js
│   │   ├── graph-registered-graphic.js
│   │   ├── graph-requires-scale-source.js
│   │   └── graph-selectable-graphic.js
│   ├── styles/
│   │   └── addon.css
│   ├── templates/
│   │   └── components/
│   │       ├── nf-area-stack.hbs
│   │       ├── nf-area.hbs
│   │       ├── nf-bars-group.hbs
│   │       ├── nf-bars.hbs
│   │       ├── nf-brush-selection.hbs
│   │       ├── nf-component.hbs
│   │       ├── nf-crosshairs.hbs
│   │       ├── nf-dot.hbs
│   │       ├── nf-graph-content.hbs
│   │       ├── nf-graph-yieldables.hbs
│   │       ├── nf-graph.hbs
│   │       ├── nf-group.hbs
│   │       ├── nf-horizontal-line.hbs
│   │       ├── nf-line.hbs
│   │       ├── nf-plot.hbs
│   │       ├── nf-plots.hbs
│   │       ├── nf-range-marker.hbs
│   │       ├── nf-range-markers.hbs
│   │       ├── nf-right-tick.hbs
│   │       ├── nf-selection-box.hbs
│   │       ├── nf-svg-image.hbs
│   │       ├── nf-svg-line.hbs
│   │       ├── nf-svg-path.hbs
│   │       ├── nf-svg-rect.hbs
│   │       ├── nf-tick-label.hbs
│   │       ├── nf-tracker.hbs
│   │       ├── nf-vertical-line.hbs
│   │       ├── nf-x-axis.hbs
│   │       ├── nf-y-axis.hbs
│   │       └── nf-y-diff.hbs
│   └── utils/
│       ├── nf/
│       │   ├── array-helpers.js
│       │   ├── graph-event.js
│       │   ├── graph-mouse-event.js
│       │   ├── graph-position.js
│       │   ├── scale-utils.js
│       │   ├── scroll-area-action-context.js
│       │   ├── svg-dom.js
│       │   └── tracked-array-property.js
│       └── parse-property-expression.js
├── app/
│   ├── .gitkeep
│   └── components/
│       ├── .gitignore
│       ├── nf-area-stack.js
│       ├── nf-area.js
│       ├── nf-bars-group.js
│       ├── nf-bars.js
│       ├── nf-brush-selection.js
│       ├── nf-component.js
│       ├── nf-crosshairs.js
│       ├── nf-dot.js
│       ├── nf-graph-content.js
│       ├── nf-graph-yieldables.js
│       ├── nf-graph.js
│       ├── nf-group.js
│       ├── nf-horizontal-line.js
│       ├── nf-line.js
│       ├── nf-plot.js
│       ├── nf-plots.js
│       ├── nf-range-marker.js
│       ├── nf-range-markers.js
│       ├── nf-right-tick.js
│       ├── nf-selection-box.js
│       ├── nf-svg-image.js
│       ├── nf-svg-line.js
│       ├── nf-svg-path.js
│       ├── nf-svg-rect.js
│       ├── nf-tick-label.js
│       ├── nf-tracker.js
│       ├── nf-vertical-line.js
│       ├── nf-x-axis.js
│       ├── nf-y-axis.js
│       └── nf-y-diff.js
├── config/
│   ├── ember-try.js
│   └── environment.js
├── docs/
│   ├── api.js
│   ├── assets/
│   │   ├── css/
│   │   │   └── main.css
│   │   ├── index.html
│   │   ├── js/
│   │   │   ├── api-filter.js
│   │   │   ├── api-list.js
│   │   │   ├── api-search.js
│   │   │   ├── apidocs.js
│   │   │   └── yui-prettify.js
│   │   └── vendor/
│   │       └── prettify/
│   │           ├── CHANGES.html
│   │           ├── COPYING
│   │           ├── README.html
│   │           ├── prettify-min.css
│   │           └── prettify-min.js
│   ├── classes/
│   │   ├── components.nf-area-stack.html
│   │   ├── components.nf-area.html
│   │   ├── components.nf-bars.html
│   │   ├── components.nf-crosshair.html
│   │   ├── components.nf-dot.html
│   │   ├── components.nf-gg.html
│   │   ├── components.nf-graph-content.html
│   │   ├── components.nf-graph.html
│   │   ├── components.nf-horizontal-line.html
│   │   ├── components.nf-line.html
│   │   ├── components.nf-plot.html
│   │   ├── components.nf-range-marker.html
│   │   ├── components.nf-range-markers.html
│   │   ├── components.nf-right-tick.html
│   │   ├── components.nf-selection-box.html
│   │   ├── components.nf-svg-image.html
│   │   ├── components.nf-svg-line.html
│   │   ├── components.nf-svg-path.html
│   │   ├── components.nf-svg-rect.html
│   │   ├── components.nf-tracker.html
│   │   ├── components.nf-vertical-line.html
│   │   ├── components.nf-x-axis.html
│   │   ├── components.nf-y-axis.html
│   │   ├── components.nf-y-diff.html
│   │   ├── index.html
│   │   ├── mixins.graph-area-utils.html
│   │   ├── mixins.graph-data-graphic.html
│   │   ├── mixins.graph-has-graph-parent.html
│   │   ├── mixins.graph-line-utils.html
│   │   ├── mixins.graph-registered-graphic.html
│   │   ├── mixins.graph-requires-scale-source.html
│   │   ├── mixins.graph-selectable-graphic.html
│   │   ├── utils.nf.graph-event.html
│   │   ├── utils.nf.graph-mouse-event.html
│   │   ├── utils.nf.graph-position.html
│   │   ├── utils.nf.scroll-area-action-context.html
│   │   └── utils.parse-property-expression.html
│   ├── data.json
│   ├── files/
│   │   ├── addon_mixins_graph-area-utils.js.html
│   │   ├── addon_mixins_graph-data-graphic.js.html
│   │   ├── addon_mixins_graph-graphic-with-tracking-dot.js.html
│   │   ├── addon_mixins_graph-has-graph-parent.js.html
│   │   ├── addon_mixins_graph-line-utils.js.html
│   │   ├── addon_mixins_graph-registered-graphic.js.html
│   │   ├── addon_mixins_graph-requires-scale-source.js.html
│   │   ├── addon_mixins_graph-selectable-graphic.js.html
│   │   ├── addon_utils_nf_array-helpers.js.html
│   │   ├── addon_utils_nf_graph-event.js.html
│   │   ├── addon_utils_nf_graph-mouse-event.js.html
│   │   ├── addon_utils_nf_graph-position.js.html
│   │   ├── addon_utils_nf_scale-utils.js.html
│   │   ├── addon_utils_nf_scroll-area-action-context.js.html
│   │   ├── addon_utils_nf_svg-dom.js.html
│   │   ├── addon_utils_parse-property-expression.js.html
│   │   ├── app_components_nf-area-stack.js.html
│   │   ├── app_components_nf-area.js.html
│   │   ├── app_components_nf-bars.js.html
│   │   ├── app_components_nf-crosshair.js.html
│   │   ├── app_components_nf-dot.js.html
│   │   ├── app_components_nf-gg.js.html
│   │   ├── app_components_nf-graph-content.js.html
│   │   ├── app_components_nf-graph.js.html
│   │   ├── app_components_nf-horizontal-line.js.html
│   │   ├── app_components_nf-line.js.html
│   │   ├── app_components_nf-plot.js.html
│   │   ├── app_components_nf-plots.js.html
│   │   ├── app_components_nf-range-marker.js.html
│   │   ├── app_components_nf-range-markers.js.html
│   │   ├── app_components_nf-right-tick.js.html
│   │   ├── app_components_nf-selection-box.js.html
│   │   ├── app_components_nf-svg-image.js.html
│   │   ├── app_components_nf-svg-line.js.html
│   │   ├── app_components_nf-svg-path.js.html
│   │   ├── app_components_nf-svg-rect.js.html
│   │   ├── app_components_nf-tracker.js.html
│   │   ├── app_components_nf-vertical-line.js.html
│   │   ├── app_components_nf-x-axis.js.html
│   │   ├── app_components_nf-y-axis.js.html
│   │   ├── app_components_nf-y-diff.js.html
│   │   └── index.html
│   ├── index.html
│   └── modules/
│       ├── index.html
│       ├── scale-utils.html
│       ├── utils_nf_array-helpers.html
│       └── utils_nf_svg-dom.html
├── ember-cli-build.js
├── index.js
├── package.json
├── protractor.conf.js
├── testem.js
├── tests/
│   ├── dummy/
│   │   ├── app/
│   │   │   ├── app.js
│   │   │   ├── components/
│   │   │   │   ├── .gitkeep
│   │   │   │   ├── bars-with-line.js
│   │   │   │   ├── basic-area-graph.js
│   │   │   │   ├── basic-bar-graph.js
│   │   │   │   ├── basic-line-graph.js
│   │   │   │   ├── brush-select-zoom.js
│   │   │   │   ├── detailed-line-graph.js
│   │   │   │   ├── graph-primitives.js
│   │   │   │   ├── mouse-tracking-data.js
│   │   │   │   ├── mouse-tracking.js
│   │   │   │   └── stacked-area-graph.js
│   │   │   ├── controllers/
│   │   │   │   ├── .gitkeep
│   │   │   │   └── nf-graph/
│   │   │   │       ├── index.js
│   │   │   │       └── nf-bars.js
│   │   │   ├── helpers/
│   │   │   │   ├── .gitkeep
│   │   │   │   └── format-hour-minute.js
│   │   │   ├── index.html
│   │   │   ├── models/
│   │   │   │   └── .gitkeep
│   │   │   ├── resolver.js
│   │   │   ├── router.js
│   │   │   ├── routes/
│   │   │   │   ├── .gitkeep
│   │   │   │   ├── index.js
│   │   │   │   └── nf-graph/
│   │   │   │       ├── index.js
│   │   │   │       └── nf-bars.js
│   │   │   ├── services/
│   │   │   │   ├── data-generator.js
│   │   │   │   └── utility.js
│   │   │   ├── styles/
│   │   │   │   └── app.css
│   │   │   └── templates/
│   │   │       ├── application.hbs
│   │   │       ├── components/
│   │   │       │   ├── bars-with-line.hbs
│   │   │       │   ├── basic-area-graph.hbs
│   │   │       │   ├── basic-bar-graph.hbs
│   │   │       │   ├── basic-line-graph.hbs
│   │   │       │   ├── brush-select-zoom.hbs
│   │   │       │   ├── detailed-line-graph.hbs
│   │   │       │   ├── graph-primitives.hbs
│   │   │       │   ├── mouse-tracking-data.hbs
│   │   │       │   ├── mouse-tracking.hbs
│   │   │       │   └── stacked-area-graph.hbs
│   │   │       ├── examples.hbs
│   │   │       └── nf-graph/
│   │   │           ├── index.hbs
│   │   │           └── nf-bars.hbs
│   │   ├── config/
│   │   │   ├── environment.js
│   │   │   └── targets.js
│   │   └── public/
│   │       ├── .gitkeep
│   │       └── robots.txt
│   ├── helpers/
│   │   ├── destroy-app.js
│   │   ├── module-for-acceptance.js
│   │   └── start-app.js
│   ├── index.html
│   ├── perf/
│   │   └── first.spec.js
│   ├── test-helper.js
│   └── unit/
│       ├── .gitkeep
│       ├── addon/
│       │   └── mixins/
│       │       └── graph-data-graphic-test.js
│       └── app/
│           └── components/
│               ├── nf-bars-test.js
│               ├── nf-graph-test.js
│               ├── nf-horizontal-line-test.js
│               ├── nf-vertical-line-test.js
│               ├── nf-x-axis-test.js
│               └── nf-y-axis-test.js
├── vendor/
│   └── .gitkeep
└── yuidoc.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true


[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2

[*.hbs]
insert_final_newline = false

[*.{diff,md}]
trim_trailing_whitespace = false


================================================
FILE: .ember-cli
================================================
{
  /**
    Ember CLI sends analytics information by default. The data is completely
    anonymous, but there are times when you might want to disable this behavior.

    Setting `disableAnalytics` to true will prevent any data from being sent.
  */
  "disableAnalytics": false
}


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  root: true,
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module'
  },
  plugins: [
    'ember'
  ],
  extends: [
    'eslint:recommended',
    'plugin:ember/recommended'
  ],
  env: {
    browser: true
  },
  globals: {
    d3: true,
    Rx: true
  },
  rules: {
    'ember/no-on-calls-in-components': 'off',
    'ember/closure-actions': 'off'
  },
  overrides: [
    // node files
    {
      files: [
        'index.js',
        'testem.js',
        'ember-cli-build.js',
        'config/**/*.js',
        'tests/dummy/config/**/*.js'
      ],
      excludedFiles: [
        'app/**',
        'addon/**',
        'tests/dummy/app/**'
      ],
      parserOptions: {
        sourceType: 'script',
        ecmaVersion: 2015
      },
      env: {
        browser: false,
        node: true
      },
      plugins: ['node'],
      rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
        // add your custom rules and overrides for node files here
      })
    },

    // test files
    {
      files: ['tests/**/*.js'],
      excludedFiles: ['tests/dummy/**/*.js'],
      env: {
        embertest: true
      },
      rules: {
        'ember/avoid-leaking-state-in-ember-objects': 'off'
      }
    },
    {
      files: ['protractor.conf.js', 'tests/perf/**/*.js'],
      env: {
        mocha: true,
        node: true
      },
      globals: {
        '$': true,
        browser: true
      },
      rules: {
        'ember/no-global-jquery': 'off'
      }
    }
  ]
};


================================================
FILE: .gitignore
================================================
# See https://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/dist
/tmp

# dependencies
/node_modules
/bower_components

# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log*
yarn-error.log
testem.log

# ember-try
.node_modules.ember-try/
bower.json.ember-try
package.json.ember-try


================================================
FILE: .npmignore
================================================
/bower_components
/config/ember-try.js
/dist
/tests
/tmp
**/.gitkeep
.bowerrc
.editorconfig
.ember-cli
.eslintrc.js
.gitignore
.watchmanconfig
.travis.yml
bower.json
ember-cli-build.js
testem.js

# ember-try
.node_modules.ember-try/
bower.json.ember-try
package.json.ember-try


================================================
FILE: .travis.yml
================================================
---
language: node_js
node_js:
  # we recommend testing addons with the same minimum supported node version as Ember CLI
  # so that your addon works for all apps
  - "6"

sudo: false
dist: trusty

addons:
  chrome: stable

cache:
  yarn: true
  directories:
    - $HOME/.npm

env:
  global:
    # See https://git.io/vdao3 for details.
    - JOBS=1
  matrix:
    # we recommend new addons test the current and previous LTS
    # as well as latest stable release (bonus points to beta/canary)
    - EMBER_TRY_SCENARIO=ember-lts-2.12
    - EMBER_TRY_SCENARIO=ember-lts-2.16
    - EMBER_TRY_SCENARIO=ember-release
    - EMBER_TRY_SCENARIO=ember-beta
    - EMBER_TRY_SCENARIO=ember-canary
    - EMBER_TRY_SCENARIO=ember-default

matrix:
  fast_finish: true
  allow_failures:
    - env: EMBER_TRY_SCENARIO=ember-canary

install:
  - yarn install

script:
  - yarn run lint:js
  # Usually, it's ok to finish the test scenario without reverting
  #  to the addon's original dependency state, skipping "cleanup".
  - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup


================================================
FILE: .watchmanconfig
================================================
{
  "ignore_dirs": ["tmp", "dist"]
}


================================================
FILE: CHANGELOG.md
================================================
# Changelog

### 1.0.0-beta.30

- FIX memory leak in nf-area caused by overriding `_unregister`
- FIX null reference error in nf-area caused when data is empty

### 1.0.0-beta.29

- UPDATE Readd tracking capabilities to nf-bars

### 1.0.0-beta.28

- REMOVE all SASS files and dependencies from styles
- BREAKING style complexity has been reduced
- BREAKING add additional path to nf-area to create tracing line at top

### 1.0.0-beta.27

- UPDATE Readd `hoverData` output to tracked components.

### 1.0.0-beta.26

- FIX Ensure tracked data updates when firstVisibleData or lastVisibleData has updated in certain tracking modes

### 1.0.0-beta.25

- FIX issue where firstVisibleData and lastVisibleData would error if no data is present

### 1.0.0-beta.24

- FIX trackingData no longer throws errors when underlying data is not present

### 1.0.0-beta.23

- FIX `snap-last` and `snap-first` type trackingModes will now initialize properly and render

### 1.0.0-beta.22

- FIX fix regression in `nf-line` tracking behavior
- FIX `nf-bars` should actually render bars again (doh)
- FIX `nf-brush-selection` will no longer throw errors to console while brushing

### 1.0.0-beta.21

- UPDATE components with tracking capabilities (`nf-line`, `nf-area` and `nf-bars`) updated to support additional data passed with trackingData, rendered values are `renderX` and `renderY`, where actual values are still at `x` and `y`. This was to account for new stacked area functionality.
- UPDATE `nf-bars` now will render a tracking dot if `showTrackingDot` is set to `true`
- BREAKING `nf-line`, `nf-area` and `nf-bars` will no longer emit values, actions or events for `hoverChange` and `hoverEnd`. `didTrack` still functions, though.

### 1.0.0-beta.20

- UPDATE `nf-graph` component now has `didAutoUpdateMinX`, `didAutoUpdateMinY`, `didAutoUpdateMaxX`, `didAutoUpdateMaxX` that will trigger when the corresponding values are updated automatically by a mode of `"push-tick"`, `"auto"` or `"tick"`.
- UPDATE `nf-graph` component now has actions `autoScaleXAction` and `autoScaleYAction` that will send when 
 the auto update events (mentioned above) trigger.

### 1.0.0-beta.19

- FIX issue where `nf-area` having null data would throw an unhandled error.

### 1.0.0-beta.18

- FIX axes components will once again use the passed templates for pre-Glimmer Ember.

### 1.0.0-beta.17

- ADD `nf-tracker` component. This is a component for templatable tracking dots
- UPDATE `nf-line` and `nf-area` to use `nf-tracker` for tracking dot generation
- UPDATE improved performance on hover/tracking
- UPDATE add `aggregate` to `nf-area-stack` component to sum y values of areas for stacking purposes. Defaults to `false` currently, but should be used with `true` from this point on, as it will default to `true` in future versions.
- UPDATE switch templates to attribute syntax
- UPDATE switch most computed properties to new computed property syntax
- FIX nf-graph should work with Glimmer now (thanks @jamesarosen)
- BREAKING graph components such as `nf-line` and `nf-area` will no longer sort your data for you. This is done for performance reasons.

### 1.0.0-beta.16

- FIX [#64](https://github.com/Netflix/ember-nf-graph/issues/64) put `tickFactory` back where it belongs on the axes components
- FIX [#59](https://github.com/Netflix/ember-nf-graph/pull/59) fixed an issue where nf-bars scales had the wrong domain values. 

### 1.0.0-beta.15

- FIX Moved ember-new-computed to deps not devDeps

### 1.0.0-beta.14

- FIX [#63](https://github.com/Netflix/ember-nf-graph/pull/63) Uses new computed property syntax w/ polyfill for host apps running older ember

### 1.0.0-beta.13

- FIX [#56](https://github.com/Netflix/ember-nf-graph/pull/56) Added rxjs to host app bower blueprint
- FIX rxjs dependency added to `index.js`
- UPDATE removed dependency on `ember-cli-rx`

### 1.0.0-beta.12

- FIX performance issue with axes caused by graphics registering after render
- FIX issue where `nf-area-stack` would not stack some `nf-area` components

### 1.0.0-beta.11

- Update Ember-CLI to `0.2.3` and Ember to `~1.11.3`
- FIX [#53](//github.com/Netflix/ember-nf-graph/issues/53) remove event hooks and registrations that were causing recalculations and re-renders.

### 1.0.0-beta.10

- FIX [#44](//github.com/Netflix/ember-nf-graph/issues/44) remove nf-scroll-area, which was leftover from splitting out nf-table
- FIX [#41](//github.com/Netflix/ember-nf-graph/issues/41) fix assertion error that was caused in certain tracking modes
- FIX [#16](//github.com/Netflix/ember-nf-graph/issues/16) patched addClass so Ember will actually update classes on SVG elements using classNameBindings
- UPDATE [#38](//github.com/Netflix/ember-nf-graph/issues/38) Test on multiple Ember versions

### 1.0.0-beta.9

- FIX [#16](//github.com/Netflix/ember-nf-graph/issues/16) SVG classes not swapping when selection changed
- FIX lock to rx-ember 0.2.5-1
- FIX [#24](//github.com/Netflix/ember-nf-graph/issues/24) SVG lines no longer complain when nf-vertical-line or nf-horizontal-line have null values
  or return a pixel value less than zero

### 1.0.0-beta.8

- [CRITICAL BUGFIX] Moved babel to `dependencies` so our addon is actually transpiled in host apps #34

### 1.0.0-beta.7

- Do not use.

### 1.0.0-beta.6

- UPDATE [#4](//github.com/Netflix/ember-nf-graph/issues/4) add block params to axis components.
- FIX [#29](//github.com/netflix/ember-nf-graph/issues/29) remove sad panda rename warning.

### 1.0.0-beta.5

- UPDATE name to ember-nf-graph
- FIX [#28](//github.com/netflix/ember-nf-graph/issues/28) Remove a few additional prototype functions. But also I had to 
  remove the addon that removed the prototype functions. (prototype functions are back for now).

### 1.0.0-beta.4

- DEPRECATE [#11](//github.com/netflix/ember-nf-graph/issues/11) Add warning message that name will be changing from ember-nf-graph 
  to ember-nf-graph for next version
- FIX [#8](//github.com/netflix/ember-nf-graph/issues/8): Removed prototype functions (e.g. `function() {}.property()`).
- UPDATE [#3](//github.com/netflix/ember-nf-graph/issues/3): [Examples are now live](//netflix.github.io/ember-nf-graph-examples/dist) special thanks to [@jeff3dx](//github.com/jeff3dx) 
  for all of his hard work!
- FIX [#25](//github.com/netflix/ember-nf-graph/issues/25): removed tabs in favor of spaces
- UPDATE rx-ember to 0.2.4, clean up bower.json
- FIX [#17](//github.com/netflix/ember-nf-graph/pull/17): stop publishing tmp and dist to npm
- FIX [#21](//github.com/netflix/ember-nf-graph/issues/21): remove leftover console.debug statement

### 1.0.0-beta.3

- FIX [#9](//github.com/netflix/ember-nf-graph/issues/9): Blueprints now properly named

### 1.0.0-beta.2
- FIX Update license
- FIX updated documentation and removed document cruft
- UPDATE: added a changelog :P
- META: made meta joke in changelog with appropriate emoticon

### 1.0.0-beta.1

- UPDATE: just releasing to public


================================================
FILE: LICENSE.md
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2015 Netflix, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

================================================
FILE: OSSMETADATA
================================================
osslifecycle=active


================================================
FILE: README.md
================================================
[![Build Status](https://travis-ci.org/Netflix/ember-nf-graph.svg?branch=master)](https://travis-ci.org/Netflix/ember-nf-graph)
[![npm version](https://badge.fury.io/js/ember-nf-graph.svg)](http://badge.fury.io/js/ember-nf-graph)

# ember-nf-graph

A Component-based DSL for creating graphs in your Ember app. The goal of the library is to
create a set of components that allows application or component authors to build graphs in a
compositional way. This includes components for templated axes, graph lines, areas, stacked areas, bar graphs, and much more. Check the [documentation](//netflix.github.io/ember-nf-graph/docs) for more information.

- [Documentation](//netflix.github.io/ember-nf-graph/docs)
- [Examples](//netflix.github.io/ember-nf-graph-examples/dist)



A basic graph example is as follows:

```js
export default Ember.Route.extend({
  model() {
    return {
      myLineData: [
        { x: 0, y: 12 },
        { x: 1, y: 32 },
        { x: 2, y: 42 },
        // ...
      ],
      myAreaData: [
        { x: 0, y: 43 },
        { x: 1, y: 54 },
        { x: 2, y: 13 },
        // ...
      ]
    };
  }
});
```

```hbs
{{#nf-graph width=500 height=300 as |nf|}}
  {{#nf.graph as |graph|}}
    <!-- add a line -->
    {{graph.line data=model.myLineData}}

    <!-- add an area -->
    {{graph.area data=model.myAreaData}}

    <!-- mix in any SVG element you want -->
    <circle cx="40" cy="40" r="10"></circle>
  {{/nf.graph}}

	<!-- axis ticks are templateable as well -->
  {{#nf.y-axis as |tick|}}
    <text>{{tick.value}}</text>
  {{/nf.y-axis}}


  {{#nf.x-axis as |tick|}}
    <text>{{tick.value}}</text>
  {{/nf.x-axis}}
{{/nf-graph}}
```


## Installation

This set of Ember components requires [Ember-CLI](http://ember-cli.com) 0.2.0 or higher and
[Ember](http://emberjs.com) 1.10.0 or higher.

To install, simply run `ember install ember-nf-graph`, or `npm install -D ember-nf-graph`

## Documentation

- Online at: [netflix.github.io/ember-nf-graph/docs](//netflix.github.io/ember-nf-graph/docs) (generated by [YUIDocs](http://yui.github.io/yuidoc/))
- In package: Documentation for these components is included in the package, and can be found under `node_modules/ember-nf-graph/docs/index.html` just open in any browser.

----

## Contributing

* `git clone` this repository
* `npm install`

### Running

* `ember server`
* Visit your app at http://localhost:4200.

### Running Tests

* `ember test`
* `ember test --server`

### Building

* `ember build`

For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/).

### Generating Documentation

This project uses YUIDoc to generate documentation. Once YUIDoc is installed run:

```sh
yuidoc -c yuidoc.json
```

The documentation is located in `docs/`.





================================================
FILE: addon/.gitkeep
================================================


================================================
FILE: addon/components/.gitignore
================================================
node_modules

================================================
FILE: addon/components/nf-area-stack.js
================================================
import { schedule } from '@ember/runloop';
import { A } from '@ember/array';
import { warn } from '@ember/debug';
import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from 'ember-nf-graph/templates/components/nf-area-stack';

/**
  A component for grouping and stacking `nf-area` components in an `nf-graph`.

  This component looks at the order of the `nf-area` components underneath it
  and uses the ydata of the next sibling `nf-area` component to determine the bottom
  of each `nf-area` components path to be drawn.

  ### Example

      {{#nf-graph width=300 height=100}}
        {{#nf-graph-content}}
          {{#nf-area-stack}}
            {{nf-area data=myData xprop="time" yprop="high"}}
            {{nf-area data=myData xprop="time" yprop="med"}}
            {{nf-area data=myData xprop="time" yprop="low"}}
          {{/nf-area-stack}}
        {{/nf-graph-content}}
      {{/nf-graph}}

  @namespace components
  @class nf-area-stack
*/
export default Component.extend({
  layout,
  tagName: 'g',

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    Whether or not to add the values together to create the stacked area
    @property aggregate
    @type {boolean}
    @default false
  */
  aggregate: computed({
    get() {
      warn('nf-area-stack.aggregate must be set. Currently defaulting to `false` but will default to `true` in the future.');
      return this._aggregate = false;
    },
    set(key, value) {
      return this._aggregate = value;
    }
  }),

  /**
    The collection of `nf-area` components under this stack.
    @property areas
    @type Array
    @readonly
  */
  areas: computed(function(){
    return A();
  }),

  /**
    Registers an area component with this stack. Also links areas to one
    another by setting `nextArea` on each area component.
    @method registerArea
    @param area {Ember.Component} The area component to register.
  */
  registerArea: function(area) {
    let areas = this.get('areas');
    let prev = areas[areas.length - 1];

    schedule('afterRender', () => {
      if(prev) {
        prev.set('nextArea', area);
        area.set('prevArea', prev);
      }

      areas.pushObject(area);
    });
  },

  /**
    Unregisters an area component from this stack. Also updates next
    and previous links.
    @method unregisterArea
    @param area {Ember.Component} the area to unregister
  */
  unregisterArea: function(area) {
    let prev = area.get('prevArea');
    let next = area.get('nextArea');

    schedule('afterRender', () => {
      if(next) {
        next.set('prevArea', prev);
      }

      if(prev) {
        prev.set('nextArea', next);
      }

      this.get('areas').removeObject(area);
    });
  },
});


================================================
FILE: addon/components/nf-area.js
================================================
import { computed } from '@ember/object';
import { on } from '@ember/object/evented';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-area';
import Selectable from 'ember-nf-graph/mixins/graph-selectable-graphic';
import RegisteredGraphic from 'ember-nf-graph/mixins/graph-registered-graphic';
import DataGraphic from 'ember-nf-graph/mixins/graph-data-graphic';
import AreaUtils from 'ember-nf-graph/mixins/graph-area-utils';
import GraphicWithTrackingDot from 'ember-nf-graph/mixins/graph-graphic-with-tracking-dot';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import LineUtils from 'ember-nf-graph/mixins/graph-line-utils';

/**
  Adds an area graph to an `nf-graph` component.

  Optionally, if it's located within an `nf-area-stack` component, it will work with
  sibling `nf-area` components to create a stacked graph.
  @namespace components
  @class nf-area
  @extends Ember.Component
  @uses mixins.graph-area-utils
  @uses mixins.graph-selectable-graphic
  @uses mixins.graph-registered-graphic
  @uses mixins.graph-data-graphic
  @uses mixins.graph-graphic-with-tracking-dot
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RegisteredGraphic, DataGraphic, Selectable, AreaUtils, GraphicWithTrackingDot, RequireScaleSource, LineUtils, {
  layout,
  tagName: 'g',

  classNameBindings: [':nf-area', 'selected', 'selectable'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The type of d3 interpolator to use to create the area
    @property interpolator
    @type String
    @default 'linear'
  */
  interpolator: 'linear',

  /**
    The previous area in the stack, if this area is part of an `nf-area-stack`
    @property prevArea
    @type components.nf-area
    @default null
  */
  prevArea: null,

  /**
    The next area in the stack, if this area is part of an `nf-area-stack`
    @property nextArea
    @type components.nf-area
    @default null
  */
  nextArea: null,

  stack: null,

  init() {
    this._super(...arguments);
    let stack = this.get('stack');
    if(stack) {
      stack.registerArea(this);
      this.set('stack', stack);
    }
  },

  /**
    Override from `graph-data-graphic` mixin
    @method getActualTrackData
  */
  getActualTrackData(renderX, renderY, data) {
    return {
      x: this.get('xPropFn')(data),
      y: this.get('yPropFn')(data)
    };
  },

  _unregisterArea: on('willDestroyElement', function(){
    let stack = this.get('stack');
    if(stack) {
      stack.unregisterArea(this);
    }
  }),

  /**
    The computed set of next y values to use for the "bottom" of the graphed area.
    If the area is part of a stack, this will be the "top" of the next area in the stack,
    otherwise it will return an array of values at the "bottom" of the graph domain.
    @property nextYData
    @type Array
    @readonly
  */
  nextYData: computed('data.length', 'nextArea.data.[]', function(){
    let data = this.get('data');
    if(!Array.isArray(data)) {
      return [];
    }
    let nextData = this.get('nextArea.mappedData');
    return data.map((d, i) => (nextData && nextData[i] && nextData[i][1]) || Number.MIN_VALUE);
  }),

  /**
    The current rendered data "zipped" together with the nextYData.
    @property mappedData
    @type Array
    @readonly
  */
  mappedData: computed('data.[]', 'xPropFn', 'yPropFn', 'nextYData.[]', 'stack.aggregate', function() {
    let { data, xPropFn, yPropFn, nextYData } = this.getProperties('data', 'xPropFn', 'yPropFn', 'nextYData');
    let aggregate = this.get('stack.aggregate');
    if(Array.isArray(data)) {
      return data.map((d, i) => {
        let x = xPropFn(d);
        let y = yPropFn(d);
        let result = aggregate ? [x, y + nextYData[i], nextYData[i]] : [x, y, nextYData[i]];
        result.data = d;
        return result;
      });
    } else {
      return [];
    }
  }),

  areaFn: computed('xScale', 'yScale', 'interpolator', function(){
    let { xScale, yScale, interpolator } = this.getProperties('xScale', 'yScale', 'interpolator');
    return this.createAreaFn(xScale, yScale, interpolator);
  }),

  lineFn: computed('xScale', 'yScale', 'interpolator', function(){
    let { xScale, yScale, interpolator } = this.getProperties('xScale', 'yScale', 'interpolator');
    return this.createLineFn(xScale, yScale, interpolator);
  }),

  d: computed('renderedData', 'areaFn', function(){
    let renderedData = this.get('renderedData');
    return this.get('areaFn')(renderedData);
  }),

  dLine: computed('renderedData', 'lineFn', function(){
    let renderedData = this.get('renderedData');
    return this.get('lineFn')(renderedData);
  }),

  click: function(){
    if(this.get('selectable')) {
      this.toggleProperty('selected');
    }
  }
  });


================================================
FILE: addon/components/nf-bars-group.js
================================================
import { schedule } from '@ember/runloop';
import { A } from '@ember/array';
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-bars-group';
import RequiresScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

export default Component.extend(RequiresScaleSource, {
  layout,
  tagName: 'g',

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  groupPadding: 0.1,

  groupOuterPadding: 0,

  // either b-arses or fat, stupid hobbitses
  barses: computed(function(){
    return A();
  }),

  registerBars: function(bars) {
    schedule('afterRender', () => {
      let barses = this.get('barses');
      barses.pushObject(bars);
      bars.set('group', this);
      bars.set('groupIndex', barses.length - 1);
    });
  },

  unregisterBars: function(bars) {
    if(bars) {
      schedule('afterRender', () => {
        bars.set('group', undefined);
        bars.set('groupIndex', undefined);
        this.get('barses').removeObject(bars);
      });
    }
  },

  groupWidth: computed('xScale', function(){
    let xScale = this.get('xScale');
    return xScale && xScale.rangeBand ? xScale.rangeBand() : NaN;
  }),

  barsDomain: computed('barses.[]', function(){
    let len = this.get('barses.length') || 0;
    return d3.range(len);
  }),

  barScale: computed(
    'groupWidth',
    'barsDomain.[]',
    'groupPadding',
    'groupOuterPadding',
    function(){
      let barsDomain = this.get('barsDomain');
      let groupWidth = this.get('groupWidth');
      let groupPadding = this.get('groupPadding');
      let groupOuterPadding = this.get('groupOuterPadding');
      return d3.scale.ordinal()
        .domain(barsDomain)
        .rangeBands([0, groupWidth], groupPadding, groupOuterPadding);
    }
  ),

  barsWidth: function() {
    let scale = this.get('barScale');
    return scale && scale.rangeBand ? scale.rangeBand() : NaN;
  },
});


================================================
FILE: addon/components/nf-bars.js
================================================
import { isArray, A } from '@ember/array';
import { oneWay } from '@ember/object/computed';
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-bars';
import DataGraphic from 'ember-nf-graph/mixins/graph-data-graphic';
import RegisteredGraphic from 'ember-nf-graph/mixins/graph-registered-graphic';
import parsePropExpr from 'ember-nf-graph/utils/parse-property-expression';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import GraphicWithTrackingDot from 'ember-nf-graph/mixins/graph-graphic-with-tracking-dot';
import { normalizeScale } from 'ember-nf-graph/utils/nf/scale-utils';
import { getRectPath } from 'ember-nf-graph/utils/nf/svg-dom';

/**
  Adds a bar graph to an `nf-graph` component.

  **Requires the graph has `xScaleType === 'ordinal'`***

  ** `showTrackingDot` defaults to `false` in this component **

  @namespace components
  @class nf-bars
  @extends Ember.Component
  @uses mixins.graph-registered-graphic
  @uses mixins.graph-data-graphic
  @uses mixins.graph-requires-scale-source
  @uses mixins.graph-graphic-with-tracking-dot
*/
export default Component.extend(RegisteredGraphic, DataGraphic, RequireScaleSource, GraphicWithTrackingDot, {
  layout,
  tagName: 'g',

  classNames: ['nf-bars'],

  _showTrackingDot: false,

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The name of the property on each data item containing the className for the bar rectangle
    @property classprop
    @type String
    @default 'className'
  */
  classprop: 'className',

  /**
    Gets the function to get the classname from each data item.
    @property getBarClass
    @readonly
    @private
  */
  getBarClass: computed('classprop', function() {
    let classprop = this.get('classprop');
    return classprop ? parsePropExpr(classprop) : null;
  }),

  /**
    The nf-bars-group this belongs to, if any.
    @property group
    @type components.nf-bars-group
    @default null
  */
  group: null,

  /**
    The index of this component within the group, if any.
    @property groupIndex
    @type Number
    @default null
  */
  groupIndex: null,

  /**
    The graph content height
    @property graphHeight
    @type Number
    @readonly
  */
  graphHeight: oneWay('graph.graphHeight'),

  /**
    A scale provided by nf-bars-group to offset the bar rectangle output
    @property barScale
    @type d3.scale
    @readonly
  */
  barScale: oneWay('group.barScale'),

  /**
    The width of each bar.
    @property barWidth
    @type Number
    @readonly
  */
  barWidth: computed('xScale', 'barScale', function(){
    let barScale = this.get('barScale');
    if(barScale) {
      return barScale.rangeBand();
    }
    let xScale = this.get('xScale');
    return xScale && xScale.rangeBand ? xScale.rangeBand() : 0;
  }),

  groupOffsetX: computed('barScale', 'groupIndex', function(){
    let barScale = this.get('barScale');
    let groupIndex = this.get('groupIndex');
    return normalizeScale(barScale, groupIndex);
  }),

  /**
    The bar models used to render the bars.
    @property bars
    @readonly
  */
  bars: computed(
    'xScale',
    'yScale',
    'renderedData.[]',
    'graphHeight',
    'getBarClass',
    'barWidth',
    'groupOffsetX',
    function(){
      let { renderedData, xScale, yScale, barWidth, graphHeight, getBarClass, groupOffsetX } =
        this.getProperties('renderedData', 'xScale', 'yScale', 'graphHeight', 'getBarClass', 'groupOffsetX', 'barWidth');

      let getRectPath = this._getRectPath;

      if(!xScale || !yScale || !isArray(renderedData)) {
        return null;
      }

      let w = barWidth;

      return A(renderedData.map(function(data) {
        let className = 'nf-bars-bar' + (getBarClass ? ' ' + getBarClass(data.data) : '');
        let x = normalizeScale(xScale, data[0]) + groupOffsetX;
        let y = normalizeScale(yScale, data[1]);
        let h = graphHeight - y;
        let path = getRectPath(x, y, w, h);

        return { path, className, data };
      }));
    }
  ),

  _getRectPath: getRectPath,

  /**
    The name of the action to fire when a bar is clicked.
    @property barClick
    @type String
    @default null
  */
  barClick: null,

  init() {
    this._super(...arguments);
    let group = this.get('group');
    if(group && group.registerBars) {
      group.registerBars(this);
    }
  },

  actions: {
    nfBarClickBar: function(dataPoint) {
      if(this.get('barClick')) {
        this.sendAction('barClick', {
          data: dataPoint.data,
          x: dataPoint[0],
          y: dataPoint[1],
          source: this,
          graph: this.get('graph'),
        });
      }
    }
  }

});


================================================
FILE: addon/components/nf-brush-selection.js
================================================
import { alias } from '@ember/object/computed';
import { scheduleOnce } from '@ember/runloop';
import { on } from '@ember/object/evented';
import { get, observer, computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-brush-selection';
import RequiresScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

export default Component.extend(RequiresScaleSource, {
  layout,
  tagName: 'g',

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  left: undefined,

  right: undefined,

  formatter: null,

  textPadding: 3,

  autoWireUp: true,

  _autoBrushHandler: function(e) {
    this.set('left', get(e, 'left.x'));
    this.set('right', get(e, 'right.x'));
  },

  _autoBrushEndHandler: function() {
    this.set('left', undefined);
    this.set('right', undefined);
  },

  _wireToGraph: function(){
    let graph = this.get('graph');
    let auto = this.get('autoWireUp');

    if(auto) {
      graph.on('didBrushStart', this, this._autoBrushHandler);
      graph.on('didBrush', this, this._autoBrushHandler);
      graph.on('didBrushEnd', this, this._autoBrushEndHandler);
    } else {
      graph.off('didBrushStart', this, this._autoBrushHandler);
      graph.off('didBrush', this, this._autoBrushHandler);
      graph.off('didBrushEnd', this, this._autoBrushEndHandler);
    }
  },

  _autoWireUpChanged: on('didInsertElement', observer('autoWireUp', function(){
    scheduleOnce('afterRender', this, this._wireToGraph);
  })),

  _updateLeftText: function(){
    let root = d3.select(this.element);
    let g = root.select('.nf-brush-selection-left-display');
    let text = g.select('.nf-brush-selection-left-text');
    let bg = g.select('.nf-brush-selection-left-text-bg');

    let display = this.get('leftDisplay');

    if(!display) {
      g.attr('hidden', true);
    } else {
      g.attr('hidden', null);
    }

    text.text(display);

    let textPadding = this.get('textPadding');
    let leftX = this.get('leftX');
    let graphHeight = this.get('graphHeight');
    let bbox = text[0][0].getBBox();

    let doublePad = textPadding * 2;
    let width = bbox.width + doublePad;
    let height = bbox.height + doublePad;
    let x = Math.max(0, leftX - width);
    let y = graphHeight - height;

    g.attr('transform', `translate(${x} ${y})`);

    text.attr('x', textPadding).
      attr('y', textPadding);

    bg.attr('width', width).
      attr('height', height);
  },

  _onLeftChange: on(
    'didInsertElement',
    observer('left', 'graphHeight', 'textPadding', function(){
      scheduleOnce('afterRender', this, this._updateLeftText);
    })
  ),

  _updateRightText: function(){
    let root = d3.select(this.element);
    let g = root.select('.nf-brush-selection-right-display');
    let text = g.select('.nf-brush-selection-right-text');
    let bg = g.select('.nf-brush-selection-right-text-bg');

    let display = this.get('rightDisplay');

    if(!display) {
      g.attr('hidden', true);
    } else {
      g.attr('hidden', null);
    }

    text.text(display);

    let textPadding = this.get('textPadding');
    let rightX = this.get('rightX');
    let graphHeight = this.get('graphHeight');
    let graphWidth = this.get('graphWidth');
    let bbox = text[0][0].getBBox();

    let doublePad = textPadding * 2;
    let width = bbox.width + doublePad;
    let height = bbox.height + doublePad;
    let x = Math.min(graphWidth - width, rightX);
    let y = graphHeight - height;

    g.attr('transform', `translate(${x} ${y})`);

    text.attr('x', textPadding).
      attr('y', textPadding);

    bg.attr('width', width).
      attr('height', height);
  },

  _onRightChange: on(
    'didInsertElement',
    observer('right', 'graphHeight', 'graphWidth', 'textPadding', function(){
      scheduleOnce('afterRender', this, this._updateRightText);
    })
  ),

  leftDisplay: computed('left', 'formatter', function(){
    let formatter = this.get('formatter');
    let left = this.get('left');
    return formatter ? formatter(left) : left;
  }),

  rightDisplay: computed('right', 'formatter', function(){
    let formatter = this.get('formatter');
    let right = this.get('right');
    return formatter ? formatter(right) : right;
  }),

  isVisible: computed('left', 'right', function(){
    let left = +this.get('left');
    let right = +this.get('right');
    return left === left && right === right;
  }),

  leftX: computed('xScale', 'left', function() {
    let left = this.get('left') || 0;
    let scale = this.get('xScale');
    return scale ? scale(left) : 0;
  }),

  rightX: computed('xScale', 'right', function() {
    let right = this.get('right') || 0;
    let scale = this.get('xScale');
    return scale ? scale(right) : 0;
  }),

  graphWidth: alias('graph.graphWidth'),

  graphHeight: alias('graph.graphHeight'),

  rightWidth: computed('rightX', 'graphWidth', function() {
    return Math.max(this.get('graphWidth') - this.get('rightX'), 0);
  }),
});


================================================
FILE: addon/components/nf-component.js
================================================
import Component from '@ember/component';
import { assert } from '@ember/debug';
import { isPresent } from '@ember/utils';
import layout from '../templates/components/nf-component';

/**
  Renders a custom component with the given component name.

  ## Example

  The following example will render a custom svg image component at (0, 50):

        {{#graph.component 'my-custom-svg-image' as |image|}}
          {{component image src="images/star.svg"}}
        {{/graph.component}}

  @namespace components
  @class nf-component
  @extends Ember.Component
*/
const NfComponent = Component.extend({
  layout,
  tagName: '',

  /**
    The name of the component to be rendered.
    @property componentName
    @type String
    @default ''
  */
  componentName: '',

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
  */
  graph: null,

  /**
    The scale source
    @property scaleSource
    @type d3.nf-graph
    @default graph
  */
 scaleSource: null,

 init(){
   this._super(...arguments);
   assert('[ember-nf-graph] A component name must be passed into nf-component.', isPresent(this.get('componentName')));
 }
});

NfComponent.reopenClass({
  positionalParams: ['componentName']
});

export default NfComponent;


================================================
FILE: addon/components/nf-crosshairs.js
================================================
import { schedule } from '@ember/runloop';
import { alias } from '@ember/object/computed';
import Component from '@ember/component';
import { on } from '@ember/object/evented';
import { observer } from '@ember/object';
import layout from 'ember-nf-graph/templates/components/nf-crosshairs';

/**
  A component that adds "crosshairs" to an `nf-graph` that follows the mouse
  while it's hovering over the graph content.
  @namespace components
  @class nf-crosshair
  @extends Ember.Component
  @uses mixins.graph-has-graph-parent
*/
export default Component.extend({
  layout,
  tagName: 'g',

  classNames: ['nf-crosshairs'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The height of the crosshair in pixels
    @property height
    @type Number
    @readonly
  */
  height: alias('graph.graphHeight'),

  /**
    The width of the crosshair in pixels
    @property width
    @type Number
    @readonly
  */
  width: alias('graph.graphWidth'),

  /**
    The x position of the crosshairs
    @property x
    @type Number
    @default 0
  */
  x: 0,

  /**
    The y position of the crosshairs
    @property y
    @type Number
    @default 0
  */
  y: 0,

  /**
    Whether to show the vertical line in the corsshairs
    @property vertical
    @type Boolean
    @default true
  */
  vertical: true,

  /**
    Whether to show the horizontal line in the corsshairs
    @property horizontal
    @type Boolean
    @default true
  */
  horizontal: true,

  /**
    The visibility of the component
    @property isVisible
    @type Boolean
    @default false
  */
  isVisible: false,

  didContentHoverChange: function(e) {
    this.set('isVisible', true);
    this.set('x', e.get('mouseX'));
    this.set('y', e.get('mouseY'));
  },

  didContentHoverEnd: function() {
    this.set('isVisible', false);
  },

  _setupBindings: on('didInsertElement', observer('graph.content', function() {
    let content = this.get('graph.content');
    if(content) {
      schedule('afterRender', () => {
        content.on('didHoverChange', this, this.didContentHoverChange);
        content.on('didHoverEnd', this, this.didContentHoverEnd);
      });
    }
  })),
});


================================================
FILE: addon/components/nf-dot.js
================================================
import { computed } from '@ember/object';
import { notEmpty, and } from '@ember/object/computed';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-dot';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

/**
  Plots a circle at a given x and y domain value on an `nf-graph`.

  @namespace components
  @class nf-dot
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RequireScaleSource, {
  layout,
  tagName: 'circle',

  attributeBindings: ['r', 'cy', 'cx'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The x domain value at which to plot the circle
    @property x
    @type Number
    @default null
  */
  x: null,

  /**
    The y domain value at which to plot the circle
    @property x
    @type Number
    @default null
  */
  y: null,

  /**
    The radius of the circle plotted
    @property r
    @type Number
    @default 2.5
  */
  r: 2.5,

  hasX: notEmpty('x'),

  hasY: notEmpty('y'),

  /**
    The computed center x coordinate of the circle
    @property cx
    @type Number
    @private
    @readonly
  */
  cx: computed('x', 'xScale', 'hasX', function(){
    let x = this.get('x');
    let xScale = this.get('xScale');
    let hasX = this.get('hasX');
    return hasX && xScale ? xScale(x) : -1;
  }),

  /**
    The computed center y coordinate of the circle
    @property cy
    @type Number
    @private
    @readonly
  */
  cy: computed('y', 'yScale', 'hasY', function() {
    let y = this.get('y');
    let yScale = this.get('yScale');
    let hasY = this.get('hasY');
    return hasY && yScale ? yScale(y) : -1;
  }),

  /**
    Toggles the visibility of the dot. If x or y are
    not numbers, will return false.
    @property isVisible
    @private
    @readonly
  */
  isVisible: and('hasX', 'hasY'),
});


================================================
FILE: addon/components/nf-graph-content.js
================================================
import { schedule } from '@ember/runloop';
import { A } from '@ember/array';
import { alias } from '@ember/object/computed';
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-graph-content';
import GraphMouseEvent from 'ember-nf-graph/utils/nf/graph-mouse-event';

/**
  Container component for graphics to display in `nf-graph`. Represents
  the area where the graphics, such as lines will display.

  Exists for layout purposes.
  @namespace components
  @class nf-graph-content
*/
export default Component.extend({
  layout,
  tagName: 'g',

  classNames: ['nf-graph-content'],

  attributeBindings: ['transform', 'clip-path'],

  'clip-path': computed('graph.contentClipPathId', function(){
    let clipPathId = this.get('graph.contentClipPathId');
    return  `url('#${clipPathId}')`;
  }),

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The SVG transform for positioning the graph content
    @property transform
    @type String
    @readonly
  */
  transform: computed('x', 'y', function(){
    let x = this.get('x');
    let y = this.get('y');
    return `translate(${x} ${y})`;
  }),

  /**
    The x position of the graph content
    @property x
    @type Number
    @readonly
  */
  x: alias('graph.graphX'),

  /**
    The calculated y position of the graph content
    @property y
    @type Number
    @readonly
  */
  y: alias('graph.graphY'),

  /**
    The calculated width of the graph content
    @property width
    @type Number
    @readonly
  */
  width: alias('graph.graphWidth'),

  /**
    The calculated height of the graph content.
    @property height
    @type Number
    @readonly
  */
  height: alias('graph.graphHeight'),


  /**
    An array containing models to render the grid lanes
    @property gridLanes
    @type Array
    @readonly
  */
  gridLanes: computed('graph.yAxis.ticks', 'width', 'height', function () {
    let ticks = this.get('graph.yAxis.ticks');
    let width = this.get('width');
    let height = this.get('height');

    if(!ticks || ticks.length === 0) {
      return A();
    }

    let sorted = ticks.slice().sort(function(a, b) {
      return a.y - b.y;
    });

    if(sorted[0].y !== 0) {
      sorted.unshift({ y: 0 });
    }

    let lanes = sorted.reduce(function(lanes, tick, i) {
      let y = tick.y;
      let next = sorted[i+1] || { y: height };
      let h = Math.max(next.y - tick.y, 0);
      lanes.push({
        x: 0,
        y: y,
        width: width,
        height: h
      });
      return lanes;
    }, []);

    return A(lanes);
  }),

  /**
    The name of the hoverChange action to fire
    @property hoverChange
    @type String
    @default null
  */
  hoverChange: null,

  mouseMove: function(e) {
    let context = GraphMouseEvent.create({
      originalEvent: e,
      source: this,
      graphContentElement: this.element,
    });

    this.trigger('didHoverChange', context);

    if(this.get('hoverChange')) {
      this.sendAction('hoverChange', context);
    }
  },

  /**
    The name of the hoverEnd action to fire
    @property hoverEnd
    @type String
    @default null
  */
  hoverEnd: null,

  mouseLeave: function(e) {
    let context = GraphMouseEvent.create({
      originalEvent: e,
      source: this,
      graphContentElement: this.element
    });
    this.trigger('didHoverEnd', context);

    if(this.get('hoverEnd')) {
      this.sendAction('hoverEnd', context);
    }
  },

  /**
    An array containing models to render fret lines
    @property frets
    @type Array
    @readonly
  */
  frets: alias('graph.xAxis.ticks'),

  init(){
    this._super(...arguments);

    schedule('afterRender', () => {
      this.set('graph.content', this);
    });
  },
});


================================================
FILE: addon/components/nf-graph-yieldables.js
================================================
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-graph-yieldables';

export default Component.extend({
  layout,
  tagName: '',

  /**
    The parent graph for the components.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The scale source for the components
    @property scaleSource
    @default null
    */
  scaleSource: null,
});


================================================
FILE: addon/components/nf-graph.js
================================================
import { bool, notEmpty } from '@ember/object/computed';
import $ from 'jquery';
import { on } from '@ember/object/evented';
import { warn } from '@ember/debug';
import { A } from '@ember/array';
import { scheduleOnce, schedule, run } from '@ember/runloop';
import Component from '@ember/component';
import { isPresent } from '@ember/utils';
import { computed, observer } from '@ember/object';
import layout from 'ember-nf-graph/templates/components/nf-graph';
import GraphPosition from 'ember-nf-graph/utils/nf/graph-position';
import { getMousePoint } from 'ember-nf-graph/utils/nf/svg-dom';
import { toArray } from 'ember-nf-graph/utils/nf/array-helpers';

const Observable = Rx.Observable;

const computedBool = bool;

let minProperty = function(axis, defaultTickCount){
  let _DataExtent_ = axis + 'DataExtent';
  let _MinMode_ = axis + 'MinMode';
  let _Axis_tickCount_ = axis + 'Axis.tickCount';
  let _ScaleFactory_ = axis + 'ScaleFactory';
  let __Min_ = '_' + axis + 'Min';
  let _prop_ = axis + 'Min';
  let _autoScaleEvent_ = 'didAutoUpdateMin' + axis.toUpperCase();

  return computed(
    _MinMode_,
    _DataExtent_,
    _Axis_tickCount_,
    _ScaleFactory_,
    'graphHeight',
    'graphWidth',
    {
      get() {
        let mode = this.get(_MinMode_);
        let ext;

        let change = val => {
          this.set(_prop_, val);
          this.trigger(_autoScaleEvent_);
        };

        if(mode === 'auto') {
          change(this.get(_DataExtent_)[0] || 0);
        }

        else if(mode === 'push') {
          ext = this.get(_DataExtent_)[0];
          if(!isNaN(ext) && ext < this[__Min_]) {
            change(ext);
          }
        }

        else if(mode === 'push-tick') {
          let extent = this.get(_DataExtent_);
          ext = extent[0];

          if(!isNaN(ext) && ext < this[__Min_]) {
            let tickCount = this.get(_Axis_tickCount_) || defaultTickCount;
            let newDomain = this.get(_ScaleFactory_)().domain(extent).nice(tickCount).domain();
            change(newDomain[0]);
          }
        }

        return this[__Min_];
      },
      set(key, value) {
        if (isPresent(value) && !isNaN(value)) {
          this[__Min_] = value;
        }
        return this[__Min_];
      }
    }
  );
};

let maxProperty = function(axis, defaultTickCount) {
  let _DataExtent_ = axis + 'DataExtent';
  let _Axis_tickCount_ = axis + 'Axis.tickCount';
  let _ScaleFactory_ = axis + 'ScaleFactory';
  let _MaxMode_ = axis + 'MaxMode';
  let __Max_ = '_' + axis + 'Max';
  let _prop_ = axis + 'Max';
  let _autoScaleEvent_ = 'didAutoUpdateMax' + axis.toUpperCase();

  return computed(
    _MaxMode_,
    _DataExtent_,
    _ScaleFactory_,
    _Axis_tickCount_,
    'graphHeight',
    'graphWidth',
    {
      get() {
        let mode = this.get(_MaxMode_);
        let ext;

        let change = val => {
          this.set(_prop_, val);
          this.trigger(_autoScaleEvent_);
        };

        if(mode === 'auto') {
          change(this.get(_DataExtent_)[1] || 1);
        }

        else if(mode === 'push') {
          ext = this.get(_DataExtent_)[1];
          if(!isNaN(ext) && this[__Max_] < ext) {
            change(ext);
          }
        }

        else if(mode === 'push-tick') {
          let extent = this.get(_DataExtent_);
          ext = extent[1];

          if(!isNaN(ext) && this[__Max_] < ext) {
            let tickCount = this.get(_Axis_tickCount_) || defaultTickCount;
            let newDomain = this.get(_ScaleFactory_)().domain(extent).nice(tickCount).domain();
            change(newDomain[1]);
          }
        }

        return this[__Max_];
      },
      set(key, value) {
        if (isPresent(value) && !isNaN(value)) {
          this[__Max_] = value;
        }
        return this[__Max_];
      }
    }
  );
};

/**
  A container component for building complex Cartesian graphs.

  ## Minimal example

       {{#nf-graph width=100 height=50}}
         {{#nf-graph-content}}
           {{nf-line data=lineData xprop="foo" yprop="bar"}}
         {{/nf-graph-content}}
       {{/nf-graph}}

  The above will create a simple 100x50 graph, with no axes, and a single line
  plotting the data it finds on each object in the array `lineData` at properties
  `foo` and `bar` for x and y values respectively.

  ## More advanced example

       {{#nf-graph width=500 height=300}}
         {{#nf-x-axis height="50" as |tick|}}
           <text>{{tick.value}}</text>
         {{/nf-x-axis}}

         {{#nf-y-axis width="120" as |tick|}}
           <text>{{tick.value}}</text>
         {{/nf-y-axis}}

         {{#nf-graph-content}}
           {{nf-line data=lineData xprop="foo" yprop="bar"}}
         {{/nf-graph-content}}
       {{/nf-graph}}

  The above example will create a 500x300 graph with both axes visible. The graph will not
  render either axis unless its component is present.


  @namespace components
  @class nf-graph
  @extends Ember.Component
*/
export default Component.extend({
  layout,
  tagName: 'div',

  /**
    The exponent to use for xScaleType "pow" or "power".
    @property xPowerExponent
    @type Number
    @default 3
  */
  xPowerExponent: 3,

  /**
    The exponent to use for yScaleType "pow" or "power".
    @property yPowerExponent
    @type Number
    @default 3
  */
  yPowerExponent: 3,

  /**
    The min value to use for xScaleType "log" if xMin <= 0
    @property xLogMin
    @type Number
    @default 0.1
  */
  xLogMin: 0.1,

  /**
    The min value to use for yScaleType "log" if yMin <= 0
    @property yLogMin
    @type Number
    @default 0.1
  */
  yLogMin: 0.1,

  /**
    @property hasRendered
    @private
  */
  hasRendered: false,

  /**
    Gets or sets the whether or not multiple selectable graphics may be
    selected simultaneously.
    @property selectMultiple
    @type Boolean
    @default false
  */
  selectMultiple: false,

  /**
    The width of the graph in pixels.
    @property width
    @type Number
    @default 300
  */
  width: 300,

  /**
    The height of the graph in pixels.
    @property height
    @type Number
    @default 100
  */
  height: 100,

  /**
    The padding at the top of the graph
    @property paddingTop
    @type Number
    @default 0
  */
  paddingTop: 0,

  /**
    The padding at the left of the graph
    @property paddingLeft
    @type Number
    @default 0
  */
  paddingLeft: 0,

  /**
    The padding at the right of the graph
    @property paddingRight
    @type Number
    @default 0
  */
  paddingRight: 0,

  /**
    The padding at the bottom of the graph
    @property paddingBottom
    @type Number
    @default 0
  */
  paddingBottom: 0,

  /**
    Determines whether to display "lanes" in the background of
    the graph.
    @property showLanes
    @type Boolean
    @default false
  */
  showLanes: false,

  /**
    Determines whether to display "frets" in the background of
    the graph.
    @property showFrets
    @type Boolean
    @default false
  */
  showFrets: false,

  /**
    The type of scale to use for x values.

    Possible Values:
    - `'linear'` - a standard linear scale
    - `'log'` - a logarithmic scale
    - `'power'` - a power-based scale (exponent = 3)
    - `'ordinal'` - an ordinal scale, used for ordinal data. required for bar graphs.

    @property xScaleType
    @type String
    @default 'linear'
  */
  xScaleType: 'linear',

  /**
    The type of scale to use for y values.

    Possible Values:
    - `'linear'` - a standard linear scale
    - `'log'` - a logarithmic scale
    - `'power'` - a power-based scale (exponent = 3)
    - `'ordinal'` - an ordinal scale, used for ordinal data. required for bar graphs.

    @property yScaleType
    @type String
    @default 'linear'
  */
  yScaleType: 'linear',

  /**
    The padding between value steps when `xScaleType` is `'ordinal'`
    @property xOrdinalPadding
    @type Number
    @default 0.1
  */
  xOrdinalPadding: 0.1,

  /**
    The padding at the ends of the domain data when `xScaleType` is `'ordinal'`
    @property xOrdinalOuterPadding
    @type Number
    @default 0.1
  */
  xOrdinalOuterPadding: 0.1,

  /**
    The padding between value steps when `xScaleType` is `'ordinal'`
    @property yOrdinalPadding
    @type Number
    @default 0.1
  */
  yOrdinalPadding: 0.1,

  /**
    The padding at the ends of the domain data when `yScaleType` is `'ordinal'`
    @property yOrdinalOuterPadding
    @type Number
    @default 0.1
  */
  yOrdinalOuterPadding: 0.1,

  /**
    the `nf-y-axis` component is registered here if there is one present
    @property yAxis
    @readonly
    @default null
  */
  yAxis: null,

  /**
    The `nf-x-axis` component is registered here if there is one present
    @property xAxis
    @readonly
    @default null
  */
  xAxis: null,

  /**
    Backing field for `xMin`
    @property _xMin
    @private
  */
  _xMin: null,

  /**
    Backing field for `xMax`
    @property _xMax
    @private
  */
  _xMax: null,

  /**
    Backing field for `yMin`
    @property _yMin
    @private
  */
  _yMin: null,

  /**
    Backing field for `yMax`
    @property _yMax
    @private
  */
  _yMax: null,

  /**
    Gets or sets the minimum x domain value to display on the graph.
    Behavior depends on `xMinMode`.
    @property xMin
  */
  xMin: minProperty('x', 8),

  /**
    Gets or sets the maximum x domain value to display on the graph.
    Behavior depends on `xMaxMode`.
    @property xMax
  */
  xMax: maxProperty('x', 8),

  /**
    Gets or sets the minimum y domain value to display on the graph.
    Behavior depends on `yMinMode`.
    @property yMin
  */
  yMin: minProperty('y', 5),

  /**
    Gets or sets the maximum y domain value to display on the graph.
    Behavior depends on `yMaxMode`.
    @property yMax
  */
  yMax: maxProperty('y', 5),


  /**
    Sets the behavior of `xMin` for the graph.

    ### Possible values:

    - 'auto': (default) xMin is always equal to the minimum domain value contained in the graphed data. Cannot be set.
    - 'fixed': xMin can be set to an exact value and will not change based on graphed data.
    - 'push': xMin can be set to a specific value, but will update if the minimum x value contained in the graph is less than
      what xMin is currently set to.
    - 'push-tick': xMin can be set to a specific value, but will update to next "nice" tick if the minimum x value contained in
      the graph is less than that xMin is set to.

    @property xMinMode
    @type String
    @default 'auto'
  */
  xMinMode: 'auto',

  /**
    Sets the behavior of `xMax` for the graph.

    ### Possible values:

    - 'auto': (default) xMax is always equal to the maximum domain value contained in the graphed data. Cannot be set.
    - 'fixed': xMax can be set to an exact value and will not change based on graphed data.
    - 'push': xMax can be set to a specific value, but will update if the maximum x value contained in the graph is greater than
      what xMax is currently set to.
    - 'push-tick': xMax can be set to a specific value, but will update to next "nice" tick if the maximum x value contained in
      the graph is greater than that xMax is set to.

    @property xMaxMode
    @type String
    @default 'auto'
  */
  xMaxMode: 'auto',

  /**
    Sets the behavior of `yMin` for the graph.

    ### Possible values:

    - 'auto': (default) yMin is always equal to the minimum domain value contained in the graphed data. Cannot be set.
    - 'fixed': yMin can be set to an exact value and will not change based on graphed data.
    - 'push': yMin can be set to a specific value, but will update if the minimum y value contained in the graph is less than
      what yMin is currently set to.
    - 'push-tick': yMin can be set to a specific value, but will update to next "nice" tick if the minimum y value contained in
      the graph is less than that yMin is set to.

    @property yMinMode
    @type String
    @default 'auto'
  */
  yMinMode: 'auto',

  /**
    Sets the behavior of `yMax` for the graph.

    ### Possible values:

    - 'auto': (default) yMax is always equal to the maximum domain value contained in the graphed data. Cannot be set.
    - 'fixed': yMax can be set to an exact value and will not change based on graphed data.
    - 'push': yMax can be set to a specific value, but will update if the maximum y value contained in the graph is greater than
      what yMax is currently set to.
    - 'push-tick': yMax can be set to a specific value, but will update to next "nice" tick if the maximum y value contained in
      the graph is greater than that yMax is set to.

    @property yMaxMode
    @type String
    @default 'auto'
  */
  yMaxMode: 'auto',

  /**
    The data extents for all data in the registered `graphics`.

    @property dataExtents
    @type {Object}
    @default {
      xMin: Number.MAX_VALUE,
      xMax: Number.MIN_VALUE,
      yMin: Number.MAX_VALUE,
      yMax: Number.MIN_VALUE
    }
  */
  dataExtents: computed('graphics.@each.data', function(){
    let graphics = this.get('graphics');
    return graphics.reduce((c, x) => c.concat(x.get('mappedData')), []).reduce((extents, [x, y]) => {
      extents.xMin = extents.xMin < x ? extents.xMin : x;
      extents.xMax = extents.xMax > x ? extents.xMax : x;
      extents.yMin = extents.yMin < y ? extents.yMin : y;
      extents.yMax = extents.yMax > y ? extents.yMax : y;
      return extents;
    }, {
      xMin: Number.MAX_VALUE,
      xMax: Number.MIN_VALUE,
      yMin: Number.MAX_VALUE,
      yMax: Number.MIN_VALUE
    });
  }),

  /**
    The action to trigger when the graph automatically updates the xScale
    due to an "auto" "push" or "push-tick" domainMode.

    sends the graph component instance value as the argument.

    @property autoScaleXAction
    @type {string}
    @default null
  */
  autoScaleXAction: null,

  _sendAutoUpdateXAction() {
    this.sendAction('autoScaleXAction', this);
  },

  _sendAutoUpdateYAction() {
    this.sendAction('autoScaleYAction', this);
  },

  /**
    Event handler that is fired for the `didAutoUpdateMaxX` event
    @method didAutoUpdateMaxX
  */
  didAutoUpdateMaxX() {
    scheduleOnce('afterRender', this, this._sendAutoUpdateXAction);
  },

  /**
    Event handler that is fired for the `didAutoUpdateMinX` event
    @method didAutoUpdateMinX
  */
  didAutoUpdateMinX() {
    scheduleOnce('afterRender', this, this._sendAutoUpdateXAction);
  },

  /**
    Event handler that is fired for the `didAutoUpdateMaxY` event
    @method didAutoUpdateMaxY
  */
  didAutoUpdateMaxY() {
    scheduleOnce('afterRender', this, this._sendAutoUpdateYAction);
  },

  /**
    Event handler that is fired for the `didAutoUpdateMinY` event
    @method didAutoUpdateMinY
  */
  didAutoUpdateMinY() {
    scheduleOnce('afterRender', this, this._sendAutoUpdateYAction);
  },

  /**
    The action to trigger when the graph automatically updates the yScale
    due to an "auto" "push" or "push-tick" domainMode.

    Sends the graph component instance as the argument.

    @property autoScaleYAction
    @type {string}
    @default null
  */
  autoScaleYAction: null,

  /**
    Gets the highest and lowest x values of the graphed data in a two element array.
    @property xDataExtent
    @type Array
    @readonly
  */
  xDataExtent: computed('dataExtents', function(){
    let { xMin, xMax } = this.get('dataExtents');
    return [xMin, xMax];
  }),

  /**
    Gets the highest and lowest y values of the graphed data in a two element array.
    @property yDataExtent
    @type Array
    @readonly
  */
  yDataExtent: computed('dataExtents', function(){
    let { yMin, yMax } = this.get('dataExtents');
    return [yMin, yMax];
  }),

  /**
    @property xUniqueData
    @type Array
    @readonly
  */
  xUniqueData: computed('graphics.@each.mappedData', function(){
    let graphics = this.get('graphics');
    let uniq = graphics.reduce((uniq, graphic) => {
      return graphic.get('mappedData').reduce((uniq, d) => {
        if(!uniq.some(x => x === d[0])) {
          uniq.push(d[0]);
        }
        return uniq;
      }, uniq);
    }, []);
    return A(uniq);
  }),


  /**
    @property yUniqueData
    @type Array
    @readonly
  */
  yUniqueData: computed('graphics.@each.mappedData', function(){
    let graphics = this.get('graphics');
    let uniq = graphics.reduce((uniq, graphic) => {
      return graphic.get('mappedData').reduce((uniq, d) => {
        if(!uniq.some(y => y === d[1])) {
          uniq.push(d[1]);
        }
        return uniq;
      }, uniq);
    }, []);
    return A(uniq);
  }),

  /**
    Gets the DOM id for the content clipPath element.
    @property contentClipPathId
    @type String
    @readonly
    @private
  */
  contentClipPathId: computed('elementId', function(){
    return this.get('elementId') + '-content-mask';
  }),

  /**
    Registry of contained graphic elements such as `nf-line` or `nf-area` components.
    This registry is used to pool data for scaling purposes.
    @property graphics
    @type Array
    @readonly
   */
  graphics: computed(function(){
    return A();
  }),

  /**
    An array of "selectable" graphics that have been selected within this graph.
    @property selected
    @type Array
    @readonly
  */
  selected: computed(function() {
    return this.get('selectMultiple') ? A() : null;
  }),

  /**
    Computed property to show yAxis. Returns `true` if a yAxis is present.
    @property showYAxis
    @type Boolean
    @default false
   */
  showYAxis: computedBool('yAxis'),

  /**
    Computed property to show xAxis. Returns `true` if an xAxis is present.
    @property showXAxis
    @type Boolean
    @default false
   */
  showXAxis: computedBool('xAxis'),

  /**
    Gets a function to create the xScale
    @property xScaleFactory
    @readonly
   */
  // xScaleFactory: scaleFactoryProperty('x'),
  xScaleFactory: computed(function() {
    return this._scaleFactoryFor('x');
  }),
  _scheduleXScaleFactory: observer('xScaleType', 'xPowerExponent', function() {
    schedule('afterRender', () => {
      this.set('xScaleFactory', this._scaleFactoryFor('x'));
    });
  }),

  /**
    Gets a function to create the yScale
    @property yScaleFactory
    @readonly
   */
  // yScaleFactory: scaleFactoryProperty('y'),
  yScaleFactory: computed(function() {
    return this._scaleFactoryFor('y');
  }),
  _scheduleYScaleFactory: observer('yScaleType', 'yPowerExponent', function() {
    schedule('afterRender', () => {
      this.set('yScaleFactory', this._scaleFactoryFor('y'));
    });
  }),

  _scaleFactoryFor(axis) {
    let type = this.get(`${axis}ScaleType`);
    let powExp = this.get(`${axis}PowerExponent`);

    type = typeof type === 'string' ? type.toLowerCase() : '';

    if(type === 'linear') {
      return d3.scale.linear;
    }

    else if(type === 'ordinal') {
      return function(){
        let scale = d3.scale.ordinal();
        // ordinal scales don't have an invert function, so we need to add one
        scale.invert = function(rv) {
          let [min, max] = d3.extent(scale.range());
          let domain = scale.domain();
          let i = Math.round((domain.length - 1) * (rv - min) / (max - min));
          return domain[i];
        };
        return scale;
      };
    }

    else if(type === 'power' || type === 'pow') {
      return function(){
        return d3.scale.pow().exponent(powExp);
      };
    }

    else if(type === 'log') {
      return d3.scale.log;
    }

    else {
      warn('unknown scale type: ' + type);
      return d3.scale.linear;
    }
  },

  /**
    Gets the domain of x values.
    @property xDomain
    @type Array
    @readonly
   */
  xDomain: computed(function() {
    return this._domainFor('x');
  }),
  _scheduleXDomain: observer(
    'xUniqueData.[]',
    'xMin',
    'xMax',
    'xScaleType',
    'xLogMin',
    function() {
      schedule('afterRender', () => {
        this.set('xDomain', this._domainFor('x'));
      });
    }
  ),

  /**
    Gets the domain of y values.
    @property yDomain
    @type Array
    @readonly
   */
  yDomain: computed(function() {
    return this._domainFor('y');
  }),

  /*
    NOTE: Although this can be done in a CP, we must compute
    this value only `afterRender` to avoid double render deprecations.
   */
  _scheduleYDomain: observer(
    'yUniqueData.[]',
    'yMin',
    'yMax',
    'yScaleType',
    'yLogMin',
    function() {
      schedule('afterRender', () => {
        this.set('yDomain', this._domainFor('y'));
      });
    }
  ),

  _domainFor(axis) {
    let data = this.get(`${axis}UniqueData`);
    let min = this.get(`${axis}Min`);
    let max = this.get(`${axis}Max`);
    let scaleType = this.get(`${axis}ScaleType`);
    let logMin = this.get(`${axis}LogMin`);
    let domain = null;

    if(scaleType === 'ordinal') {
      domain = data;
    } else {
      let extent = [min, max];

      if(scaleType === 'log') {
        if (extent[0] <= 0) {
          extent[0] = logMin;
        }
        if (extent[1] <= 0) {
          extent[1] = logMin;
        }
      }

      domain = extent;
    }

    return domain;
  },

  /**
    Gets the current xScale used to draw the graph.
    @property xScale
    @type Function
    @readonly
   */
  xScale: computed(function() {
    return this._scaleFor('x');
  }),

  /*
    NOTE: Although this can be done in a CP, we must compute
    this value only `afterRender` to avoid double render deprecations.
   */
  _scheduleXScale: observer(
    'xScaleFactory',
    'xRange',
    'xDomain',
    'xScaleType',
    'xOrdinalPadding',
    'xOrdinalOuterPadding',
    function() {
      schedule('afterRender', () => {
        this.set('xScale', this._scaleFor('x'));
      });
    }
  ),

  /**
    Gets the current yScale used to draw the graph.
    @property yScale
    @type Function
    @readonly
   */
  yScale: computed(function() {
    return this._scaleFor('y');
  }),

  /*
    NOTE: Although this can be done in a CP, we must compute
    this value only `afterRender` to avoid double render deprecations.
   */
  _scheduleYScale: observer(
    'yScaleFactory',
    'yRange',
    'yDomain',
    'yScaleType',
    'yOrdinalPadding',
    'yOrdinalOuterPadding',
    function() {
      schedule('afterRender', () => {
        this.set('yScale', this._scaleFor('y'));
      });
    }
  ),

  _scaleFor(axis) {
    let scaleFactory = this.get(`${axis}ScaleFactory`);
    let range = this.get(`${axis}Range`);
    let domain = this.get(`${axis}Domain`);
    let scaleType = this.get(`${axis}ScaleType`);
    let ordinalPadding = this.get(`${axis}OrdinalPadding`);
    let ordinalOuterPadding = this.get(`${axis}OrdinalOuterPadding`);

    let scale = scaleFactory();

    if(scaleType === 'ordinal') {
      scale = scale.domain(domain).rangeBands(range, ordinalPadding, ordinalOuterPadding);
    } else {
      scale = scale.domain(domain).range(range).clamp(true);
    }

    return scale;
  },

  /**
    Registers a graphic such as `nf-line` or `nf-area` components with the graph.
    @method registerGraphic
    @param graphic {Ember.Component} The component object to register
   */
  registerGraphic: function (graphic) {
    schedule('afterRender', () => {
      let graphics = this.get('graphics');
      graphic.on('hasData', this, this.updateExtents);
      graphics.pushObject(graphic);
    });
  },

  /**
    Unregisters a graphic such as an `nf-line` or `nf-area` from the graph.
    @method unregisterGraphic
    @param graphic {Ember.Component} The component to unregister
   */
  unregisterGraphic: function(graphic) {
    schedule('afterRender', () => {
      let graphics = this.get('graphics');
      graphic.off('hasData', this, this.updateExtents);
      graphics.removeObject(graphic);
    });
  },

  updateExtents() {
    this.get('xDataExtent');
    this.get('yDataExtent');
  },

  /**
    The y range of the graph in pixels. The min and max pixel values
    in an array form.
    @property yRange
    @type Array
    @readonly
   */
  yRange: computed('graphHeight', function(){
    return [this.get('graphHeight'), 0];
  }),

  /**
    The x range of the graph in pixels. The min and max pixel values
    in an array form.
    @property xRange
    @type Array
    @readonly
   */
  xRange: computed('graphWidth', function(){
    return [0, this.get('graphWidth')];
  }),

  /**
    Returns `true` if the graph has data to render. Data is conveyed
    to the graph by registered graphics.
    @property hasData
    @type Boolean
    @default false
    @readonly
   */
  hasData: notEmpty('graphics'),

  /**
    The x coordinate position of the graph content
    @property graphX
    @type Number
    @readonly
   */
  graphX: computed('paddingLeft', 'yAxis.{orient,width}', function() {
    let paddingLeft = this.get('paddingLeft');
    let yAxisWidth = this.get('yAxis.width') || 0;
    let yAxisOrient = this.get('yAxis.orient');
    if(yAxisOrient === 'right') {
      return paddingLeft;
    }
    return paddingLeft + yAxisWidth;
  }),

  /**
    The y coordinate position of the graph content
    @property graphY
    @type Number
    @readonly
   */
  graphY: computed('paddingTop', 'xAxis.{orient,height}', function(){
    let paddingTop = this.get('paddingTop');
    let xAxisOrient = this.get('xAxis.orient');
    if(xAxisOrient === 'top') {
      let xAxisHeight = this.get('xAxis.height') || 0;
      return xAxisHeight + paddingTop;
    }
    return paddingTop;
  }),

  /**
    The width, in pixels, of the graph content
    @property graphWidth
    @type Number
    @readonly
   */
  graphWidth: computed('width', 'paddingRight', 'paddingLeft', 'yAxis.width', function() {
    let paddingRight = this.get('paddingRight') || 0;
    let paddingLeft = this.get('paddingLeft') || 0;
    let yAxisWidth = this.get('yAxis.width') || 0;
    let width = this.get('width') || 0;
    return Math.max(0, width - paddingRight - paddingLeft - yAxisWidth);
  }),

  /**
    The height, in pixels, of the graph content
    @property graphHeight
    @type Number
    @readonly
   */
  graphHeight: computed('height', 'paddingTop', 'paddingBottom', 'xAxis.height', function(){
    let paddingTop = this.get('paddingTop') || 0;
    let paddingBottom = this.get('paddingBottom') || 0;
    let xAxisHeight = this.get('xAxis.height') || 0;
    let height = this.get('height') || 0;
    return Math.max(0, height - paddingTop - paddingBottom - xAxisHeight);
  }),

  /**
    An SVG transform to position the graph content
    @property graphTransform
    @type String
    @readonly
   */
  graphTransform: computed('graphX', 'graphY', function(){
    let graphX = this.get('graphX');
    let graphY = this.get('graphY');
    return `translate(${graphX} ${graphY})`;
  }),

  /**
    Sets `hasRendered` to `true` on `willInsertElement`.
    @method _notifyHasRendered
    @private
  */
  _notifyHasRendered: on('willInsertElement', function () {
    schedule('afterRender', () => {
      this.set('hasRendered', true);
    });
  }),

  /**
    Gets the mouse position relative to the container
    @method mousePoint
    @param container {SVGElement} the SVG element that contains the mouse event
    @param e {Object} the DOM mouse event
    @return {Array} an array of `[xMouseCoord, yMouseCoord]`
   */
  mousePoint: function (container, e) {
    let svg = container.ownerSVGElement || container;
    if (svg.createSVGPoint) {
      let point = svg.createSVGPoint();
      point.x = e.clientX;
      point.y = e.clientY;
      point = point.matrixTransform(container.getScreenCTM().inverse());
      return [ point.x, point.y ];
    }
    let rect = container.getBoundingClientRect();
    return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
  },

  /**
    Selects the graphic passed. If `selectMultiple` is false, it will deselect the currently
    selected graphic if it's different from the one passed.
    @method selectGraphic
    @param graphic {Ember.Component} the graph component to select within the graph.
  */
  selectGraphic: function(graphic) {
    if(!graphic.get('selected')) {
      graphic.set('selected', true);
    }
    if(this.selectMultiple) {
      this.get('selected').pushObject(graphic);
    } else {
      let current = this.get('selected');
      if(current && current !== graphic) {
        current.set('selected', false);
      }
      this.set('selected', graphic);
    }
  },

  /**
    deselects the graphic passed.
    @method deselectGraphic
    @param graphic {Ember.Component} the graph child component to deselect.
  */
  deselectGraphic: function(graphic) {
    graphic.set('selected', false);
    if(this.selectMultiple) {
      this.get('selected').removeObject(graphic);
    } else {
      let current = this.get('selected');
      if(current && current === graphic) {
        this.set('selected', null);
      }
    }
  },

  /**
    The amount of leeway, in pixels, to give before triggering a brush start.
    @property brushThreshold
    @type {Number}
    @default 7
  */
  brushThreshold: 7,

  /**
    The name of the action to trigger when brushing starts
    @property brushStartAction
    @type {String}
    @default null
  */
  brushStartAction: null,

  /**
    The name of the action to trigger when brushing emits a new value
    @property brushAction
    @type {String}
    @default null
  */
  brushAction: null,

  /**
    The name of the action to trigger when brushing ends
    @property brushEndAction
    @type {String}
    @default null
  */
  brushEndAction: null,

  _setupBrushAction: on('didInsertElement', function(){
    let content = this.$('.nf-graph-content');

    let mouseMoves = Observable.fromEvent(content, 'mousemove');
    let mouseDowns = Observable.fromEvent(content, 'mousedown');
    let mouseUps = Observable.fromEvent($(document), 'mouseup');
    let mouseLeaves = Observable.fromEvent(content, 'mouseleave');

    this._brushDisposable = Observable.merge(mouseDowns, mouseMoves, mouseLeaves).
      // get a streams of mouse events that start on mouse down and end on mouse up
      window(mouseDowns, function() { return mouseUps; })
      // filter out all of them if there are no brush actions registered
      // map the mouse event streams into brush event streams
      .map(x => this._toBrushEventStreams(x)).
      // flatten to a stream of action names and event objects
      flatMap(x => this._toComponentEventStream(x)).
      // HACK: this is fairly cosmetic, so skip errors.
      retry().
      // subscribe and send the brush actions via Ember
      subscribe(x => {
        run(this, () => this._triggerComponentEvent(x));
      });
  }),

  _toBrushEventStreams: function(mouseEvents) {
    // get the starting mouse event
    return mouseEvents.take(1).
      // calculate it's mouse point and info
      map( this._getStartInfo ).
      // combine the start with the each subsequent mouse event
      combineLatest(mouseEvents.skip(1), toArray).
      // filter out everything until the brushThreshold is crossed
      filter(x => this._byBrushThreshold(x)).
      // create the brush event object
      map(x => this._toBrushEvent(x));
  },

  _triggerComponentEvent: function(d) {
    this.trigger(d[0], d[1]);
  },

  _toComponentEventStream: function(events) {
    return Observable.merge(
      events.take(1).map(function(e) {
        return ['didBrushStart', e];
      }), events.map(function(e) {
        return ['didBrush', e];
      }), events.last().map(function(e) {
        return ['didBrushEnd', e];
      })
    );
  },

  didBrush: function(e) {
    if(this.get('brushAction')) {
      this.sendAction('brushAction', e);
    }
  },

  didBrushStart: function(e) {
    document.body.style.setProperty('-webkit-user-select', 'none');
    document.body.style.setProperty('-moz-user-select', 'none');
    document.body.style.setProperty('user-select', 'none');
    if(this.get('brushStartAction')) {
      this.sendAction('brushStartAction', e);
    }
  },

  didBrushEnd: function(e) {
    document.body.style.removeProperty('-webkit-user-select');
    document.body.style.removeProperty('-moz-user-select');
    document.body.style.removeProperty('user-select');
    if(this.get('brushEndAction')) {
      this.sendAction('brushEndAction', e);
    }
  },

  _toBrushEvent: function(d) {
    let start = d[0];
    let currentEvent =  d[1];
    let currentPoint = getMousePoint(currentEvent.currentTarget, d[1]);

    let startPosition = GraphPosition.create({
      originalEvent: start.originalEvent,
      graph: this,
      graphX: start.mousePoint.x,
      graphY: start.mousePoint.y
    });

    let currentPosition = GraphPosition.create({
      originalEvent: currentEvent,
      graph: this,
      graphX: currentPoint.x,
      graphY: currentPoint.y
    });

    let left = startPosition;
    let right = currentPosition;

    if(start.originalEvent.clientX > currentEvent.clientX) {
      left = currentPosition;
      right = startPosition;
    }

    return {
      start: startPosition,
      current: currentPosition,
      left: left,
      right: right
    };
  },

  _byBrushThreshold: function(d) {
    let startEvent = d[0].originalEvent;
    let currentEvent = d[1];
    return Math.abs(currentEvent.clientX - startEvent.clientX) > this.get('brushThreshold');
  },

  _getStartInfo: function(e) {
    return {
      originalEvent: e,
      mousePoint: getMousePoint(e.currentTarget, e)
    };
  },

  willDestroyElement: function(){
    this._super(...arguments);

    if(this._brushDisposable) {
      this._brushDisposable.dispose();
    }
  },
});


================================================
FILE: addon/components/nf-group.js
================================================
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-group';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import SelectableGraphic from 'ember-nf-graph/mixins/graph-selectable-graphic';

/**
  A grouping tag that provides zooming and offset functionality to it's children.

  ## Example

  The following example will show a line of `someData` with a 2x zoom, offset by 30px in both x and y
  directions:

        {{#nf-gg scaleZoomX="2" scaleZoomY="2" scaleOffsetX="30" scaleOffsetY="30"}}
          {{nf-line data=someData}}
        {{/nf-gg}}

  @namespace components
  @class nf-gg
  @extends Ember.Component
  @uses mixins.graph-require-scale-source
  @uses mixins.graph-selecteble-graphic
*/
export default Component.extend(RequireScaleSource, SelectableGraphic, {
  layout,
  tagName: 'g',

  classNameBindings: [':nf-group', 'selectable', 'selected'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  click: function() {
    if(this.get('selectable')) {
      this.toggleProperty('selected');
    }
  }
});


================================================
FILE: addon/components/nf-horizontal-line.js
================================================
import { alias } from '@ember/object/computed';
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-horizontal-line';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

/**
  Draws a horizontal line on the graph at a given y domain value
  @namespace components
  @class nf-horizontal-line
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RequireScaleSource, {
  layout,
  tagName: 'line',

  attributeBindings: ['lineY:y1', 'lineY:y2', 'x1', 'x2'],

  classNames: ['nf-horizontal-line'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The y domain value at which to draw the horizontal line
    @property y
    @type Number
    @default null
  */
  y: null,

  /**
    The computed y coordinate of the line to draw
    @property lineY
    @type Number
    @private
    @readonly
  */
  lineY: computed('y', 'yScale', function(){
    let y = this.get('y');
    let yScale = this.get('yScale');
    let py = yScale ? yScale(y) : -1;
    return py && py > 0 ? py : 0;
  }),

  /**
    The left x coordinate of the line
    @property x1
    @type Number
    @default 0
    @private
  */
  x1: 0,

  /**
    The right x coordinate of the line
    @property x2
    @type Number
    @private
    @readonly
  */
  x2: alias('graph.graphWidth'),
});


================================================
FILE: addon/components/nf-line.js
================================================
import { on } from '@ember/object/evented';
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-line';
import DataGraphic from 'ember-nf-graph/mixins/graph-data-graphic';
import LineUtils from 'ember-nf-graph/mixins/graph-line-utils';
import SelectableGraphic from 'ember-nf-graph/mixins/graph-selectable-graphic';
import RegisteredGraphic from 'ember-nf-graph/mixins/graph-registered-graphic';
import GraphicWithTrackingDot from 'ember-nf-graph/mixins/graph-graphic-with-tracking-dot';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

/**
  A line graphic for `nf-graph`. Displays a line for the data it's passed.
  @namespace components
  @class nf-line
  @extends Ember.Component
  @uses mixins.graph-line-utils
  @uses mixins.graph-selectable-graphic
  @uses mixins.graph-registered-graphic
  @uses mixins.graph-data-graphic
  @uses mixins.graph-graphic-with-tracking-dot
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(DataGraphic, SelectableGraphic, LineUtils, RegisteredGraphic, GraphicWithTrackingDot, RequireScaleSource, {
  layout,
  tagName: 'g',

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The type of D3 interpolator to use to create the line.
    @property interpolator
    @type String
    @default 'linear'
  */
  interpolator: 'linear',

  classNameBindings: ['selected', 'selectable'],

  classNames: ['nf-line'],

  /**
    The d3 line function to create the line path.
    @method lineFn
    @param data {Array} the array of coordinate arrays to plot as an SVG path
    @private
    @return {String} an SVG path data string
  */
  lineFn: computed('xScale', 'yScale', 'interpolator', function() {
    let xScale = this.get('xScale');
    let yScale = this.get('yScale');
    let interpolator = this.get('interpolator');
    return this.createLineFn(xScale, yScale, interpolator);
  }),

  /**
    The SVG path data string to render the line
    @property d
    @type String
    @private
    @readonly
  */
  d: computed('renderedData.[]', 'lineFn', function(){
    let renderedData = this.get('renderedData');
    let lineFn = this.get('lineFn');
    return lineFn(renderedData);
  }),

  /**
    Event handler to toggle the `selected` property on click
    @method _toggleSelected
    @private
  */
  _toggleSelected: on('click', function(){
    if(this.get('selectable')) {
      this.toggleProperty('selected');
    }
  }),
});


================================================
FILE: addon/components/nf-plot.js
================================================
import { computed } from '@ember/object';
import { notEmpty, and } from '@ember/object/computed';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-plot';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import GraphEvent from 'ember-nf-graph/utils/nf/graph-event';

/**
  Plots a group tag on a graph at a given x and y domain coordinate.
  @namespace components
  @class nf-plot
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RequireScaleSource, {
  layout,
  tagName: 'g',

  attributeBindings: ['transform'],

  classNames: ['nf-plot'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The x domain value to set the plot at
    @property x
    @default null
  */
  x: null,

  /**
    The y domain value to set the plot at
    @property x
    @default null
  */
  y: null,

  /**
    True if an `x` value is present (defined, not null and non-empty)
    @property hasX
    @type Boolean
    @readonly
  */
  hasX: notEmpty('x'),

  /**
    True if an `y` value is present (defined, not null and non-empty)
    @property hasY
    @type Boolean
    @readonly
  */
  hasY: notEmpty('y'),

  /**
    The calculated visibility of the component
    @property isVisible
    @type Boolean
    @readonly
  */
  isVisible: and('hasX', 'hasY'),

  /**
    The calculated x coordinate
    @property rangeX
    @type Number
    @readonly
  */
  rangeX: computed('x', 'xScale', function(){
    let xScale = this.get('xScale');
    let x = this.get('x');
    let hasX = this.get('hasX');
    return (hasX && xScale ? xScale(x) : 0) || 0;
  }),

  /**
    The calculated y coordinate
    @property rangeY
    @type Number
    @readonly
  */
  rangeY: computed('y', 'yScale', function(){
    let yScale = this.get('yScale');
    let y = this.get('y');
    let hasY = this.get('hasY');
    return (hasY && yScale ? yScale(y) : 0) || 0;
  }),

  /**
    The SVG transform of the component's `<g>` tag.
    @property transform
    @type String
    @readonly
  */
  transform: computed('rangeX', 'rangeY', function(){
    let rangeX = this.get('rangeX');
    let rangeY = this.get('rangeY');
    return `translate(${rangeX} ${rangeY})`;
  }),

  data: null,

  click: function(e) {
    let context = GraphEvent.create({
      x: this.get('x'),
      y: this.get('y'),
      data: this.get('data'),
      source: this,
      graph: this.get('graph'),
      originalEvent: e,
    });
    this.sendAction('action', context);
  },
});


================================================
FILE: addon/components/nf-plots.js
================================================
import { isArray, A } from '@ember/array';
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-plots';
import DataGraphic from 'ember-nf-graph/mixins/graph-data-graphic';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

export default Component.extend(DataGraphic, RequireScaleSource, {
  layout,
  tagName: 'g',

  classNames: ['nf-plots'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The model for adding plots to the graph
    @property plotData
    @readonly
    @private
  */
  plotData: computed('renderedData.[]', function(){
    let renderedData = this.get('renderedData');
    if(renderedData && isArray(renderedData)) {
      return A(renderedData.map(function(d) {
        return {
          x: d[0],
          y: d[1],
          data: d.data,
        };
      }));
    }
  }),


  actions: {
    itemClicked: function(e) {
      this.sendAction('action', e);
    },
  },
});


================================================
FILE: addon/components/nf-range-marker.js
================================================
import { on } from '@ember/object/evented';
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-range-marker';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

/**
  Draws a rectangular strip with a templated label on an `nf-graph`.
  Should always be used in conjunction with an `nf-range-markers` container component.
  @namespace components
  @class nf-range-marker
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RequireScaleSource, {
  layout,
  tagName: 'g',

  attributeBindings: ['transform'],

  classNames: ['nf-range-marker'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The parent `nf-range-markers` component.
    @property container
    @type {components.nf-range-markers}
    @default null
  */
  container: null,

  /**
    The minimum domain value for the range to mark.
    @property xMin
    @default 0
  */
  xMin: 0,

  /**
    The maximum domain value for the range to mark.
    @property xMax
    @default 0
  */
  xMax: 0,

  /**
    The spacing above the range marker.
    @property marginTop
    @type Number
    @default 10
  */
  marginTop: 10,

  /**
    The spacing below the range marker.
    @property marginBottom
    @type Number
    @default 3
  */
  marginBottom: 3,

  /**
    The height of the range marker.
    @property height
    @type Number
    @default 10
  */
  height: 10,

  /**
    The computed x position of the range marker.
    @property x
    @type Number
    @readonly
  */
  x: computed('xMin', 'xScale', function(){
    let xScale = this.get('xScale');
    let xMin = this.get('xMin');
    return xScale(xMin);
  }),

  /**
    The computed width of the range marker.
    @property width
    @type Number
    @readonly
  */
  width: computed('xScale', 'xMin', 'xMax', function() {
    let xScale = this.get('xScale');
    let xMax = this.get('xMax');
    let xMin = this.get('xMin');
    return xScale(xMax) - xScale(xMin);
  }),

  /**
    The computed y position of the range marker.
    @property y
    @type Number
    @readonly
  */
  y: computed(
    'container.orient',
    'prevMarker.{bottom,y}',
    'graph.graphHeight',
    'totalHeight',
    function() {
      let orient = this.get('container.orient');
      let prevBottom = this.get('prevMarker.bottom');
      let prevY = this.get('prevMarker.y');
      let graphHeight = this.get('graph.graphHeight');
      let totalHeight = this.get('totalHeight');

      prevBottom = prevBottom || 0;

      if(orient === 'bottom') {
        return (prevY || graphHeight) - totalHeight;
      }

      if(orient === 'top') {
        return prevBottom;
      }
    }
  ),

  /**
    The computed total height of the range marker including its margins.
    @property totalHeight
    @type Number
    @readonly
  */
  totalHeight: computed('height', 'marginTop', 'marginBottom', function() {
    let height = this.get('height');
    let marginTop = this.get('marginTop');
    let marginBottom = this.get('marginBottom');
    return height + marginTop + marginBottom;
  }),

  /**
    The computed bottom of the range marker, not including the bottom margin.
    @property bottom
    @type Number
    @readonly
  */
  bottom: computed('y', 'totalHeight', function(){
    let y = this.get('y');
    let totalHeight = this.get('totalHeight');
    return y + totalHeight;
  }),

  /**
    The computed SVG transform of the range marker container
    @property transform
    @type String
    @readonly
  */
  transform: computed('y', function(){
    let y = this.get('y') || 0;
    return `translate(0 ${y})`;
  }),

  /**
    The computed SVG transform fo the range marker label container.
    @property labelTransform
    @type String
    @readonly
  */
  labelTransform: computed('x', function(){
    let x = this.get('x') || 0;
    return `translate(${x} 0)`;
  }),

  /**
    Initialization function that registers the range marker with its parent
    and populates the container property
    @method _setup
    @private
  */
  init() {
    this._super(...arguments);
    let container = this.get('container');
    container.registerMarker(this);
  },

  /**
    Unregisters the range marker from its parent when the range marker is destroyed.
    @method _unregister
    @private
  */
  _unregisterMarker: on('willDestroyElement', function() {
    this.get('container').unregisterMarker(this);
  })
});


================================================
FILE: addon/components/nf-range-markers.js
================================================
import { schedule } from '@ember/runloop';
import { A } from '@ember/array';
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-range-markers';

/**
  A container and manager for `nf-range-marker` components.
  Used to draw an association between `nf-range-marker` components so they
  can be laid out in a manner in which they don't collide.
  @namespace components
  @class nf-range-markers
  @extends Ember.Component
*/
export default Component.extend({
  layout,
  tagName: 'g',

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    Sets the orientation of the range markers.

    - `'bottom'` - Range markers start at the bottom and stack upward
    - `'top'` - Range markers start at the top and stack downward
    @property orient
    @type String
    @default 'bottom'
  */
  orient: 'bottom',

  /**
    The margin, in pixels, between the markers
    @property markerMargin
    @type Number
    @default 10
  */
  markerMargin: 10,

  /**
    The marker components registered with this container
    @property markers
    @type Array
    @readonly
  */
  markers: computed(function() {
    return A();
  }),

  /**
    Adds the passed marker to the `markers` list, and sets the `prevMarker` and `nextMarker`
    properties on the marker component and it's neighbor.
    @method registerMarker
    @param marker {nf-range-marker} the range marker to register with this container
  */
  registerMarker: function(marker) {
    let markers = this.get('markers');
    let prevMarker = markers[markers.length - 1];

    schedule('afterRender', () => {
      if(prevMarker) {
        marker.set('prevMarker', prevMarker);
        prevMarker.set('nextMarker', marker);
      }

      markers.pushObject(marker);
    });
  },

  /**
    Removes the marker from the `markers` list. Also updates the `nextMarker` and `prevMarker`
    properties of it's neighboring components.
    @method unregisterMarker
    @param marker {nf-range-marker} the range marker to remove from the `markers` list.
  */
  unregisterMarker: function(marker) {
    if(marker) {
      schedule('afterRender', () => {
        let next = marker.nextMarker;
        let prev = marker.prevMarker;
        if(prev) {
          prev.set('nextMarker', next);
        }
        if(next) {
          next.set('prevMarker', prev);
        }
        this.get('markers').removeObject(marker);
      });
    }
  },
});


================================================
FILE: addon/components/nf-right-tick.js
================================================
import { scheduleOnce } from '@ember/runloop';
import { on } from '@ember/object/evented';
import { computed, observer } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-right-tick';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

/**
  Draws a line and a chevron at the specified domain value
  on the right side of an `nf-graph`.

  ### Tips

  - Adding `paddingRight` to `nf-graph` component will not affect `nf-right-tick`'s position.

  @namespace components
  @class nf-right-tick
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RequireScaleSource, {
  layout,
  tagName: 'g',

  classNames: ['nf-right-tick'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The transition duration in milliseconds
    @property duration
    @type Number
    @default 400
  */
  duration: 400,

  /**
    The domain value at which to place the tick
    @property value
    @type Number
    @default null
  */
  value: null,

  /**
    Sets the visibility of the component. Returns false if `y` is not
    a numeric data type.
    @property isVisible
    @private
    @readonly
  */
  isVisible: computed('y', function(){
    return !isNaN(this.get('y'));
  }),

  /**
    The calculated y coordinate of the tick
    @property y
    @type Number
    @readonly
  */
  y: computed('value', 'yScale', 'graph.paddingTop', function() {
    let value = this.get('value');
    let yScale = this.get('yScale');
    let paddingTop = this.get('graph.paddingTop');
    let vy = 0;
    if(yScale) {
      vy = yScale(value) || 0;
    }
    return vy + paddingTop;
  }),

  /**
    The SVG transform used to render the tick
    @property transform
    @type String
    @private
    @readonly
  */
  transform: computed('y', 'graph.width', function(){
    let y = this.get('y');
    let graphWidth = this.get('graph.width');
    let x0 = graphWidth - 6;
    let y0 = y - 3;
    return `translate(${x0} ${y0})`;
  }),

  /**
    performs the D3 transition to move the tick to the proper position.
    @method _transitionalUpdate
    @private
  */
  _transitionalUpdate: function(){
    let transform = this.get('transform');
    let path = this.get('path');
    let duration = this.get('duration');
    path.transition().duration(duration)
      .attr('transform', transform);
  },

  /**
    Schedules the transition when `value` changes on on init.
    @method _triggerTransition
    @private
  */
  _triggerTransition: on('init', observer('value', function(){
    scheduleOnce('afterRender', this, this._transitionalUpdate);
  })),

  /**
    Updates the tick position without a transition.
    @method _nonTransitionalUpdate
    @private
  */
  _nonTransitionalUpdate: function(){
    let transform = this.get('transform');
    let path = this.get('path');
    path.attr('transform', transform);
  },

  /**
    Schedules the update of non-transitional positions
    @method _triggerNonTransitionalUpdate
    @private
  */
  _triggerNonTransitionalUpdate: observer('graph.width', function(){
    scheduleOnce('afterRender', this, this._nonTransitionalUpdate);
  }),

  /**
    Gets the elements required to do the d3 transitions
    @method _getElements
    @private
  */
  _getElements: on('didInsertElement', function(){
    let g = d3.select(this.$()[0]);
    let path = g.selectAll('path').data([0]);
    this.set('path', path);
  })
});


================================================
FILE: addon/components/nf-selection-box.js
================================================
import { on } from '@ember/object/evented';
import { once } from '@ember/runloop';
import { computed, observer } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-selection-box';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import { normalizeScale } from 'ember-nf-graph/utils/nf/scale-utils';

/**
  Draws a rectangle on an `nf-graph` given domain values `xMin`, `xMax`, `yMin` and `yMax`.
  @namespace components
  @class nf-selection-box
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RequireScaleSource, {
  layout,
  tagName: 'g',

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The duration of the transition in ms
    @property duration
    @type Number
    @default 400
  */
  duration: 400,

  /**
    The minimum x domain value to encompass.
    @property xMin
    @default null
  */
  xMin: null,

  /**
    The maximum x domain value to encompoass.
    @property xMax
    @default null
  */
  xMax: null,

  /**
    The minimum y domain value to encompass.
    @property yMin
    @default null
  */
  yMin: null,

  /**
    The maximum y domain value to encompass
    @property yMax
    @default null
  */
  yMax: null,

  classNames: ['nf-selection-box'],

  /**
    The x pixel position of xMin
    @property x0
    @type Number
  */
  x0: computed('xMin', 'xScale', function(){
    return normalizeScale(this.get('xScale'), this.get('xMin'));
  }),

  /**
    The x pixel position of xMax
    @property x1
    @type Number
  */
  x1: computed('xMax', 'xScale', function(){
    return normalizeScale(this.get('xScale'), this.get('xMax'));
  }),

  /**
    The y pixel position of yMin
    @property y0
    @type Number
  */
  y0: computed('yMin', 'yScale', function(){
    return normalizeScale(this.get('yScale'), this.get('yMin'));
  }),

  /**
    The y pixel position of yMax
    @property y1
    @type Number
  */
  y1: computed('yMax', 'yScale', function(){
    return normalizeScale(this.get('yScale'), this.get('yMax'));
  }),

  /**
    The SVG path string for the box's rectangle.
    @property rectPath
    @type String
  */
  rectPath: computed('x0', 'x1', 'y0', 'y1', function(){
    let x0 = this.get('x0');
    let x1 = this.get('x1');
    let y0 = this.get('y0');
    let y1 = this.get('y1');
    return `M${x0},${y0} L${x0},${y1} L${x1},${y1} L${x1},${y0} L${x0},${y0}`;
  }),

  /**
    Updates the position of the box with a transition
    @method doUpdatePosition
  */
  doUpdatePosition: function(){
    let boxRect = this.get('boxRectElement');
    let rectPath = this.get('rectPath');
    let duration = this.get('duration');

    boxRect.transition().duration(duration)
      .attr('d', rectPath);
  },

  doUpdatePositionStatic: function(){
    let boxRect = this.get('boxRectElement');
    let rectPath = this.get('rectPath');

    boxRect.attr('d', rectPath);
  },

  /**
    Schedules an update to the position of the box after render.
    @method updatePosition
    @private
  */
  updatePosition: observer('xMin', 'xMax', 'yMin', 'yMax', function(){
    once(this, this.doUpdatePosition);
  }),

  staticPositionChange: on('didInsertElement', observer('xScale', 'yScale', function(){
    once(this, this.doUpdatePositionStatic);
  })),

  /**
    Sets up the required d3 elements after component
    is inserted into the DOM
    @method didInsertElement
  */
  didInsertElement: function(){
    let element = this.get('element');
    let g = d3.select(element);
    let boxRect = g.append('path')
      .attr('class', 'nf-selection-box-rect')
      .attr('d', this.get('rectPath'));

    this.set('boxRectElement', boxRect);
  },
});


================================================
FILE: addon/components/nf-svg-image.js
================================================
import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from 'ember-nf-graph/templates/components/nf-svg-image';
import RequiresScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import { normalizeScale } from 'ember-nf-graph/utils/nf/scale-utils';
import SelectableGraphic from 'ember-nf-graph/mixins/graph-selectable-graphic';

/**
  An image to be displayed in a graph with that takes domain based measurements and
  uses the scale of the graph. Creates an `<image class="nf-image"/>` SVG element.
  @namespace components
  @class nf-svg-image
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
  @uses mixins.graph-selectable-graphic
*/
export default Component.extend(RequiresScaleSource, SelectableGraphic, {
  layout,
  tagName: 'image',

  classNameBindings: [':nf-svg-image', 'selectable', 'selected'],

  attributeBindings: ['svgX:x', 'svgY:y', 'svgWidth:width', 'svgHeight:height', 'src:href'],

  click: function(){
    if(this.get('selectable')) {
      this.toggleProperty('selected');
    }
  },

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The domain x value to place the image at.
    @property x
    @default null
  */
  x: null,

  /**
    The domain y value to place the image at.
    @property y
    @default null
  */
  y: null,

  _width: 0,

  /**
    The width as a domain value. Does not handle ordinal
    scales. To set a pixel value, set `svgWidth` directly.
    @property width
    @type Number
    @default 0
  */
  width: computed({
    get() {
      return this._width;
    },
    set(key, value) {
      return this._width = Math.max(0, +value) || 0;
    }
  }),

  _height: 0,

  /**
    The height as a domain value. Does not
    handle ordinal scales. To set a pixel value, just
    set `svgHeight` directly.
    @property height
    @default null
  */
  height: computed({
    get() {
      return this._height;
    },
    set(key, value) {
      this._height = Math.max(0, +value) || 0;
    }
  }),

  /**
    The image source url
    @property src
    @type String
  */
  src: '',

  x0: computed('x', 'xScale', function(){
    return normalizeScale(this.get('xScale'), this.get('x'));
  }),

  y0: computed('y', 'yScale', function(){
    return normalizeScale(this.get('yScale'), this.get('y'));
  }),

  x1: computed('xScale', 'width', 'x', function(){
    let scale = this.get('xScale');
    if(scale.rangeBands) {
      throw new Error('nf-image does not support ordinal scales');
    }
    return normalizeScale(scale, this.get('width') + this.get('x'));
  }),

  y1: computed('yScale', 'height', 'y', function(){
    let scale = this.get('yScale');
    if(scale.rangeBands) {
      throw new Error('nf-image does not support ordinal scales');
    }
    return normalizeScale(scale, this.get('height') + this.get('y'));
  }),

  /**
    The pixel value at which to plot the image.
    @property svgX
    @type Number
  */
  svgX: computed('x0', 'x1', function(){
    return Math.min(this.get('x0'), this.get('x1'));
  }),

  /**
    The pixel value at which to plot the image.
    @property svgY
    @type Number
  */
  svgY: computed('y0', 'y1', function(){
    return Math.min(this.get('y0'), this.get('y1'));
  }),

  /**
    The width, in pixels, of the image.
    @property svgWidth
    @type Number
  */
  svgWidth: computed('x0', 'x1', function(){
    return Math.abs(this.get('x0') - this.get('x1'));
  }),

  /**
    The height, in pixels of the image.
    @property svgHeight
    @type Number
  */
  svgHeight: computed('y0', 'y1', function(){
    return Math.abs(this.get('y0') - this.get('y1'));
  }),
});


================================================
FILE: addon/components/nf-svg-line.js
================================================
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-svg-line';
import RequiresScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import { normalizeScale } from 'ember-nf-graph/utils/nf/scale-utils';
import SelectableGraphic from 'ember-nf-graph/mixins/graph-selectable-graphic';

/**
  Draws a basic line between two points on the graph.
  @namespace components
  @class nf-svg-line
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
  @uses mixins.graph-selectable-graphic
*/
export default Component.extend(RequiresScaleSource, SelectableGraphic, {
  layout,
  tagName: 'line',

  classNameBindings: [':nf-svg-line', 'selectable', 'selected'],

  attributeBindings: ['svgX1:x1', 'svgX2:x2', 'svgY1:y1', 'svgY2:y2'],

  click: function(){
    if(this.get('selectable')) {
      this.toggleProperty('selected');
    }
  },

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The domain value to plot the SVGLineElement's x1 at.
    @property x1
    @default null
  */
  x1: null,

  /**
    The domain value to plot the SVGLineElement's x2 at.
    @property x2
    @default null
  */
  x2: null,

  /**
    The domain value to plot the SVGLineElement's y1 at.
    @property y1
    @default null
  */
  y1: null,

  /**
    The domain value to plot the SVGLineElement's y2 at.
    @property y2
    @default null
  */
  y2: null,

  /**
    The pixel value to plot the SVGLineElement's x1 at.
    @property svgX1
    @type Number
  */
  svgX1: computed('x1', 'xScale', function(){
    return normalizeScale(this.get('xScale'), this.get('x1'));
  }),

  /**
    The pixel value to plot the SVGLineElement's x2 at.
    @property svgX2
    @type Number
  */
  svgX2: computed('x2', 'xScale', function(){
    return normalizeScale(this.get('xScale'), this.get('x2'));
  }),

  /**
    The pixel value to plot the SVGLineElement's y1 at.
    @property svgY1
    @type Number
  */
  svgY1: computed('y1', 'yScale', function(){
    return normalizeScale(this.get('yScale'), this.get('y1'));
  }),

  /**
    The pixel value to plot the SVGLineElement's y2 at.
    @property svgY2
    @type Number
  */
  svgY2: computed('y2', 'yScale', function(){
    return normalizeScale(this.get('yScale'), this.get('y2'));
  }),
});


================================================
FILE: addon/components/nf-svg-path.js
================================================
import { isArray } from '@ember/array';
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-svg-path';
import RequiresScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import { normalizeScale } from 'ember-nf-graph/utils/nf/scale-utils';
import SelectableGraphic from 'ember-nf-graph/mixins/graph-selectable-graphic';

/**
  An SVG path primitive that plots based on a graph's scale.
  @namespace components
  @class nf-svg-path
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
  @uses mixins.graph-selectable-graphic
*/
export default Component.extend(RequiresScaleSource, SelectableGraphic, {
  layout,
  type: 'path',

  classNameBindings: [':nf-svg-path', 'selectable', 'selected'],

  attributeBindings: ['d'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The array of points to use to plot the path. This is an array of arrays, in the following format:

          // specify path pen commands
          [
            [50, 50, 'L'],
            [100, 100, 'L']
          ]

          // or they will default to 'L'
          [
            [50, 50],
            [100, 100]
          ]

  @property points
  @type Array
  */
  points: null,

  /**
    The data points mapped to scale
    @property svgPoints
    @type Array
  */
  svgPoints: computed('points.[]', 'xScale', 'yScale', function(){
    let points = this.get('points');
    let xScale = this.get('xScale');
    let yScale = this.get('yScale');
    if(isArray(points) && points.length > 0) {
      return points.map(function(v) {
        let dx = normalizeScale(xScale, v[0]);
        let dy = normalizeScale(yScale, v[1]);
        let c = v.length > 2 ? v[2] : 'L';
        return [dx, dy, c];
      });
    }
  }),

  click: function(){
    if(this.get('selectable')) {
      this.toggleProperty('selected');
    }
  },

  /**
    The raw svg path d attribute output
    @property d
    @type String
  */
  d: computed('svgPoints', function(){
    let svgPoints = this.get('svgPoints');
    if(isArray(svgPoints) && svgPoints.length > 0) {
      return svgPoints.reduce(function(d, pt, i) {
        if(i === 0) {
          d += 'M' + pt[0] + ',' + pt[1];
        }
        d += ' ' + pt[2] + pt[0] + ',' + pt[1];
        return d;
      }, '');
    } else {
      return 'M0,0';
    }
  }),
});


================================================
FILE: addon/components/nf-svg-rect.js
================================================
import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from 'ember-nf-graph/templates/components/nf-svg-rect';
import RequiresScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import { normalizeScale } from 'ember-nf-graph/utils/nf/scale-utils';
import SelectableGraphic from 'ember-nf-graph/mixins/graph-selectable-graphic';

/**
  A rectangle that plots using domain values from the graph. Uses an SVGPathElement
  to plot the rectangle, to allow for rectangles with "negative" heights.
  @namespace components
  @class nf-svg-rect
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
  @uses mixins.graph-selectable-graphic
*/
export default Component.extend(RequiresScaleSource, SelectableGraphic, {
  layout,
  tagName: 'path',

  attributeBindings: ['d'],

  classNameBindings: [':nf-svg-rect', 'selectable', 'selected'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The domain x value to place the rect at.
    @property x
    @default null
  */
  x: null,

  /**
    The domain y value to place the rect at.
    @property y
    @default null
  */
  y: null,

  _width: 0,

  /**
    The width as a domain value. If xScale is ordinal,
    then this value is the indice offset to which to draw the
    rectangle. In other words, if it's `2`, then draw the rectangle
    to two ordinals past whatever `x` is set to.
    @property width
    @type Number
    @default 0
  */
  width: computed({
    get() {
      return this._width;
    },
    set(key, value) {
      return this._width = +value;
    }
  }),

  _height: 0,

  /**
    The height as a domain value. If the yScale is ordinal,
    this value is the indice offset to which to draw the rectangle.
    For example, if the height is `3` then draw the rectangle
    to two ordinals passed whatever `y` is set to.
    @property height
    @type Number
    @default 0
  */
  height: computed({
    get() {
      return this._height;
    },
    set(key, value) {
      return this._height = +value;
    }
  }),

  /**
    The x value of the bottom right corner of the rectangle.
    @property x1
    @type Number
  */
  x1: computed('width', 'x', 'xScale', function(){
    let xScale = this.get('xScale');
    let w = this.get('width');
    let x = this.get('x');
    if(xScale.rangeBands) {
      let domain = xScale.domain();
      let fromIndex = domain.indexOf(x);
      let toIndex = fromIndex + w;
      return normalizeScale(xScale, domain[toIndex]);
    } else {
      x = +x || 0;
      return normalizeScale(xScale, w + x);
    }
  }),

  /**
    The y value of the bottom right corner of the rectangle
    @property y1
    @type Number
  */
  y1: computed('height', 'y', 'yScale', function(){
    let yScale = this.get('yScale');
    let h = this.get('height');
    let y = this.get('y');
    if(yScale.rangeBands) {
      let domain = yScale.domain();
      let fromIndex = domain.indexOf(y);
      let toIndex = fromIndex + h;
      return normalizeScale(yScale, domain[toIndex]);
    } else {
      y = +y || 0;
      return normalizeScale(yScale, h + y);
    }
  }),

  /**
    The x value of the top right corner of the rectangle
    @property x0
    @type Number
  */
  x0: computed('x', 'xScale', function(){
    return normalizeScale(this.get('xScale'), this.get('x'));
  }),

  /**
    The y value of the top right corner of the rectangle.
    @property y0
    @type Number
  */
  y0: computed('y', 'yScale', function() {
    return normalizeScale(this.get('yScale'), this.get('y'));
  }),

  /**
    The SVG path data for the rectangle
    @property d
    @type String
  */
  d: computed('x0', 'y0', 'x1', 'y1', function(){
    let x0 = this.get('x0');
    let y0 = this.get('y0');
    let x1 = this.get('x1');
    let y1 = this.get('y1');
    return `M${x0},${y0} L${x0},${y1} L${x1},${y1} L${x1},${y0} L${x0},${y0}`;
  }),

  /**
    Click event handler. Toggles selected if selectable.
    @method click
  */
  click: function(){
    if(this.get('selectable')) {
      this.toggleProperty('selected');
    }
  }
});


================================================
FILE: addon/components/nf-tick-label.js
================================================
import { computed } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-tick-label';

export default Component.extend({
  layout,
  tagName: 'g',

  attributeBindings: ['transform'],

  transform: computed('x', 'y', function(){
    let x = this.get('x');
    let y = this.get('y');
    return `translate(${x} ${y})`;
  }),

  className: 'nf-tick-label'
});


================================================
FILE: addon/components/nf-tracker.js
================================================
import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from 'ember-nf-graph/templates/components/nf-tracker';
import DataGraphic from 'ember-nf-graph/mixins/graph-data-graphic';
import RequiresScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import GraphicWithTrackingDot from 'ember-nf-graph/mixins/graph-graphic-with-tracking-dot';

/**
  A tracking graphic component used to do things like create tracking dots for nf-area or nf-line.
  @namespace components
  @class nf-tracker
  @uses mixins.graph-data-graphic
  @uses mixins.graph-requires-scale-source
  @uses mixins.graph-graphic-with-tracking-dot
  */
export default Component.extend(DataGraphic, RequiresScaleSource, GraphicWithTrackingDot, {
  layout,
  tagName: 'g',

  classNameBindings: [':nf-tracker'],

  attributeBindings: ['transform'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  transform: computed('trackedData.{x,y}', 'xScale', 'yScale', {
    get() {
      let xScale = this.get('xScale');
      let yScale = this.get('yScale');
      let x = xScale && xScale(this.get('trackedData.x') || 0);
      let y = yScale && yScale(this.get('trackedData.y') || 0);
      return 'translate(' + x + ',' + y + ')';
    }
  })
});


================================================
FILE: addon/components/nf-vertical-line.js
================================================
import { computed } from '@ember/object';
import { alias } from '@ember/object/computed';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-vertical-line';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

/**
  Draws a vertical line on a graph at a given x domain value
  @namespace components
  @class nf-vertical-line
  @extends Ember.Component
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RequireScaleSource, {
  layout,
  tagName: 'line',

  classNames: ['nf-vertical-line'],

  attributeBindings: ['lineX:x1', 'lineX:x2', 'y1', 'y2'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The top y coordinate of the line
    @property y1
    @type Number
    @default 0
    @private
  */
  y1: 0,

  /**
    The bottom y coordinate of the line
    @property y2
    @type Number
    @private
    @readonly
  */
  y2: alias('graph.graphHeight'),

  /**
    The x domain value at which to draw the vertical line on the graph
    @property x
    @type Number
    @default null
  */
  x: null,

  /**
    The calculated x coordinate of the vertical line
    @property lineX
    @type Number
    @private
    @readonly
  */
  lineX: computed('xScale', 'x', function(){
    let xScale = this.get('xScale');
    let x = this.get('x');
    let px = xScale ? xScale(x) : -1;
    return px && px > 0 ? px : 0;
  }),
});


================================================
FILE: addon/components/nf-x-axis.js
================================================
import { alias, uniq } from '@ember/object/computed';
import { A } from '@ember/array';
import { schedule } from '@ember/runloop';
import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from 'ember-nf-graph/templates/components/nf-x-axis';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

/**
  A component for adding a templated x axis to an `nf-graph` component.
  All items contained within this component are used to template each tick mark on the
  rendered graph. Tick values are supplied to the inner scope of this component on the
  view template via `tick`.

  ### Styling

  The main container will have a `nf-x-axis` class.
  A `orient-top` or `orient-bottom` container will be applied to the container
  depending on the `orient` setting.

  Ticks are positioned via a `<g>` tag, that will contain whatever is passed into it via
  templating, along with the tick line. `<text>` tags within tick templates do have some
  default styling applied to them to position them appropriately based off of orientation.

  ### Example

        {{#nf-graph width=500 height=300}}
          {{#nf-x-axis height=40 as |tick|}}
            <text>x is {{tick.value}}</text>
          {{/nf-x-axis}}
        {{/nf-graph}}


  @namespace components
  @class nf-x-axis
  @extends Ember.Component
  @uses mixins.graph-has-graph-parent
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RequireScaleSource, {
  layout,
  tagName: 'g',

  attributeBindings: ['transform'],
  classNameBindings: ['orientClass'],
  classNames: ['nf-x-axis'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The height of the x axis in pixels.
    @property height
    @type Number
    @default 20
  */
  height: 20,

  /**
    The number of ticks to display
    @property tickCount
    @type Number
    @default 12
  */
  tickCount: 12,

  /**
    The length of the tick line (the small vertical line indicating the tick)
    @property tickLength
    @type Number
    @default 0
  */
  tickLength: 0,

  /**
    The spacing between the end of the tick line and the origin of the templated
    tick content
    @property tickPadding
    @type Number
    @default 5
  */
  tickPadding: 5,

  /**
    The orientation of the x axis. Value can be `'top'` or `'bottom'`.
    @property orient
    @type String
    @default 'bottom'
  */
  orient: 'bottom',

  _tickFilter: null,

  /**
    An optional filtering function to allow more control over what tick marks are displayed.
    The function should have exactly the same signature as the function you'd use for an
    `Array.prototype.filter()`.

    @property tickFilter
    @type Function
    @default null
    @example

          {{#nf-x-axis tickFilter=myFilter as |tick|}}
            <text>{{tick.value}}</text>
          {{/nf-x-axis}}

    And on your controller:

          myFilter: function(tick, index, ticks) {
            return tick.value < 1000;
          },

    The above example will filter down the set of ticks to only those that are less than 1000.
  */
  tickFilter: alias('_tickFilter'),

  /**
    The class applied due to orientation (e.g. `'orient-top'`)
    @property orientClass
    @type String
    @readonly
  */
  orientClass: computed('orient', function(){
    return 'orient-' + this.get('orient');
  }),

  /**
    The SVG Transform applied to this component's container.
    @property transform
    @type String
    @readonly
  */
  transform: computed('x', 'y', function(){
    let x = this.get('x') || 0;
    let y = this.get('y') || 0;
    return `translate(${x} ${y})`;
  }),

  /**
    The y position of this component's container.
    @property y
    @type Number
    @readonly
  */
  y: computed(
    'orient',
    'graph.{paddingTop,paddingBottom,height}',
    'height',
    function(){
      let orient = this.get('orient');
      let graphHeight = this.get('graph.height');
      let height = this.get('height');
      let paddingBottom = this.get('graph.paddingBottom');
      let paddingTop = this.get('graph.paddingTop');
      let y;

      if(orient === 'bottom') {
        y = graphHeight - paddingBottom - height;
      } else {
        y = paddingTop;
      }

      return y || 0;
    }
  ),

  /**
    This x position of this component's container
    @property x
    @type Number
    @readonly
  */
  x: computed('graph.graphX', function(){
    return this.get('graph.graphX') || 0;
  }),

  init() {
    this._super(...arguments);

    schedule('afterRender', () => {
      this.set('graph.xAxis', this);
    });
  },

  /**
    The width of the component
    @property width
    @type Number
    @readonly
  */
  width: alias('graph.graphWidth'),

  /**
    A method to call to override the default behavior of how ticks are created.

    The function signature should match:

          // - scale: d3.Scale
          // - tickCount: number of ticks
          // - uniqueData: unique data points for the axis
          // - scaleType: string of "linear" or "ordinal"
          // returns: an array of tick values.
          function(scale, tickCount, uniqueData, scaleType) {
            return [100,200,300];
          }

    @property tickFactory
    @type {Function}
    @default null
  */
  tickFactory: null,

  tickData: computed('xScale', 'graph.xScaleType', 'uniqueXData', 'tickCount', 'tickFactory', function(){
    let tickFactory = this.get('tickFactory');
    let scale = this.get('xScale');
    let uniqueData = this.get('uniqueXData');
    let tickCount = this.get('tickCount');
    let scaleType = this.get('graph.xScaleType');

    if(tickFactory) {
      return tickFactory(scale, tickCount, uniqueData, scaleType);
    }
    else if(scaleType === 'ordinal') {
      return uniqueData;
    }
    else {
      return scale.ticks(tickCount);
    }
  }),

  /**
    A unique set of all x data on the graph
    @property uniqueXData
    @type Array
    @readonly
  */
  uniqueXData: uniq('graph.xData'),

  /**
    The models for the ticks to display on the axis.
    @property ticks
    @type Array
    @readonly
  */
  ticks: computed(
    'xScale',
    'tickPadding',
    'tickLength',
    'height',
    'orient',
    'tickFilter',
    'tickData',
    'graph.xScaleType',
    function(){
      let xScale = this.get('xScale');
      let xScaleType = this.get('graph.xScaleType');
      let tickPadding = this.get('tickPadding');
      let tickLength = this.get('tickLength');
      let height = this.get('height');
      let orient = this.get('orient');
      let tickFilter = this.get('tickFilter');
      let ticks = this.get('tickData');
      let y1 = orient === 'top' ? height : 0;
      let y2 = y1 + tickLength;
      let labely = orient === 'top' ? (y1 - tickPadding) : (y1 + tickPadding);
      let halfBandWidth = (xScaleType === 'ordinal') ? xScale.rangeBand() / 2 : 0;
      let result = ticks.map(function(tick) {
        return {
          value: tick,
          x: xScale(tick) + halfBandWidth,
          y1: y1,
          y2: y2,
          labely: labely
        };
      });

      if(tickFilter) {
        result = result.filter(tickFilter);
      }

      return A(result);
    }
  ),

  /**
    The y position, in pixels, of the axis line
    @property axisLineY
    @type Number
    @readonly
  */
  axisLineY: computed('orient', 'height', function(){
    return this.get('orient') === 'top' ? this.get('height') : 0;
  })

});


================================================
FILE: addon/components/nf-y-axis.js
================================================
import { alias, equal, uniq } from '@ember/object/computed';
import { A } from '@ember/array';
import { schedule } from '@ember/runloop';
import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from 'ember-nf-graph/templates/components/nf-y-axis';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';

/**
  A component for adding a templated y axis to an `nf-graph` component.
  All items contained within this component are used to template each tick mark on the
  rendered graph. Tick values are supplied to the inner scope of this component on the
  view template via `tick`.

  ### Styling

  The main container will have a `nf-y-axis` class.
  A `orient-left` or `orient-right` container will be applied to the container
  depending on the `orient` setting.

  Ticks are positioned via a `<g>` tag, that will contain whatever is passed into it via
  templating, along with the tick line. `<text>` tags within tick templates do have some
  default styling applied to them to position them appropriately based off of orientation.

  ### Example

        {{#nf-graph width=500 height=300}}
          {{#nf-y-axis width=40 as |tick|}}
            <text>y is {{tick.value}}</text>
          {{/nf-y-axis}}
        {{/nf-graph}}


  @namespace components
  @class nf-y-axis
  @uses mixins.graph-has-graph-parent
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RequireScaleSource, {
  layout,
  tagName: 'g',

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The number of ticks to display
    @property tickCount
    @type Number
    @default 5
  */
  tickCount: 5,

  /**
    The length of the tick's accompanying line.
    @property tickLength
    @type Number
    @default 5
  */
  tickLength: 5,

  /**
    The distance between the tick line and the origin tick's templated output
    @property tickPadding
    @type Number
    @default 3
  */
  tickPadding: 3,

  /**
    The total width of the y axis
    @property width
    @type Number
    @default 40
  */
  width: 40,

  /**
    The orientation of the y axis. Possible values are `'left'` and `'right'`
    @property orient
    @type String
    @default 'left'
  */
  orient: 'left',

  attributeBindings: ['transform'],

  classNameBindings: [':nf-y-axis', 'isOrientRight:orient-right:orient-left'],

  _tickFilter: null,

  /**
    An optional filtering function to allow more control over what tick marks are displayed.
    The function should have exactly the same signature as the function you'd use for an
    `Array.prototype.filter()`.

    @property tickFilter
    @type Function
    @default null
    @example

          {{#nf-y-axis tickFilter=myFilter as |tick|}}
            <text>{{tick.value}}</text>
          {{/nf-y-axis}}

    And on your controller:

          myFilter: function(tick, index, ticks) {
            return tick.value < 1000;
          },

    The above example will filter down the set of ticks to only those that are less than 1000.
  */
  tickFilter: alias('_tickFilter'),

  /**
    computed property. returns true if `orient` is equal to `'right'`.
    @property isOrientRight
    @type Boolean
    @readonly
  */
  isOrientRight: equal('orient', 'right'),


  /**
    The SVG transform for positioning the component.
    @property transform
    @type String
    @readonly
  */
  transform: computed('x', 'y', function(){
    let x = this.get('x') || 0;
    let y = this.get('y') || 0;
    return `translate(${x} ${y})`;
  }),

  /**
    The x position of the component
    @property x
    @type Number
    @readonly
  */
  x: computed(
    'orient',
    'width',
    'graph.{paddingLeft,paddingRight,width}',
    function(){
      let orient = this.get('orient');
      if(orient !== 'left') {
        return this.get('graph.width') - this.get('width') - this.get('graph.paddingRight');
      }
      return this.get('graph.paddingLeft');
    }
  ),

  /**
    The y position of the component
    @property y
    @type Number
    @readonly
  */
  y: alias('graph.graphY'),

  /**
    the height of the component
    @property height
    @type Number
    @readonly
  */
  height: alias('graph.graphHeight'),

  init() {
    this._super(...arguments);

    schedule('afterRender', () => {
      this.set('graph.yAxis', this);
    });
  },

  /**
    A method to call to override the default behavior of how ticks are created.

    The function signature should match:

          // - scale: d3.Scale
          // - tickCount: number of ticks
          // - uniqueData: unique data points for the axis
          // - scaleType: string of "linear" or "ordinal"
          // returns: an array of tick values.
          function(scale, tickCount, uniqueData, scaleType) {
            return [100,200,300];
          }

    @property tickFactory
    @type {Function}
    @default null
  */
  tickFactory: null,

  tickData: computed('graph.yScaleType', 'uniqueYData', 'yScale', 'tickCount', 'tickFactory', function(){
    let tickFactory = this.get('tickFactory');
    let scale = this.get('yScale');
    let uniqueData = this.get('uniqueYData');
    let scaleType = this.get('graph.yScaleType');
    let tickCount = this.get('tickCount');

    if(tickFactory) {
      return tickFactory(scale, tickCount, uniqueData, scaleType);
    }
    else if(scaleType === 'ordinal') {
      return uniqueData;
    }
    else {
      let ticks = scale.ticks(tickCount);
      if (scaleType === 'log') {
        let step = Math.round(ticks.length / tickCount);
        ticks = ticks.filter(function (tick, i) {
          return i % step === 0;
        });
      }
      return ticks;
    }
  }),

  /**
    All y data from the graph, filtered to unique values.
    @property uniqueYData
    @type Array
    @readonly
  */
  uniqueYData: uniq('graph.yData'),

  /**
    The ticks to be displayed.
    @property ticks
    @type Array
    @readonly
  */
  ticks: computed(
    'yScale',
    'tickPadding',
    'axisLineX',
    'tickLength',
    'isOrientRight',
    'tickFilter',
    'tickData',
    function() {
      let yScale = this.get('yScale');
      let tickPadding = this.get('tickPadding');
      let axisLineX = this.get('axisLineX');
      let tickLength = this.get('tickLength');
      let isOrientRight = this.get('isOrientRight');
      let tickFilter = this.get('tickFilter');
      let ticks = this.get('tickData');
      let x1 = isOrientRight ? axisLineX + tickLength : axisLineX - tickLength;
      let x2 = axisLineX;
      let labelx = isOrientRight ? (tickLength + tickPadding) : (axisLineX - tickLength - tickPadding);

      let result = ticks.map(function (tick) {
        return {
          value: tick,
          y: yScale(tick),
          x1: x1,
          x2: x2,
          labelx: labelx,
        };
      });

      if(tickFilter) {
        result = result.filter(tickFilter);
      }

      return A(result);
    }
  ),


  /**
    The x position of the axis line.
    @property axisLineX
    @type Number
    @readonly
  */
  axisLineX: computed('isOrientRight', 'width', function(){
    return this.get('isOrientRight') ? 0 : this.get('width');
  }),
});


================================================
FILE: addon/components/nf-y-diff.js
================================================
import { on } from '@ember/object/evented';
import { once } from '@ember/runloop';
import { alias, gte, equal } from '@ember/object/computed';
import { computed, observer } from '@ember/object';
import Component from '@ember/component';
import layout from 'ember-nf-graph/templates/components/nf-y-diff';
import RequireScaleSource from 'ember-nf-graph/mixins/graph-requires-scale-source';
import { normalizeScale } from 'ember-nf-graph/utils/nf/scale-utils';

/**
  Draws a box underneath (or over) the y axis to between the given `a` and `b`
  domain values. Component content is used to template a label in that box.

  ## Tips

  - Should be outside of `nf-graph-content`.
  - Should be "above" `nf-y-axis` in the markup.
  - As a convenience, `<text>` elements will automatically be positioned based on y-axis orientation
    due to default styling.

  @namespace components
  @class nf-y-diff
  @extends Ember.Component
  @uses mixins.graph-has-graph-parent
  @uses mixins.graph-requires-scale-source
*/
export default Component.extend(RequireScaleSource, {
  layout,
  tagName: 'g',

  attributeBindings: ['transform'],

  classNameBindings: [':nf-y-diff', 'isPositive:positive:negative', 'isOrientRight:orient-right:orient-left'],

  /**
    The parent graph for a component.
    @property graph
    @type components.nf-graph
    @default null
    */
  graph: null,

  /**
    The starting domain value of the difference measurement. The subrahend of the difference calculation.
    @property a
    @type Number
    @default null
  */
  a: null,

  /**
    The ending domain value of the difference measurement. The minuend of the difference calculation.
    @property b
    @type Number
    @default null
  */
  b: null,

  /**
    The amount of padding, in pixels, between the edge of the difference "box" and the content container
    @property contentPadding
    @type Number
    @default 5
  */
  contentPadding: 5,

  /**
    The duration of the transition, in milliseconds, as the difference slides vertically
    @property duration
    @type Number
    @default 400
  */
  duration: 400,

  /**
    The calculated vertical center of the difference box, in pixels.
    @property yCenter
    @type Number
    @readonly
  */
  yCenter: computed('yA', 'yB', function(){
    let yA = +this.get('yA') || 0;
    let yB = +this.get('yB') || 0;
    return (yA + yB) / 2;
  }),

  /**
    The y pixel value of b.
    @property yB
    @type Number
  */
  yB: computed('yScale', 'b', function(){
    return normalizeScale(this.get('yScale'), this.get('b'));
  }),

  /**
    The y pixel value of a.
    @property yA
    @type Number
  */
  yA: computed('yScale', 'a', function() {
    return normalizeScale(this.get('yScale'), this.get('a'));
  }),

  /**
    The SVG transformation of the component.
    @property transform
    @type String
    @private
    @readonly
  */
  transform: alias('graph.yAxis.transform'),

  /**
    The calculated difference between `a` and `b`.
    @property diff
    @type Number
    @readonly
  */
  diff: computed('a', 'b', function(){
    return +this.get('b') - this.get('a');
  }),

  /**
    Returns `true` if `diff` is a positive number
    @property isPositive
    @type Boolean
    @readonly
  */
  isPositive: gte('diff', 0),

  /**
    Returns `true` if the graph's y-axis component is configured to orient right.
    @property isOrientRight
    @type Boolean
    @readonly
  */
  isOrientRight: equal('graph.yAxis.orient', 'right'),

  /**
    The width of the difference box
    @property width
    @type Number
    @readonly
  */
  width: alias('graph.yAxis.width'),

  /**
    The x pixel coordinate of the content container.
    @property contentX
    @type Number
    @readonly
  */
  contentX: computed('isOrientRight', 'width', 'contentPadding', function(){
    let contentPadding = this.get('contentPadding');
    let width = this.get('width');
    return this.get('isOrientRight') ? width - contentPadding : contentPadding;
  }),

  rectPath: computed('yA', 'yB', 'width', function(){
    let x = 0;
    let w = +this.get('width') || 0;
    let x2 = x + w;
    let yA = +this.get('yA') || 0;
    let yB = +this.get('yB') || 0;
    return `M${x},${yA} L${x},${yB} L${x2},${yB} L${x2},${yA} L${x},${yA}`;
  }),

  /**
    The SVG transformation used to position the content container.
    @property contentTransform
    @type String
    @private
    @readonly
  */
  contentTransform: computed('contentX', 'yCenter', function(){
    let contentX = this.get('contentX');
    let yCenter = this.get('yCenter');
    return `translate(${contentX} ${yCenter})`;
  }),

  /**
    Sets up the d3 related elements when component is inserted
    into the DOM
    @method didInsertElement
  */
  didInsertElement: function(){
    let element = this.get('element');
    let g = d3.select(element);

    let rectPath = this.get('rectPath');
    let rect = g.insert('path', ':first-child')
      .attr('class', 'nf-y-diff-rect')
      .attr('d', rectPath);

    let contentTransform = this.get('contentTransform');
    let content = g.select('.nf-y-diff-content');
    content.attr('transform', contentTransform);

    this.set('rectElement', rect);
    this.set('contentElement', content);
  },

  /**
    Performs the transition (animation) of the elements.
    @method doTransition
  */
  doTransition: function(){
    let duration = this.get('duration');
    let rectElement = this.get('rectElement');
    let contentElement = this.get('contentElement');

    if(rectElement) {
      rectElement.transition().duration(duration)
        .attr('d', this.get('rectPath'));
    }

    if(contentElement) {
      contentElement.transition().duration(duration)
        .attr('transform', this.get('contentTransform'));
    }
  },

  /**
    Schedules a transition once at afterRender.
    @method transition
  */
  transition: observer('a', 'b', function(){
    once(this, this.doTransition);
  }),

  /**
    Updates to d3 managed DOM elments that do
    not require transitioning, because they're width-related.
    @method doAdjustWidth
  */
  doAdjustWidth: function(){
    let contentElement = this.get('contentElement');
    if(contentElement) {
      let contentTransform = this.get('contentTransform');
      contentElement.attr('transform', contentTransform);
    }
  },

  adjustGraphHeight: on('didInsertElement', observer('graph.graphHeight', function(){
    let rectElement = this.get('rectElement');
    let contentElement = this.get('contentElement');

    if(rectElement) {
      rectElement.attr('d', this.get('rectPath'));
    }

    if(contentElement) {
      contentElement.attr('transform', this.get('contentTransform'));
    }
  })),

  /**
    Schedules a call to `doAdjustWidth` on afterRender
    @method adjustWidth
  */
  adjustWidth: on(
    'didInsertElement',
    observer('isOrientRight', 'width', 'contentPadding', function(){
      once(this, this.doAdjustWidth);
    })
  ),
});


================================================
FILE: addon/mixins/.gitignore
================================================
node_modules/
bower_components/

================================================
FILE: addon/mixins/graph-area-utils.js
================================================
import Mixin from '@ember/object/mixin';

/**
  Utility functions for drawing an area.
  
  @namespace mixins
  @class graph-area-utils
  @extends Ember.Mixin
*/
export default Mixin.create({

  /**
    Creates a d3 area function from a given set of scales and an interpolator

    @method createAreaFn
    @param xScale {Function} a d3 scale
    @param yScale {Function} a d3 scale
    @param interpolator {String} the name of the d3 interpolator to use.
    @return {Function} a function that when called will create SVG path data.
  */
  createAreaFn: function(xScale, yScale, interpolator) {
      let interp = interpolator || 'linear';
      let xMod = xScale.rangeBand ? xScale.rangeBand() / 2 : 0;
      let yMod = yScale.rangeBand ? yScale.rangeBand() / 2 : 0;

      return function(data) {
        if(!data || data.length === 0) {
          return 'M0,0';
        }

        return d3.svg.area()
          .x(function (d) {
            return (xScale(d[0]) || 0) + xMod;
          })
          .y0(function (d) {
            return (yScale(d[1]) || 0) + yMod;
          })
          .y1(function (d) {
            return (yScale(d[2]) || 0) + yMod;
          })
          .interpolate(interp)(data);
      };
  }
});

================================================
FILE: addon/mixins/graph-data-graphic.js
================================================
import { scheduleOnce, schedule } from '@ember/runloop';
import { isArray } from '@ember/array';
import Mixin from '@ember/object/mixin';
import { on } from '@ember/object/evented';
import { computed, observer } from '@ember/object';
import parsePropertyExpr from '../utils/parse-property-expression';
import { nearestIndexTo } from '../utils/nf/array-helpers';

let noop = function(){};

/**
  This is mixed in to {{#crossLink components.nf-graph}}nf-graph{{/crossLink}} child components that need to register data
  with the graph. Includes methods for extracting, sorting and scrubbing data
  for use in graphing components.

  Requires {{#crossLink "mixins.graph-registered-graphic"}}{{/crossLink}} and
  {{#crossLink "mixins.graph-has-graph-parent"}}{{/crossLink}}

  @namespace mixins
  @class graph-data-graphic
  @extends Ember.Mixin
*/
export default Mixin.create({
  /**
    Gets or sets the data used by the component to plot itself.

    @property data
    @type Array
    @default null
  */
  data: null,

  mappedData: computed('data.[]', {
    get() {
      let yPropFn = this.get('yPropFn');
      let xPropFn = this.get('xPropFn');
      let data = this.get('data');
      if(isArray(data)) {
        return data.map(function(d, i) {
          let item = [xPropFn(d), yPropFn(d)];
          item.data = d;
          item.origIndex = i;
          return item;
        });
      }
      return [];
    }
  }),

  _triggerHasData: on('init', observer('data.[]', function(){
    scheduleOnce('afterRender', this, this._sendTriggerHasData);
  })),

  _sendTriggerHasData() {
    this.trigger('hasData', this.get('mappedData'));
  },

  /**
    The path of the property on each object in
    {{#crossLink "mixins.graph-data-graphic/data:property"}}{{/crossLink}}
    to use as x data to plot on the graph.

    @property xprop
    @type String
    @default 'x'
  */
  xprop: 'x',

  /**
    The path of the property on each object in
    {{#crossLink "mixins.graph-data-graphic/data:property"}}{{/crossLink}}
    to use as y data to plot on the graph.

    @property yprop
    @type String
    @default 'y'
  */
  yprop: 'y',

  /**
    The function to get the x value from each
    {{#crossLink "mixins.graph-data-graphic/data:property"}}{{/crossLink}} object

    @property xPropFn
    @type Function
    @readonly
  */
  xPropFn: computed('xprop', {
    get() {
      let xprop = this.get('xprop');
      return xprop ? parsePropertyExpr(xprop) : noop;
    }
  }),

  /**
    The function to get the y value from each
    {{#crossLink "mixins.graph-data-graphic/data:property"}}{{/crossLink}} object

    @property yPropFn
    @type Function
    @readonly
  */
  yPropFn: computed('yprop', {
    get() {
      let yprop = this.get('yprop');
      return yprop ? parsePropertyExpr(yprop) : noop;
    }
  }),

  /**
    The list of data points from {{#crossLink "mixins.graph-data-graphc/mappedData:property"}}{{/crossLink}} that
    fits within the x domain, plus up to one data point outside of that domain in each direction.
    @property renderedData
    @type Array
    @readonly
  */
  renderedData: computed(function() {
    return this._computeRenderedData();
  }),

  _scheduleComputeRenderedData: observer('mappedData.[]', 'graph.xScaleType', 'graph.xMin', 'graph.xMax', function() {
    schedule('afterRender', () => {
      this.set('renderedData', this._computeRenderedData());
    });
  }),

  _computeRenderedData() {
    let mappedData = this.get('mappedData');
    let graph = this.get('graph');
    let xScaleType = graph.get('xScaleType');
    let xMin = graph.get('xMin');
    let xMax = graph.get('xMax');

    if(!mappedData || mappedData.length === 0) {
      return [];
    }

    if(xScaleType === 'ordinal') {
      return mappedData;
    }

    return mappedData.filter(function(d, i) {
      let x = d[0];
      let prev = mappedData[i-1];
      let next = mappedData[i+1];
      let prevX = prev ? prev[0] : null;
      let nextX = next ? next[0] : null;

      return between(x, xMin, xMax) || between(prevX, xMin, xMax) || between(nextX, xMin, xMax);
    });
  },

  /**
    The first element from {{#crossLink "mixins.graph-data-graphic/renderedData:property"}}{{/crossLink}}
    that is actually visible within the x domain.
    @property firstVisibleData
    @type {Object}
    @readonly
  */
  firstVisibleData: computed('renderedData.[]', 'xMin', {
    get() {
      let { renderedData, xPropFn, yPropFn, xMin } = this.getProperties('renderedData', 'xPropFn', 'yPropFn', 'xMin');

      let first = renderedData[0];
      if(first && xMin > first[0] && renderedData.length > 1) {
        first = renderedData[1];
      }

      return first ? {
        x: xPropFn(first.data),
        y: yPropFn(first.data),
        data: first.data,
        renderX: first[0],
        renderY: first[1]
      } : null;
    }
  }),


  /**
    The last element from {{#crossLink "mixins.graph-data-graphic/renderedData:property"}}{{/crossLink}}
    that is actually visible within the x domain.
    @property lastVisibleData
    @type {Object}
    @readonly
  */
  lastVisibleData: computed('renderedData.[]', 'yPropFn', 'xPropFn', 'xMax', {
    get() {
      let { renderedData, xPropFn, yPropFn, xMax } = this.getProperties('renderedData', 'xPropFn', 'yPropFn', 'xMax');
      let last = renderedData[renderedData.length - 1];

      if(last && xMax < last[0] && renderedData.length > 1) {
        last = renderedData[renderedData.length - 2];
      }

      return last ? {
        x: xPropFn(last.data),
        y: yPropFn(last.data),
        data: last.data,
        renderX: last[0],
        renderY: last[1]
      }: null;
    }
  }),

  _getRenderedDataNearXRange: function(rangeX) {
    let xScale = this.get('xScale');
    let isLinear = xScale && xScale.invert;
    if(isLinear) {
      return this.getDataNearX(xScale.invert(rangeX));
    } else {
      //ordinal
      let range = this.get('graph.xRange');
      let v = Math.abs(rangeX - range[0]) / Math.abs(range[1] - range[0]);
      let renderedData = this.get('renderedData');
      let i = Math.floor(v * renderedData.length);
      return renderedData[i];
    }
  },

  getDataNearXRange(rangeX) {
    let rendered = this._getRenderedDataNearXRange(rangeX);

    if(!rendered) {
      return null;
    }

    let renderX = rendered[0];
    let renderY = rendered[1];
    let data = rendered.data;
    let { x, y } = this.getActualTrackData(renderX, renderY, data);

    return { renderX, renderY, data, x, y };
  },

  /**
    Gets the actual data at a rendered tracking point passed to it.
    This is overridden in nf-area to account for stacking of data.
    @method getActualTrackData
    @param renderX {number} the x domain value the data is rendered at
    @param renderY {number} the y domain value the data is rendered at
    @param data {Object} the raw data from the point
    @return {Object} simple x, y point structure
  */
  getActualTrackData(renderX, renderY, data) {
    return { x: renderX, y: renderY, data };
  },

  getDataNearX: function(x) {
    x = +x;
    if(x === x) {
      let renderedData = this.get('renderedData');
      let index = nearestIndexTo(renderedData, x, function(d){
        return d ? d[0] : null;
      });
      return index !== -1 ? renderedData[index] : null;
    }
  },
});

function between(x, a, b) {
  return a <= x && x <= b;
}


================================================
FILE: addon/mixins/graph-graphic-with-tracking-dot.js
================================================
import { schedule, scheduleOnce } from '@ember/runloop';
import Mixin from '@ember/object/mixin';
import { on } from '@ember/object/evented';
import { computed, observer } from '@ember/object';
import { getMousePoint } from '../utils/nf/svg-dom';

export default Mixin.create({
  /**
    Gets or sets the tracking mode of the component.

    Possible values are:

    - 'none': no tracking behavior
    - 'hover': only track while mouse hover
    - 'snap-last': track while mouse hover, but snap to the last data element when not hovering
    - 'snap-first': track while mouse hover, but snap to the first data element when not hovering
    - 'selected-hover': The same as `'hover'` tracking mode, but only when the compononent is
    {{#crossLink "mixins.graph-selectable-graphic/selected:property"}}{{/crossLink}}
    - 'selected-snap-last': The same as `'snap-last'` tracking mode, but only when the compononent is
    {{#crossLink "mixins.graph-selectable-graphic/selected:property"}}{{/crossLink}}
    - 'selected-snap-first': The same as `'snap-first'` tracking mode, but only when the compononent is
    {{#crossLink "mixins.graph-selectable-graphic/selected:property"}}{{/crossLink}}

    @property trackingMode
    @type String
    @default 'none'
  */
  trackingMode: 'none',

  /**
    The radius of the tracking dot in pixels
    @property trackingDotRadius
    @type {number}
    @default 2.5
  */
  trackingDotRadius: 2.5,

  /**
    The action to send on `didTrack`.
    @property didTrack
    @type String
    @default null
  */
  didTrack: null,

  /**
    The value of the data that is being tracked by the component.
    @property trackedData
    @type {Object} an object with the following values:
      - point: an { x, y } pair for the exact px coordinates inside the graph-content
      - graphX: domain x value at mouse position
      - graphY: domain y value at mouse position
      - x: nearest x data value
      - y: nearest y data value
      - data: nearest raw data
      - renderX: domain x value to render a tracking dot at (stacked areas are offset)
      - renderY: domain x value to render a tracking dot at (stacked areas are offset)
      - mouseX: mouse x position in pixels
      - mouseY: mouse y position in pixels
    @default null
  */
  trackedData: null,

  /**
    The value of the data that is being tracked by the component, ONLY if the
    graph-content is currently being hovered.
    @property hoverData
    @type {Object} an object with the following values:
      - point: an { x, y } pair for the exact px coordinates inside the graph-content
      - graphX: domain x value at mouse position
      - graphY: domain y value at mouse position
      - x: nearest x data value
      - y: nearest y data value
      - data: nearest raw data
      - renderX: domain x value to render a tracking dot at (stacked areas are offset)
      - renderY: domain x value to render a tracking dot at (stacked areas are offset)
      - mouseX: mouse x position in pixels
      - mouseY: mouse y position in pixels
    @default null
  */
  hoverData: null,

  _showTrackingDot: true,

  /**
    Gets or sets whether the tracking dot should be shown at all.
    @property showTrackingDot
    @type {boolean}
    @default true
  */
  showTrackingDot: computed('trackedData', {
    get() {
      return Boolean(this._showTrackingDot && this.get('trackedData'));
    },

    set(value) {
      this._showTrackingDot = value;
    }
  }),

  /**
    Observes changes to tracked data and sends the
    didTrack action.
    @method _trackedDataChanged
    @private
  */
  _trackedDataChanged: observer('trackedData', function(){
    let trackedData = this.get('trackedData');
    this.set('hoverData', this._hovered ? trackedData : null);

    if(this.get('didTrack') && trackedData) {
      this.sendAction('didTrack', {
        x: trackedData.x,
        y: trackedData.y,
        data: trackedData.data,
        source: this,
        graph: this.get('graph'),
      });
    }
  }),

  _cleanup: function(){
    if(this._onHoverCleanup) {
      this._onHoverCleanup();
    }
    if(this._onEndCleanup) {
      this._onEndCleanup();
    }
  },

  _updateTrackingHandling() {
    let { trackingMode, selected } = this.getProperties('trackingMode', 'selected');

    this._cleanup();

    switch(trackingMode) {
      case 'hover':
        this._onHoverTrack();
        this._onEndUntrack();
        break;
      case 'snap-first':
        this._onHoverTrack();
        this._onEndSnapFirst();
        break;
      case 'snap-last':
        this._onHoverTrack();
        this._onEndSnapLast();
        break;
      case 'selected-hover':
        if(selected) {
          this._onHoverTrack();
          this._onEndUntrack();
        }
        break;
      case 'selected-snap-first':
        if(selected) {
          this._onHoverTrack();
          this._onEndSnapFirst();
        }
        break;
      case 'selected-snap-last':
        if(selected) {
          this._onHoverTrack();
          this._onEndSnapLast();
        }
        break;
    }
  },

  _onHoverTrack() {
    let content = this._content;

    let mousemoveHandler = e => {
      schedule('afterRender', () => {
        this._hovered = true;
        let evt = this._getEventObject(e);
        this.set('trackedData', evt);
      });
    };

    content.on('mousemove', mousemoveHandler);

    this._onHoverCleanup = () => {
      content.off('mousemove', mousemoveHandler);
    };
  },

  _hovered: false,

  _onEndUntrack() {
    let content = this._content;

    let mouseoutHandler = () => {
      this.set('trackedData', null);
    };

    content.on('mouseout', mouseoutHandler);

    this._onEndCleanup = () => {
      content.off('mouseout', mouseoutHandler);
    };

    if(!this._hovered) {
      this.set('trackedData', null);
    }
  },

  _onEndSnapLast() {
    let content = this._content;

    let mouseoutHandler = () => {
      schedule('afterRender', () => {
        this._hovered = false;
        this.set('trackedData', this.get('lastVisibleData'));
      });
    };

    let changeHandler = () => {
      if(!this._hovered) {
        schedule('afterRender', () => {
          this.set('trackedData', this.get('lastVisibleData'));
        });
      }
    };

    content.on('mouseout', mouseoutHandler);
    this.addObserver('lastVisibleData', this, changeHandler);

    this._onEndCleanup = () => {
      content.off('mouseout', mouseoutHandler);
      this.removeObserver('lastVisibleData', this, changeHandler);
    };

    changeHandler();
  },

  _onEndSnapFirst() {
    let content = this._content;

    let mouseoutHandler = () => {
      this._hovered = false;
      this.set('trackedData', this.get('firstVisibleData'));
    };

    let changeHandler = () => {
      if(!this._hovered) {
        this.set('trackedData', this.get('firstVisibleData'));
      }
    };

    content.on('mouseout', mouseoutHandler);
    this.addObserver('firstVisibleData', this, changeHandler);

    this._onEndCleanup = () => {
      content.off('mouseout', mouseoutHandler);
      this.removeObserver('firstVisibleData', this, changeHandler);
    };

    changeHandler();
  },

  _trackingModeChanged: on('init', observer('trackingMode', 'selected', function() {
    scheduleOnce('afterRender', this, this._updateTrackingHandling);
  })),

  _getEventObject(e) {
    let { xScale, yScale } = this.getProperties('xScale', 'yScale');
    let content = this._content;
    let point = getMousePoint(content[0], e);
    let graphX = xScale.invert(point.x);
    let graphY = yScale.invert(point.y);
    let near = this.getDataNearXRange(point.x);

    if(!near) {
      return {
        point,
        graphX,
        graphY,
        mouseX: point.x,
        mouseY: point.y,
      };
    }

    let { x, y, data, renderX, renderY } = near;
    return {
      point,
      graphX,
      graphY,
      x,
      y,
      data,
      renderX,
      renderY,
      mouseX: point.x,
      mouseY: point.y
    };
  },

  didInsertElement() {
    this._super.apply(arguments);
    this._content = this.$().parents('.nf-graph-content');
  },

  willDestroyElement() {
    this._super.apply(arguments);
    this._cleanup();
  }
});


================================================
FILE: addon/mixins/graph-line-utils.js
================================================
  import Mixin from '@ember/object/mixin';

  /**
    @namespace mixins
    @class graph-line-utils
    @extends Ember.Mixin
    */
  export default Mixin.create({

    /**
      Create a d3 line function from a given scales and interpolation

      @method createLineFn
      @param xScale {Function} d3 scale function
      @param yScale {Function} d3 scale function
      @param interpolate {String} d3 interpolator name
      @return {Function} a d3 function that will create SVG path data from a given data set.
      */
    createLineFn: function(xScale, yScale, interpolate){ 
      let interp = interpolate || 'linear';

      let xMod = xScale.rangeBand ? xScale.rangeBand() / 2 : 0;
      let yMod = yScale.rangeBand ? yScale.rangeBand() / 2 : 0;

      return function(data) {
        if(!data || data.length === 0) {
          return 'M0,0';
        }

        return d3.svg.line()
          .x(function (d) { return (xScale(d[0]) || 0) + xMod; })
          .y(function (d) { return (yScale(d[1]) || 0) + yMod; })
          .interpolate(interp)(data);
      };
    }
  });

================================================
FILE: addon/mixins/graph-registered-graphic.js
================================================
import { on } from '@ember/object/evented';
import Mixin from '@ember/object/mixin';

/**
  @namespace mixins
  @class graph-registered-graphic
  @extends Ember.Mixin
*/
export default Mixin.create({
  init() {
    this._super(...arguments);
    let graph = this.get('graph');

    if (graph) {
      graph.registerGraphic(this);
    }
  },

  /**
    calls {{#crossLink "components.nf-graph/unregisterGraphic"}}{{/crossLink}} on
    `didInsertElement`.
    @method _unregisterGraphic
    @private
  */
  _unregisterGraphic: on('willDestroyElement', function(){
    let graph = this.get('graph');

    if (graph) {
      graph.unregisterGraphic(this);
    }
  })
});


================================================
FILE: addon/mixins/graph-requires-scale-source.js
================================================
import Mixin from '@ember/object/mixin';
import { computed } from '@ember/object';

let scaleProperty = function(scaleKey, zoomKey, offsetKey){
  return computed(scaleKey, zoomKey, offsetKey, {
    get() {
      // console.log('HERE');
      let scale = this.get(scaleKey);
      let zoom = this.get(zoomKey);
      let offset = this.get(offsetKey);

      if(zoom === 1 && offset === 0) {
        return scale;
      }

      let copy = scale.copy();
      let domain = copy.domain();
      copy.domain([domain[0] / zoom, domain[1] / zoom]);

      let range = copy.range();
      copy.range([range[0] - offset, range[1] - offset]);

      return copy;
    }
  });
};

/**
  Adds functionality to identify a parent control that will provide an x and
  y scale, then adds scaling properties to the component it's mixed in to.
  @namespace mixins
  @class graph-requires-scale-source
*/
export default Mixin.create({

  /**
    The scale source
    @property scaleSource
    @type d3.nf-graph
    @default graph
  */
  scaleSource: computed(function() {
    return this.get('graph');
  }),

  /**
    The x scale used by this component
    @property xScale
    @type d3.scale
    @readonly
  */
  xScale: scaleProperty('scaleSource.xScale', 'scaleZoomX', 'scaleOffsetX'),

  /**
    The y scale used by this component
    @property yScale
    @type d3.scale
    @readonly
  */
  yScale: scaleProperty('scaleSource.yScale', 'scaleZoomY', 'scaleOffsetY'),

  _scaleOffsetX: 0,

  _scaleOffsetY: 0,

  _scaleZoomX: 1,

  _scaleZoomY: 1,

  /**
    The zoom multiplier for the x scale
    @property scaleZoomX
    @type Number
    @default 1
  */
  scaleZoomX: computed({
    get() {
      return this._scaleZoomX || 1;
    },
    set(key, value) {
      return this._scaleZoomX = +value || 1;
    }
  }),

  /**
    The zoom multiplier for the y scale
    @property scaleZoomY
    @type Number
    @default 1
  */
  scaleZoomY: computed({
    get() {
      return this._scaleZoomY || 1;
    },
    set(key, value) {
      return this._scaleZoomY = +value || 1;
    }
  }),

  /**
    The offset, in pixels, for the x scale
    @property scaleOffsetX
    @type Number
    @default 0
  */
  scaleOffsetX: computed({
    get() {
      return this._scaleOffsetX || 0;
    },
    set(key, value) {
      return this._scaleOffsetX = +value || 0;
    }
  }),

  /**
    The offset, in pixels, for the y scale
    @property scaleOffsetY
    @type Number
    @default 0
  */
  scaleOffsetY: computed({
    get() {
      return this._scaleOffsetY || 0;
    },
    set(key, value) {
      return this._scaleOffsetY = +value || 0;
    }
  })
});


================================================
FILE: addon/mixins/graph-selectable-graphic.js
================================================
import { once } from '@ember/runloop';
import { observer } from '@ember/object';
import { on } from '@ember/object/evented';
import { alias } from '@ember/object/computed';
import Mixin from '@ember/object/mixin';

/**
  Adds functionality to a component to make it a selectable graphic
  within it's parent nf-graph.
  @namespace mixins
  @class graph-selectable-graphic
  @extends Ember.Mixin
*/
export default Mixin.create({
  _selected: false,

  /**
    Gets or sets whether or not the graphic is "selectable". Meaning can be "selected" on the nf-graph 
    via some action (usually click). The component will then show up in the nf-graph parent's selected 
    property.
    @property selectable
    @type Boolean
    @default false
  */
  selectable: false,

  /**
    Gets or sets whether or not the graphic is selected.
    @property selected
    @type Boolean
    @default false
  */
  selected: false,

  /**
    Alias of selected
    @property isSelected
    @deprecated use `selected`
  */
  isSelected: alias('selected'),

  /**
    Makes calls to the parent nf-graph to update it's
    `selected` property. Observes changes to `selected` and also 
    fires on `didInsertElement`.
    @method _updateGraphSelected
    @private
  */
  _updateGraphSelected: on('didInsertElement', observer('selected', function() {
    once(this, function(){
      let selected = this.get('selected');
      let graph = this.get('graph');
      if(selected) {
        graph.selectGraphic(this);
      } else {
        graph.deselectGraphic(this);
      }
    });
  })),
});

================================================
FILE: addon/styles/addon.css
================================================
.nf-graph-background {
  fill: #f5f6f7;
}

.nf-graph text {
  font-size: 10px;
}

.nf-graph * {
  -moz-user-select: none;
  -webkit-user-select: none;
  user-select: none;
  -moz-user-drag: none;
  -webkit-user-drag: none;
  user-drag: none;
}

.nf-graph-content-background {
  fill: #ffffff;
}

.nf-grid-lane:nth-child(even) {
  fill: #f5f6f7;
}

.nf-grid-lane:nth-child(odd) {
  fill: #ffffff;
}

.nf-grid-fret {
  stroke: #d3dbdd;
  stroke-width: .5;
}

.nf-x-axis-line {
  stroke: #d3dbdd;
  stroke-width: 1;
}

.nf-x-axis-tick text {
  dominant-baseline: text-before-edge;
  text-anchor: middle;
}

.nf-x-axis-tick-line {
  stroke: #d3dbdd;
  stroke-width: 1;
}

.nf-x-axis.orient-top .nf-x-axis-tick text {
  dominant-baseline: auto;
}

.nf-y-axis-line {
  stroke: #d3dbdd;
  stroke-width: 1;
}

.nf-y-axis-tick text {
  dominant-baseline: central;
  text-anchor: end;
}

.nf-y-axis-tick-line {
  stroke: #d3dbdd;
  stroke-width: 1;
}

.nf-y-axis.orient-right .nf-y-axis-tick text {
  text-anchor: start;
}

.nf-line-line {
  stroke-width: 1;
  fill: none;
  cursor: default;
  stroke: #ca0000;
}

.nf-line-interaction-mask {
  fill: none;
  stroke: transparent;
  stroke-width: 15;
  cursor: pointer;
}

.nf-line-tracking-dot {
  visibility: visible;
  cursor: default;
}

.nf-line.selectable .nf-line-tracking-dot {
  cursor: pointer;
}

.nf-line.selected .nf-line-line {
  stroke-width: 2;
}

.nf-area-area {
  fill: rgba(255, 0, 0, 0.5);
  stroke: none;
  stroke-width: 0;
}

.nf-area-line {
  stroke-width: 1px;
  stroke: red;
  fill: none;
}

.nf-area.selected .nf-area-line {
  stroke-width: 2px;
  stroke: blue;
}

.nf-area.selected .nf-area-area {
  fill: rgba(0, 0, 255, 0.5);
}

.nf-area.selectable {
  cursor: pointer;
}

.nf-area.selectable .nf-area-tracking-dot {
  cursor: pointer;
}

.nf-area-tracking-dot {
  visibility: visible;
  cursor: default;
}

.nf-y-diff.positive .nf-y-diff-rect {
  fill: lime;
}

.nf-y-diff.positive text {
  fill: black;
}

.nf-y-diff.negative .nf-y-diff-rect {
  fill: #ca0000;
}

.nf-y-diff.negative text {
  fill: white;
}

.nf-y-diff-content text {
  dominant-baseline: center;
  text-anchor: start;
}

.nf-y-diff.orient-right .nf-y-diff-content text {
  text-anchor: end;
}

.nf-right-tick-path {
  fill: white;
  stroke: #999999;
  stroke-width: 1;
}

.nf-right-tick-line {
  stroke: #999999;
  stroke-width: 1;
}

.nf-crosshair-vertical {
  stroke: #d3dbdd;
  stroke-width: 1px;
  visibility: visible;
}

.nf-crosshair-horizontal {
  stroke: #d3dbdd;
  stroke-width: 1px;
  visibility: visible;
}

.nf-selection-box-rect {
  fill: none;
  stroke: #999999;
  stroke-width: 1;
}

.nf-range-marker-marker {
  fill: #ca0000;
}

.nf-range-marker-label text {
  text-anchor: start;
  dominant-baseline: hanging; }

.nf-vertical-line {
  stroke: #d3dbdd;
  stroke-width: 1px; }

.nf-horizontal-line {
  stroke: #d3dbdd;
  stroke-width: 1px; }

.nf-brush-selection-overlay {
  fill: rgba(153, 153, 153, 0.3); }

.nf-brush-selection-line {
  stroke-width: 1px;
  stroke: #999999; }

.nf-brush-selection-left-text-bg, .nf-brush-selection-right-text-bg {
  fill: white; }

.nf-brush-selection-left-text {
  dominant-baseline: text-before-edge; }

.nf-brush-selection-right-text {
  dominant-baseline: text-before-edge; }


================================================
FILE: addon/templates/components/nf-area-stack.hbs
================================================
{{yield (hash
		area=(component 'nf-area' stack=this graph=graph scaleSource=scaleSource )
	)
}}


================================================
FILE: addon/templates/components/nf-area.hbs
================================================
<path class="nf-area-area" d="{{d}}"></path>
<path class="nf-area-line" d="{{dLine}}"></path>
{{#if showTrackingDot}}
	{{nf-dot class="nf-area-tracking-dot" graph=graph scaleSource=scaleSource x=trackedData.renderX y=trackedData.renderY r=trackingDotRadius}}
{{/if}}


================================================
FILE: addon/templates/components/nf-bars-group.hbs
================================================
{{yield (hash
		area=(component 'nf-bars' graph=graph scaleSource=scaleSource group=this)
	)
}}


================================================
FILE: addon/templates/components/nf-bars.hbs
================================================
{{#each bars as |bar|}}
	<path d="{{bar.path}}" class="{{bar.className}}" {{action 'nfBarClickBar' bar.data bar.index}}></path>
{{/each}}

{{#if showTrackingDot}}
  {{nf-dot graph=graph scaleSource=scaleSource x=trackedData.renderX y=trackedData.renderY r=trackingDotRadius}}
{{/if}}


================================================
FILE: addon/templates/components/nf-brush-selection.hbs
================================================
<rect class="nf-brush-selection-overlay" x="0" y="0" width="{{leftX}}" height="{{graphHeight}}"></rect>
<rect class="nf-brush-selection-overlay" y="0" x="{{rightX}}" width="{{rightWidth}}" height="{{graphHeight}}"></rect>

<line class="nf-brush-selection-line" y1="0" x1="{{leftX}}" x2="{{leftX}}" y2="{{graphHeight}}"></line>
<line class="nf-brush-selection-line" y1="0" x1="{{rightX}}" x2="{{rightX}}" y2="{{graphHeight}}"></line>

<g class="nf-brush-selection-left-display">
	<rect class="nf-brush-selection-left-text-bg"></rect>
	<text class="nf-brush-selection-left-text"></text>
</g>

<g class="nf-brush-selection-right-display">
	<rect class="nf-brush-selection-right-text-bg"></rect>
	<text class="nf-brush-selection-right-text"></text>
</g>


================================================
FILE: addon/templates/components/nf-component.hbs
================================================
{{yield (component componentName graph=graph scaleSource=scaleSource)}}


================================================
FILE: addon/templates/components/nf-crosshairs.hbs
================================================
{{#if vertical}}
  <line class="nf-crosshair-vertical" x1="{{x}}" x2="{{x}}" y1="0" y2="{{height}}" />
{{/if}}

{{#if horizontal}}
  <line class="nf-crosshair-horizontal" x1="0" x2="{{width}}" y1="{{y}}" y2="{{y}}" />
{{/if}}


================================================
FILE: addon/templates/components/nf-dot.hbs
================================================
{{yield}}


================================================
FILE: addon/templates/components/nf-graph-content.hbs
================================================
<rect x=0 y=0 class="nf-graph-content-background" width="{{width}}" height="{{height}}"></rect>
  {{#if graph.hasData}}
    {{#if graph.showLanes}}
      <g class="nf-grid-lanes">
        {{#each gridLanes as |lane|}}
          <rect class="nf-grid-lane" x="{{lane.x}}" y="{{lane.y}}" width="{{width}}" height="{{lane.height}}"></rect>
        {{/each}}
      </g>
    {{/if}}

    {{#if graph.showFrets}}
      <g class="nf-grid-frets">
        {{#each frets as |fret|}}
          <line class="nf-grid-fret" x1="{{fret.x}}" y1="0" x2="{{fret.x}}" y2="{{height}}"></line>
        {{/each}}
      </g>
    {{/if}}
  {{/if}}

  {{#unless graph.hasData}}
    <text x=0 y=0>No data</text>
  {{/unless}}


{{#nf-graph-yieldables graph=graph scaleSource=graph as |yieldables|}}
  {{yield yieldables}}
{{/nf-graph-yieldables}}


================================================
FILE: addon/templates/components/nf-graph-yieldables.hbs
================================================
{{yield (hash
    group=(component 'nf-group' graph=graph)
    component=(component 'nf-component' graph=graph scaleSource=scaleSource)
    crosshairs=(component 'nf-crosshairs' graph=graph)
    selection-box=(component 'nf-selection-box' graph=graph scaleSource=scaleSource)
    svg-image=(component 'nf-svg-image' graph=graph scaleSource=scaleSource)
    svg-line=(component 'nf-svg-line' graph=graph scaleSource=scaleSource)
    svg-path=(component 'nf-svg-path' graph=graph scaleSource=scaleSource)
    svg-rect=(component 'nf-svg-rect' graph=graph scaleSource=scaleSource)
    tracker=(component 'nf-tracker' graph=graph scaleSource=scaleSource)
    range-markers=(component 'nf-range-markers' graph=graph scaleSource=scaleSource)
    dot=(component 'nf-dot' graph=graph scaleSource=scaleSource)
    line=(component 'nf-line' graph=graph scaleSource=scaleSource)
    vertical-line=(component 'nf-vertical-line' graph=graph scaleSource=scaleSource)
    horizontal-line=(component 'nf-horizontal-line' graph=graph scaleSource=scaleSource)
    area=(component 'nf-area' graph=graph scaleSource=scaleSource)
    area-stack=(component 'nf-area-stack' graph=graph scaleSource=scaleSource)
    bars=(component 'nf-bars' graph=graph scaleSource=scaleSource)
    bars-group=(component 'nf-bars-group' graph=graph scaleSource=scaleSource)
    brush-selection=(component 'nf-brush-selection' graph=graph scaleSource=scaleSource)
  )
}}


================================================
FILE: addon/templates/components/nf-graph.hbs
================================================
<svg class="nf-graph" width="{{width}}" height="{{height}}">
	<defs>
		<clipPath id="{{contentClipPathId}}">
			<rect x=0 y=0 width="{{graphWidth}}" height="{{graphHeight}}"></rect>
		</clipPath>
	</defs>

	<rect class="nf-graph-background" x=0 y=0 width="{{width}}" height="{{height}}"></rect>

  {{yield (hash
			x-axis=(component 'nf-x-axis' graph=this scaleSource=this)
			y-axis=(component 'nf-y-axis' graph=this scaleSource=this)
			graph=(component 'nf-graph-content' graph=this)
			right-tick=(component 'nf-right-tick' graph=this scaleSource=this)
			y-diff=(component 'nf-right-tick' graph=this scaleSource=this)
			component=(component 'nf-component' graph=this scaleSource=this)
		)
	}}


</svg>

{{#if debug}}
<div style="position:relative">
	<pre style="position:absolute; z-index:1000">{{debugInfo}}</pre>
</div>
{{/if}}


================================================
FILE: addon/templates/components/nf-group.hbs
================================================
{{#nf-graph-yieldables graph=graph scaleSource=this as |yieldables|}}
  {{yield yieldables}}
{{/nf-graph-yieldables}}


================================================
FILE: addon/templates/components/nf-horizontal-line.hbs
================================================
{{yield}}


================================================
FILE: addon/templates/components/nf-line.hbs
================================================
<path class="nf-line-line" d="{{d}}"></path>
{{#if selectable}}
	<path class="nf-line-interaction-mask" d="{{d}}"></path>
{{/if}}

{{#if showTrackingDot}}
	{{nf-dot class="nf-line-tracking-dot" graph=graph scaleSource=scaleSource x=trackedData.renderX y=trackedData.renderY r=trackingDotRadius}}
{{/if}}


================================================
FILE: addon/templates/components/nf-plot.hbs
================================================
{{yield}}


================================================
FILE: addon/templates/components/nf-plots.hbs
================================================
{{#each plotData as |item|}}
	{{#nf-plot graph=graph scaleSource=scaleSource x=item.x y=item.y action='itemClicked' data=item.data multiplierY=multiplierY multiplierX=multiplierX}}
		{{yield}}
	{{/nf-plot}}
{{/each}}


================================================
FILE: addon/templates/components/nf-range-marker.hbs
================================================
<g class="nf-range-marker-label" transform="{{labelTransform}}">{{yield}}</g>
<rect class="nf-range-marker-marker" y="{{marginTop}}" x="{{x}}" width="{{width}}" height="{{height}}"></rect>

================================================
FILE: addon/templates/components/nf-range-markers.hbs
================================================
{{yield (hash
		range-marker=(component 'nf-range-marker' container=this graph=graph scaleSource=scaleSource)
	)
}}


================================================
FILE: addon/templates/components/nf-right-tick.hbs
================================================
<line class="nf-right-tick-line" x1="{{graph.width}}" x2="{{graph.width}}" y1="0" y2="{{graph.height}}"></line>
<path class="nf-right-tick-path" d="M6,0 0,6 6,12"></path>

================================================
FILE: addon/templates/components/nf-selection-box.hbs
================================================
{{yield}}


================================================
FILE: addon/templates/components/nf-svg-image.hbs
================================================
{{yield}}


================================================
FILE: addon/templates/components/nf-svg-line.hbs
================================================
{{yield}}


================================================
FILE: addon/templates/components/nf-svg-path.hbs
================================================
{{yield}}


================================================
FILE: addon/templates/components/nf-svg-rect.hbs
================================================
{{yield}}


================================================
FILE: addon/templates/components/nf-tick-label.hbs
================================================
{{yield}}


================================================
FILE: addon/templates/components/nf-tracker.hbs
================================================
{{yield trackedData}}

================================================
FILE: addon/templates/components/nf-vertical-line.hbs
================================================
{{yield}}


================================================
FILE: addon/templates/components/nf-x-axis.hbs
================================================
<line class="nf-x-axis-line" x1="0" y1="{{axisLineY}}" x2="{{width}}" y2="{{axisLineY}}"></line>

{{#each ticks as |tick|}}
  <g class="nf-x-axis-tick">
    {{#if hasBlock}}
      {{#nf-tick-label x=tick.x y=tick.labely}}
        {{yield tick}}
      {{/nf-tick-label}}
    {{else}}
      {{#nf-tick-label x=tick.x y=tick.labely}}
        <text>{{tick.value}}</text>
      {{/nf-tick-label}}
    {{/if}}

    <line class="nf-x-axis-tick-line" x1="{{tick.x}}" y1="{{tick.y1}}" x2="{{tick.x}}" y2="{{tick.y2}}"></line>
  </g>
{{/each}}


================================================
FILE: addon/templates/components/nf-y-axis.hbs
================================================
<line class="nf-y-axis-line" x1="{{axisLineX}}" y1="0" x2="{{axisLineX}}" y2="{{height}}"></line>

{{#each ticks as |tick|}}
  <g class="nf-y-axis-tick">
    {{#if hasBlock}}
      {{#nf-tick-label x=tick.labelx y=tick.y}}
        {{yield tick}}
      {{/nf-tick-label}}
    {{else}}
      {{#nf-tick-label x=tick.labelx y=tick.y}}
        <text>{{tick.value}}</text>
      {{/nf-tick-label}}
    {{/if}}

    <line class="nf-y-axis-tick-line" x1="{{tick.x1}}" y1="{{tick.y}}" x2="{{tick.x2}}" y2="{{tick.y}}"></line>
  </g>
{{/each}}


================================================
FILE: addon/templates/components/nf-y-diff.hbs
================================================
<g class="nf-y-diff-content">
	{{yield}}
</g>

================================================
FILE: addon/utils/nf/array-helpers.js
================================================
/**
  @module utils/nf/array-helpers
*/

/**
  returns whatever you pass into it.
  @method identity
  @param x {Any}
  @private
  @return {Any} x
*/
function identity(x) {
  return x;
}

/**
  Performs a binary search on the array and finds the nearest index to the value passed.
  @method nearestIndexTo
  @param arr {Array} the *sorted* array to search.
  @param val {Number} the value to find the nearest index to.
  @param mappingFn {Function} an optional function for pulling values out of the
  array items.
*/
export function nearestIndexTo(arr, val, mappingFn) {
  mappingFn = mappingFn || identity;
  let startIndex  = 0;
  let stopIndex = arr.length - 1;
  let middle = (stopIndex + startIndex) / 2;
  let a = Math.floor(middle);
  let b = Math.floor(middle + 1);

  let getItem = function(i){
    return mappingFn(arr[i]);
  };

  let av = getItem(a);
  let bv = getItem(b);

  while(!(av <= val && val <= bv) && startIndex < stopIndex){

    if (val < av){
        stopIndex = middle - 1;
    } else if (val > av){
        startIndex = middle + 1;
    }

    middle = (stopIndex + startIndex) / 2;
    a = Math.floor(middle);
    b = Math.floor(middle + 1);
    av = getItem(a);
    bv = getItem(b);
  }

  return (Math.abs(val - av) < Math.abs(val - bv)) ? a : b;
}

let NATURAL_SORT_REGEXP = /[+-]?\d+\.?\d*|\S+/g;

/**
  breaks a string into an array of tokens in preparation for natural
  comparison and sorting.
  @method naturalTokenize
  @param item {String} the value to tokenize
  @return {Array} an array of tokens found in the item
  @private
*/
function naturalTokenize(item) {
  NATURAL_SORT_REGEXP.lastIndex = 0;
  let matches;
  let tokens = [];
  while(matches === NATURAL_SORT_REGEXP.exec(item)) {
    tokens.push(matches[0]);
  }
  return tokens;
}

/**
  A JavaScript sorting predicate for natural sorting.
  @method naturalCompare
  @param a {Any} the value to compare to b
  @param b {Any} the value to compare to a
  @return {Number} `-1`, `0` or `1` if a is less than, equal to, or
    greater than b, respectively.
*/
export function naturalCompare(a, b) {
  let aTokens = naturalTokenize(a);
  let bTokens = naturalTokenize(b);
  let i = 0, bx, ax, na, nb;

  while((ax = aTokens[i]) && (bx = bTokens[i++])) {
    na = +ax;
    nb = +bx;

    if(nb === nb && na === na) {
      if(na !== nb) {
         return na > nb ? 1 : -1;
      } else {
        if(ax.length !== bx.length) {
          return ax.length > bx.length ? 1 : -1;
        }
      }
    }

    if(ax !== bx) {
      return ax > bx ? 1 : -1;
    }
  }

  return 0;
}

/**
  Sorts the array "naturally". Meaning taking into account both
  alphabetical and numeric sorting within strings.

  @method natualSort
  @param arr {Array} the array to sort
*/
export function naturalSort(arr) {
  arr.sort(naturalCompare);
}

export function sliceArrayLike(arrayLike, from, to) {
  return Array.prototype.slice.call(arrayLike, from, to);
}

export function toArray() {
  return sliceArrayLike(arguments);
}


================================================
FILE: addon/utils/nf/graph-event.js
================================================
import GraphPosition from './graph-position';

/**
  Event object for graph events
  @namespace utils.nf
  @class graph-event
  @extends graph-position
*/
export default GraphPosition.extend({
  /**
    The original event that triggered this event or action
    @property originalEvent
    @type Event
    @default null
  */
  originalEvent: null,

  /**
    A data value passed with the event
    @property data
    @type any
    @default null
  */
  data: null,
});

================================================
FILE: addon/utils/nf/graph-mouse-event.js
================================================
import { reads } from '@ember/object/computed';
import { computed } from '@ember/object';
import GraphPosition from './graph-position';
import { getMousePoint } from './svg-dom';

/**
  An event context object generally returned by tracking events. Also used as
  `trackedData` in components such as `nf-line`, `nf-area` and `nf-bars`.

  @namespace utils.nf
  @class graph-mouse-event
  @extends graph-position
*/
export default GraphPosition.extend({
  /**
    The original event that triggered the action or ember event
    @property originalEvent
    @type MouseEvent
    @default null
  */
  originalEvent: null,

  /**
    Method used to get the mouse position relative to a container
    @method _getMousePoint
    @private
  */
  _getMousePoint: getMousePoint,

  /**
    The coordinates of the mouse relative to eh nf-graph-content
    @property _mousePoint
    @type Object
    @readonly
    @private
  */
  _mousePoint: computed('originalEvent', 'graphContentElement', {
    get() {
      return this._getMousePoint(this.get('graphContentElement'), this.get('originalEvent'));
    }
  }),

  /**
    The nf-graph-content element of the nf-graph
    @property graphContentElement
    @type SVGGElement
    @readonly
  */
  graphContentElement: null,

  /**
    The mouse x position relative to the graph content
    @property mouseX
    @type Number
    @readonly
  */
  mouseX: reads('_mousePoint.x'),

  /**
    The mouse y position relative to the graph content
    @property mouseY
    @type Number
    @readonly
  */
  mouseY: reads(
Download .txt
gitextract_jjd60v_k/

├── .editorconfig
├── .ember-cli
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── .watchmanconfig
├── CHANGELOG.md
├── LICENSE.md
├── OSSMETADATA
├── README.md
├── addon/
│   ├── .gitkeep
│   ├── components/
│   │   ├── .gitignore
│   │   ├── nf-area-stack.js
│   │   ├── nf-area.js
│   │   ├── nf-bars-group.js
│   │   ├── nf-bars.js
│   │   ├── nf-brush-selection.js
│   │   ├── nf-component.js
│   │   ├── nf-crosshairs.js
│   │   ├── nf-dot.js
│   │   ├── nf-graph-content.js
│   │   ├── nf-graph-yieldables.js
│   │   ├── nf-graph.js
│   │   ├── nf-group.js
│   │   ├── nf-horizontal-line.js
│   │   ├── nf-line.js
│   │   ├── nf-plot.js
│   │   ├── nf-plots.js
│   │   ├── nf-range-marker.js
│   │   ├── nf-range-markers.js
│   │   ├── nf-right-tick.js
│   │   ├── nf-selection-box.js
│   │   ├── nf-svg-image.js
│   │   ├── nf-svg-line.js
│   │   ├── nf-svg-path.js
│   │   ├── nf-svg-rect.js
│   │   ├── nf-tick-label.js
│   │   ├── nf-tracker.js
│   │   ├── nf-vertical-line.js
│   │   ├── nf-x-axis.js
│   │   ├── nf-y-axis.js
│   │   └── nf-y-diff.js
│   ├── mixins/
│   │   ├── .gitignore
│   │   ├── graph-area-utils.js
│   │   ├── graph-data-graphic.js
│   │   ├── graph-graphic-with-tracking-dot.js
│   │   ├── graph-line-utils.js
│   │   ├── graph-registered-graphic.js
│   │   ├── graph-requires-scale-source.js
│   │   └── graph-selectable-graphic.js
│   ├── styles/
│   │   └── addon.css
│   ├── templates/
│   │   └── components/
│   │       ├── nf-area-stack.hbs
│   │       ├── nf-area.hbs
│   │       ├── nf-bars-group.hbs
│   │       ├── nf-bars.hbs
│   │       ├── nf-brush-selection.hbs
│   │       ├── nf-component.hbs
│   │       ├── nf-crosshairs.hbs
│   │       ├── nf-dot.hbs
│   │       ├── nf-graph-content.hbs
│   │       ├── nf-graph-yieldables.hbs
│   │       ├── nf-graph.hbs
│   │       ├── nf-group.hbs
│   │       ├── nf-horizontal-line.hbs
│   │       ├── nf-line.hbs
│   │       ├── nf-plot.hbs
│   │       ├── nf-plots.hbs
│   │       ├── nf-range-marker.hbs
│   │       ├── nf-range-markers.hbs
│   │       ├── nf-right-tick.hbs
│   │       ├── nf-selection-box.hbs
│   │       ├── nf-svg-image.hbs
│   │       ├── nf-svg-line.hbs
│   │       ├── nf-svg-path.hbs
│   │       ├── nf-svg-rect.hbs
│   │       ├── nf-tick-label.hbs
│   │       ├── nf-tracker.hbs
│   │       ├── nf-vertical-line.hbs
│   │       ├── nf-x-axis.hbs
│   │       ├── nf-y-axis.hbs
│   │       └── nf-y-diff.hbs
│   └── utils/
│       ├── nf/
│       │   ├── array-helpers.js
│       │   ├── graph-event.js
│       │   ├── graph-mouse-event.js
│       │   ├── graph-position.js
│       │   ├── scale-utils.js
│       │   ├── scroll-area-action-context.js
│       │   ├── svg-dom.js
│       │   └── tracked-array-property.js
│       └── parse-property-expression.js
├── app/
│   ├── .gitkeep
│   └── components/
│       ├── .gitignore
│       ├── nf-area-stack.js
│       ├── nf-area.js
│       ├── nf-bars-group.js
│       ├── nf-bars.js
│       ├── nf-brush-selection.js
│       ├── nf-component.js
│       ├── nf-crosshairs.js
│       ├── nf-dot.js
│       ├── nf-graph-content.js
│       ├── nf-graph-yieldables.js
│       ├── nf-graph.js
│       ├── nf-group.js
│       ├── nf-horizontal-line.js
│       ├── nf-line.js
│       ├── nf-plot.js
│       ├── nf-plots.js
│       ├── nf-range-marker.js
│       ├── nf-range-markers.js
│       ├── nf-right-tick.js
│       ├── nf-selection-box.js
│       ├── nf-svg-image.js
│       ├── nf-svg-line.js
│       ├── nf-svg-path.js
│       ├── nf-svg-rect.js
│       ├── nf-tick-label.js
│       ├── nf-tracker.js
│       ├── nf-vertical-line.js
│       ├── nf-x-axis.js
│       ├── nf-y-axis.js
│       └── nf-y-diff.js
├── config/
│   ├── ember-try.js
│   └── environment.js
├── docs/
│   ├── api.js
│   ├── assets/
│   │   ├── css/
│   │   │   └── main.css
│   │   ├── index.html
│   │   ├── js/
│   │   │   ├── api-filter.js
│   │   │   ├── api-list.js
│   │   │   ├── api-search.js
│   │   │   ├── apidocs.js
│   │   │   └── yui-prettify.js
│   │   └── vendor/
│   │       └── prettify/
│   │           ├── CHANGES.html
│   │           ├── COPYING
│   │           ├── README.html
│   │           ├── prettify-min.css
│   │           └── prettify-min.js
│   ├── classes/
│   │   ├── components.nf-area-stack.html
│   │   ├── components.nf-area.html
│   │   ├── components.nf-bars.html
│   │   ├── components.nf-crosshair.html
│   │   ├── components.nf-dot.html
│   │   ├── components.nf-gg.html
│   │   ├── components.nf-graph-content.html
│   │   ├── components.nf-graph.html
│   │   ├── components.nf-horizontal-line.html
│   │   ├── components.nf-line.html
│   │   ├── components.nf-plot.html
│   │   ├── components.nf-range-marker.html
│   │   ├── components.nf-range-markers.html
│   │   ├── components.nf-right-tick.html
│   │   ├── components.nf-selection-box.html
│   │   ├── components.nf-svg-image.html
│   │   ├── components.nf-svg-line.html
│   │   ├── components.nf-svg-path.html
│   │   ├── components.nf-svg-rect.html
│   │   ├── components.nf-tracker.html
│   │   ├── components.nf-vertical-line.html
│   │   ├── components.nf-x-axis.html
│   │   ├── components.nf-y-axis.html
│   │   ├── components.nf-y-diff.html
│   │   ├── index.html
│   │   ├── mixins.graph-area-utils.html
│   │   ├── mixins.graph-data-graphic.html
│   │   ├── mixins.graph-has-graph-parent.html
│   │   ├── mixins.graph-line-utils.html
│   │   ├── mixins.graph-registered-graphic.html
│   │   ├── mixins.graph-requires-scale-source.html
│   │   ├── mixins.graph-selectable-graphic.html
│   │   ├── utils.nf.graph-event.html
│   │   ├── utils.nf.graph-mouse-event.html
│   │   ├── utils.nf.graph-position.html
│   │   ├── utils.nf.scroll-area-action-context.html
│   │   └── utils.parse-property-expression.html
│   ├── data.json
│   ├── files/
│   │   ├── addon_mixins_graph-area-utils.js.html
│   │   ├── addon_mixins_graph-data-graphic.js.html
│   │   ├── addon_mixins_graph-graphic-with-tracking-dot.js.html
│   │   ├── addon_mixins_graph-has-graph-parent.js.html
│   │   ├── addon_mixins_graph-line-utils.js.html
│   │   ├── addon_mixins_graph-registered-graphic.js.html
│   │   ├── addon_mixins_graph-requires-scale-source.js.html
│   │   ├── addon_mixins_graph-selectable-graphic.js.html
│   │   ├── addon_utils_nf_array-helpers.js.html
│   │   ├── addon_utils_nf_graph-event.js.html
│   │   ├── addon_utils_nf_graph-mouse-event.js.html
│   │   ├── addon_utils_nf_graph-position.js.html
│   │   ├── addon_utils_nf_scale-utils.js.html
│   │   ├── addon_utils_nf_scroll-area-action-context.js.html
│   │   ├── addon_utils_nf_svg-dom.js.html
│   │   ├── addon_utils_parse-property-expression.js.html
│   │   ├── app_components_nf-area-stack.js.html
│   │   ├── app_components_nf-area.js.html
│   │   ├── app_components_nf-bars.js.html
│   │   ├── app_components_nf-crosshair.js.html
│   │   ├── app_components_nf-dot.js.html
│   │   ├── app_components_nf-gg.js.html
│   │   ├── app_components_nf-graph-content.js.html
│   │   ├── app_components_nf-graph.js.html
│   │   ├── app_components_nf-horizontal-line.js.html
│   │   ├── app_components_nf-line.js.html
│   │   ├── app_components_nf-plot.js.html
│   │   ├── app_components_nf-plots.js.html
│   │   ├── app_components_nf-range-marker.js.html
│   │   ├── app_components_nf-range-markers.js.html
│   │   ├── app_components_nf-right-tick.js.html
│   │   ├── app_components_nf-selection-box.js.html
│   │   ├── app_components_nf-svg-image.js.html
│   │   ├── app_components_nf-svg-line.js.html
│   │   ├── app_components_nf-svg-path.js.html
│   │   ├── app_components_nf-svg-rect.js.html
│   │   ├── app_components_nf-tracker.js.html
│   │   ├── app_components_nf-vertical-line.js.html
│   │   ├── app_components_nf-x-axis.js.html
│   │   ├── app_components_nf-y-axis.js.html
│   │   ├── app_components_nf-y-diff.js.html
│   │   └── index.html
│   ├── index.html
│   └── modules/
│       ├── index.html
│       ├── scale-utils.html
│       ├── utils_nf_array-helpers.html
│       └── utils_nf_svg-dom.html
├── ember-cli-build.js
├── index.js
├── package.json
├── protractor.conf.js
├── testem.js
├── tests/
│   ├── dummy/
│   │   ├── app/
│   │   │   ├── app.js
│   │   │   ├── components/
│   │   │   │   ├── .gitkeep
│   │   │   │   ├── bars-with-line.js
│   │   │   │   ├── basic-area-graph.js
│   │   │   │   ├── basic-bar-graph.js
│   │   │   │   ├── basic-line-graph.js
│   │   │   │   ├── brush-select-zoom.js
│   │   │   │   ├── detailed-line-graph.js
│   │   │   │   ├── graph-primitives.js
│   │   │   │   ├── mouse-tracking-data.js
│   │   │   │   ├── mouse-tracking.js
│   │   │   │   └── stacked-area-graph.js
│   │   │   ├── controllers/
│   │   │   │   ├── .gitkeep
│   │   │   │   └── nf-graph/
│   │   │   │       ├── index.js
│   │   │   │       └── nf-bars.js
│   │   │   ├── helpers/
│   │   │   │   ├── .gitkeep
│   │   │   │   └── format-hour-minute.js
│   │   │   ├── index.html
│   │   │   ├── models/
│   │   │   │   └── .gitkeep
│   │   │   ├── resolver.js
│   │   │   ├── router.js
│   │   │   ├── routes/
│   │   │   │   ├── .gitkeep
│   │   │   │   ├── index.js
│   │   │   │   └── nf-graph/
│   │   │   │       ├── index.js
│   │   │   │       └── nf-bars.js
│   │   │   ├── services/
│   │   │   │   ├── data-generator.js
│   │   │   │   └── utility.js
│   │   │   ├── styles/
│   │   │   │   └── app.css
│   │   │   └── templates/
│   │   │       ├── application.hbs
│   │   │       ├── components/
│   │   │       │   ├── bars-with-line.hbs
│   │   │       │   ├── basic-area-graph.hbs
│   │   │       │   ├── basic-bar-graph.hbs
│   │   │       │   ├── basic-line-graph.hbs
│   │   │       │   ├── brush-select-zoom.hbs
│   │   │       │   ├── detailed-line-graph.hbs
│   │   │       │   ├── graph-primitives.hbs
│   │   │       │   ├── mouse-tracking-data.hbs
│   │   │       │   ├── mouse-tracking.hbs
│   │   │       │   └── stacked-area-graph.hbs
│   │   │       ├── examples.hbs
│   │   │       └── nf-graph/
│   │   │           ├── index.hbs
│   │   │           └── nf-bars.hbs
│   │   ├── config/
│   │   │   ├── environment.js
│   │   │   └── targets.js
│   │   └── public/
│   │       ├── .gitkeep
│   │       └── robots.txt
│   ├── helpers/
│   │   ├── destroy-app.js
│   │   ├── module-for-acceptance.js
│   │   └── start-app.js
│   ├── index.html
│   ├── perf/
│   │   └── first.spec.js
│   ├── test-helper.js
│   └── unit/
│       ├── .gitkeep
│       ├── addon/
│       │   └── mixins/
│       │       └── graph-data-graphic-test.js
│       └── app/
│           └── components/
│               ├── nf-bars-test.js
│               ├── nf-graph-test.js
│               ├── nf-horizontal-line-test.js
│               ├── nf-vertical-line-test.js
│               ├── nf-x-axis-test.js
│               └── nf-y-axis-test.js
├── vendor/
│   └── .gitkeep
└── yuidoc.json
Download .txt
SYMBOL INDEX (147 symbols across 38 files)

FILE: addon/components/nf-area-stack.js
  method get (line 49) | get() {
  method set (line 53) | set(key, value) {

FILE: addon/components/nf-area.js
  method init (line 68) | init() {
  method getActualTrackData (line 81) | getActualTrackData(renderX, renderY, data) {

FILE: addon/components/nf-bars.js
  method init (line 164) | init() {

FILE: addon/components/nf-component.js
  method init (line 49) | init(){

FILE: addon/components/nf-graph-content.js
  method init (line 173) | init(){

FILE: addon/components/nf-graph.js
  method get (line 36) | get() {
  method set (line 69) | set(key, value) {
  method get (line 96) | get() {
  method set (line 129) | set(key, value) {
  method _sendAutoUpdateXAction (line 542) | _sendAutoUpdateXAction() {
  method _sendAutoUpdateYAction (line 546) | _sendAutoUpdateYAction() {
  method didAutoUpdateMaxX (line 554) | didAutoUpdateMaxX() {
  method didAutoUpdateMinX (line 562) | didAutoUpdateMinX() {
  method didAutoUpdateMaxY (line 570) | didAutoUpdateMaxY() {
  method didAutoUpdateMinY (line 578) | didAutoUpdateMinY() {
  method _scaleFactoryFor (line 731) | _scaleFactoryFor(axis) {
  method _domainFor (line 820) | _domainFor(axis) {
  method _scaleFor (line 904) | _scaleFor(axis) {
  method updateExtents (line 949) | updateExtents() {

FILE: addon/components/nf-range-marker.js
  method init (line 185) | init() {

FILE: addon/components/nf-svg-image.js
  method get (line 63) | get() {
  method set (line 66) | set(key, value) {
  method get (line 81) | get() {
  method set (line 84) | set(key, value) {

FILE: addon/components/nf-svg-rect.js
  method get (line 59) | get() {
  method set (line 62) | set(key, value) {
  method get (line 79) | get() {
  method set (line 82) | set(key, value) {

FILE: addon/components/nf-tracker.js
  method get (line 33) | get() {

FILE: addon/components/nf-x-axis.js
  method init (line 183) | init() {

FILE: addon/components/nf-y-axis.js
  method init (line 177) | init() {

FILE: addon/mixins/graph-data-graphic.js
  method get (line 34) | get() {
  method _sendTriggerHasData (line 54) | _sendTriggerHasData() {
  method get (line 89) | get() {
  method get (line 104) | get() {
  method _computeRenderedData (line 127) | _computeRenderedData() {
  method get (line 161) | get() {
  method get (line 188) | get() {
  method getDataNearXRange (line 221) | getDataNearXRange(rangeX) {
  method getActualTrackData (line 245) | getActualTrackData(renderX, renderY, data) {
  function between (line 261) | function between(x, a, b) {

FILE: addon/mixins/graph-graphic-with-tracking-dot.js
  method get (line 92) | get() {
  method set (line 96) | set(value) {
  method _updateTrackingHandling (line 131) | _updateTrackingHandling() {
  method _onHoverTrack (line 170) | _onHoverTrack() {
  method _onEndUntrack (line 190) | _onEndUntrack() {
  method _onEndSnapLast (line 208) | _onEndSnapLast() {
  method _onEndSnapFirst (line 237) | _onEndSnapFirst() {
  method _getEventObject (line 266) | _getEventObject(e) {
  method didInsertElement (line 299) | didInsertElement() {
  method willDestroyElement (line 304) | willDestroyElement() {

FILE: addon/mixins/graph-registered-graphic.js
  method init (line 10) | init() {

FILE: addon/mixins/graph-requires-scale-source.js
  method get (line 6) | get() {
  method get (line 77) | get() {
  method set (line 80) | set(key, value) {
  method get (line 92) | get() {
  method set (line 95) | set(key, value) {
  method get (line 107) | get() {
  method set (line 110) | set(key, value) {
  method get (line 122) | get() {
  method set (line 125) | set(key, value) {

FILE: addon/utils/nf/array-helpers.js
  function identity (line 12) | function identity(x) {
  function nearestIndexTo (line 24) | function nearestIndexTo(arr, val, mappingFn) {
  constant NATURAL_SORT_REGEXP (line 57) | let NATURAL_SORT_REGEXP = /[+-]?\d+\.?\d*|\S+/g;
  function naturalTokenize (line 67) | function naturalTokenize(item) {
  function naturalCompare (line 85) | function naturalCompare(a, b) {
  function naturalSort (line 119) | function naturalSort(arr) {
  function sliceArrayLike (line 123) | function sliceArrayLike(arrayLike, from, to) {
  function toArray (line 127) | function toArray() {

FILE: addon/utils/nf/graph-mouse-event.js
  method get (line 38) | get() {
  method get (line 74) | get() {
  method get (line 91) | get() {
  method get (line 105) | get() {
  method get (line 119) | get() {

FILE: addon/utils/nf/graph-position.js
  method get (line 30) | get() {
  method set (line 38) | set(key, value) {
  method get (line 49) | get() {
  method set (line 57) | set(key, value) {
  method get (line 68) | get() {
  method set (line 76) | set(key, value) {
  method get (line 87) | get() {
  method set (line 95) | set(key, value) {
  method get (line 106) | get() {
  method get (line 122) | get() {
  method get (line 139) | get() {
  method get (line 151) | get() {
  method get (line 163) | get() {
  method get (line 179) | get() {
  method get (line 197) | get() {

FILE: addon/utils/nf/scale-utils.js
  function normalizeScale (line 14) | function normalizeScale(scale, val) {

FILE: addon/utils/nf/scroll-area-action-context.js
  method get (line 64) | get() {
  method get (line 76) | get() {
  method get (line 88) | get() {
  method get (line 100) | get() {

FILE: addon/utils/nf/svg-dom.js
  function inlineAllStyles (line 11) | function inlineAllStyles(element) {
  function svgToImageUrl (line 32) | function svgToImageUrl(svg, callback) {
  function downloadSvg (line 68) | function downloadSvg(svg) {
  function getMousePoint (line 81) | function getMousePoint(container, e) {
  function getRectPath (line 115) | function getRectPath(x, y, w, h) {

FILE: addon/utils/nf/tracked-array-property.js
  function trackedArrayProperty (line 5) | function trackedArrayProperty(arraySourceProp, trackByProp, backingField) {

FILE: addon/utils/parse-property-expression.js
  function parsePropertyExpression (line 30) | function parsePropertyExpression(expr) {

FILE: docs/assets/js/api-list.js
  function getFilterResultNode (line 99) | function getFilterResultNode() {
  function onFilterResults (line 104) | function onFilterResults(e) {
  function onSearchClear (line 134) | function onSearchClear(e) {
  function onSearchKey (line 139) | function onSearchKey(e) {
  function onSearchResults (line 153) | function onSearchResults(e) {
  function onTabSelectionChange (line 173) | function onTabSelectionChange(e) {
  function onTabSwitchKey (line 223) | function onTabSwitchKey(e) {

FILE: docs/assets/js/apidocs.js
  function scrollToNode (line 194) | function scrollToNode() {

FILE: docs/assets/vendor/prettify/prettify-min.js
  function l (line 1) | function l(ab){var af=0;var U=false;var ae=false;for(var X=0,W=ab.length...
  function b (line 1) | function b(aa,Y){var W=/(?:^|\s)nocode(?:\s|$)/;var ab=[];var Z=0;var X=...
  function C (line 1) | function C(U,W,Y,V){if(!W){return}var X={sourceCode:W,basePos:U};Y(X);V....
  function p (line 1) | function p(U){var X=undefined;for(var W=U.firstChild;W;W=W.nextSibling){...
  function h (line 1) | function h(W,V){var U={};var X;(function(){var af=W.concat(V);var aj=[];...
  function i (line 1) | function i(V){var Y=[],U=[];if(V.tripleQuotedStrings){Y.push([D,/^(?:\'\...
  function S (line 1) | function S(W,ah,aa){var V=/(?:^|\s)nocode(?:\s|$)/;var ac=/\r\n?|\n/;var...
  function E (line 1) | function E(af){var X=/\bMSIE\s(\d+)/.exec(navigator.userAgent);X=X&&+X[1...
  function d (line 1) | function d(W,X){for(var U=X.length;--U>=0;){var V=X[U];if(!u.hasOwnPrope...
  function r (line 1) | function r(V,U){if(!(V&&u.hasOwnProperty(V))){V=/^\s*</.test(U)?"default...
  function e (line 1) | function e(X){var W=X.langExtension;try{var U=b(X.sourceNode,X.pre);var ...
  function z (line 1) | function z(Y,X,W){var U=document.createElement("pre");U.innerHTML=Y;if(W...
  function c (line 1) | function c(aj){function ab(al){return document.getElementsByTagName(al)}...

FILE: tests/dummy/app/controllers/nf-graph/index.js
  function generateLineData (line 10) | function generateLineData(xStart, yMin, yMax, variance, count, yStart){
  function range (line 25) | function range(count) {
  method init (line 42) | init(){
  method updateAreas (line 58) | updateAreas() {

FILE: tests/dummy/app/helpers/format-hour-minute.js
  function format (line 7) | function format([ ms ]) {

FILE: tests/dummy/app/routes/index.js
  method redirect (line 4) | redirect() {

FILE: tests/dummy/app/routes/nf-graph/index.js
  function generateLineData (line 16) | function generateLineData(xStart, yMin, yMax, variance, count, yStart){
  function range (line 31) | function range(count) {

FILE: tests/dummy/app/routes/nf-graph/nf-bars.js
  method model (line 4) | model() {

FILE: tests/helpers/destroy-app.js
  function destroyApp (line 3) | function destroyApp(application) {

FILE: tests/helpers/module-for-acceptance.js
  method beforeEach (line 8) | beforeEach() {
  method afterEach (line 16) | afterEach() {

FILE: tests/helpers/start-app.js
  function startApp (line 6) | function startApp(attrs) {

FILE: tests/unit/app/components/nf-graph-test.js
  method didAutoUpdateMinX (line 18) | didAutoUpdateMinX() {
  method didAutoUpdateMinX (line 37) | didAutoUpdateMinX() {
  method didAutoUpdateMaxX (line 57) | didAutoUpdateMaxX() {
  method didAutoUpdateMaxX (line 77) | didAutoUpdateMaxX() {
  method sendAction (line 95) | sendAction() {
  method sendAction (line 114) | sendAction() {
  method sendAction (line 136) | sendAction() {
  method sendAction (line 155) | sendAction() {

FILE: tests/unit/app/components/nf-x-axis-test.js
  method tickFactory (line 20) | tickFactory() {

FILE: tests/unit/app/components/nf-y-axis-test.js
  method tickFactory (line 19) | tickFactory() {
Condensed preview — 290 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,706K chars).
[
  {
    "path": ".editorconfig",
    "chars": 368,
    "preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
  },
  {
    "path": ".ember-cli",
    "chars": 280,
    "preview": "{\n  /**\n    Ember CLI sends analytics information by default. The data is completely\n    anonymous, but there are times "
  },
  {
    "path": ".eslintrc.js",
    "chars": 1540,
    "preview": "module.exports = {\n  root: true,\n  parserOptions: {\n    ecmaVersion: 2017,\n    sourceType: 'module'\n  },\n  plugins: [\n  "
  },
  {
    "path": ".gitignore",
    "chars": 346,
    "preview": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/tmp\n\n# dependencies"
  },
  {
    "path": ".npmignore",
    "chars": 277,
    "preview": "/bower_components\n/config/ember-try.js\n/dist\n/tests\n/tmp\n**/.gitkeep\n.bowerrc\n.editorconfig\n.ember-cli\n.eslintrc.js\n.git"
  },
  {
    "path": ".travis.yml",
    "chars": 1076,
    "preview": "---\nlanguage: node_js\nnode_js:\n  # we recommend testing addons with the same minimum supported node version as Ember CLI"
  },
  {
    "path": ".watchmanconfig",
    "chars": 37,
    "preview": "{\n  \"ignore_dirs\": [\"tmp\", \"dist\"]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 6952,
    "preview": "# Changelog\n\n### 1.0.0-beta.30\n\n- FIX memory leak in nf-area caused by overriding `_unregister`\n- FIX null reference err"
  },
  {
    "path": "LICENSE.md",
    "chars": 11343,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "OSSMETADATA",
    "chars": 20,
    "preview": "osslifecycle=active\n"
  },
  {
    "path": "README.md",
    "chars": 2788,
    "preview": "[![Build Status](https://travis-ci.org/Netflix/ember-nf-graph.svg?branch=master)](https://travis-ci.org/Netflix/ember-nf"
  },
  {
    "path": "addon/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "addon/components/.gitignore",
    "chars": 12,
    "preview": "node_modules"
  },
  {
    "path": "addon/components/nf-area-stack.js",
    "chars": 2834,
    "preview": "import { schedule } from '@ember/runloop';\nimport { A } from '@ember/array';\nimport { warn } from '@ember/debug';\nimport"
  },
  {
    "path": "addon/components/nf-area.js",
    "chars": 4909,
    "preview": "import { computed } from '@ember/object';\nimport { on } from '@ember/object/evented';\nimport Component from '@ember/comp"
  },
  {
    "path": "addon/components/nf-bars-group.js",
    "chars": 2042,
    "preview": "import { schedule } from '@ember/runloop';\nimport { A } from '@ember/array';\nimport { computed } from '@ember/object';\ni"
  },
  {
    "path": "addon/components/nf-bars.js",
    "chars": 4833,
    "preview": "import { isArray, A } from '@ember/array';\nimport { oneWay } from '@ember/object/computed';\nimport { computed } from '@e"
  },
  {
    "path": "addon/components/nf-brush-selection.js",
    "chars": 5092,
    "preview": "import { alias } from '@ember/object/computed';\nimport { scheduleOnce } from '@ember/runloop';\nimport { on } from '@embe"
  },
  {
    "path": "addon/components/nf-component.js",
    "chars": 1282,
    "preview": "import Component from '@ember/component';\nimport { assert } from '@ember/debug';\nimport { isPresent } from '@ember/utils"
  },
  {
    "path": "addon/components/nf-crosshairs.js",
    "chars": 2253,
    "preview": "import { schedule } from '@ember/runloop';\nimport { alias } from '@ember/object/computed';\nimport Component from '@ember"
  },
  {
    "path": "addon/components/nf-dot.js",
    "chars": 1976,
    "preview": "import { computed } from '@ember/object';\nimport { notEmpty, and } from '@ember/object/computed';\nimport Component from "
  },
  {
    "path": "addon/components/nf-graph-content.js",
    "chars": 3864,
    "preview": "import { schedule } from '@ember/runloop';\nimport { A } from '@ember/array';\nimport { alias } from '@ember/object/comput"
  },
  {
    "path": "addon/components/nf-graph-yieldables.js",
    "chars": 441,
    "preview": "import Component from '@ember/component';\nimport layout from 'ember-nf-graph/templates/components/nf-graph-yieldables';\n"
  },
  {
    "path": "addon/components/nf-graph.js",
    "chars": 33605,
    "preview": "import { bool, notEmpty } from '@ember/object/computed';\nimport $ from 'jquery';\nimport { on } from '@ember/object/event"
  },
  {
    "path": "addon/components/nf-group.js",
    "chars": 1190,
    "preview": "import Component from '@ember/component';\nimport layout from 'ember-nf-graph/templates/components/nf-group';\nimport Requ"
  },
  {
    "path": "addon/components/nf-horizontal-line.js",
    "chars": 1522,
    "preview": "import { alias } from '@ember/object/computed';\nimport { computed } from '@ember/object';\nimport Component from '@ember/"
  },
  {
    "path": "addon/components/nf-line.js",
    "chars": 2610,
    "preview": "import { on } from '@ember/object/evented';\nimport { computed } from '@ember/object';\nimport Component from '@ember/comp"
  },
  {
    "path": "addon/components/nf-plot.js",
    "chars": 2655,
    "preview": "import { computed } from '@ember/object';\nimport { notEmpty, and } from '@ember/object/computed';\nimport Component from "
  },
  {
    "path": "addon/components/nf-plots.js",
    "chars": 1116,
    "preview": "import { isArray, A } from '@ember/array';\nimport { computed } from '@ember/object';\nimport Component from '@ember/compo"
  },
  {
    "path": "addon/components/nf-range-marker.js",
    "chars": 4596,
    "preview": "import { on } from '@ember/object/evented';\nimport { computed } from '@ember/object';\nimport Component from '@ember/comp"
  },
  {
    "path": "addon/components/nf-range-markers.js",
    "chars": 2564,
    "preview": "import { schedule } from '@ember/runloop';\nimport { A } from '@ember/array';\nimport { computed } from '@ember/object';\ni"
  },
  {
    "path": "addon/components/nf-right-tick.js",
    "chars": 3586,
    "preview": "import { scheduleOnce } from '@ember/runloop';\nimport { on } from '@ember/object/evented';\nimport { computed, observer }"
  },
  {
    "path": "addon/components/nf-selection-box.js",
    "chars": 3846,
    "preview": "import { on } from '@ember/object/evented';\nimport { once } from '@ember/runloop';\nimport { computed, observer } from '@"
  },
  {
    "path": "addon/components/nf-svg-image.js",
    "chars": 3744,
    "preview": "import Component from '@ember/component';\nimport { computed } from '@ember/object';\nimport layout from 'ember-nf-graph/t"
  },
  {
    "path": "addon/components/nf-svg-line.js",
    "chars": 2435,
    "preview": "import { computed } from '@ember/object';\nimport Component from '@ember/component';\nimport layout from 'ember-nf-graph/t"
  },
  {
    "path": "addon/components/nf-svg-path.js",
    "chars": 2495,
    "preview": "import { isArray } from '@ember/array';\nimport { computed } from '@ember/object';\nimport Component from '@ember/componen"
  },
  {
    "path": "addon/components/nf-svg-rect.js",
    "chars": 4180,
    "preview": "import Component from '@ember/component';\nimport { computed } from '@ember/object';\nimport layout from 'ember-nf-graph/t"
  },
  {
    "path": "addon/components/nf-tick-label.js",
    "chars": 428,
    "preview": "import { computed } from '@ember/object';\nimport Component from '@ember/component';\nimport layout from 'ember-nf-graph/t"
  },
  {
    "path": "addon/components/nf-tracker.js",
    "chars": 1350,
    "preview": "import Component from '@ember/component';\nimport { computed } from '@ember/object';\nimport layout from 'ember-nf-graph/t"
  },
  {
    "path": "addon/components/nf-vertical-line.js",
    "chars": 1527,
    "preview": "import { computed } from '@ember/object';\nimport { alias } from '@ember/object/computed';\nimport Component from '@ember/"
  },
  {
    "path": "addon/components/nf-x-axis.js",
    "chars": 7546,
    "preview": "import { alias, uniq } from '@ember/object/computed';\nimport { A } from '@ember/array';\nimport { schedule } from '@ember"
  },
  {
    "path": "addon/components/nf-y-axis.js",
    "chars": 7267,
    "preview": "import { alias, equal, uniq } from '@ember/object/computed';\nimport { A } from '@ember/array';\nimport { schedule } from "
  },
  {
    "path": "addon/components/nf-y-diff.js",
    "chars": 6953,
    "preview": "import { on } from '@ember/object/evented';\nimport { once } from '@ember/runloop';\nimport { alias, gte, equal } from '@e"
  },
  {
    "path": "addon/mixins/.gitignore",
    "chars": 31,
    "preview": "node_modules/\nbower_components/"
  },
  {
    "path": "addon/mixins/graph-area-utils.js",
    "chars": 1226,
    "preview": "import Mixin from '@ember/object/mixin';\n\n/**\n  Utility functions for drawing an area.\n  \n  @namespace mixins\n  @class g"
  },
  {
    "path": "addon/mixins/graph-data-graphic.js",
    "chars": 7384,
    "preview": "import { scheduleOnce, schedule } from '@ember/runloop';\nimport { isArray } from '@ember/array';\nimport Mixin from '@emb"
  },
  {
    "path": "addon/mixins/graph-graphic-with-tracking-dot.js",
    "chars": 8223,
    "preview": "import { schedule, scheduleOnce } from '@ember/runloop';\nimport Mixin from '@ember/object/mixin';\nimport { on } from '@e"
  },
  {
    "path": "addon/mixins/graph-line-utils.js",
    "chars": 1084,
    "preview": "  import Mixin from '@ember/object/mixin';\n\n  /**\n    @namespace mixins\n    @class graph-line-utils\n    @extends Ember.M"
  },
  {
    "path": "addon/mixins/graph-registered-graphic.js",
    "chars": 667,
    "preview": "import { on } from '@ember/object/evented';\nimport Mixin from '@ember/object/mixin';\n\n/**\n  @namespace mixins\n  @class g"
  },
  {
    "path": "addon/mixins/graph-requires-scale-source.js",
    "chars": 2630,
    "preview": "import Mixin from '@ember/object/mixin';\nimport { computed } from '@ember/object';\n\nlet scaleProperty = function(scaleKe"
  },
  {
    "path": "addon/mixins/graph-selectable-graphic.js",
    "chars": 1569,
    "preview": "import { once } from '@ember/runloop';\nimport { observer } from '@ember/object';\nimport { on } from '@ember/object/event"
  },
  {
    "path": "addon/styles/addon.css",
    "chars": 3268,
    "preview": ".nf-graph-background {\n  fill: #f5f6f7;\n}\n\n.nf-graph text {\n  font-size: 10px;\n}\n\n.nf-graph * {\n  -moz-user-select: none"
  },
  {
    "path": "addon/templates/components/nf-area-stack.hbs",
    "chars": 97,
    "preview": "{{yield (hash\n\t\tarea=(component 'nf-area' stack=this graph=graph scaleSource=scaleSource )\n\t)\n}}\n"
  },
  {
    "path": "addon/templates/components/nf-area.hbs",
    "chars": 267,
    "preview": "<path class=\"nf-area-area\" d=\"{{d}}\"></path>\n<path class=\"nf-area-line\" d=\"{{dLine}}\"></path>\n{{#if showTrackingDot}}\n\t{"
  },
  {
    "path": "addon/templates/components/nf-bars-group.hbs",
    "chars": 96,
    "preview": "{{yield (hash\n\t\tarea=(component 'nf-bars' graph=graph scaleSource=scaleSource group=this)\n\t)\n}}\n"
  },
  {
    "path": "addon/templates/components/nf-bars.hbs",
    "chars": 284,
    "preview": "{{#each bars as |bar|}}\n\t<path d=\"{{bar.path}}\" class=\"{{bar.className}}\" {{action 'nfBarClickBar' bar.data bar.index}}>"
  },
  {
    "path": "addon/templates/components/nf-brush-selection.hbs",
    "chars": 750,
    "preview": "<rect class=\"nf-brush-selection-overlay\" x=\"0\" y=\"0\" width=\"{{leftX}}\" height=\"{{graphHeight}}\"></rect>\n<rect class=\"nf-"
  },
  {
    "path": "addon/templates/components/nf-component.hbs",
    "chars": 72,
    "preview": "{{yield (component componentName graph=graph scaleSource=scaleSource)}}\n"
  },
  {
    "path": "addon/templates/components/nf-crosshairs.hbs",
    "chars": 226,
    "preview": "{{#if vertical}}\n  <line class=\"nf-crosshair-vertical\" x1=\"{{x}}\" x2=\"{{x}}\" y1=\"0\" y2=\"{{height}}\" />\n{{/if}}\n\n{{#if ho"
  },
  {
    "path": "addon/templates/components/nf-dot.hbs",
    "chars": 10,
    "preview": "{{yield}}\n"
  },
  {
    "path": "addon/templates/components/nf-graph-content.hbs",
    "chars": 820,
    "preview": "<rect x=0 y=0 class=\"nf-graph-content-background\" width=\"{{width}}\" height=\"{{height}}\"></rect>\n  {{#if graph.hasData}}\n"
  },
  {
    "path": "addon/templates/components/nf-graph-yieldables.hbs",
    "chars": 1430,
    "preview": "{{yield (hash\n    group=(component 'nf-group' graph=graph)\n    component=(component 'nf-component' graph=graph scaleSour"
  },
  {
    "path": "addon/templates/components/nf-graph.hbs",
    "chars": 836,
    "preview": "<svg class=\"nf-graph\" width=\"{{width}}\" height=\"{{height}}\">\n\t<defs>\n\t\t<clipPath id=\"{{contentClipPathId}}\">\n\t\t\t<rect x="
  },
  {
    "path": "addon/templates/components/nf-group.hbs",
    "chars": 118,
    "preview": "{{#nf-graph-yieldables graph=graph scaleSource=this as |yieldables|}}\n  {{yield yieldables}}\n{{/nf-graph-yieldables}}\n"
  },
  {
    "path": "addon/templates/components/nf-horizontal-line.hbs",
    "chars": 10,
    "preview": "{{yield}}\n"
  },
  {
    "path": "addon/templates/components/nf-line.hbs",
    "chars": 304,
    "preview": "<path class=\"nf-line-line\" d=\"{{d}}\"></path>\n{{#if selectable}}\n\t<path class=\"nf-line-interaction-mask\" d=\"{{d}}\"></path"
  },
  {
    "path": "addon/templates/components/nf-plot.hbs",
    "chars": 10,
    "preview": "{{yield}}\n"
  },
  {
    "path": "addon/templates/components/nf-plots.hbs",
    "chars": 217,
    "preview": "{{#each plotData as |item|}}\n\t{{#nf-plot graph=graph scaleSource=scaleSource x=item.x y=item.y action='itemClicked' data"
  },
  {
    "path": "addon/templates/components/nf-range-marker.hbs",
    "chars": 188,
    "preview": "<g class=\"nf-range-marker-label\" transform=\"{{labelTransform}}\">{{yield}}</g>\n<rect class=\"nf-range-marker-marker\" y=\"{{"
  },
  {
    "path": "addon/templates/components/nf-range-markers.hbs",
    "chars": 116,
    "preview": "{{yield (hash\n\t\trange-marker=(component 'nf-range-marker' container=this graph=graph scaleSource=scaleSource)\n\t)\n}}\n"
  },
  {
    "path": "addon/templates/components/nf-right-tick.hbs",
    "chars": 170,
    "preview": "<line class=\"nf-right-tick-line\" x1=\"{{graph.width}}\" x2=\"{{graph.width}}\" y1=\"0\" y2=\"{{graph.height}}\"></line>\n<path cl"
  },
  {
    "path": "addon/templates/components/nf-selection-box.hbs",
    "chars": 10,
    "preview": "{{yield}}\n"
  },
  {
    "path": "addon/templates/components/nf-svg-image.hbs",
    "chars": 10,
    "preview": "{{yield}}\n"
  },
  {
    "path": "addon/templates/components/nf-svg-line.hbs",
    "chars": 10,
    "preview": "{{yield}}\n"
  },
  {
    "path": "addon/templates/components/nf-svg-path.hbs",
    "chars": 10,
    "preview": "{{yield}}\n"
  },
  {
    "path": "addon/templates/components/nf-svg-rect.hbs",
    "chars": 10,
    "preview": "{{yield}}\n"
  },
  {
    "path": "addon/templates/components/nf-tick-label.hbs",
    "chars": 10,
    "preview": "{{yield}}\n"
  },
  {
    "path": "addon/templates/components/nf-tracker.hbs",
    "chars": 21,
    "preview": "{{yield trackedData}}"
  },
  {
    "path": "addon/templates/components/nf-vertical-line.hbs",
    "chars": 10,
    "preview": "{{yield}}\n"
  },
  {
    "path": "addon/templates/components/nf-x-axis.hbs",
    "chars": 534,
    "preview": "<line class=\"nf-x-axis-line\" x1=\"0\" y1=\"{{axisLineY}}\" x2=\"{{width}}\" y2=\"{{axisLineY}}\"></line>\n\n{{#each ticks as |tick"
  },
  {
    "path": "addon/templates/components/nf-y-axis.hbs",
    "chars": 535,
    "preview": "<line class=\"nf-y-axis-line\" x1=\"{{axisLineX}}\" y1=\"0\" x2=\"{{axisLineX}}\" y2=\"{{height}}\"></line>\n\n{{#each ticks as |tic"
  },
  {
    "path": "addon/templates/components/nf-y-diff.hbs",
    "chars": 45,
    "preview": "<g class=\"nf-y-diff-content\">\n\t{{yield}}\n</g>"
  },
  {
    "path": "addon/utils/nf/array-helpers.js",
    "chars": 3000,
    "preview": "/**\n  @module utils/nf/array-helpers\n*/\n\n/**\n  returns whatever you pass into it.\n  @method identity\n  @param x {Any}\n  "
  },
  {
    "path": "addon/utils/nf/graph-event.js",
    "chars": 467,
    "preview": "import GraphPosition from './graph-position';\n\n/**\n  Event object for graph events\n  @namespace utils.nf\n  @class graph-"
  },
  {
    "path": "addon/utils/nf/graph-mouse-event.js",
    "chars": 3226,
    "preview": "import { reads } from '@ember/object/computed';\nimport { computed } from '@ember/object';\nimport GraphPosition from './g"
  },
  {
    "path": "addon/utils/nf/graph-position.js",
    "chars": 4918,
    "preview": "import { reads } from '@ember/object/computed';\nimport EmberObject, { computed } from '@ember/object';\n\n/**\n  Position c"
  },
  {
    "path": "addon/utils/nf/scale-utils.js",
    "chars": 502,
    "preview": "/**\n  utilities for dealing with d3 scales\n  @namespace utils.nf\n  @module scale-utils\n*/\n\n/**\n  Ensures the output of a"
  },
  {
    "path": "addon/utils/nf/scroll-area-action-context.js",
    "chars": 2468,
    "preview": "import EmberObject, { computed } from '@ember/object';\n\n/**\n  Action context event object for an nf-scroll-area scroll e"
  },
  {
    "path": "addon/utils/nf/svg-dom.js",
    "chars": 3367,
    "preview": "/* globals getComputedStyle, Image, Blob, URL */\nimport { isPresent } from '@ember/utils';\n\n/**\n  Traverses an element a"
  },
  {
    "path": "addon/utils/nf/tracked-array-property.js",
    "chars": 1758,
    "preview": "import { keys } from '@ember/polyfills';\nimport { isArray, A } from '@ember/array';\nimport { computed, get, set } from '"
  },
  {
    "path": "addon/utils/parse-property-expression.js",
    "chars": 1600,
    "preview": "/**\n  A function that will parse a path expression into a \"getter\" function that\n  can retrieve a value from that path o"
  },
  {
    "path": "app/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "app/components/.gitignore",
    "chars": 12,
    "preview": "node_modules"
  },
  {
    "path": "app/components/nf-area-stack.js",
    "chars": 67,
    "preview": "export { default } from 'ember-nf-graph/components/nf-area-stack';\n"
  },
  {
    "path": "app/components/nf-area.js",
    "chars": 61,
    "preview": "export { default } from 'ember-nf-graph/components/nf-area';\n"
  },
  {
    "path": "app/components/nf-bars-group.js",
    "chars": 67,
    "preview": "export { default } from 'ember-nf-graph/components/nf-bars-group';\n"
  },
  {
    "path": "app/components/nf-bars.js",
    "chars": 61,
    "preview": "export { default } from 'ember-nf-graph/components/nf-bars';\n"
  },
  {
    "path": "app/components/nf-brush-selection.js",
    "chars": 72,
    "preview": "export { default } from 'ember-nf-graph/components/nf-brush-selection';\n"
  },
  {
    "path": "app/components/nf-component.js",
    "chars": 65,
    "preview": "export { default } from 'ember-nf-graph/components/nf-component';"
  },
  {
    "path": "app/components/nf-crosshairs.js",
    "chars": 67,
    "preview": "export { default } from 'ember-nf-graph/components/nf-crosshairs';\n"
  },
  {
    "path": "app/components/nf-dot.js",
    "chars": 60,
    "preview": "export { default } from 'ember-nf-graph/components/nf-dot';\n"
  },
  {
    "path": "app/components/nf-graph-content.js",
    "chars": 70,
    "preview": "export { default } from 'ember-nf-graph/components/nf-graph-content';\n"
  },
  {
    "path": "app/components/nf-graph-yieldables.js",
    "chars": 73,
    "preview": "export { default } from 'ember-nf-graph/components/nf-graph-yieldables';\n"
  },
  {
    "path": "app/components/nf-graph.js",
    "chars": 62,
    "preview": "export { default } from 'ember-nf-graph/components/nf-graph';\n"
  },
  {
    "path": "app/components/nf-group.js",
    "chars": 62,
    "preview": "export { default } from 'ember-nf-graph/components/nf-group';\n"
  },
  {
    "path": "app/components/nf-horizontal-line.js",
    "chars": 72,
    "preview": "export { default } from 'ember-nf-graph/components/nf-horizontal-line';\n"
  },
  {
    "path": "app/components/nf-line.js",
    "chars": 61,
    "preview": "export { default } from 'ember-nf-graph/components/nf-line';\n"
  },
  {
    "path": "app/components/nf-plot.js",
    "chars": 61,
    "preview": "export { default } from 'ember-nf-graph/components/nf-plot';\n"
  },
  {
    "path": "app/components/nf-plots.js",
    "chars": 62,
    "preview": "export { default } from 'ember-nf-graph/components/nf-plots';\n"
  },
  {
    "path": "app/components/nf-range-marker.js",
    "chars": 69,
    "preview": "export { default } from 'ember-nf-graph/components/nf-range-marker';\n"
  },
  {
    "path": "app/components/nf-range-markers.js",
    "chars": 70,
    "preview": "export { default } from 'ember-nf-graph/components/nf-range-markers';\n"
  },
  {
    "path": "app/components/nf-right-tick.js",
    "chars": 67,
    "preview": "export { default } from 'ember-nf-graph/components/nf-right-tick';\n"
  },
  {
    "path": "app/components/nf-selection-box.js",
    "chars": 70,
    "preview": "export { default } from 'ember-nf-graph/components/nf-selection-box';\n"
  },
  {
    "path": "app/components/nf-svg-image.js",
    "chars": 66,
    "preview": "export { default } from 'ember-nf-graph/components/nf-svg-image';\n"
  },
  {
    "path": "app/components/nf-svg-line.js",
    "chars": 65,
    "preview": "export { default } from 'ember-nf-graph/components/nf-svg-line';\n"
  },
  {
    "path": "app/components/nf-svg-path.js",
    "chars": 65,
    "preview": "export { default } from 'ember-nf-graph/components/nf-svg-path';\n"
  },
  {
    "path": "app/components/nf-svg-rect.js",
    "chars": 65,
    "preview": "export { default } from 'ember-nf-graph/components/nf-svg-rect';\n"
  },
  {
    "path": "app/components/nf-tick-label.js",
    "chars": 67,
    "preview": "export { default } from 'ember-nf-graph/components/nf-tick-label';\n"
  },
  {
    "path": "app/components/nf-tracker.js",
    "chars": 64,
    "preview": "export { default } from 'ember-nf-graph/components/nf-tracker';\n"
  },
  {
    "path": "app/components/nf-vertical-line.js",
    "chars": 70,
    "preview": "export { default } from 'ember-nf-graph/components/nf-vertical-line';\n"
  },
  {
    "path": "app/components/nf-x-axis.js",
    "chars": 63,
    "preview": "export { default } from 'ember-nf-graph/components/nf-x-axis';\n"
  },
  {
    "path": "app/components/nf-y-axis.js",
    "chars": 63,
    "preview": "export { default } from 'ember-nf-graph/components/nf-y-axis';\n"
  },
  {
    "path": "app/components/nf-y-diff.js",
    "chars": 63,
    "preview": "export { default } from 'ember-nf-graph/components/nf-y-diff';\n"
  },
  {
    "path": "config/ember-try.js",
    "chars": 1287,
    "preview": "module.exports = {\n  scenarios: [\n    {\n      name: 'ember-lts-2.12',\n      npm: {\n        devDependencies: {\n          "
  },
  {
    "path": "config/environment.js",
    "chars": 90,
    "preview": "'use strict';\n\nmodule.exports = function(/* environment, appConfig */) {\n  return { };\n};\n"
  },
  {
    "path": "docs/api.js",
    "chars": 1906,
    "preview": "YUI.add(\"yuidoc-meta\", function(Y) {\n   Y.YUIDoc = { meta: {\n    \"classes\": [\n        \"components.nf-area\",\n        \"com"
  },
  {
    "path": "docs/assets/css/main.css",
    "chars": 18199,
    "preview": "/*\nFont sizes for all selectors other than the body are given in percentages,\nwith 100% equal to 13px. To calculate a fo"
  },
  {
    "path": "docs/assets/index.html",
    "chars": 216,
    "preview": "<!doctype html>\n<html>\n    <head>\n        <title>Redirector</title>\n        <meta http-equiv=\"refresh\" content=\"0;url=.."
  },
  {
    "path": "docs/assets/js/api-filter.js",
    "chars": 1445,
    "preview": "YUI.add('api-filter', function (Y) {\n\nY.APIFilter = Y.Base.create('apiFilter', Y.Base, [Y.AutoCompleteBase], {\n    // --"
  },
  {
    "path": "docs/assets/js/api-list.js",
    "chars": 6568,
    "preview": "YUI.add('api-list', function (Y) {\n\nvar Lang   = Y.Lang,\n    YArray = Y.Array,\n\n    APIList = Y.namespace('APIList'),\n\n "
  },
  {
    "path": "docs/assets/js/api-search.js",
    "chars": 2924,
    "preview": "YUI.add('api-search', function (Y) {\n\nvar Lang   = Y.Lang,\n    Node   = Y.Node,\n    YArray = Y.Array;\n\nY.APISearch = Y.B"
  },
  {
    "path": "docs/assets/js/apidocs.js",
    "chars": 10297,
    "preview": "YUI().use(\n    'yuidoc-meta',\n    'api-list', 'history-hash', 'node-screen', 'node-style', 'pjax',\nfunction (Y) {\n\nvar w"
  },
  {
    "path": "docs/assets/js/yui-prettify.js",
    "chars": 482,
    "preview": "YUI().use('node', function(Y) {\n    var code = Y.all('.prettyprint.linenums');\n    if (code.size()) {\n        code.each("
  },
  {
    "path": "docs/assets/vendor/prettify/CHANGES.html",
    "chars": 6084,
    "preview": "<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <title>Change Log</title>\n  "
  },
  {
    "path": "docs/assets/vendor/prettify/COPYING",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "docs/assets/vendor/prettify/README.html",
    "chars": 7912,
    "preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
  },
  {
    "path": "docs/assets/vendor/prettify/prettify-min.css",
    "chars": 675,
    "preview": ".pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,"
  },
  {
    "path": "docs/assets/vendor/prettify/prettify-min.js",
    "chars": 17803,
    "preview": "window.PR_SHOULD_USE_CONTINUATION=true;var prettyPrintOne;var prettyPrint;(function(){var O=window;var j=[\"break,continu"
  },
  {
    "path": "docs/classes/components.nf-area-stack.html",
    "chars": 16983,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-area-stack - ember-nf-graph<"
  },
  {
    "path": "docs/classes/components.nf-area.html",
    "chars": 61798,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-area - ember-nf-graph</title"
  },
  {
    "path": "docs/classes/components.nf-bars.html",
    "chars": 59439,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-bars - ember-nf-graph</title"
  },
  {
    "path": "docs/classes/components.nf-crosshair.html",
    "chars": 30506,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-crosshair - ember-nf-graph</"
  },
  {
    "path": "docs/classes/components.nf-dot.html",
    "chars": 40065,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-dot - ember-nf-graph</title>"
  },
  {
    "path": "docs/classes/components.nf-gg.html",
    "chars": 25193,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-gg - ember-nf-graph</title>\n"
  },
  {
    "path": "docs/classes/components.nf-graph-content.html",
    "chars": 35118,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-graph-content - ember-nf-gra"
  },
  {
    "path": "docs/classes/components.nf-graph.html",
    "chars": 109054,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-graph - ember-nf-graph</titl"
  },
  {
    "path": "docs/classes/components.nf-horizontal-line.html",
    "chars": 37834,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-horizontal-line - ember-nf-g"
  },
  {
    "path": "docs/classes/components.nf-line.html",
    "chars": 62066,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-line - ember-nf-graph</title"
  },
  {
    "path": "docs/classes/components.nf-plot.html",
    "chars": 42043,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-plot - ember-nf-graph</title"
  },
  {
    "path": "docs/classes/components.nf-range-marker.html",
    "chars": 52545,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-range-marker - ember-nf-grap"
  },
  {
    "path": "docs/classes/components.nf-range-markers.html",
    "chars": 33031,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-range-markers - ember-nf-gra"
  },
  {
    "path": "docs/classes/components.nf-right-tick.html",
    "chars": 43151,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-right-tick - ember-nf-graph<"
  },
  {
    "path": "docs/classes/components.nf-selection-box.html",
    "chars": 46798,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-selection-box - ember-nf-gra"
  },
  {
    "path": "docs/classes/components.nf-svg-image.html",
    "chars": 49383,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-svg-image - ember-nf-graph</"
  },
  {
    "path": "docs/classes/components.nf-svg-line.html",
    "chars": 47948,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-svg-line - ember-nf-graph</t"
  },
  {
    "path": "docs/classes/components.nf-svg-path.html",
    "chars": 42456,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-svg-path - ember-nf-graph</t"
  },
  {
    "path": "docs/classes/components.nf-svg-rect.html",
    "chars": 50271,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-svg-rect - ember-nf-graph</t"
  },
  {
    "path": "docs/classes/components.nf-tracker.html",
    "chars": 47385,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-tracker - ember-nf-graph</ti"
  },
  {
    "path": "docs/classes/components.nf-vertical-line.html",
    "chars": 37820,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-vertical-line - ember-nf-gra"
  },
  {
    "path": "docs/classes/components.nf-x-axis.html",
    "chars": 53310,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-x-axis - ember-nf-graph</tit"
  },
  {
    "path": "docs/classes/components.nf-y-axis.html",
    "chars": 53118,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-y-axis - ember-nf-graph</tit"
  },
  {
    "path": "docs/classes/components.nf-y-diff.html",
    "chars": 54660,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>components.nf-y-diff - ember-nf-graph</tit"
  },
  {
    "path": "docs/classes/index.html",
    "chars": 216,
    "preview": "<!doctype html>\n<html>\n    <head>\n        <title>Redirector</title>\n        <meta http-equiv=\"refresh\" content=\"0;url=.."
  },
  {
    "path": "docs/classes/mixins.graph-area-utils.html",
    "chars": 12019,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>mixins.graph-area-utils - ember-nf-graph</"
  },
  {
    "path": "docs/classes/mixins.graph-data-graphic.html",
    "chars": 23466,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>mixins.graph-data-graphic - ember-nf-graph"
  },
  {
    "path": "docs/classes/mixins.graph-has-graph-parent.html",
    "chars": 23197,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>mixins.graph-has-graph-parent - ember-nf-g"
  },
  {
    "path": "docs/classes/mixins.graph-line-utils.html",
    "chars": 11969,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>mixins.graph-line-utils - ember-nf-graph</"
  },
  {
    "path": "docs/classes/mixins.graph-registered-graphic.html",
    "chars": 10295,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>mixins.graph-registered-graphic - ember-nf"
  },
  {
    "path": "docs/classes/mixins.graph-requires-scale-source.html",
    "chars": 16808,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>mixins.graph-requires-scale-source - ember"
  },
  {
    "path": "docs/classes/mixins.graph-selectable-graphic.html",
    "chars": 15012,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>mixins.graph-selectable-graphic - ember-nf"
  },
  {
    "path": "docs/classes/utils.nf.graph-event.html",
    "chars": 21644,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>utils.nf.graph-event - ember-nf-graph</tit"
  },
  {
    "path": "docs/classes/utils.nf.graph-mouse-event.html",
    "chars": 22880,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>utils.nf.graph-mouse-event - ember-nf-grap"
  },
  {
    "path": "docs/classes/utils.nf.graph-position.html",
    "chars": 27004,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>utils.nf.graph-position - ember-nf-graph</"
  },
  {
    "path": "docs/classes/utils.nf.scroll-area-action-context.html",
    "chars": 28082,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>utils.nf.scroll-area-action-context - embe"
  },
  {
    "path": "docs/classes/utils.parse-property-expression.html",
    "chars": 20859,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>utils.parse-property-expression - ember-nf"
  },
  {
    "path": "docs/data.json",
    "chars": 216078,
    "preview": "{\n    \"project\": {\n        \"name\": \"ember-nf-graph\",\n        \"description\": \"A DSL for creating graphs with Ember\"\n    }"
  },
  {
    "path": "docs/files/addon_mixins_graph-area-utils.js.html",
    "chars": 9596,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/mixins/graph-area-utils.js - ember-n"
  },
  {
    "path": "docs/files/addon_mixins_graph-data-graphic.js.html",
    "chars": 16163,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/mixins/graph-data-graphic.js - ember"
  },
  {
    "path": "docs/files/addon_mixins_graph-graphic-with-tracking-dot.js.html",
    "chars": 17047,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/mixins/graph-graphic-with-tracking-d"
  },
  {
    "path": "docs/files/addon_mixins_graph-has-graph-parent.js.html",
    "chars": 9131,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/mixins/graph-has-graph-parent.js - e"
  },
  {
    "path": "docs/files/addon_mixins_graph-line-utils.js.html",
    "chars": 9454,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/mixins/graph-line-utils.js - ember-n"
  },
  {
    "path": "docs/files/addon_mixins_graph-registered-graphic.js.html",
    "chars": 9041,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/mixins/graph-registered-graphic.js -"
  },
  {
    "path": "docs/files/addon_mixins_graph-requires-scale-source.js.html",
    "chars": 11058,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/mixins/graph-requires-scale-source.j"
  },
  {
    "path": "docs/files/addon_mixins_graph-selectable-graphic.js.html",
    "chars": 9924,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/mixins/graph-selectable-graphic.js -"
  },
  {
    "path": "docs/files/addon_utils_nf_array-helpers.js.html",
    "chars": 11447,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/utils/nf/array-helpers.js - ember-nf"
  },
  {
    "path": "docs/files/addon_utils_nf_graph-event.js.html",
    "chars": 8819,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/utils/nf/graph-event.js - ember-nf-g"
  },
  {
    "path": "docs/files/addon_utils_nf_graph-mouse-event.js.html",
    "chars": 11906,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/utils/nf/graph-mouse-event.js - embe"
  },
  {
    "path": "docs/files/addon_utils_nf_graph-position.js.html",
    "chars": 13812,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/utils/nf/graph-position.js - ember-n"
  },
  {
    "path": "docs/files/addon_utils_nf_scale-utils.js.html",
    "chars": 8844,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/utils/nf/scale-utils.js - ember-nf-g"
  },
  {
    "path": "docs/files/addon_utils_nf_scroll-area-action-context.js.html",
    "chars": 11037,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/utils/nf/scroll-area-action-context."
  },
  {
    "path": "docs/files/addon_utils_nf_svg-dom.js.html",
    "chars": 11866,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/utils/nf/svg-dom.js - ember-nf-graph"
  },
  {
    "path": "docs/files/addon_utils_parse-property-expression.js.html",
    "chars": 10103,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>addon/utils/parse-property-expression.js -"
  },
  {
    "path": "docs/files/app_components_nf-area-stack.js.html",
    "chars": 11253,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>app/components/nf-area-stack.js - ember-nf"
  },
  {
    "path": "docs/files/app_components_nf-area.js.html",
    "chars": 14044,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>app/components/nf-area.js - ember-nf-graph"
  },
  {
    "path": "docs/files/app_components_nf-bars.js.html",
    "chars": 13559,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>app/components/nf-bars.js - ember-nf-graph"
  },
  {
    "path": "docs/files/app_components_nf-crosshair.js.html",
    "chars": 10133,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>app/components/nf-crosshair.js - ember-nf-"
  },
  {
    "path": "docs/files/app_components_nf-dot.js.html",
    "chars": 10436,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>app/components/nf-dot.js - ember-nf-graph<"
  },
  {
    "path": "docs/files/app_components_nf-gg.js.html",
    "chars": 9605,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>app/components/nf-gg.js - ember-nf-graph</"
  },
  {
    "path": "docs/files/app_components_nf-graph-content.js.html",
    "chars": 12359,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>app/components/nf-graph-content.js - ember"
  },
  {
    "path": "docs/files/app_components_nf-graph.js.html",
    "chars": 43711,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>app/components/nf-graph.js - ember-nf-grap"
  }
]

// ... and 90 more files (download for full content)

About this extraction

This page contains the full source code of the Netflix/ember-nf-graph GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 290 files (2.4 MB), approximately 655.8k tokens, and a symbol index with 147 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!