Code Monkey home page Code Monkey logo

xy.js's Introduction

Xy.js

Xy.js is a lightweight, highly-customizable, Canvas-based JavaScript 2D plotting library. It will draw a Chart.js-flavored lovely point-line chart by default.

Demo

Installation

Run the following:

bower install xy

Using the package

Include dist/xy.js or dist/xy.min.js in your html.

Example

Defining datasets

var datasets = [
  {
    lineColor : 'rgba(220,220,220,1)',
    pointColor : 'rgba(220,220,220,1)',
    pointStrokeColor : '#fff',
    data : [[-4, -2], [-2.5, 1.3], [0, 0], [1, 1.5], [3, 1]]
  },
  {
    lineColor : 'rgba(151,187,205,1)',
    pointColor : 'rgba(151,187,205,1)',
    pointStrokeColor : '#fff',
    data : [[-3, 3], [-1, -1], [0.5, 1], [1.5, -3], [2.8, -1.6]]
  }
];

Drawing default chart

var ctx = document.getElementById('canvas').getContext('2d');

var xy = new Xy(ctx);

xy.draw(datasets);

Fig.1

Disable smoothing

var xy = new Xy(ctx, { smooth: false });

xy.draw(datasets);

Fig.2

Hide lines

var xy = new Xy(ctx, { line: false });

xy.draw(datasets);

Fig.3

Hide points

var xy = new Xy(ctx, { point: false });

xy.draw(datasets);

Fig.4

Set fixed axis range

var xy = new Xy(ctx, { rangeX: [0, 'auto'] } ); // set lower bound of x-axis to 0

xy.draw(datasets);

Fig.5

Set fixed tick increment

var xy = new Xy(ctx, { tickStepX: 0.5 });

xy.draw(datasets);

xy.options.tickStepX = 0.34;

xy.draw(datasets);

Fig.6

Fig.7

Live updating

var xy = new Xy(ctx);

setInterval(function() {

  // If the second argument of draw() is set to true, the axis range
  // and ticks will be updated based on the current config and datasets.
  xy.draw(datasets, true);

  // Modify datasets here
}, 1000 / 60);

Strange effect

Large magnitude, negative smoothing factor will produce over smooted, opposite-directed curves.

var xy = new Xy(ctx, { smooth: -3 });

xy.draw(datasets);

Fig.9

Options

Options can be passed to the constructor or set via options property of the instance.

Name Default Description
rangeX ['auto', 'auto'] range of x-axis, [lower, upper], 'auto' = auto setup
rangeY ['auto', 'auto'] range of y-axis, [lower, upper], 'auto' = auto setup
tickStepX 'auto' tick increment of x-axis, 'auto' = auto setup
tickStepY 'auto' tick increment of y-axis, 'auto' = auto setup
scale true visibility of scale lines, true = show, false = hide
scaleColor 'rgba(0,0,0,.1)' color of scale lines
scaleWidth 1 width of scale lines in px
grid true visibility of grid lines, true = show, false = hide
gridColor 'rgba(0,0,0,.05)' color of grid lines
gridWidth 1 width of grid lines in px
label true visibility of labels, true = show, false = hide
labelFontName 'Arial' font name of label characters
labelFontSize 20 font size of label characters in px
labelFontStyle 'normal' font style of label characters
labelColor '#666' color of label characters
point true visibility of points, true = show, false = hide
pointCircleRadius 8 radius of the circles that represent points in px
pointStrokeWidth 4 width of peripheral line of the circles in px
line true visibility of lines, true = show, false = hide
lineWidth 4 width of lines in px
smooth 0.3 degree of smoothing, falsy = disabled smoothing
scalingRadius 'x' type of scaling for the argument radius of the CanvasRenderingContext2D's methods, 'x' = same scaling as x-axis, 'y' = same scaling as y-axis
width undefined horizontal dimension of canvas in px
height undefined vertical dimension of canvas in px

Customization

Overridable methods

In Xy, all drawing operations are customizable. To define your own drawing operations, you can override the following methods:

Xy.prototype.measureLabelSizeX = function(ticks, fontSize, width) {...}

