@hackcapital/hd3

1.0.4 • Public • Published

HD3 (HackCapital D3)

Installation

npm install @hackcapital/hd3

Examples

Basic usage:

Here's an example of a very basic graph:

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.

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.

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.

Readme

Keywords

Package Sidebar

Install

npm i @hackcapital/hd3

Weekly Downloads

0

Version

1.0.4

License

MIT

Unpacked Size

37 kB

Total Files

8

Last publish

Collaborators

  • alexkushner
  • brettcampbell
  • hackcapitaladmin
  • slajax
  • hackcapitalbot
  • jeff_johnson