npm package: @hackcapital/hd3
Description: A module D3 library for React.
Author: Mark Niehe
Latest version: 1.0.4

# HD3 (_HackCapital D3_)
## Installation

`npm install @hackcapital/hd3`

## Examples

### Basic usage:
Here's an example of a very basic graph:
```javascript
import React, { Component } from 'react';
import { scaleLinear } from 'd3-scale';
import { line } from 'd3-shape';
import { GraphElement, Graph } from '@hackcapital/hd3';

// Some random test data
const testData = [
  { x: 1, y: 2 },
  { x: 2, y: 4 },
  { x: 3, y: 9 },
  { x: 4, y: 15 },
  { x: 5, y: 5 },
];

/**
 * This is a very basic Line that extends the GraphElement. The only method that needs to
 * exist within a class that extends GraphElement is the `draw()` method. This is the one
 * that will be called by the parent Graph to actually draw the element.
 * 
 * Inside the `draw()` function you will have access to:
 * - `this.graph` is a reference to the base graph.
 *   - `this.graph.plotNode` is a reference to the base SVG element that's already wrapped by
 *     the D3 select function.
 * - `this.props` includes any properties that were passed in to this element.
 */
class Line extends GraphElement {
  draw() {

    // Take the data that was passed in to the props.
    const { data } = this.props;
    
    // Define a basic line using the x and y scale of the parent graph.
    const meanLine = line()
      .x(d => this.graph.xScale(d.x))
      .y(d => this.graph.yScale(d.y));

    const lineObj = this.graph.plotNode
      .append('g')
      .append('path')
      .attr('d', meanLine(data));

    this.applyStyles(lineObj);
  }
}

/**
 * This component will be the wrapper for the graph. If we need to position the graph in an specific
 * way, it should be done using the wrapper div.
 */
class BasicGraph extends Component {
  render() {

    // Find the xRange and yRange of our data set
    const xRange = [Number.MAX_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER];
    const yRange = [Number.MAX_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER];
    
    for (let point of testData) {
      if (point.x < xRange[0]) {
        xRange[0] = point.x;
      } else if (point.x > xRange[1]) {
        xRange[1] = point.x;
      }

      if (point.y < yRange[0]) {
        yRange[0] = point.y;
      } else if (point.y > yRange[1]) {
        yRange[1] = point.y;
      }
    }

    return (
      <div>
        <Graph
          name={'basic-graph'}
          style={{ backgroundColor: 'blue' }}
          xRange={xRange}
          yRange={yRange}
          scaleType={scaleLinear}
          height={500}
          width={500}
        >
          <Line 
            data={testData} 
            name={'scope'}
            style={{ fill: 'none', 'stroke-width': 2, stroke: '#50E7F1' }}
          />
        </Graph>
      </div>
    );
  }
}

export default BasicGraph;
```

### Responsive Graph:
This next example demonstrates how to make that same graph responsive. When the 
`responsive` property is set to true on the `Graph` component, the graph will size
itself based on whatever it is placed inside of. In this example, the `Graph` is
placed inside a `div` that is placed in the middle of the page and sized to be
50% of the page.
```javascript
import React, { Component } from 'react';
import { scaleLinear } from 'd3-scale';
import { line } from 'd3-shape';
import { GraphElement, Graph } from '@hackcapital/hd3';

// Some random test data
const testData = [
  { x: 1, y: 2 },
  { x: 2, y: 4 },
  { x: 3, y: 9 },
  { x: 4, y: 15 },
  { x: 5, y: 5 },
];

class Line extends GraphElement {
  draw() {

    // Take the data that was passed in to the props.
    const { data } = this.props;
    
    // Define a basic line using the x and y scale of the parent graph.
    const meanLine = line()
      .x(d => this.graph.xScale(d.x))
      .y(d => this.graph.yScale(d.y));

    const lineObj = this.graph.plotNode
      .append('g')
      .append('path')
      .attr('d', meanLine(data));

    this.applyStyles(lineObj);
  }
}

class BasicResponsiveGraph extends Component {
  render() {

    // Find the xRange and yRange of our data set
    const xRange = [Number.MAX_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER];
    const yRange = [Number.MAX_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER];
    
    for (let point of testData) {
      if (point.x < xRange[0]) {
        xRange[0] = point.x;
      } else if (point.x > xRange[1]) {
        xRange[1] = point.x;
      }

      if (point.y < yRange[0]) {
        yRange[0] = point.y;
      } else if (point.y > yRange[1]) {
        yRange[1] = point.y;
      }
    }

    return (
      <div
        style={{ 
          width: '100vw', 
          height: '100vh', 
          backgroundColor: 'pink',
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center'
        }}
      >
        <div
          style={{ width: '50vw', height: '50vh' }}
        >
          <Graph
            name={'basic-graph'}
            style={{ backgroundColor: 'blue' }}
            xRange={xRange}
            yRange={yRange}
            scaleType={scaleLinear}
            height={500}
            width={500}
            responsive={true} // Responsive defaults to false. Set it to true here to enable responsiveness.
          >
            <Line 
              data={testData} 
              name={'scope'}
              style={{ fill: 'none', 'stroke-width': 2, stroke: '#50E7F1' }}
            />
          </Graph>
        </div>
      </div>
    );
  }
}

export default BasicResponsiveGraph;
```