This method should return an object that has width, height and rot properties. The width and height properies should be set the maximum sizes of the labels that are drawn based on the given ticks (array of numbers), fontSize (font size of label characters in px) and width (the length of x-axis range in px) in px. The rot property should be set the rotation angle of the labels.

Xy.prototype.measureLabelSizeY = function(ticks, fontSize) {...}

This method should return an object that has width and height properties. Each property should be set the maximum sizes of the labels that are drawn based on the given ticks (array of numbers) and fontSize (font size of label characters in px) in px.

Xy.prototype.before = function() {}

This method will be called before all drawing operations. You can draw a background graphics in this method.

Xy.prototype.drawXGrids = function(ticks, rangeY) {...}

This method should draw the grid lines that are crossing at given ticks (numbers on the x-axis). The lines should be drawn in the given range of y-axis (rangeY).

Xy.prototype.drawYGrids = function(ticks, rangeX) {...}

This method should the draw grid lines that are crossing at given ticks (numbers on the y-axis). The lines should be drawn in the given range of x-axis (rangeX).

Xy.prototype.drawXScale = function(rangeX, ticks, y) {...}

This method should draw the scale line that are crossing at given point of the y-axis (y). The line should be drawn in the given range of the x-axis (rangeX). You can use ticks (numbers on the x-axis) to draw ticks.

Xy.prototype.drawYScale = function(rangeY, ticks, x) {...}

This method should draw the scale line that are crossing at given point of the x-axis (x). The line should be drawn in the given range of the y-axis (rangeY). You can use ticks (numbers on the y-axis) to draw ticks.

Xy.prototype.drawXLabels = function(ticks, y, rot) {...}

