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.
Run the following:
bower install xy
Include dist/xy.js
or dist/xy.min.js
in your html.
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]]
}
];
var ctx = document.getElementById('canvas').getContext('2d');
var xy = new Xy(ctx);
xy.draw(datasets);
var xy = new Xy(ctx, { smooth: false });
xy.draw(datasets);
var xy = new Xy(ctx, { line: false });
xy.draw(datasets);
var xy = new Xy(ctx, { point: false });
xy.draw(datasets);
var xy = new Xy(ctx, { rangeX: [0, 'auto'] } ); // set lower bound of x-axis to 0
xy.draw(datasets);
var xy = new Xy(ctx, { tickStepX: 0.5 });
xy.draw(datasets);
xy.options.tickStepX = 0.34;
xy.draw(datasets);
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);
Large magnitude, negative smoothing factor will produce over smooted, opposite-directed curves.
var xy = new Xy(ctx, { smooth: -3 });
xy.draw(datasets);
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 |
In Xy, all drawing operations are customizable. To define your own drawing operations, you can override the following methods:
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.
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.
This method will be called before all drawing operations. You can draw a background graphics in this method.
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
).
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
).
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.
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.
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
).
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
.
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.
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.
This method should draw lines or cureves based on the given datasets
.
This method should plot points (circles typically) based on the given datasets
.
You can plot something (based on datasets
typically) to the clipped chart region.
This method will be called after all drawing operations. You can draw an overlaid graphics in this method.
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.
This method should return data points (subset of given datasets
) that are intersected by given relative position (x
, y
) in canvas element.
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
.
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 . |
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);
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>
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);
Copyright (c) 2013-2014 thunder9 licensed under the MIT license.