### An Element Outside
This example shows how to handle a `GraphElement` that needs to be drawn outside the plot. For example,
lets say you have an X-axis that should be drawn below the graph.
```javascript
import React, { Component } from 'react';
import { scaleLinear } from 'd3-scale';
import { line } from 'd3-shape';
import { axisBottom } from 'd3-axis';
import { GraphElement, Graph } from '@hackcapital/hd3';

// Some random test data
const testData = [
  { x: 1, y: 2 },
  { x: 2, y: 4 },
  { x: 3, y: 9 },
  { x: 4, y: 15 },
  { x: 5, y: 5 },
];

class Line extends GraphElement {
  draw() {

    // Take the data that was passed in to the props.
    const { data } = this.props;
    
    // Define a basic line using the x and y scale of the parent graph.
    const meanLine = line()
      .x(d => this.graph.xScale(d.x))
      .y(d => this.graph.yScale(d.y));

    const lineObj = this.graph.plotNode
      .append('g')
      .append('path')
      .attr('d', meanLine(data));

    this.applyStyles(lineObj);
  }
}

/**
 * A basic X-Axis that would normally be outside the bounds of the SVG. We can
 * add padding to account for this element by explicitly stating how much
 * padding will be need and in what direction.
 */
class XAxis extends GraphElement {

  /**
   * Overriding the paddingBottom to `30`, will let the parent graph know that
   * this element needs at least an extra `30px` to be able to render properly.
   */
  get paddingBottom() {
    return 30;
  }

  /**
   * The rest of the directions do not need to be set to `0` since they default
   * to `0`. They are set here to show how they can be set.
   */
  get paddingTop() {
    return 0;
  }

  get paddingRight() {
    return 0;
  }

  get paddingLeft() {
    return 0;
  }

  draw() {
    let axis = axisBottom()
      .scale(this.graph.xScale);

    const axisNode = this.graph.plotNode
      .append('g')
      .attr('transform', `translate(0, ${this.graph.height + 25})`)
      .call(axis);
    
    this.applyStyles(axisNode);
  }
}

class BasicPaddingGraph extends Component {
  render() {

    // Find the xRange and yRange of our data set
    const xRange = [Number.MAX_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER];
    const yRange = [Number.MAX_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER];
    
    for (let point of testData) {
      if (point.x < xRange[0]) {
        xRange[0] = point.x;
      } else if (point.x > xRange[1]) {
        xRange[1] = point.x;
      }

      if (point.y < yRange[0]) {
        yRange[0] = point.y;
      } else if (point.y > yRange[1]) {
        yRange[1] = point.y;
      }
    }

    /**
     * Any padding that is set on the `Graph` component will be added to the
     * largest minimum of all the children.
     */
    return (
      <div>
        <Graph
          name={'basic-graph'}
          style={{ backgroundColor: 'blue', padding: 20 }}
          xRange={xRange}
          yRange={yRange}
          scaleType={scaleLinear}
          height={500}
          width={500}
        >
          <Line 
            data={testData} 
            name={'scope'}
            style={{ fill: 'none', 'stroke-width': 2, stroke: 'red' }}
          />
          <XAxis style={{ color: 'white' }}/>
        </Graph>
      </div>
    );
  }
}

export default BasicPaddingGraph;
```

## API

### `Graph` 
- Component Properties:
  - `name`: The name for the graph being drawn.
  - `style`: Regular styling object that React Javascript uses. (ie. `{ "color": "blue" }`)
  - `xRange`: An array with 2 elements. First element is the minimum of the X-range and the second is the maximum of the X-range. (ie. `[0, 15]`)
  - `yRange`: An array with 2 elements. First element is the minimum of the Y-range and the second is the maximum of the Y-range. (ie. `[0, 15]`)
  - `scaleType`: The D3 scale function to be used for this graph. (ie. `scaleLinear`, `scaleLog`)
  - `height`: Height for the graph in pixels. This will be ignored if `responsive` is set. (ie. `400` is `400px`)
  - `width`: Width for the graph in pixels. This will be ignore if `responsive` is set. (ie. `400` is `400px`)
  - `responsive`: Use this to force you graph to take up the entire height and width of the parent element (whatever that size may be).
- API (accessible from within `GraphElements`):
  - `plotNode`: An instance of a D3 selection for the plot that was drawn for the graph. All `GraphElements` should draw themselves within this.
  - `height`: Height of the plot in pixels.
  - `width`: Width of the plot in pixels.
  - `name`: Name of the graph.
  - `xRange`: Minimum and maximum of the X-Axis. (ie. `[0, 15]`)
  - `yRange`: Minimum and maximum of the Y-Axis. (ie. `[0, 15]`)
  - `xScale`: The X-scale generated by the `xRange` and the `scaleType`.
  - `yScale`: The Y-scale generated by the `YRange` and the `scaleType`.

### `GraphElement`
- Component Properties
  - `style`: Any styling that should be applied using the `applyStyles()` method. *Any styles that are passed in should be the regular CSS3 
             property names rather than the React style property names (which are `camelCase`). (ie. use `stroke-width` instead of `strokeWidth`).
  - Any property can be passed in here and be accessible within the `draw()` method. The only reserved property is `style`.
- API:
  - `draw()`: The function that will be called by the parent `Graph` component during rendering.
  - `applyStyles(node)`: Apply any styles that were passed to the component to a given node.
    - `node`: should be an instance of an element selected using D3's `select()` function.
  - `graph`: The parent `Graph` component.
  - `props`: An object that contains any properties that were passed in to the component.
  - `paddingTop`: The amount of padding in pixels that this element needs on the top of the `Graph`.
  - `paddingRight`: The amount of padding in pixels that this element needs on the right of the `Graph`.
  - `paddingBottom`: The amount of padding in pixels that this element needs on the bottom of the `Graph`.
  - `paddingLeft`: The amount of padding in pixels that this element needs on the left of the `Graph`.