This method should draw the labels (stringified ticks's numbers) for the x-axis. The top of the label characters should be located at y. The lebels should be rotated at given angle (rot).

Xy.prototype.drawYLabels = function(ticks, x) {...}

This method should draw the labels (stringified ticks's numbers) for the y-axis. The right edge of the labels should be located at x.

Xy.prototype.formatXLabel = function(value) {...}

You can specify the format of the labels for the x-axis by returning a formatted string based on each value of tick's numbers.

Xy.prototype.formatYLabel = function(value) {...}

You can specify the format of the labels for the y-axis by returning a formatted string based on each value of tick's numbers.

Xy.prototype.drawLines = function(datasets) {...}

This method should draw lines or cureves based on the given datasets.

Xy.prototype.drawPoints = function(datasets) {...}

This method should plot points (circles typically) based on the given datasets.

Xy.prototype.plot = function(datasets) {}

You can plot something (based on datasets typically) to the clipped chart region.

Xy.prototype.after = function() {}

This method will be called after all drawing operations. You can draw an overlaid graphics in this method.

Xy.prototype.draw = function(datasets, update) {...}

This method should draw a chart based on the given datasets by using other abstract methods. The method also should call the updateChart internal function to update scales, ranges and ticks for the chart with Xy's core. If update is true, the update should be forced.

Xy.prototype.hitTest = function(datasets, x, y) {...}

This method should return data points (subset of given datasets) that are intersected by given relative position (x, y) in canvas element.

Helper

Xy.extend = function(protoProps, staticProps) {...}

Helper function to correctly set up the prototype chain, for subclasses. You can create a subclass that overrides Xy's prototype methods by passing a hash of custom methods as protoProps.

Drawing

To draw something freely in the above methods, you can use directly the CanvasRenderingContext2D's methods and its proxies, via the ctx / ctx.xy / ctx.xywhr / ctx.nxy / ctx.nxywhr properties of the instance.

Name Description
ctx an instance of the CanvasRenderingContext2D
ctx.xy an object that has proxies of the CanvasRenderingContext2D's methods. The arguments x and y can be specified as the coordinates of a standard x-y coordinate system.
ctx.xywhr an extended version of ctx.xy. The arguments width, height and radius can be specified based on the scale of the coordinate system in addition to x and y.
ctx.nxy an object that has proxies of the CanvasRenderingContext2D's methods. The arguments x and y can be specified as the coordinates of a normalized version of a x-y coordinate system (the length of x/y-axis range in the chart is normalized to 1).
ctx.nxywhr an extended version of ctx.nxy. The arguments width, height and radius can be specified based on the scale of the coordinate system in addition to x and y.

Examples

Example 1

Here is an example that overrides before and plot methods.

// Get a Canvas 2d context.
var ctx = document.getElementById('myCanvas').getContext('2d');

// Create an instance of `Xy`.
var xy = new Xy(ctx, {
  point: false,
  smooth: -0.6
});

// A function to generate color randomly.
function genColor() {
  function c() { return (Math.round(Math.random() * 255)); }
  var rgb = [c(), c(), c()];
  return '#' + ((rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16);
}

// Define datasets
var datasets = [
  {
    lineColor: 'rgba(220,220,220,0.5)',
    pointStrokeColor: '#fff',
    data: [
      // x, y, radius, color
      [ 1,  0, 0.05, genColor()],
      [-1,  0,  0.1, genColor()],
      [ 0,  1, 0.15, genColor()],
      [ 0, -1,  0.2, genColor()],
    ]
  }
];

var rectColor = genColor();

// Override the `before`.
xy.before = function() {
  var ctx = this.ctx;
  ctx.fillStyle = rectColor;

  ctx.beginPath();
  ctx.xywhr.rect(-0.75, 0.6, 0.2, 0.2);
  ctx.xywhr.rect(-0.75, -0.6, 1.5, 0.2);
  ctx.fill();
};

// Override the `plot`.
xy.plot = function(datasets) {
  var ctx = this.ctx;

  for (var i = 0; i < datasets.length; i++) {
    var data = datasets[i].data;
    ctx.strokeStyle = datasets[i].pointStrokeColor;

    for (var j = 0; j < data.length; j++) {
      ctx.fillStyle = data[j][3];

      ctx.beginPath();
      ctx.xywhr.arc(data[j][0], data[j][1], data[j][2], 0, Math.PI * 2);
      ctx.fill();
      ctx.stroke();
    }

    ctx.textBaseline = 'middle';
    ctx.textAlign = 'center';

    for (var j = 0; j < data.length; j++) {
      ctx.fillStyle = data[j > 0 ? j - 1 : data.length - 1][3];
      var x = data[j][0];
      var y = data[j][1];
      ctx.xy.fillText('(' + x.toPrecision(2) + ',' + y.toPrecision(2) + ')', x, y);
    }
  }
};

// Animation loop
setInterval(function() {

  // Draw chart
  xy.draw(datasets);

  // Change the coordinates
  for (var i = 0; i < datasets.length; i++) {
    var data = datasets[i].data;
    for (var j = 0; j < data.length; j++) {
      data[j][0] += (Math.random() - 0.5) * 0.0165;
      data[j][1] += (Math.random() - 0.5) * 0.0165;
    }
  }
}, 1000 / 60);

Customize01

JSFiddle

Example 2

Here is an example that overrides formatXLabel method to display date string for x-labels. This example also adds a draggable control for changing chart range with Angular.

angular.module('example', [])
.config(function($provide) {

  // Chart settings.
  $provide.factory('options', function($window) {
    return {
      width: $window.document.querySelector('body > div').clientWidth,
      height: 180,
      labelFontSize: 15,
      pointCircleRadius: 4,
      pointStrokeWidth: 2,
      lineWidth: 2,
      smooth: 0
    }
  });

  $provide.constant('today', (new Date((new Date()).toDateString())).getTime());

  // Service for generating mock datasets.
  $provide.factory('datasets', function($window) {

    function gen() {
      var numPoints = 100;
      var magY = 200;

      function genDataPoints() {
        var points = [];
        for (var i = -numPoints; i < 0; i++) {
          points.push([i + 1, Math.random() * magY]);
        }
        return points;
      }

      return [
        {
          lineColor : 'rgba(220,220,220,1)',
          pointColor : 'rgba(220,220,220,1)',
          pointStrokeColor : '#fff',
          data : genDataPoints()
        },
        {
          lineColor : 'rgba(151,187,205,1)',
          pointColor : 'rgba(151,187,205,1)',
          pointStrokeColor : '#fff',
          data : genDataPoints()
        }
      ];
    }

    return {
      gen: gen
    };
  });
})
// Canvas based chart using Xy.
.directive('chart', function(datasets, today, options, $window, $filter) {

  function link(scope, element, attrs) {

    var ds = datasets.gen();

    scope.$watch('rangeX', function(value) {
      var data = ds[0].data;
      var start = data[0][0]
      var end = data[data.length - 1][0];
      xy.options.rangeX = [Math.max(start, end - (end - start) * value), end];
      xy.draw(ds, true);
    });

    scope.$watch('rangeY', function(value) {
      xy.options.rangeY = [0, value * 200];
      xy.draw(ds, true);
    });

    var xy = new Xy(element.find('canvas')[0].getContext('2d'), options);

    var DAY = 24 * 60 * 60 * 1000;

    // Mix-in a custom `formatXLabel` that converts integer value to date string.
    // It is also possible to override the Xy.prototype.formatXLabel.
    xy.formatXLabel = function(value) {
      return $filter('date')(today + value * DAY, 'yyyy-MM-dd');
    }
  }

  return {
    restrict: 'E',
    scope: {
      rangeX: '=',
      rangeY: '='
    },
    link: link,
    template: '<canvas></canvas>'
  };
})
// Draggable to change chart range. (based on example code of Angular's doc)
.directive('draggable', function($document) {
  function link(scope, element, attr) {
    var startX = 0, startY = 0, x = 0, y = 0;

    element.css({
     position: 'relative',
     border: '1px solid red',
     backgroundColor: 'lightgrey',
     cursor: 'pointer'
    });

    element.on('mousedown', function(event) {
      event.preventDefault();
      startX = event.pageX - x;
      startY = event.pageY - y;
      $document.on('mousemove', mousemove);
      $document.on('mouseup', mouseup);
    });

    var canvas = $document.find('canvas')[0];

    function mousemove(event) {
      y = event.pageY - startY;
      x = event.pageX - startX;
      element.css({
        top: y + 'px',
        left: x + 'px'
      });
      scope.$apply(function(scope) {
        scope.rangeX = Math.max(0, x / canvas.clientWidth);
        scope.rangeY = Math.max(0, y / canvas.clientHeight);
      });
    }

    function mouseup() {
      $document.off('mousemove', mousemove);
      $document.off('mouseup', mouseup);
    }
  };

  return {
    link: link
  }
});
<div ng-app="example" ng-init="rangeX=0; rangeY=0;">
  <span draggable>
    Drag me
  </span>
  <br>
  <chart range-x="rangeX" range-y="rangeY"></chart>
  <chart range-x="rangeX" range-y="rangeY"></chart>
</div>

Customize02

JSFiddle

Example 3

Here is an example that uses hitTest method for displaying a DOM-based tooltip.

// Get a DOM element of tooltip.
var tooltip = document.getElementById('tooltip');

// Get a Canvas element.
var canvas = document.getElementById('myCanvas');

// Get a Canvas 2d context.
var ctx = canvas.getContext('2d');

// Create an instance of `Xy`.
var xy = new Xy(ctx);

// Define datasets
var datasets = [
  {
    lineColor : 'rgba(220,220,220,1)',
    pointColor : 'rgba(220,220,220,1)',
    pointStrokeColor : '#fff',
    data : [[-4, -2], [-2.5, 1.3], [0, 0], [1, 1.5], [3, 1]]
  },
  {
    lineColor : 'rgba(151,187,205,1)',
    pointColor : 'rgba(151,187,205,1)',
    pointStrokeColor : '#fff',
    data : [[-3, 3], [-1, -1], [0.5, 1], [1.5, -3], [2.8, -1.6]]
  }
];

// Setup event listener for mousemove.
canvas.addEventListener('mousemove', function(e) {

  var boundingRect = canvas.getBoundingClientRect();
  var mouseX = e.clientX - boundingRect.left;
  var mouseY = e.clientY - boundingRect.top;

  // Get intersected points.
  var points = xy.hitTest(datasets, mouseX, mouseY);

  if (points.length > 0) {
    // Display tooltip.
    tooltip.innerHTML = points[0].data;
    tooltip.style.left = (mouseX + 25) + 'px';
    tooltip.style.top = (mouseY - 7) + 'px';
    tooltip.style.display = 'block';
  } else {
    tooltip.style.display = 'none';
  }

}, false);

// Draw chart
xy.draw(datasets);

Customize03

JSFiddle

License

Copyright (c) 2013-2014 thunder9 licensed under the MIT license.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.