Initial commit

This commit is contained in:
Khanh Ngo
2015-12-13 16:34:12 +07:00
commit 2dac8205f6
3113 changed files with 514935 additions and 0 deletions

View File

@ -0,0 +1,234 @@
# Morris.js - pretty time-series line graphs
[![Build Status](https://secure.travis-ci.org/morrisjs/morris.js.png?branch=master)](http://travis-ci.org/morrisjs/morris.js)
Morris.js is the library that powers the graphs on http://howmanyleft.co.uk/.
It's a very simple API for drawing line, bar, area and donut charts.
Cheers!
\- Olly (olly@oesmith.co.uk)
## Contributors wanted
I'm unfortunately not able to actively support Morris.js any more. I keep an eye
on the issues, but I rarely have the time to fix bugs or review pull requests.
If you're interested in actively contributing to Morris.js, please contact me on
the email address above.
## Requirements
- [jQuery](http://jquery.com/) (>= 1.7 recommended, but it'll probably work with
older versions)
- [Raphael.js](http://raphaeljs.com/) (>= 2.0)
## Usage
See [the website](http://morrisjs.github.com/morris.js/).
## Development
Very daring.
Fork, hack, possibly even add some tests, then send a pull request :)
Remember that Morris.js is a coffeescript project. Please make your changes in
the `.coffee` files, not in the compiled javascript files in the root directory
of the project.
### Developer quick-start
You'll need [node.js](https://nodejs.org). I recommend using
[nvm](https://github.com/creationix/nvm) for installing node in
development environments.
With node installed, install [grunt](https://github.com/cowboy/grunt) using
`npm install -g grunt-cli`, and then the rest of the test/build dependencies
with `npm install` in the morris.js project folder.
Once you're all set up, you can compile, minify and run the tests using `grunt`.
Note: I'm experimenting with using perceptual diffs to catch rendering
regressions. Due to font rendering differences between platforms, the pdiff
tests currently *only* pass on OS X.
## Changelog
### 0.5.1 - 15th June 2014
- Fix touch event handling.
- Fix stacked=false in bar chart [#275](https://github.com/morrisjs/morris.js/issues/275)
- Configurable vertical segments [#297](https://github.com/morrisjs/morris.js/issues/297)
- Deprecate continuousLine option.
### 0.5.0 - 19th March 2014
- Update grunt dependency [#288](https://github.com/morrisjs/morris.js/issues/228)
- Donut segment color config in data objects [#281](https://github.com/morrisjs/morris.js/issues/281)
- Customisable line widths and point drawing [#272](https://github.com/morrisjs/morris.js/issues/272)
- Bugfix for @options.smooth [#266](https://github.com/morrisjs/morris.js/issues/266)
- Option to disable axes individually [#253](https://github.com/morrisjs/morris.js/issues/253)
- Range selection [#252](https://github.com/morrisjs/morris.js/issues/252)
- Week format for x-labels [#250](https://github.com/morrisjs/morris.js/issues/250)
- Update developer quickstart instructions [#243](https://github.com/morrisjs/morris.js/issues/243)
- Experimenting with perceptual diffs.
- Add original data row to hover callback [#264](https://github.com/morrisjs/morris.js/issues/264)
- setData method for donut charts [#211](https://github.com/morrisjs/morris.js/issues/211)
- Automatic resizing [#111](https://github.com/morrisjs/morris.js/issues/111)
- Fix travis builds [#298](https://github.com/morrisjs/morris.js/issues/298)
- Option for rounded corners on bar charts [#305](https://github.com/morrisjs/morris.js/issues/305)
- Option to set padding for X axis labels [#306](https://github.com/morrisjs/morris.js/issues/306)
- Use local javascript for examples.
- Events on non-time series [#314](https://github.com/morrisjs/morris.js/issues/314)
### 0.4.3 - 12th May 2013
- Fix flickering hover box [#186](https://github.com/morrisjs/morris.js/issues/186)
- xLabelAngle option (diagonal labels!!) [#239](https://github.com/morrisjs/morris.js/issues/239)
- Fix area chart fill bug [#190](https://github.com/morrisjs/morris.js/issues/190)
- Make event handlers chainable
- gridTextFamily and gridTextWeight options
- Fix hovers with setData [#213](https://github.com/morrisjs/morris.js/issues/213)
- Fix hideHover behaviour [#236](https://github.com/morrisjs/morris.js/issues/236)
### 0.4.2 - 14th April 2013
- Fix DST handling [#191](https://github.com/morrisjs/morris.js/issues/191)
- Parse data values from strings in Morris.Donut [#189](https://github.com/morrisjs/morris.js/issues/189)
- Non-cumulative area charts [#199](https://github.com/morrisjs/morris.js/issues/199)
- Round Y-axis labels to significant numbers [#162](https://github.com/morrisjs/morris.js/162)
- Customising default hover content [#179](https://github.com/morrisjs/morris.js/179)
### 0.4.1 - 8th February 2013
- Fix goal and event rendering. [#181](https://github.com/morrisjs/morris.js/issues/181)
- Don't break when empty data is passed to setData [#142](https://github.com/morrisjs/morris.js/issues/142)
- labelColor option for donuts [#159](https://github.com/morrisjs/morris.js/issues/159)
### 0.4.0 - 26th January 2013
- Goals and events [#103](https://github.com/morrisjs/morris.js/issues/103).
- Bower package manager metadata.
- More flexible formatters [#107](https://github.com/morrisjs/morris.js/issues/107).
- Color callbacks.
- Decade intervals for time-axis labels.
- Non-continous line tweaks [#116](https://github.com/morrisjs/morris.js/issues/116).
- Stacked bars [#120](https://github.com/morrisjs/morris.js/issues/120).
- HTML hover [#134](https://github.com/morrisjs/morris.js/issues/134).
- yLabelFormat [#139](https://github.com/morrisjs/morris.js/issues/139).
- Disable axes [#114](https://github.com/morrisjs/morris.js/issues/114).
### 0.3.3 - 1st November 2012
- **Bar charts!** [#101](https://github.com/morrisjs/morris.js/issues/101).
### 0.3.2 - 28th October 2012
- **Area charts!** [#47](https://github.com/morrisjs/morris.js/issues/47).
- Some major refactoring and test suite improvements.
- Set smooth parameter per series [#91](https://github.com/morrisjs/morris.js/issues/91).
- Custom dateFormat for string x-values [#90](https://github.com/morrisjs/morris.js/issues/90).
### 0.3.1 - 13th October 2012
- Add `formatter` option for customising value labels in donuts [#75](https://github.com/morrisjs/morris.js/issues/75).
- Cycle `lineColors` on line charts to avoid running out of colours [#78](https://github.com/morrisjs/morris.js/issues/78).
- Add method to select donut segments. [#79](https://github.com/morrisjs/morris.js/issues/79).
- Don't go negative on yMin when all y values are zero. [#80](https://github.com/morrisjs/morris.js/issues/80).
- Don't sort data when parseTime is false [#83](https://github.com/morrisjs/morris.js/issues/83).
- Customise styling for points. [#87](https://github.com/morrisjs/morris.js/issues/87).
### 0.3.0 - 15th September 2012
- Donut charts!
- Bugfix: ymin/ymax bug [#71](https://github.com/morrisjs/morris.js/issues/71).
- Bugfix: infinite loop when data indicates horizontal line [#66](https://github.com/morrisjs/morris.js/issues/66).
### 0.2.10 - 26th June 2012
- Support for decimal labels on y-axis [#58](https://github.com/morrisjs/morris.js/issues/58).
- Better axis label clipping [#63](https://github.com/morrisjs/morris.js/issues/63).
- Redraw graphs with updated data using `setData` method [#64](https://github.com/morrisjs/morris.js/issues/64).
- Bugfix: series with zero or one non-null values [#65](https://github.com/morrisjs/morris.js/issues/65).
### 0.2.9 - 15th May 2012
- Bugfix: Fix zero-value regression
- Bugfix: Don't modify user-supplied data
### 0.2.8 - 10th May 2012
- Customising x-axis labels with `xLabelFormat` option
- Only use timezones when timezone info is specified
- Fix old IE bugs (mostly in examples!)
- Added `preunits` and `postunits` options
- Better non-continuous series data support
### 0.2.7 - 2nd April 2012
- Added `xLabels` option
- Refactored x-axis labelling
- Better ISO date support
- Fix bug with single value in non time-series graphs
### 0.2.6 - 18th March 2012
- Partial series support (see `null` y-values in `examples/quarters.html`)
- `parseTime` option bugfix for non-time-series data
### 0.2.5 - 15th March 2012
- Raw millisecond timestamp support (with `dateFormat` option)
- YYYY-MM-DD HH:MM[:SS[.SSS]] date support
- Decimal number labels
### 0.2.4 - 8th March 2012
- Negative y-values support
- `ymin` option
- `units` options
### 0.2.3 - 6th Mar 2012
- jQuery no-conflict compatibility
- Support ISO week-number dates
- Optionally hide hover on mouseout (`hideHover`)
- Optionally skip parsing dates, treating X values as an equally-spaced series (`parseTime`)
### 0.2.2 - 29th Feb 2012
- Bugfix: mouseover error when options.data.length == 2
- Automatically sort options.data
### 0.2.1 - 28th Feb 2012
- Accept a DOM element *or* an ID in `options.element`
- Add `smooth` option
- Bugfix: clone `@default`
- Add `ymax` option
## License
Copyright (c) 2012-2014, Olly Smith
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,18 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
</head>
<body>
<h1>Title</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
// Insert code here:
// it'll get eval()-ed and prettyprinted.
</pre>
</body>

View File

@ -0,0 +1,31 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Area charts behaving like line charts</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
// Use Morris.Area instead of Morris.Line
Morris.Area({
element: 'graph',
behaveLikeLine: true,
data: [
{x: '2011 Q1', y: 3, z: 3},
{x: '2011 Q2', y: 2, z: 1},
{x: '2011 Q3', y: 2, z: 4},
{x: '2011 Q4', y: 3, z: 3}
],
xkey: 'x',
ykeys: ['y', 'z'],
labels: ['Y', 'Z']
});
</pre>
</body>

View File

@ -0,0 +1,34 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Area charts</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
// Use Morris.Area instead of Morris.Line
Morris.Area({
element: 'graph',
data: [
{x: '2010 Q4', y: 3, z: 7},
{x: '2011 Q1', y: 3, z: 4},
{x: '2011 Q2', y: null, z: 1},
{x: '2011 Q3', y: 2, z: 5},
{x: '2011 Q4', y: 8, z: 2},
{x: '2012 Q1', y: 4, z: 4}
],
xkey: 'x',
ykeys: ['y', 'z'],
labels: ['Y', 'Z']
}).on('click', function(i, row){
console.log(i, row);
});
</pre>
</body>

View File

@ -0,0 +1,44 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Bar charts</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
// Use Morris.Bar
Morris.Bar({
element: 'graph',
data: [
{x: '2011 Q1', y: 0},
{x: '2011 Q2', y: 1},
{x: '2011 Q3', y: 2},
{x: '2011 Q4', y: 3},
{x: '2012 Q1', y: 4},
{x: '2012 Q2', y: 5},
{x: '2012 Q3', y: 6},
{x: '2012 Q4', y: 7},
{x: '2013 Q1', y: 8}
],
xkey: 'x',
ykeys: ['y'],
labels: ['Y'],
barColors: function (row, series, type) {
if (type === 'bar') {
var red = Math.ceil(255 * row.y / this.ymax);
return 'rgb(' + red + ',0,0)';
}
else {
return '#000';
}
}
});
</pre>
</body>

View File

@ -0,0 +1,31 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Bar charts</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
// Use Morris.Bar
Morris.Bar({
element: 'graph',
axes: false,
data: [
{x: '2011 Q1', y: 3, z: 2, a: 3},
{x: '2011 Q2', y: 2, z: null, a: 1},
{x: '2011 Q3', y: 0, z: 2, a: 4},
{x: '2011 Q4', y: 2, z: 4, a: 3}
],
xkey: 'x',
ykeys: ['y', 'z', 'a'],
labels: ['Y', 'Z', 'A']
});
</pre>
</body>

View File

@ -0,0 +1,32 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Bar charts</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
// Use Morris.Bar
Morris.Bar({
element: 'graph',
data: [
{x: '2011 Q1', y: 3, z: 2, a: 3},
{x: '2011 Q2', y: 2, z: null, a: 1},
{x: '2011 Q3', y: 0, z: 2, a: 4},
{x: '2011 Q4', y: 2, z: 4, a: 3}
],
xkey: 'x',
ykeys: ['y', 'z', 'a'],
labels: ['Y', 'Z', 'A']
}).on('click', function(i, row){
console.log(i, row);
});
</pre>
</body>

View File

@ -0,0 +1,37 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates YYYY-MM-DD</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
{"period": "2012-10-01", "licensed": 3407, "sorned": 660},
{"period": "2012-09-30", "licensed": 3351, "sorned": 629},
{"period": "2012-09-29", "licensed": 3269, "sorned": 618},
{"period": "2012-09-20", "licensed": 3246, "sorned": 661},
{"period": "2012-09-19", "licensed": 3257, "sorned": 667},
{"period": "2012-09-18", "licensed": 3248, "sorned": 627},
{"period": "2012-09-17", "licensed": 3171, "sorned": 660},
{"period": "2012-09-16", "licensed": 3171, "sorned": 676},
{"period": "2012-09-15", "licensed": 3201, "sorned": 656},
{"period": "2012-09-10", "licensed": 3215, "sorned": 622}
];
Morris.Line({
element: 'graph',
data: day_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN']
});
</pre>
</body>

View File

@ -0,0 +1,37 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Decimal Data</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
var decimal_data = [];
for (var x = 0; x <= 360; x += 10) {
decimal_data.push({
x: x,
y: 1.5 + 1.5 * Math.sin(Math.PI * x / 180).toFixed(4)
});
}
window.m = Morris.Line({
element: 'graph',
data: decimal_data,
xkey: 'x',
ykeys: ['y'],
labels: ['sin(x)'],
parseTime: false,
hoverCallback: function (index, options, default_content, row) {
return default_content.replace("sin(x)", "1.5 + 1.5 sin(" + row.x + ")");
},
xLabelMargin: 10,
integerYLabels: true
});
</pre>
</body>

View File

@ -0,0 +1,38 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Displaying X Labels Diagonally (Bar Chart)</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
{"period": "2012-10-01", "licensed": 3407, "sorned": 660},
{"period": "2012-09-30", "licensed": 3351, "sorned": 629},
{"period": "2012-09-29", "licensed": 3269, "sorned": 618},
{"period": "2012-09-20", "licensed": 3246, "sorned": 661},
{"period": "2012-09-19", "licensed": 3257, "sorned": 667},
{"period": "2012-09-18", "licensed": 3248, "sorned": 627},
{"period": "2012-09-17", "licensed": 3171, "sorned": 660},
{"period": "2012-09-16", "licensed": 3171, "sorned": 676},
{"period": "2012-09-15", "licensed": 3201, "sorned": 656},
{"period": "2012-09-10", "licensed": 3215, "sorned": 622}
];
Morris.Bar({
element: 'graph',
data: day_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN'],
xLabelAngle: 60
});
</pre>
</body>

View File

@ -0,0 +1,38 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Displaying X Labels Diagonally</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
{"period": "2012-10-30", "licensed": 3407, "sorned": 660},
{"period": "2012-09-30", "licensed": 3351, "sorned": 629},
{"period": "2012-09-29", "licensed": 3269, "sorned": 618},
{"period": "2012-09-20", "licensed": 3246, "sorned": 661},
{"period": "2012-09-19", "licensed": 3257, "sorned": 667},
{"period": "2012-09-18", "licensed": 3248, "sorned": 627},
{"period": "2012-09-17", "licensed": 3171, "sorned": 660},
{"period": "2012-09-16", "licensed": 3171, "sorned": 676},
{"period": "2012-09-15", "licensed": 3201, "sorned": 656},
{"period": "2012-09-10", "licensed": 3215, "sorned": 622}
];
Morris.Line({
element: 'graph',
data: day_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN'],
xLabelAngle: 60
});
</pre>
</body>

View File

@ -0,0 +1,38 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
<style>
body { background:#ccc; }
</style>
</head>
<body>
<h1>Donut Chart</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
Morris.Donut({
element: 'graph',
data: [
{value: 70, label: 'foo'},
{value: 15, label: 'bar'},
{value: 10, label: 'baz'},
{value: 5, label: 'A really really long label'}
],
backgroundColor: '#ccc',
labelColor: '#060',
colors: [
'#0BA462',
'#39B580',
'#67C69D',
'#95D7BB'
],
formatter: function (x) { return x + "%"}
});
</pre>
</body>

View File

@ -0,0 +1,27 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Donut Chart</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
Morris.Donut({
element: 'graph',
data: [
{value: 70, label: 'foo', formatted: 'at least 70%' },
{value: 15, label: 'bar', formatted: 'approx. 15%' },
{value: 10, label: 'baz', formatted: 'approx. 10%' },
{value: 5, label: 'A really really long label', formatted: 'at most 5%' }
],
formatter: function (x, data) { return data.formatted; }
});
</pre>
</body>

View File

@ -0,0 +1,29 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Donut Chart</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
Morris.Donut({
element: 'graph',
data: [
{value: 70, label: 'foo'},
{value: 15, label: 'bar'},
{value: 10, label: 'baz'},
{value: 5, label: 'A really really long label'}
],
formatter: function (x) { return x + "%"}
}).on('click', function(i, row){
console.log(i, row);
});
</pre>
</body>

View File

@ -0,0 +1,30 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Daylight-savings time</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
// This crosses a DST boundary in the UK.
Morris.Area({
element: 'graph',
data: [
{x: '2013-03-30 22:00:00', y: 3, z: 3},
{x: '2013-03-31 00:00:00', y: 2, z: 0},
{x: '2013-03-31 02:00:00', y: 0, z: 2},
{x: '2013-03-31 04:00:00', y: 4, z: 4}
],
xkey: 'x',
ykeys: ['y', 'z'],
labels: ['Y', 'Z']
});
</pre>
</body>

View File

@ -0,0 +1,57 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Time Events</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
var week_data = [
{"period": "2011 W27", "licensed": 3407, "sorned": 660},
{"period": "2011 W26", "licensed": 3351, "sorned": 629},
{"period": "2011 W25", "licensed": 3269, "sorned": 618},
{"period": "2011 W24", "licensed": 3246, "sorned": 661},
{"period": "2011 W23", "licensed": 3257, "sorned": 667},
{"period": "2011 W22", "licensed": 3248, "sorned": 627},
{"period": "2011 W21", "licensed": 3171, "sorned": 660},
{"period": "2011 W20", "licensed": 3171, "sorned": 676},
{"period": "2011 W19", "licensed": 3201, "sorned": 656},
{"period": "2011 W18", "licensed": 3215, "sorned": 622},
{"period": "2011 W17", "licensed": 3148, "sorned": 632},
{"period": "2011 W16", "licensed": 3155, "sorned": 681},
{"period": "2011 W15", "licensed": 3190, "sorned": 667},
{"period": "2011 W14", "licensed": 3226, "sorned": 620},
{"period": "2011 W13", "licensed": 3245, "sorned": null},
{"period": "2011 W12", "licensed": 3289, "sorned": null},
{"period": "2011 W11", "licensed": 3263, "sorned": null},
{"period": "2011 W10", "licensed": 3189, "sorned": null},
{"period": "2011 W09", "licensed": 3079, "sorned": null},
{"period": "2011 W08", "licensed": 3085, "sorned": null},
{"period": "2011 W07", "licensed": 3055, "sorned": null},
{"period": "2011 W06", "licensed": 3063, "sorned": null},
{"period": "2011 W05", "licensed": 2943, "sorned": null},
{"period": "2011 W04", "licensed": 2806, "sorned": null},
{"period": "2011 W03", "licensed": 2674, "sorned": null},
{"period": "2011 W02", "licensed": 1702, "sorned": null},
{"period": "2011 W01", "licensed": 1732, "sorned": null}
];
Morris.Line({
element: 'graph',
data: week_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN'],
events: [
'2011-04',
'2011-08'
]
});
</pre>
</body>

View File

@ -0,0 +1,33 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Value Goals</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
var decimal_data = [];
for (var x = 0; x <= 360; x += 10) {
decimal_data.push({
x: x,
y: Math.sin(Math.PI * x / 180).toFixed(4)
});
}
window.m = Morris.Line({
element: 'graph',
data: decimal_data,
xkey: 'x',
ykeys: ['y'],
labels: ['sin(x)'],
parseTime: false,
goals: [-1, 0, 1]
});
</pre>
</body>

View File

@ -0,0 +1,13 @@
body {
width: 800px;
margin: 0 auto;
}
#graph {
width: 800px;
height: 250px;
margin: 20px auto 0 auto;
}
pre {
height: 250px;
overflow: auto;
}

View File

@ -0,0 +1,4 @@
$(function () {
eval($('#code').text());
prettyPrint();
});

View File

@ -0,0 +1,38 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates with YYYY-MM</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var month_data = [
{"period": "2012-10", "licensed": 3407, "sorned": 660},
{"period": "2011-08", "licensed": 3351, "sorned": 629},
{"period": "2011-03", "licensed": 3269, "sorned": 618},
{"period": "2010-08", "licensed": 3246, "sorned": 661},
{"period": "2010-05", "licensed": 3257, "sorned": 667},
{"period": "2010-03", "licensed": 3248, "sorned": 627},
{"period": "2010-01", "licensed": 3171, "sorned": 660},
{"period": "2009-12", "licensed": 3171, "sorned": 676},
{"period": "2009-10", "licensed": 3201, "sorned": 656},
{"period": "2009-09", "licensed": 3215, "sorned": 622}
];
Morris.Line({
element: 'graph',
data: month_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN'],
smooth: false
});
</pre>
</body>

View File

@ -0,0 +1,36 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Negative values</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
var neg_data = [
{"period": "2011-08-12", "a": 100},
{"period": "2011-03-03", "a": 75},
{"period": "2010-08-08", "a": 50},
{"period": "2010-05-10", "a": 25},
{"period": "2010-03-14", "a": 0},
{"period": "2010-01-10", "a": -25},
{"period": "2009-12-10", "a": -50},
{"period": "2009-10-07", "a": -75},
{"period": "2009-09-25", "a": -100}
];
Morris.Line({
element: 'graph',
data: neg_data,
xkey: 'period',
ykeys: ['a'],
labels: ['Series A'],
units: '%'
});
</pre>
</body>

View File

@ -0,0 +1,38 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates YYYY-MM-DD</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
{"period": "2012-10-01", "licensed": 3407, "sorned": 660},
{"period": "2012-09-30", "licensed": 3351, "sorned": 629},
{"period": "2012-09-29", "licensed": 3269, "sorned": 618},
{"period": "2012-09-20", "licensed": 3246, "sorned": 661},
{"period": "2012-09-19", "licensed": 3257, "sorned": 667},
{"period": "2012-09-18", "licensed": 3248, "sorned": 627},
{"period": "2012-09-17", "licensed": 3171, "sorned": 660},
{"period": "2012-09-16", "licensed": 3171, "sorned": 676},
{"period": "2012-09-15", "licensed": 3201, "sorned": 656},
{"period": "2012-09-10", "licensed": 3215, "sorned": 622}
];
Morris.Line({
element: 'graph',
grid: false,
data: day_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN']
});
</pre>
</body>

View File

@ -0,0 +1,42 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Non-continuous data</h1>
<p>Null series values will break the line when rendering, missing values will be skipped</p>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
{"period": "2012-10-01", "licensed": 3407},
{"period": "2012-09-30", "sorned": 0},
{"period": "2012-09-29", "sorned": 618},
{"period": "2012-09-20", "licensed": 3246, "sorned": 661},
{"period": "2012-09-19", "licensed": 3257, "sorned": null},
{"period": "2012-09-18", "licensed": 3248, "other": 1000},
{"period": "2012-09-17", "sorned": 0},
{"period": "2012-09-16", "sorned": 0},
{"period": "2012-09-15", "licensed": 3201, "sorned": 656},
{"period": "2012-09-10", "licensed": 3215}
];
Morris.Line({
element: 'graph',
data: day_data,
xkey: 'period',
ykeys: ['licensed', 'sorned', 'other'],
labels: ['Licensed', 'SORN', 'Other'],
/* custom label formatting with `xLabelFormat` */
xLabelFormat: function(d) { return (d.getMonth()+1)+'/'+d.getDate()+'/'+d.getFullYear(); },
/* setting `xLabels` is recommended when using xLabelFormat */
xLabels: 'day'
});
</pre>
</body>

View File

@ -0,0 +1,37 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Non-date Arbitrary X-axis</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
var day_data = [
{"elapsed": "I", "value": 34},
{"elapsed": "II", "value": 24},
{"elapsed": "III", "value": 3},
{"elapsed": "IV", "value": 12},
{"elapsed": "V", "value": 13},
{"elapsed": "VI", "value": 22},
{"elapsed": "VII", "value": 5},
{"elapsed": "VIII", "value": 26},
{"elapsed": "IX", "value": 12},
{"elapsed": "X", "value": 19}
];
Morris.Line({
element: 'graph',
data: day_data,
xkey: 'elapsed',
ykeys: ['value'],
labels: ['value'],
parseTime: false
});
</pre>
</body>

View File

@ -0,0 +1,54 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates with Quarters</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_e_type */
var quarter_data = [
{"period": "2011 Q3", "licensed": 3407, "sorned": 660},
{"period": "2011 Q2", "licensed": 3351, "sorned": 629},
{"period": "2011 Q1", "licensed": 3269, "sorned": 618},
{"period": "2010 Q4", "licensed": 3246, "sorned": 661},
{"period": "2010 Q3", "licensed": 3257, "sorned": 667},
{"period": "2010 Q2", "licensed": 3248, "sorned": 627},
{"period": "2010 Q1", "licensed": 3171, "sorned": 660},
{"period": "2009 Q4", "licensed": 3171, "sorned": 676},
{"period": "2009 Q3", "licensed": 3201, "sorned": 656},
{"period": "2009 Q2", "licensed": 3215, "sorned": 622},
{"period": "2009 Q1", "licensed": 3148, "sorned": 632},
{"period": "2008 Q4", "licensed": 3155, "sorned": 681},
{"period": "2008 Q3", "licensed": 3190, "sorned": 667},
{"period": "2007 Q4", "licensed": 3226, "sorned": 620},
{"period": "2006 Q4", "licensed": 3245, "sorned": null},
{"period": "2005 Q4", "licensed": 3289, "sorned": null},
{"period": "2004 Q4", "licensed": 3263, "sorned": null},
{"period": "2003 Q4", "licensed": 3189, "sorned": null},
{"period": "2002 Q4", "licensed": 3079, "sorned": null},
{"period": "2001 Q4", "licensed": 3085, "sorned": null},
{"period": "2000 Q4", "licensed": 3055, "sorned": null},
{"period": "1999 Q4", "licensed": 3063, "sorned": null},
{"period": "1998 Q4", "licensed": 2943, "sorned": null},
{"period": "1997 Q4", "licensed": 2806, "sorned": null},
{"period": "1996 Q4", "licensed": 2674, "sorned": null},
{"period": "1995 Q4", "licensed": 1702, "sorned": null},
{"period": "1994 Q4", "licensed": 1732, "sorned": null}
];
Morris.Line({
element: 'graph',
data: quarter_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN']
});
</pre>
</body>

View File

@ -0,0 +1,42 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
<style>
body { width: 100%; }
#graph { width: 100%; }
</style>
</head>
<body>
<h1>Formatting Dates YYYY-MM-DD</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
{"period": "2012-10-01", "licensed": 3407, "sorned": 660},
{"period": "2012-09-30", "licensed": 3351, "sorned": 629},
{"period": "2012-09-29", "licensed": 3269, "sorned": 618},
{"period": "2012-09-20", "licensed": 3246, "sorned": 661},
{"period": "2012-09-19", "licensed": 3257, "sorned": 667},
{"period": "2012-09-18", "licensed": 3248, "sorned": 627},
{"period": "2012-09-17", "licensed": 3171, "sorned": 660},
{"period": "2012-09-16", "licensed": 3171, "sorned": 676},
{"period": "2012-09-15", "licensed": 3201, "sorned": 656},
{"period": "2012-09-10", "licensed": 3215, "sorned": 622}
];
Morris.Line({
element: 'graph',
data: day_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN'],
resize: true
});
</pre>
</body>

View File

@ -0,0 +1,31 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Stacked Bars chart</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
// Use Morris.Bar
Morris.Bar({
element: 'graph',
data: [
{x: '2011 Q1', y: 3, z: 2, a: 3},
{x: '2011 Q2', y: 2, z: null, a: 1},
{x: '2011 Q3', y: 0, z: 2, a: 4},
{x: '2011 Q4', y: 2, z: 4, a: 3}
],
xkey: 'x',
ykeys: ['y', 'z', 'a'],
labels: ['Y', 'Z', 'A'],
stacked: true
});
</pre>
</body>

View File

@ -0,0 +1,38 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Timestamps</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var timestamp_data = [
{"period": 1349046000000, "licensed": 3407, "sorned": 660},
{"period": 1313103600000, "licensed": 3351, "sorned": 629},
{"period": 1299110400000, "licensed": 3269, "sorned": 618},
{"period": 1281222000000, "licensed": 3246, "sorned": 661},
{"period": 1273446000000, "licensed": 3257, "sorned": 667},
{"period": 1268524800000, "licensed": 3248, "sorned": 627},
{"period": 1263081600000, "licensed": 3171, "sorned": 660},
{"period": 1260403200000, "licensed": 3171, "sorned": 676},
{"period": 1254870000000, "licensed": 3201, "sorned": 656},
{"period": 1253833200000, "licensed": 3215, "sorned": 622}
];
Morris.Line({
element: 'graph',
data: timestamp_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN'],
dateFormat: function (x) { return new Date(x).toDateString(); }
});
</pre>
</body>

View File

@ -0,0 +1,49 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Updating data</h1>
<div id="graph"></div>
<div id="reloadStatus">
<pre id="code" class="prettyprint linenums">
var nReloads = 0;
function data(offset) {
var ret = [];
for (var x = 0; x <= 360; x += 10) {
var v = (offset + x) % 360;
ret.push({
x: x,
y: Math.sin(Math.PI * v / 180).toFixed(4),
z: Math.cos(Math.PI * v / 180).toFixed(4)
});
}
return ret;
}
var graph = Morris.Line({
element: 'graph',
data: data(0),
xkey: 'x',
ykeys: ['y', 'z'],
labels: ['sin()', 'cos()'],
parseTime: false,
ymin: -1.0,
ymax: 1.0,
hideHover: true
});
function update() {
nReloads++;
graph.setData(data(5 * nReloads));
$('#reloadStatus').text(nReloads + ' reloads');
}
setInterval(update, 100);
</pre>
</body>

View File

@ -0,0 +1,53 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates With Weeks</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
var week_data = [
{"period": "2011 W27", "licensed": 3407, "sorned": 660},
{"period": "2011 W26", "licensed": 3351, "sorned": 629},
{"period": "2011 W25", "licensed": 3269, "sorned": 618},
{"period": "2011 W24", "licensed": 3246, "sorned": 661},
{"period": "2011 W23", "licensed": 3257, "sorned": 667},
{"period": "2011 W22", "licensed": 3248, "sorned": 627},
{"period": "2011 W21", "licensed": 3171, "sorned": 660},
{"period": "2011 W20", "licensed": 3171, "sorned": 676},
{"period": "2011 W19", "licensed": 3201, "sorned": 656},
{"period": "2011 W18", "licensed": 3215, "sorned": 622},
{"period": "2011 W17", "licensed": 3148, "sorned": 632},
{"period": "2011 W16", "licensed": 3155, "sorned": 681},
{"period": "2011 W15", "licensed": 3190, "sorned": 667},
{"period": "2011 W14", "licensed": 3226, "sorned": 620},
{"period": "2011 W13", "licensed": 3245, "sorned": null},
{"period": "2011 W12", "licensed": 3289, "sorned": null},
{"period": "2011 W11", "licensed": 3263, "sorned": null},
{"period": "2011 W10", "licensed": 3189, "sorned": null},
{"period": "2011 W09", "licensed": 3079, "sorned": null},
{"period": "2011 W08", "licensed": 3085, "sorned": null},
{"period": "2011 W07", "licensed": 3055, "sorned": null},
{"period": "2011 W06", "licensed": 3063, "sorned": null},
{"period": "2011 W05", "licensed": 2943, "sorned": null},
{"period": "2011 W04", "licensed": 2806, "sorned": null},
{"period": "2011 W03", "licensed": 2674, "sorned": null},
{"period": "2011 W02", "licensed": 1702, "sorned": null},
{"period": "2011 W01", "licensed": 1732, "sorned": null}
];
Morris.Line({
element: 'graph',
data: week_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN']
});
</pre>
</body>

View File

@ -0,0 +1,37 @@
<!doctype html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.min.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates YYYY</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var year_data = [
{"period": "2012", "licensed": 3407, "sorned": 660},
{"period": "2011", "licensed": 3351, "sorned": 629},
{"period": "2010", "licensed": 3269, "sorned": 618},
{"period": "2009", "licensed": 3246, "sorned": 661},
{"period": "2008", "licensed": 3257, "sorned": 667},
{"period": "2007", "licensed": 3248, "sorned": 627},
{"period": "2006", "licensed": 3171, "sorned": 660},
{"period": "2005", "licensed": 3171, "sorned": 676},
{"period": "2004", "licensed": 3201, "sorned": 656},
{"period": "2003", "licensed": 3215, "sorned": 622}
];
Morris.Line({
element: 'graph',
data: year_data,
xkey: 'period',
ykeys: ['licensed', 'sorned'],
labels: ['Licensed', 'SORN']
});
</pre>
</body>

View File

@ -0,0 +1,27 @@
.morris-hover {
position: absolute;
z-index: 1000;
&.morris-default-style {
border-radius: 10px;
padding: 6px;
color: #666;
background: rgba(255, 255, 255, 0.8);
border: solid 2px rgba(230, 230, 230, 0.8);
font-family: sans-serif;
font-size: 12px;
text-align: center;
.morris-hover-row-label {
font-weight: bold;
margin: 0.25em 0;
}
.morris-hover-point {
white-space: nowrap;
margin: 0.1em 0;
}
}
}

View File

@ -0,0 +1,66 @@
class Morris.Area extends Morris.Line
# Initialise
#
areaDefaults =
fillOpacity: 'auto'
behaveLikeLine: false
constructor: (options) ->
return new Morris.Area(options) unless (@ instanceof Morris.Area)
areaOptions = $.extend {}, areaDefaults, options
@cumulative = not areaOptions.behaveLikeLine
if areaOptions.fillOpacity is 'auto'
areaOptions.fillOpacity = if areaOptions.behaveLikeLine then .8 else 1
super(areaOptions)
# calculate series data point coordinates
#
# @private
calcPoints: ->
for row in @data
row._x = @transX(row.x)
total = 0
row._y = for y in row.y
if @options.behaveLikeLine
@transY(y)
else
total += (y || 0)
@transY(total)
row._ymax = Math.max row._y...
# draw the data series
#
# @private
drawSeries: ->
@seriesPoints = []
if @options.behaveLikeLine
range = [0..@options.ykeys.length-1]
else
range = [@options.ykeys.length-1..0]
for i in range
@_drawFillFor i
@_drawLineFor i
@_drawPointFor i
_drawFillFor: (index) ->
path = @paths[index]
if path isnt null
path = path + "L#{@transX(@xmax)},#{@bottom}L#{@transX(@xmin)},#{@bottom}Z"
@drawFilledPath path, @fillForSeries(index)
fillForSeries: (i) ->
color = Raphael.rgb2hsl @colorFor(@data[i], i, 'line')
Raphael.hsl(
color.h,
if @options.behaveLikeLine then color.s * 0.9 else color.s * 0.75,
Math.min(0.98, if @options.behaveLikeLine then color.l * 1.2 else color.l * 1.25))
drawFilledPath: (path, fill) ->
@raphael.path(path)
.attr('fill', fill)
.attr('fill-opacity', @options.fillOpacity)
.attr('stroke', 'none')

View File

@ -0,0 +1,208 @@
class Morris.Bar extends Morris.Grid
constructor: (options) ->
return new Morris.Bar(options) unless (@ instanceof Morris.Bar)
super($.extend {}, options, parseTime: false)
init: ->
@cumulative = @options.stacked
if @options.hideHover isnt 'always'
@hover = new Morris.Hover(parent: @el)
@on('hovermove', @onHoverMove)
@on('hoverout', @onHoverOut)
@on('gridclick', @onGridClick)
# Default configuration
#
defaults:
barSizeRatio: 0.75
barGap: 3
barColors: [
'#0b62a4'
'#7a92a3'
'#4da74d'
'#afd8f8'
'#edc240'
'#cb4b4b'
'#9440ed'
],
barOpacity: 1.0
barRadius: [0, 0, 0, 0]
xLabelMargin: 50
# Do any size-related calculations
#
# @private
calc: ->
@calcBars()
if @options.hideHover is false
@hover.update(@hoverContentForRow(@data.length - 1)...)
# calculate series data bars coordinates and sizes
#
# @private
calcBars: ->
for row, idx in @data
row._x = @left + @width * (idx + 0.5) / @data.length
row._y = for y in row.y
if y? then @transY(y) else null
# Draws the bar chart.
#
draw: ->
@drawXAxis() if @options.axes in [true, 'both', 'x']
@drawSeries()
# draw the x-axis labels
#
# @private
drawXAxis: ->
# draw x axis labels
ypos = @bottom + (@options.xAxisLabelTopPadding || @options.padding / 2)
prevLabelMargin = null
prevAngleMargin = null
for i in [0...@data.length]
row = @data[@data.length - 1 - i]
label = @drawXAxisLabel(row._x, ypos, row.label)
textBox = label.getBBox()
label.transform("r#{-@options.xLabelAngle}")
labelBox = label.getBBox()
label.transform("t0,#{labelBox.height / 2}...")
if @options.xLabelAngle != 0
offset = -0.5 * textBox.width *
Math.cos(@options.xLabelAngle * Math.PI / 180.0)
label.transform("t#{offset},0...")
# try to avoid overlaps
if (not prevLabelMargin? or
prevLabelMargin >= labelBox.x + labelBox.width or
prevAngleMargin? and prevAngleMargin >= labelBox.x) and
labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width()
if @options.xLabelAngle != 0
margin = 1.25 * @options.gridTextSize /
Math.sin(@options.xLabelAngle * Math.PI / 180.0)
prevAngleMargin = labelBox.x - margin
prevLabelMargin = labelBox.x - @options.xLabelMargin
else
label.remove()
# draw the data series
#
# @private
drawSeries: ->
groupWidth = @width / @options.data.length
numBars = if @options.stacked then 1 else @options.ykeys.length
barWidth = (groupWidth * @options.barSizeRatio - @options.barGap * (numBars - 1)) / numBars
barWidth = Math.min(barWidth, @options.barSize) if @options.barSize
spaceLeft = groupWidth - barWidth * numBars - @options.barGap * (numBars - 1)
leftPadding = spaceLeft / 2
zeroPos = if @ymin <= 0 and @ymax >= 0 then @transY(0) else null
@bars = for row, idx in @data
lastTop = 0
for ypos, sidx in row._y
if ypos != null
if zeroPos
top = Math.min(ypos, zeroPos)
bottom = Math.max(ypos, zeroPos)
else
top = ypos
bottom = @bottom
left = @left + idx * groupWidth + leftPadding
left += sidx * (barWidth + @options.barGap) unless @options.stacked
size = bottom - top
if @options.verticalGridCondition and @options.verticalGridCondition(row.x)
@drawBar(@left + idx * groupWidth, @top, groupWidth, Math.abs(@top - @bottom), @options.verticalGridColor, @options.verticalGridOpacity, @options.barRadius)
top -= lastTop if @options.stacked
@drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar'),
@options.barOpacity, @options.barRadius)
lastTop += size
else
null
# @private
#
# @param row [Object] row data
# @param sidx [Number] series index
# @param type [String] "bar", "hover" or "label"
colorFor: (row, sidx, type) ->
if typeof @options.barColors is 'function'
r = { x: row.x, y: row.y[sidx], label: row.label }
s = { index: sidx, key: @options.ykeys[sidx], label: @options.labels[sidx] }
@options.barColors.call(@, r, s, type)
else
@options.barColors[sidx % @options.barColors.length]
# hit test - returns the index of the row at the given x-coordinate
#
hitTest: (x) ->
return null if @data.length == 0
x = Math.max(Math.min(x, @right), @left)
Math.min(@data.length - 1,
Math.floor((x - @left) / (@width / @data.length)))
# click on grid event handler
#
# @private
onGridClick: (x, y) =>
index = @hitTest(x)
@fire 'click', index, @data[index].src, x, y
# hover movement event handler
#
# @private
onHoverMove: (x, y) =>
index = @hitTest(x)
@hover.update(@hoverContentForRow(index)...)
# hover out event handler
#
# @private
onHoverOut: =>
if @options.hideHover isnt false
@hover.hide()
# hover content for a point
#
# @private
hoverContentForRow: (index) ->
row = @data[index]
content = "<div class='morris-hover-row-label'>#{row.label}</div>"
for y, j in row.y
content += """
<div class='morris-hover-point' style='color: #{@colorFor(row, j, 'label')}'>
#{@options.labels[j]}:
#{@yLabelFormat(y)}
</div>
"""
if typeof @options.hoverCallback is 'function'
content = @options.hoverCallback(index, @options, content, row.src)
x = @left + (index + 0.5) * @width / @data.length
[content, x]
drawXAxisLabel: (xPos, yPos, text) ->
label = @raphael.text(xPos, yPos, text)
.attr('font-size', @options.gridTextSize)
.attr('font-family', @options.gridTextFamily)
.attr('font-weight', @options.gridTextWeight)
.attr('fill', @options.gridTextColor)
drawBar: (xPos, yPos, width, height, barColor, opacity, radiusArray) ->
maxRadius = Math.max(radiusArray...)
if maxRadius == 0 or maxRadius > height
path = @raphael.rect(xPos, yPos, width, height)
else
path = @raphael.path @roundedRect(xPos, yPos, width, height, radiusArray)
path
.attr('fill', barColor)
.attr('fill-opacity', opacity)
.attr('stroke', 'none')
roundedRect: (x, y, w, h, r = [0,0,0,0]) ->
[ "M", x, r[0] + y, "Q", x, y, x + r[0], y,
"L", x + w - r[1], y, "Q", x + w, y, x + w, y + r[1],
"L", x + w, y + h - r[2], "Q", x + w, y + h, x + w - r[2], y + h,
"L", x + r[3], y + h, "Q", x, y + h, x, y + h - r[3], "Z" ]

View File

@ -0,0 +1,43 @@
Morris = window.Morris = {}
$ = jQuery
# Very simple event-emitter class.
#
# @private
class Morris.EventEmitter
on: (name, handler) ->
unless @handlers?
@handlers = {}
unless @handlers[name]?
@handlers[name] = []
@handlers[name].push(handler)
@
fire: (name, args...) ->
if @handlers? and @handlers[name]?
for handler in @handlers[name]
handler(args...)
# Make long numbers prettier by inserting commas.
#
# @example
# Morris.commas(1234567) -> '1,234,567'
Morris.commas = (num) ->
if num?
ret = if num < 0 then "-" else ""
absnum = Math.abs(num)
intnum = Math.floor(absnum).toFixed(0)
ret += intnum.replace(/(?=(?:\d{3})+$)(?!^)/g, ',')
strabsnum = absnum.toString()
if strabsnum.length > intnum.length
ret += strabsnum.slice(intnum.length)
ret
else
'-'
# Zero-pad numbers to two characters wide.
#
# @example
# Morris.pad2(1) -> '01'
Morris.pad2 = (number) -> (if number < 10 then '0' else '') + number

View File

@ -0,0 +1,213 @@
# Donut charts.
#
# @example
# Morris.Donut({
# el: $('#donut-container'),
# data: [
# { label: 'yin', value: 50 },
# { label: 'yang', value: 50 }
# ]
# });
class Morris.Donut extends Morris.EventEmitter
defaults:
colors: [
'#0B62A4'
'#3980B5'
'#679DC6'
'#95BBD7'
'#B0CCE1'
'#095791'
'#095085'
'#083E67'
'#052C48'
'#042135'
],
backgroundColor: '#FFFFFF',
labelColor: '#000000',
formatter: Morris.commas
resize: false
# Create and render a donut chart.
#
constructor: (options) ->
return new Morris.Donut(options) unless (@ instanceof Morris.Donut)
@options = $.extend {}, @defaults, options
if typeof options.element is 'string'
@el = $ document.getElementById(options.element)
else
@el = $ options.element
if @el == null || @el.length == 0
throw new Error("Graph placeholder not found.")
# bail if there's no data
if options.data is undefined or options.data.length is 0
return
@raphael = new Raphael(@el[0])
if @options.resize
$(window).bind 'resize', (evt) =>
if @timeoutId?
window.clearTimeout @timeoutId
@timeoutId = window.setTimeout @resizeHandler, 100
@setData options.data
# Clear and redraw the chart.
redraw: ->
@raphael.clear()
cx = @el.width() / 2
cy = @el.height() / 2
w = (Math.min(cx, cy) - 10) / 3
total = 0
total += value for value in @values
min = 5 / (2 * w)
C = 1.9999 * Math.PI - min * @data.length
last = 0
idx = 0
@segments = []
for value, i in @values
next = last + min + C * (value / total)
seg = new Morris.DonutSegment(
cx, cy, w*2, w, last, next,
@data[i].color || @options.colors[idx % @options.colors.length],
@options.backgroundColor, idx, @raphael)
seg.render()
@segments.push seg
seg.on 'hover', @select
seg.on 'click', @click
last = next
idx += 1
@text1 = @drawEmptyDonutLabel(cx, cy - 10, @options.labelColor, 15, 800)
@text2 = @drawEmptyDonutLabel(cx, cy + 10, @options.labelColor, 14)
max_value = Math.max @values...
idx = 0
for value in @values
if value == max_value
@select idx
break
idx += 1
setData: (data) ->
@data = data
@values = (parseFloat(row.value) for row in @data)
@redraw()
# @private
click: (idx) =>
@fire 'click', idx, @data[idx]
# Select the segment at the given index.
select: (idx) =>
s.deselect() for s in @segments
segment = @segments[idx]
segment.select()
row = @data[idx]
@setLabels(row.label, @options.formatter(row.value, row))
# @private
setLabels: (label1, label2) ->
inner = (Math.min(@el.width() / 2, @el.height() / 2) - 10) * 2 / 3
maxWidth = 1.8 * inner
maxHeightTop = inner / 2
maxHeightBottom = inner / 3
@text1.attr(text: label1, transform: '')
text1bbox = @text1.getBBox()
text1scale = Math.min(maxWidth / text1bbox.width, maxHeightTop / text1bbox.height)
@text1.attr(transform: "S#{text1scale},#{text1scale},#{text1bbox.x + text1bbox.width / 2},#{text1bbox.y + text1bbox.height}")
@text2.attr(text: label2, transform: '')
text2bbox = @text2.getBBox()
text2scale = Math.min(maxWidth / text2bbox.width, maxHeightBottom / text2bbox.height)
@text2.attr(transform: "S#{text2scale},#{text2scale},#{text2bbox.x + text2bbox.width / 2},#{text2bbox.y}")
drawEmptyDonutLabel: (xPos, yPos, color, fontSize, fontWeight) ->
text = @raphael.text(xPos, yPos, '')
.attr('font-size', fontSize)
.attr('fill', color)
text.attr('font-weight', fontWeight) if fontWeight?
return text
resizeHandler: =>
@timeoutId = null
@raphael.setSize @el.width(), @el.height()
@redraw()
# A segment within a donut chart.
#
# @private
class Morris.DonutSegment extends Morris.EventEmitter
constructor: (@cx, @cy, @inner, @outer, p0, p1, @color, @backgroundColor, @index, @raphael) ->
@sin_p0 = Math.sin(p0)
@cos_p0 = Math.cos(p0)
@sin_p1 = Math.sin(p1)
@cos_p1 = Math.cos(p1)
@is_long = if (p1 - p0) > Math.PI then 1 else 0
@path = @calcSegment(@inner + 3, @inner + @outer - 5)
@selectedPath = @calcSegment(@inner + 3, @inner + @outer)
@hilight = @calcArc(@inner)
calcArcPoints: (r) ->
return [
@cx + r * @sin_p0,
@cy + r * @cos_p0,
@cx + r * @sin_p1,
@cy + r * @cos_p1]
calcSegment: (r1, r2) ->
[ix0, iy0, ix1, iy1] = @calcArcPoints(r1)
[ox0, oy0, ox1, oy1] = @calcArcPoints(r2)
return (
"M#{ix0},#{iy0}" +
"A#{r1},#{r1},0,#{@is_long},0,#{ix1},#{iy1}" +
"L#{ox1},#{oy1}" +
"A#{r2},#{r2},0,#{@is_long},1,#{ox0},#{oy0}" +
"Z")
calcArc: (r) ->
[ix0, iy0, ix1, iy1] = @calcArcPoints(r)
return (
"M#{ix0},#{iy0}" +
"A#{r},#{r},0,#{@is_long},0,#{ix1},#{iy1}")
render: ->
@arc = @drawDonutArc(@hilight, @color)
@seg = @drawDonutSegment(
@path,
@color,
@backgroundColor,
=> @fire('hover', @index),
=> @fire('click', @index)
)
drawDonutArc: (path, color) ->
@raphael.path(path)
.attr(stroke: color, 'stroke-width': 2, opacity: 0)
drawDonutSegment: (path, fillColor, strokeColor, hoverFunction, clickFunction) ->
@raphael.path(path)
.attr(fill: fillColor, stroke: strokeColor, 'stroke-width': 3)
.hover(hoverFunction)
.click(clickFunction)
select: =>
unless @selected
@seg.animate(path: @selectedPath, 150, '<>')
@arc.animate(opacity: 1, 150, '<>')
@selected = true
deselect: =>
if @selected
@seg.animate(path: @path, 150, '<>')
@arc.animate(opacity: 0, 150, '<>')
@selected = false

View File

@ -0,0 +1,499 @@
class Morris.Grid extends Morris.EventEmitter
# A generic pair of axes for line/area/bar charts.
#
# Draws grid lines and axis labels.
#
constructor: (options) ->
# find the container to draw the graph in
if typeof options.element is 'string'
@el = $ document.getElementById(options.element)
else
@el = $ options.element
if not @el? or @el.length == 0
throw new Error("Graph container element not found")
if @el.css('position') == 'static'
@el.css('position', 'relative')
@options = $.extend {}, @gridDefaults, (@defaults || {}), options
# backwards compatibility for units -> postUnits
if typeof @options.units is 'string'
@options.postUnits = options.units
# the raphael drawing instance
@raphael = new Raphael(@el[0])
# some redraw stuff
@elementWidth = null
@elementHeight = null
@dirty = false
# range selection
@selectFrom = null
# more stuff
@init() if @init
# load data
@setData @options.data
# hover
@el.bind 'mousemove', (evt) =>
offset = @el.offset()
x = evt.pageX - offset.left
if @selectFrom
left = @data[@hitTest(Math.min(x, @selectFrom))]._x
right = @data[@hitTest(Math.max(x, @selectFrom))]._x
width = right - left
@selectionRect.attr({ x: left, width: width })
else
@fire 'hovermove', x, evt.pageY - offset.top
@el.bind 'mouseleave', (evt) =>
if @selectFrom
@selectionRect.hide()
@selectFrom = null
@fire 'hoverout'
@el.bind 'touchstart touchmove touchend', (evt) =>
touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0]
offset = @el.offset()
@fire 'hovermove', touch.pageX - offset.left, touch.pageY - offset.top
@el.bind 'click', (evt) =>
offset = @el.offset()
@fire 'gridclick', evt.pageX - offset.left, evt.pageY - offset.top
if @options.rangeSelect
@selectionRect = @raphael.rect(0, 0, 0, @el.innerHeight())
.attr({ fill: @options.rangeSelectColor, stroke: false })
.toBack()
.hide()
@el.bind 'mousedown', (evt) =>
offset = @el.offset()
@startRange evt.pageX - offset.left
@el.bind 'mouseup', (evt) =>
offset = @el.offset()
@endRange evt.pageX - offset.left
@fire 'hovermove', evt.pageX - offset.left, evt.pageY - offset.top
if @options.resize
$(window).bind 'resize', (evt) =>
if @timeoutId?
window.clearTimeout @timeoutId
@timeoutId = window.setTimeout @resizeHandler, 100
# Disable tap highlight on iOS.
@el.css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)')
@postInit() if @postInit
# Default options
#
gridDefaults:
dateFormat: null
axes: true
grid: true
gridLineColor: '#aaa'
gridStrokeWidth: 0.5
gridTextColor: '#888'
gridTextSize: 12
gridTextFamily: 'sans-serif'
gridTextWeight: 'normal'
hideHover: false
yLabelFormat: null
xLabelAngle: 0
numLines: 5
padding: 25
parseTime: true
postUnits: ''
preUnits: ''
ymax: 'auto'
ymin: 'auto 0'
goals: []
goalStrokeWidth: 1.0
goalLineColors: [
'#666633'
'#999966'
'#cc6666'
'#663333'
]
events: []
eventStrokeWidth: 1.0
eventLineColors: [
'#005a04'
'#ccffbb'
'#3a5f0b'
'#005502'
]
rangeSelect: null
rangeSelectColor: '#eef'
resize: false
# Update the data series and redraw the chart.
#
setData: (data, redraw = true) ->
@options.data = data
if !data? or data.length == 0
@data = []
@raphael.clear()
@hover.hide() if @hover?
return
ymax = if @cumulative then 0 else null
ymin = if @cumulative then 0 else null
if @options.goals.length > 0
minGoal = Math.min @options.goals...
maxGoal = Math.max @options.goals...
ymin = if ymin? then Math.min(ymin, minGoal) else minGoal
ymax = if ymax? then Math.max(ymax, maxGoal) else maxGoal
@data = for row, index in data
ret = {src: row}
ret.label = row[@options.xkey]
if @options.parseTime
ret.x = Morris.parseDate(ret.label)
if @options.dateFormat
ret.label = @options.dateFormat ret.x
else if typeof ret.label is 'number'
ret.label = new Date(ret.label).toString()
else
ret.x = index
if @options.xLabelFormat
ret.label = @options.xLabelFormat ret
total = 0
ret.y = for ykey, idx in @options.ykeys
yval = row[ykey]
yval = parseFloat(yval) if typeof yval is 'string'
yval = null if yval? and typeof yval isnt 'number'
if yval?
if @cumulative
total += yval
else
if ymax?
ymax = Math.max(yval, ymax)
ymin = Math.min(yval, ymin)
else
ymax = ymin = yval
if @cumulative and total?
ymax = Math.max(total, ymax)
ymin = Math.min(total, ymin)
yval
ret
if @options.parseTime
@data = @data.sort (a, b) -> (a.x > b.x) - (b.x > a.x)
# calculate horizontal range of the graph
@xmin = @data[0].x
@xmax = @data[@data.length - 1].x
@events = []
if @options.events.length > 0
if @options.parseTime
@events = (Morris.parseDate(e) for e in @options.events)
else
@events = @options.events
@xmax = Math.max(@xmax, Math.max(@events...))
@xmin = Math.min(@xmin, Math.min(@events...))
if @xmin is @xmax
@xmin -= 1
@xmax += 1
@ymin = @yboundary('min', ymin)
@ymax = @yboundary('max', ymax)
if @ymin is @ymax
@ymin -= 1 if ymin
@ymax += 1
if @options.axes in [true, 'both', 'y'] or @options.grid is true
if (@options.ymax == @gridDefaults.ymax and
@options.ymin == @gridDefaults.ymin)
# calculate 'magic' grid placement
@grid = @autoGridLines(@ymin, @ymax, @options.numLines)
@ymin = Math.min(@ymin, @grid[0])
@ymax = Math.max(@ymax, @grid[@grid.length - 1])
else
step = (@ymax - @ymin) / (@options.numLines - 1)
@grid = (y for y in [@ymin..@ymax] by step)
@dirty = true
@redraw() if redraw
yboundary: (boundaryType, currentValue) ->
boundaryOption = @options["y#{boundaryType}"]
if typeof boundaryOption is 'string'
if boundaryOption[0..3] is 'auto'
if boundaryOption.length > 5
suggestedValue = parseInt(boundaryOption[5..], 10)
return suggestedValue unless currentValue?
Math[boundaryType](currentValue, suggestedValue)
else
if currentValue? then currentValue else 0
else
parseInt(boundaryOption, 10)
else
boundaryOption
autoGridLines: (ymin, ymax, nlines) ->
span = ymax - ymin
ymag = Math.floor(Math.log(span) / Math.log(10))
unit = Math.pow(10, ymag)
# calculate initial grid min and max values
gmin = Math.floor(ymin / unit) * unit
gmax = Math.ceil(ymax / unit) * unit
step = (gmax - gmin) / (nlines - 1)
if unit == 1 and step > 1 and Math.ceil(step) != step
step = Math.ceil(step)
gmax = gmin + step * (nlines - 1)
# ensure zero is plotted where the range includes zero
if gmin < 0 and gmax > 0
gmin = Math.floor(ymin / step) * step
gmax = Math.ceil(ymax / step) * step
# special case for decimal numbers
if step < 1
smag = Math.floor(Math.log(step) / Math.log(10))
grid = for y in [gmin..gmax] by step
parseFloat(y.toFixed(1 - smag))
else
grid = (y for y in [gmin..gmax] by step)
grid
_calc: ->
w = @el.width()
h = @el.height()
if @elementWidth != w or @elementHeight != h or @dirty
@elementWidth = w
@elementHeight = h
@dirty = false
# recalculate grid dimensions
@left = @options.padding
@right = @elementWidth - @options.padding
@top = @options.padding
@bottom = @elementHeight - @options.padding
if @options.axes in [true, 'both', 'y']
yLabelWidths = for gridLine in @grid
@measureText(@yAxisFormat(gridLine)).width
@left += Math.max(yLabelWidths...)
if @options.axes in [true, 'both', 'x']
bottomOffsets = for i in [0...@data.length]
@measureText(@data[i].text, -@options.xLabelAngle).height
@bottom -= Math.max(bottomOffsets...)
@width = Math.max(1, @right - @left)
@height = Math.max(1, @bottom - @top)
@dx = @width / (@xmax - @xmin)
@dy = @height / (@ymax - @ymin)
@calc() if @calc
# Quick translation helpers
#
transY: (y) -> @bottom - (y - @ymin) * @dy
transX: (x) ->
if @data.length == 1
(@left + @right) / 2
else
@left + (x - @xmin) * @dx
# Draw it!
#
# If you need to re-size your charts, call this method after changing the
# size of the container element.
redraw: ->
@raphael.clear()
@_calc()
@drawGrid()
@drawGoals()
@drawEvents()
@draw() if @draw
# @private
#
measureText: (text, angle = 0) ->
tt = @raphael.text(100, 100, text)
.attr('font-size', @options.gridTextSize)
.attr('font-family', @options.gridTextFamily)
.attr('font-weight', @options.gridTextWeight)
.rotate(angle)
ret = tt.getBBox()
tt.remove()
ret
# @private
#
yAxisFormat: (label) -> @yLabelFormat(label)
# @private
#
yLabelFormat: (label) ->
if typeof @options.yLabelFormat is 'function'
@options.yLabelFormat(label)
else
"#{@options.preUnits}#{Morris.commas(label)}#{@options.postUnits}"
# draw y axis labels, horizontal lines
#
drawGrid: ->
return if @options.grid is false and @options.axes not in [true, 'both', 'y']
for lineY in @grid
y = @transY(lineY)
if @options.axes in [true, 'both', 'y']
@drawYAxisLabel(@left - @options.padding / 2, y, @yAxisFormat(lineY))
if @options.grid
@drawGridLine("M#{@left},#{y}H#{@left + @width}")
# draw goals horizontal lines
#
drawGoals: ->
for goal, i in @options.goals
color = @options.goalLineColors[i % @options.goalLineColors.length]
@drawGoal(goal, color)
# draw events vertical lines
drawEvents: ->
for event, i in @events
color = @options.eventLineColors[i % @options.eventLineColors.length]
@drawEvent(event, color)
drawGoal: (goal, color) ->
@raphael.path("M#{@left},#{@transY(goal)}H#{@right}")
.attr('stroke', color)
.attr('stroke-width', @options.goalStrokeWidth)
drawEvent: (event, color) ->
@raphael.path("M#{@transX(event)},#{@bottom}V#{@top}")
.attr('stroke', color)
.attr('stroke-width', @options.eventStrokeWidth)
drawYAxisLabel: (xPos, yPos, text) ->
@raphael.text(xPos, yPos, text)
.attr('font-size', @options.gridTextSize)
.attr('font-family', @options.gridTextFamily)
.attr('font-weight', @options.gridTextWeight)
.attr('fill', @options.gridTextColor)
.attr('text-anchor', 'end')
drawGridLine: (path) ->
@raphael.path(path)
.attr('stroke', @options.gridLineColor)
.attr('stroke-width', @options.gridStrokeWidth)
# Range selection
#
startRange: (x) ->
@hover.hide()
@selectFrom = x
@selectionRect.attr({ x: x, width: 0 }).show()
endRange: (x) ->
if @selectFrom
start = Math.min(@selectFrom, x)
end = Math.max(@selectFrom, x)
@options.rangeSelect.call @el,
start: @data[@hitTest(start)].x
end: @data[@hitTest(end)].x
@selectFrom = null
resizeHandler: =>
@timeoutId = null
@raphael.setSize @el.width(), @el.height()
@redraw()
# Parse a date into a javascript timestamp
#
#
Morris.parseDate = (date) ->
if typeof date is 'number'
return date
m = date.match /^(\d+) Q(\d)$/
n = date.match /^(\d+)-(\d+)$/
o = date.match /^(\d+)-(\d+)-(\d+)$/
p = date.match /^(\d+) W(\d+)$/
q = date.match /^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+)(Z|([+-])(\d\d):?(\d\d))?$/
r = date.match /^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+):(\d+(\.\d+)?)(Z|([+-])(\d\d):?(\d\d))?$/
if m
new Date(
parseInt(m[1], 10),
parseInt(m[2], 10) * 3 - 1,
1).getTime()
else if n
new Date(
parseInt(n[1], 10),
parseInt(n[2], 10) - 1,
1).getTime()
else if o
new Date(
parseInt(o[1], 10),
parseInt(o[2], 10) - 1,
parseInt(o[3], 10)).getTime()
else if p
# calculate number of weeks in year given
ret = new Date(parseInt(p[1], 10), 0, 1);
# first thursday in year (ISO 8601 standard)
if ret.getDay() isnt 4
ret.setMonth(0, 1 + ((4 - ret.getDay()) + 7) % 7);
# add weeks
ret.getTime() + parseInt(p[2], 10) * 604800000
else if q
if not q[6]
# no timezone info, use local
new Date(
parseInt(q[1], 10),
parseInt(q[2], 10) - 1,
parseInt(q[3], 10),
parseInt(q[4], 10),
parseInt(q[5], 10)).getTime()
else
# timezone info supplied, use UTC
offsetmins = 0
if q[6] != 'Z'
offsetmins = parseInt(q[8], 10) * 60 + parseInt(q[9], 10)
offsetmins = 0 - offsetmins if q[7] == '+'
Date.UTC(
parseInt(q[1], 10),
parseInt(q[2], 10) - 1,
parseInt(q[3], 10),
parseInt(q[4], 10),
parseInt(q[5], 10) + offsetmins)
else if r
secs = parseFloat(r[6])
isecs = Math.floor(secs)
msecs = Math.round((secs - isecs) * 1000)
if not r[8]
# no timezone info, use local
new Date(
parseInt(r[1], 10),
parseInt(r[2], 10) - 1,
parseInt(r[3], 10),
parseInt(r[4], 10),
parseInt(r[5], 10),
isecs,
msecs).getTime()
else
# timezone info supplied, use UTC
offsetmins = 0
if r[8] != 'Z'
offsetmins = parseInt(r[10], 10) * 60 + parseInt(r[11], 10)
offsetmins = 0 - offsetmins if r[9] == '+'
Date.UTC(
parseInt(r[1], 10),
parseInt(r[2], 10) - 1,
parseInt(r[3], 10),
parseInt(r[4], 10),
parseInt(r[5], 10) + offsetmins,
isecs,
msecs)
else
new Date(parseInt(date, 10), 0, 1).getTime()

View File

@ -0,0 +1,44 @@
class Morris.Hover
# Displays contextual information in a floating HTML div.
@defaults:
class: 'morris-hover morris-default-style'
constructor: (options = {}) ->
@options = $.extend {}, Morris.Hover.defaults, options
@el = $ "<div class='#{@options.class}'></div>"
@el.hide()
@options.parent.append(@el)
update: (html, x, y) ->
if not html
@hide()
else
@html(html)
@show()
@moveTo(x, y)
html: (content) ->
@el.html(content)
moveTo: (x, y) ->
parentWidth = @options.parent.innerWidth()
parentHeight = @options.parent.innerHeight()
hoverWidth = @el.outerWidth()
hoverHeight = @el.outerHeight()
left = Math.min(Math.max(0, x - hoverWidth / 2), parentWidth - hoverWidth)
if y?
top = y - hoverHeight - 10
if top < 0
top = y + 10
if top + hoverHeight > parentHeight
top = parentHeight / 2 - hoverHeight / 2
else
top = parentHeight / 2 - hoverHeight / 2
@el.css(left: left + "px", top: parseInt(top) + "px")
show: ->
@el.show()
hide: ->
@el.hide()

View File

@ -0,0 +1,405 @@
class Morris.Line extends Morris.Grid
# Initialise the graph.
#
constructor: (options) ->
return new Morris.Line(options) unless (@ instanceof Morris.Line)
super(options)
init: ->
# Some instance variables for later
if @options.hideHover isnt 'always'
@hover = new Morris.Hover(parent: @el)
@on('hovermove', @onHoverMove)
@on('hoverout', @onHoverOut)
@on('gridclick', @onGridClick)
# Default configuration
#
defaults:
lineWidth: 3
pointSize: 4
lineColors: [
'#0b62a4'
'#7A92A3'
'#4da74d'
'#afd8f8'
'#edc240'
'#cb4b4b'
'#9440ed'
]
pointStrokeWidths: [1]
pointStrokeColors: ['#ffffff']
pointFillColors: []
smooth: true
xLabels: 'auto'
xLabelFormat: null
xLabelMargin: 24
hideHover: false
# Do any size-related calculations
#
# @private
calc: ->
@calcPoints()
@generatePaths()
# calculate series data point coordinates
#
# @private
calcPoints: ->
for row in @data
row._x = @transX(row.x)
row._y = for y in row.y
if y? then @transY(y) else y
row._ymax = Math.min [@bottom].concat(y for y in row._y when y?)...
# hit test - returns the index of the row at the given x-coordinate
#
hitTest: (x) ->
return null if @data.length == 0
# TODO better search algo
for r, index in @data.slice(1)
break if x < (r._x + @data[index]._x) / 2
index
# click on grid event handler
#
# @private
onGridClick: (x, y) =>
index = @hitTest(x)
@fire 'click', index, @data[index].src, x, y
# hover movement event handler
#
# @private
onHoverMove: (x, y) =>
index = @hitTest(x)
@displayHoverForRow(index)
# hover out event handler
#
# @private
onHoverOut: =>
if @options.hideHover isnt false
@displayHoverForRow(null)
# display a hover popup over the given row
#
# @private
displayHoverForRow: (index) ->
if index?
@hover.update(@hoverContentForRow(index)...)
@hilight(index)
else
@hover.hide()
@hilight()
# hover content for a point
#
# @private
hoverContentForRow: (index) ->
row = @data[index]
content = "<div class='morris-hover-row-label'>#{row.label}</div>"
for y, j in row.y
content += """
<div class='morris-hover-point' style='color: #{@colorFor(row, j, 'label')}'>
#{@options.labels[j]}:
#{@yLabelFormat(y)}
</div>
"""
if typeof @options.hoverCallback is 'function'
content = @options.hoverCallback(index, @options, content, row.src)
[content, row._x, row._ymax]
# generate paths for series lines
#
# @private
generatePaths: ->
@paths = for i in [0...@options.ykeys.length]
smooth = if typeof @options.smooth is "boolean" then @options.smooth else @options.ykeys[i] in @options.smooth
coords = ({x: r._x, y: r._y[i]} for r in @data when r._y[i] isnt undefined)
if coords.length > 1
Morris.Line.createPath coords, smooth, @bottom
else
null
# Draws the line chart.
#
draw: ->
@drawXAxis() if @options.axes in [true, 'both', 'x']
@drawSeries()
if @options.hideHover is false
@displayHoverForRow(@data.length - 1)
# draw the x-axis labels
#
# @private
drawXAxis: ->
# draw x axis labels
ypos = @bottom + @options.padding / 2
prevLabelMargin = null
prevAngleMargin = null
drawLabel = (labelText, xpos) =>
label = @drawXAxisLabel(@transX(xpos), ypos, labelText)
textBox = label.getBBox()
label.transform("r#{-@options.xLabelAngle}")
labelBox = label.getBBox()
label.transform("t0,#{labelBox.height / 2}...")
if @options.xLabelAngle != 0
offset = -0.5 * textBox.width *
Math.cos(@options.xLabelAngle * Math.PI / 180.0)
label.transform("t#{offset},0...")
# try to avoid overlaps
labelBox = label.getBBox()
if (not prevLabelMargin? or
prevLabelMargin >= labelBox.x + labelBox.width or
prevAngleMargin? and prevAngleMargin >= labelBox.x) and
labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width()
if @options.xLabelAngle != 0
margin = 1.25 * @options.gridTextSize /
Math.sin(@options.xLabelAngle * Math.PI / 180.0)
prevAngleMargin = labelBox.x - margin
prevLabelMargin = labelBox.x - @options.xLabelMargin
else
label.remove()
if @options.parseTime
if @data.length == 1 and @options.xLabels == 'auto'
# where there's only one value in the series, we can't make a
# sensible guess for an x labelling scheme, so just use the original
# column label
labels = [[@data[0].label, @data[0].x]]
else
labels = Morris.labelSeries(@xmin, @xmax, @width, @options.xLabels, @options.xLabelFormat)
else
labels = ([row.label, row.x] for row in @data)
labels.reverse()
for l in labels
drawLabel(l[0], l[1])
# draw the data series
#
# @private
drawSeries: ->
@seriesPoints = []
for i in [@options.ykeys.length-1..0]
@_drawLineFor i
for i in [@options.ykeys.length-1..0]
@_drawPointFor i
_drawPointFor: (index) ->
@seriesPoints[index] = []
for row in @data
circle = null
if row._y[index]?
circle = @drawLinePoint(row._x, row._y[index], @colorFor(row, index, 'point'), index)
@seriesPoints[index].push(circle)
_drawLineFor: (index) ->
path = @paths[index]
if path isnt null
@drawLinePath path, @colorFor(null, index, 'line'), index
# create a path for a data series
#
# @private
@createPath: (coords, smooth, bottom) ->
path = ""
grads = Morris.Line.gradients(coords) if smooth
prevCoord = {y: null}
for coord, i in coords
if coord.y?
if prevCoord.y?
if smooth
g = grads[i]
lg = grads[i - 1]
ix = (coord.x - prevCoord.x) / 4
x1 = prevCoord.x + ix
y1 = Math.min(bottom, prevCoord.y + ix * lg)
x2 = coord.x - ix
y2 = Math.min(bottom, coord.y - ix * g)
path += "C#{x1},#{y1},#{x2},#{y2},#{coord.x},#{coord.y}"
else
path += "L#{coord.x},#{coord.y}"
else
if not smooth or grads[i]?
path += "M#{coord.x},#{coord.y}"
prevCoord = coord
return path
# calculate a gradient at each point for a series of points
#
# @private
@gradients: (coords) ->
grad = (a, b) -> (a.y - b.y) / (a.x - b.x)
for coord, i in coords
if coord.y?
nextCoord = coords[i + 1] or {y: null}
prevCoord = coords[i - 1] or {y: null}
if prevCoord.y? and nextCoord.y?
grad(prevCoord, nextCoord)
else if prevCoord.y?
grad(prevCoord, coord)
else if nextCoord.y?
grad(coord, nextCoord)
else
null
else
null
# @private
hilight: (index) =>
if @prevHilight isnt null and @prevHilight isnt index
for i in [0..@seriesPoints.length-1]
if @seriesPoints[i][@prevHilight]
@seriesPoints[i][@prevHilight].animate @pointShrinkSeries(i)
if index isnt null and @prevHilight isnt index
for i in [0..@seriesPoints.length-1]
if @seriesPoints[i][index]
@seriesPoints[i][index].animate @pointGrowSeries(i)
@prevHilight = index
colorFor: (row, sidx, type) ->
if typeof @options.lineColors is 'function'
@options.lineColors.call(@, row, sidx, type)
else if type is 'point'
@options.pointFillColors[sidx % @options.pointFillColors.length] || @options.lineColors[sidx % @options.lineColors.length]
else
@options.lineColors[sidx % @options.lineColors.length]
drawXAxisLabel: (xPos, yPos, text) ->
@raphael.text(xPos, yPos, text)
.attr('font-size', @options.gridTextSize)
.attr('font-family', @options.gridTextFamily)
.attr('font-weight', @options.gridTextWeight)
.attr('fill', @options.gridTextColor)
drawLinePath: (path, lineColor, lineIndex) ->
@raphael.path(path)
.attr('stroke', lineColor)
.attr('stroke-width', @lineWidthForSeries(lineIndex))
drawLinePoint: (xPos, yPos, pointColor, lineIndex) ->
@raphael.circle(xPos, yPos, @pointSizeForSeries(lineIndex))
.attr('fill', pointColor)
.attr('stroke-width', @pointStrokeWidthForSeries(lineIndex))
.attr('stroke', @pointStrokeColorForSeries(lineIndex))
# @private
pointStrokeWidthForSeries: (index) ->
@options.pointStrokeWidths[index % @options.pointStrokeWidths.length]
# @private
pointStrokeColorForSeries: (index) ->
@options.pointStrokeColors[index % @options.pointStrokeColors.length]
# @private
lineWidthForSeries: (index) ->
if (@options.lineWidth instanceof Array)
@options.lineWidth[index % @options.lineWidth.length]
else
@options.lineWidth
# @private
pointSizeForSeries: (index) ->
if (@options.pointSize instanceof Array)
@options.pointSize[index % @options.pointSize.length]
else
@options.pointSize
# @private
pointGrowSeries: (index) ->
Raphael.animation r: @pointSizeForSeries(index) + 3, 25, 'linear'
# @private
pointShrinkSeries: (index) ->
Raphael.animation r: @pointSizeForSeries(index), 25, 'linear'
# generate a series of label, timestamp pairs for x-axis labels
#
# @private
Morris.labelSeries = (dmin, dmax, pxwidth, specName, xLabelFormat) ->
ddensity = 200 * (dmax - dmin) / pxwidth # seconds per `margin` pixels
d0 = new Date(dmin)
spec = Morris.LABEL_SPECS[specName]
# if the spec doesn't exist, search for the closest one in the list
if spec is undefined
for name in Morris.AUTO_LABEL_ORDER
s = Morris.LABEL_SPECS[name]
if ddensity >= s.span
spec = s
break
# if we run out of options, use second-intervals
if spec is undefined
spec = Morris.LABEL_SPECS["second"]
# check if there's a user-defined formatting function
if xLabelFormat
spec = $.extend({}, spec, {fmt: xLabelFormat})
# calculate labels
d = spec.start(d0)
ret = []
while (t = d.getTime()) <= dmax
if t >= dmin
ret.push [spec.fmt(d), t]
spec.incr(d)
return ret
# @private
minutesSpecHelper = (interval) ->
span: interval * 60 * 1000
start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours())
fmt: (d) -> "#{Morris.pad2(d.getHours())}:#{Morris.pad2(d.getMinutes())}"
incr: (d) -> d.setUTCMinutes(d.getUTCMinutes() + interval)
# @private
secondsSpecHelper = (interval) ->
span: interval * 1000
start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes())
fmt: (d) -> "#{Morris.pad2(d.getHours())}:#{Morris.pad2(d.getMinutes())}:#{Morris.pad2(d.getSeconds())}"
incr: (d) -> d.setUTCSeconds(d.getUTCSeconds() + interval)
Morris.LABEL_SPECS =
"decade":
span: 172800000000 # 10 * 365 * 24 * 60 * 60 * 1000
start: (d) -> new Date(d.getFullYear() - d.getFullYear() % 10, 0, 1)
fmt: (d) -> "#{d.getFullYear()}"
incr: (d) -> d.setFullYear(d.getFullYear() + 10)
"year":
span: 17280000000 # 365 * 24 * 60 * 60 * 1000
start: (d) -> new Date(d.getFullYear(), 0, 1)
fmt: (d) -> "#{d.getFullYear()}"
incr: (d) -> d.setFullYear(d.getFullYear() + 1)
"month":
span: 2419200000 # 28 * 24 * 60 * 60 * 1000
start: (d) -> new Date(d.getFullYear(), d.getMonth(), 1)
fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}"
incr: (d) -> d.setMonth(d.getMonth() + 1)
"week":
span: 604800000 # 7 * 24 * 60 * 60 * 1000
start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate())
fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}-#{Morris.pad2(d.getDate())}"
incr: (d) -> d.setDate(d.getDate() + 7)
"day":
span: 86400000 # 24 * 60 * 60 * 1000
start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate())
fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}-#{Morris.pad2(d.getDate())}"
incr: (d) -> d.setDate(d.getDate() + 1)
"hour": minutesSpecHelper(60)
"30min": minutesSpecHelper(30)
"15min": minutesSpecHelper(15)
"10min": minutesSpecHelper(10)
"5min": minutesSpecHelper(5)
"minute": minutesSpecHelper(1)
"30sec": secondsSpecHelper(30)
"15sec": secondsSpecHelper(15)
"10sec": secondsSpecHelper(10)
"5sec": secondsSpecHelper(5)
"second": secondsSpecHelper(1)
Morris.AUTO_LABEL_ORDER = [
"decade", "year", "month", "week", "day", "hour",
"30min", "15min", "10min", "5min", "minute",
"30sec", "15sec", "10sec", "5sec", "second"
]

View File

@ -0,0 +1,2 @@
.morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255,255,255,0.8);border:solid 2px rgba(230,230,230,0.8);font-family:sans-serif;font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0}
.morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,60 @@
describe 'Morris.Area', ->
describe 'svg structure', ->
defaults =
element: 'graph'
data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}]
lineColors: [ '#0b62a4', '#7a92a3']
gridLineColor: '#aaa'
xkey: 'x'
ykeys: ['y']
labels: ['Y']
it 'should contain a line path for each line', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[stroke='#0b62a4']").size().should.equal 1
it 'should contain a path with stroke-width 0 for each line', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[stroke='#0b62a4']").size().should.equal 1
it 'should contain 5 grid lines', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[stroke='#aaaaaa']").size().should.equal 5
it 'should contain 9 text elements', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("text").size().should.equal 9
describe 'svg attributes', ->
defaults =
element: 'graph'
data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}]
xkey: 'x'
ykeys: ['y']
labels: ['Y']
lineColors: [ '#0b62a4', '#7a92a3']
lineWidth: 3
pointWidths: [5]
pointStrokeColors: ['#ffffff']
gridLineColor: '#aaa'
gridStrokeWidth: 0.5
gridTextColor: '#888'
gridTextSize: 12
it 'should not be cumulative if behaveLikeLine', ->
chart = Morris.Area $.extend {}, defaults, behaveLikeLine: true
chart.cumulative.should.equal false
it 'should have a line with transparent fill if behaveLikeLine', ->
chart = Morris.Area $.extend {}, defaults, behaveLikeLine: true
$('#graph').find("path[fill-opacity='0.8']").size().should.equal 1
it 'should not have a line with transparent fill', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[fill-opacity='0.8']").size().should.equal 0
it 'should have a line with the fill of a modified line color', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[fill='#0b62a4']").size().should.equal 0
$('#graph').find("path[fill='#7a92a3']").size().should.equal 0

View File

@ -0,0 +1,127 @@
describe 'Morris.Bar', ->
describe 'when using vertical grid', ->
defaults =
element: 'graph'
data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['Y', 'Z']
barColors: [ '#0b62a4', '#7a92a3']
gridLineColor: '#aaa'
gridStrokeWidth: 0.5
gridTextColor: '#888'
gridTextSize: 12
verticalGridCondition: (index) -> index % 2
verticalGridColor: '#888888'
verticalGridOpacity: '0.2'
describe 'svg structure', ->
it 'should contain extra rectangles for vertical grid', ->
$('#graph').css('height', '250px').css('width', '800px')
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect").size().should.equal 6
describe 'svg attributes', ->
it 'should have to bars with verticalGrid.color', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect[fill='#{defaults.verticalGridColor}']").size().should.equal 2
it 'should have to bars with verticalGrid.color', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect[fill-opacity='#{defaults.verticalGridOpacity}']").size().should.equal 2
describe 'svg structure', ->
defaults =
element: 'graph'
data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['Y', 'Z']
it 'should contain a rect for each bar', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect").size().should.equal 4
it 'should contain 5 grid lines', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("path").size().should.equal 5
it 'should contain 7 text elements', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("text").size().should.equal 7
describe 'svg attributes', ->
defaults =
element: 'graph'
data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['Y', 'Z']
barColors: [ '#0b62a4', '#7a92a3']
gridLineColor: '#aaa'
gridStrokeWidth: 0.5
gridTextColor: '#888'
gridTextSize: 12
it 'should have a bar with the first default color', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect[fill='#0b62a4']").size().should.equal 2
it 'should have a bar with no stroke', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect[stroke='none']").size().should.equal 4
it 'should have text with configured fill color', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("text[fill='#888888']").size().should.equal 7
it 'should have text with configured font size', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("text[font-size='12px']").size().should.equal 7
describe 'when setting bar radius', ->
describe 'svg structure', ->
defaults =
element: 'graph'
data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['Y', 'Z']
barRadius: [5, 5, 0, 0]
it 'should contain a path for each bar', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("path").size().should.equal 9
it 'should use rects if radius is too big', ->
delete defaults.barStyle
chart = Morris.Bar $.extend {}, defaults,
barRadius: [300, 300, 0, 0]
$('#graph').find("rect").size().should.equal 4
describe 'barSize option', ->
describe 'svg attributes', ->
defaults =
element: 'graph'
barSize: 20
data: [
{x: '2011 Q1', y: 3, z: 2, a: 3}
{x: '2011 Q2', y: 2, z: null, a: 1}
{x: '2011 Q3', y: 0, z: 2, a: 4}
{x: '2011 Q4', y: 2, z: 4, a: 3}
],
xkey: 'x'
ykeys: ['y', 'z', 'a']
labels: ['Y', 'Z', 'A']
it 'should calc the width if too narrow for barSize', ->
$('#graph').width('200px')
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect").filter((i) ->
parseFloat($(@).attr('width'), 10) < 10
).size().should.equal 11
it 'should set width to @options.barSize if possible', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect[width='#{defaults.barSize}']").size().should.equal 11

View File

@ -0,0 +1,36 @@
describe 'Morris.Bar#colorFor', ->
defaults =
element: 'graph'
data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['Y', 'Z']
it 'should fetch colours from an array', ->
chart = Morris.Bar $.extend {}, defaults, barColors: ['#f00', '#0f0', '#00f']
chart.colorFor(chart.data[0], 0, 'bar').should.equal '#f00'
chart.colorFor(chart.data[0], 0, 'hover').should.equal '#f00'
chart.colorFor(chart.data[0], 1, 'bar').should.equal '#0f0'
chart.colorFor(chart.data[0], 1, 'hover').should.equal '#0f0'
chart.colorFor(chart.data[0], 2, 'bar').should.equal '#00f'
chart.colorFor(chart.data[0], 2, 'hover').should.equal '#00f'
chart.colorFor(chart.data[0], 3, 'bar').should.equal '#f00'
chart.colorFor(chart.data[0], 4, 'hover').should.equal '#0f0'
it 'should defer to a callback', ->
stub = sinon.stub().returns '#f00'
chart = Morris.Bar $.extend {}, defaults, barColors: stub
stub.reset()
chart.colorFor(chart.data[0], 0, 'bar')
stub.should.have.been.calledWith(
{x:0, y:2, label:'foo'},
{index:0, key:'y', label:'Y'},
'bar')
chart.colorFor(chart.data[0], 1, 'hover')
stub.should.have.been.calledWith(
{x:0, y:3, label:'foo'},
{index:1, key:'z', label:'Z'},
'hover')

View File

@ -0,0 +1,38 @@
describe '#commas', ->
it 'should insert commas into long numbers', ->
# zero
Morris.commas(0).should.equal("0")
# positive integers
Morris.commas(1).should.equal("1")
Morris.commas(12).should.equal("12")
Morris.commas(123).should.equal("123")
Morris.commas(1234).should.equal("1,234")
Morris.commas(12345).should.equal("12,345")
Morris.commas(123456).should.equal("123,456")
Morris.commas(1234567).should.equal("1,234,567")
# negative integers
Morris.commas(-1).should.equal("-1")
Morris.commas(-12).should.equal("-12")
Morris.commas(-123).should.equal("-123")
Morris.commas(-1234).should.equal("-1,234")
Morris.commas(-12345).should.equal("-12,345")
Morris.commas(-123456).should.equal("-123,456")
Morris.commas(-1234567).should.equal("-1,234,567")
# positive decimals
Morris.commas(1.2).should.equal("1.2")
Morris.commas(12.34).should.equal("12.34")
Morris.commas(123.456).should.equal("123.456")
Morris.commas(1234.56).should.equal("1,234.56")
# negative decimals
Morris.commas(-1.2).should.equal("-1.2")
Morris.commas(-12.34).should.equal("-12.34")
Morris.commas(-123.456).should.equal("-123.456")
Morris.commas(-1234.56).should.equal("-1,234.56")
# null
Morris.commas(null).should.equal('-')

View File

@ -0,0 +1,76 @@
describe 'Morris.Donut', ->
describe 'svg structure', ->
defaults =
element: 'graph'
data: [ {label: 'Jam', value: 25 },
{label: 'Frosted', value: 40 },
{label: 'Custard', value: 25 },
{label: 'Sugar', value: 10 } ]
formatter: (y) -> "#{y}%"
it 'should contain 2 paths for each segment', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path").size().should.equal 8
it 'should contain 2 text elements for the label', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("text").size().should.equal 2
describe 'svg attributes', ->
defaults =
element: 'graph'
data: [ {label: 'Jam', value: 25 },
{label: 'Frosted', value: 40 },
{label: 'Custard', value: 25 },
{label: 'Sugar', value: 10 } ]
formatter: (y) -> "#{y}%"
colors: [ '#0B62A4', '#3980B5', '#679DC6', '#95BBD7']
it 'should have a label with font size 15', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("text[font-size='15px']").size().should.equal 1
it 'should have a label with font size 14', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("text[font-size='14px']").size().should.equal 1
it 'should have a label with font-weight 800', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("text[font-weight='800']").size().should.equal 1
it 'should have 1 paths with fill of first color', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[fill='#0b62a4']").size().should.equal 1
it 'should have 1 paths with stroke of first color', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[stroke='#0b62a4']").size().should.equal 1
it 'should have a path with white stroke', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[stroke='#ffffff']").size().should.equal 4
it 'should have a path with stroke-width 3', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[stroke-width='3']").size().should.equal 4
it 'should have a path with stroke-width 2', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[stroke-width='2']").size().should.equal 4
describe 'setData', ->
defaults =
element: 'graph'
data: [ {label: 'One', value: 25 }, {label: "Two", value: 30} ]
colors: ['#ff0000', '#00ff00', '#0000ff']
it 'should update the chart', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[stroke='#0000ff']").size().should.equal 0
chart.setData [
{ label: 'One', value: 25 }
{ label: 'Two', value: 30 }
{ label: 'Three', value: 35 }
]
$('#graph').find("path[stroke='#0000ff']").size().should.equal 1

View File

@ -0,0 +1,25 @@
describe 'Morris.Grid#autoGridLines', ->
beforeEach ->
@subject = Morris.Grid.prototype.autoGridLines
it 'should draw at fixed intervals', ->
@subject(0, 4, 5).should.deep.equal [0, 1, 2, 3, 4]
@subject(0, 400, 5).should.deep.equal [0, 100, 200, 300, 400]
it 'should pick intervals that show significant numbers', ->
@subject(102, 499, 5).should.deep.equal [100, 200, 300, 400, 500]
it 'should draw zero when it falls within [ymin..ymax]', ->
@subject(-100, 300, 5).should.deep.equal [-100, 0, 100, 200, 300]
@subject(-50, 350, 5).should.deep.equal [-125, 0, 125, 250, 375]
@subject(-400, 400, 5).should.deep.equal [-400, -200, 0, 200, 400]
@subject(100, 500, 5).should.deep.equal [100, 200, 300, 400, 500]
@subject(-500, -100, 5).should.deep.equal [-500, -400, -300, -200, -100]
it 'should generate decimal labels to 2 significant figures', ->
@subject(0, 1, 5).should.deep.equal [0, 0.25, 0.5, 0.75, 1]
@subject(0.1, 0.5, 5).should.deep.equal [0.1, 0.2, 0.3, 0.4, 0.5]
it 'should use integer intervals for intervals larger than 1', ->
@subject(0, 9, 5).should.deep.equal [0, 3, 6, 9, 12]

View File

@ -0,0 +1,208 @@
describe 'Morris.Grid#setData', ->
it 'should not alter user-supplied data', ->
my_data = [{x: 1, y: 1}, {x: 2, y: 2}]
expected_data = [{x: 1, y: 1}, {x: 2, y: 2}]
Morris.Line
element: 'graph'
data: my_data
xkey: 'x'
ykeys: ['y']
labels: ['dontcare']
my_data.should.deep.equal expected_data
describe 'ymin/ymax', ->
beforeEach ->
@defaults =
element: 'graph'
xkey: 'x'
ykeys: ['y', 'z']
labels: ['y', 'z']
it 'should use a user-specified minimum and maximum value', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1, y: 1}]
ymin: 10
ymax: 20
line.ymin.should.equal 10
line.ymax.should.equal 20
describe 'auto', ->
it 'should automatically calculate the minimum and maximum value', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1, y: 10}, {x: 2, y: 15}, {x: 3, y: null}, {x: 4}]
ymin: 'auto'
ymax: 'auto'
line.ymin.should.equal 10
line.ymax.should.equal 15
it 'should automatically calculate the minimum and maximum value given no y data', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1}, {x: 2}, {x: 3}, {x: 4}]
ymin: 'auto'
ymax: 'auto'
line.ymin.should.equal 0
line.ymax.should.equal 1
describe 'auto [n]', ->
it 'should automatically calculate the minimum and maximum value', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1, y: 10}, {x: 2, y: 15}, {x: 3, y: null}, {x: 4}]
ymin: 'auto 11'
ymax: 'auto 13'
line.ymin.should.equal 10
line.ymax.should.equal 15
it 'should automatically calculate the minimum and maximum value given no data', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1}, {x: 2}, {x: 3}, {x: 4}]
ymin: 'auto 11'
ymax: 'auto 13'
line.ymin.should.equal 11
line.ymax.should.equal 13
it 'should use a user-specified minimum and maximum value', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1, y: 10}, {x: 2, y: 15}, {x: 3, y: null}, {x: 4}]
ymin: 'auto 5'
ymax: 'auto 20'
line.ymin.should.equal 5
line.ymax.should.equal 20
it 'should use a user-specified minimum and maximum value given no data', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1}, {x: 2}, {x: 3}, {x: 4}]
ymin: 'auto 5'
ymax: 'auto 20'
line.ymin.should.equal 5
line.ymax.should.equal 20
describe 'xmin/xmax', ->
it 'should calculate the horizontal range', ->
line = Morris.Line
element: 'graph'
data: [{x: 2, y: 2}, {x: 1, y: 1}, {x: 4, y: 4}, {x: 3, y: 3}]
xkey: 'x'
ykeys: ['y']
labels: ['y']
line.xmin.should == 1
line.xmax.should == 4
it "should pad the range if there's only one data point", ->
line = Morris.Line
element: 'graph'
data: [{x: 2, y: 2}]
xkey: 'x'
ykeys: ['y']
labels: ['y']
line.xmin.should == 1
line.xmax.should == 3
describe 'sorting', ->
it 'should sort data when parseTime is true', ->
line = Morris.Line
element: 'graph'
data: [
{x: '2012 Q1', y: 2},
{x: '2012 Q3', y: 1},
{x: '2012 Q4', y: 4},
{x: '2012 Q2', y: 3}]
xkey: 'x'
ykeys: ['y']
labels: ['y']
line.data.map((row) -> row.label).should.deep.equal ['2012 Q1', '2012 Q2', '2012 Q3', '2012 Q4']
it 'should not sort data when parseTime is false', ->
line = Morris.Line
element: 'graph'
data: [{x: 1, y: 2}, {x: 4, y: 1}, {x: 3, y: 4}, {x: 2, y: 3}]
xkey: 'x'
ykeys: ['y']
labels: ['y']
parseTime: false
line.data.map((row) -> row.label).should.deep.equal [1, 4, 3, 2]
describe 'timestamp data', ->
it 'should generate default labels for timestamp x-values', ->
d = [
new Date 2012, 0, 1
new Date 2012, 0, 2
new Date 2012, 0, 3
new Date 2012, 0, 4
]
line = Morris.Line
element: 'graph'
data: [
{x: d[0].getTime(), y: 2},
{x: d[1].getTime(), y: 1},
{x: d[2].getTime(), y: 4},
{x: d[3].getTime(), y: 3}]
xkey: 'x'
ykeys: ['y']
labels: ['y']
line.data.map((row) -> row.label).should.deep.equal d.map((t) -> t.toString())
it 'should use a user-supplied formatter for labels', ->
line = Morris.Line
element: 'graph'
data: [
{x: new Date(2012, 0, 1).getTime(), y: 2},
{x: new Date(2012, 0, 2).getTime(), y: 1},
{x: new Date(2012, 0, 3).getTime(), y: 4},
{x: new Date(2012, 0, 4).getTime(), y: 3}]
xkey: 'x'
ykeys: ['y']
labels: ['y']
dateFormat: (ts) ->
date = new Date(ts)
"#{date.getFullYear()}-#{date.getMonth()+1}-#{date.getDate()}"
line.data.map((row) -> row.label).should.deep.equal ['2012-1-1', '2012-1-2', '2012-1-3', '2012-1-4']
it 'should parse y-values in strings', ->
line = Morris.Line
element: 'graph'
data: [{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}]
xkey: 'x'
ykeys: ['y']
labels: ['y']
line.ymin.should == 12
line.ymax.should == 16
line.data.map((row) -> row.y).should.deep.equal [[13.5], [12], [16], [14]]
it 'should clear the chart when empty data is supplied', ->
line = Morris.Line
element: 'graph',
data: [{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}]
xkey: 'x'
ykeys: ['y']
labels: ['y']
line.data.length.should.equal 4
line.setData([])
line.data.length.should.equal 0
line.setData([{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}])
line.data.length.should.equal 4
it 'should be able to add data if the chart is initialised with empty data', ->
line = Morris.Line
element: 'graph',
data: []
xkey: 'x'
ykeys: ['y']
labels: ['y']
line.data.length.should.equal 0
line.setData([{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}])
line.data.length.should.equal 4
it 'should automatically choose significant numbers for y-labels', ->
line = Morris.Line
element: 'graph',
data: [{x: 1, y: 0}, {x: 2, y: 3600}]
xkey: 'x'
ykeys: ['y']
labels: ['y']
line.grid.should == [0, 1000, 2000, 3000, 4000]

View File

@ -0,0 +1,15 @@
describe 'Morris.Grid#yLabelFormat', ->
it 'should use custom formatter for y labels', ->
formatter = (label) ->
flabel = parseFloat(label) / 1000
"#{flabel.toFixed(1)}k"
line = Morris.Line
element: 'graph'
data: [{x: 1, y: 1500}, {x: 2, y: 2500}]
xkey: 'x'
ykeys: ['y']
labels: ['dontcare']
preUnits: "$"
yLabelFormat: formatter
line.yLabelFormat(1500).should.equal "1.5k"

View File

@ -0,0 +1,64 @@
describe "Morris.Hover", ->
describe "with dummy content", ->
beforeEach ->
parent = $('<div style="width:200px;height:180px"></div>')
.appendTo($('#test'))
@hover = new Morris.Hover(parent: parent)
@element = $('#test .morris-hover')
it "should initialise a hidden, empty popup", ->
@element.should.exist
@element.should.be.hidden
@element.should.be.empty
describe "#show", ->
it "should show the popup", ->
@hover.show()
@element.should.be.visible
describe "#hide", ->
it "should hide the popup", ->
@hover.show()
@hover.hide()
@element.should.be.hidden
describe "#html", ->
it "should replace the contents of the element", ->
@hover.html('<div>Foobarbaz</div>')
@element.should.have.html('<div>Foobarbaz</div>')
describe "#moveTo", ->
beforeEach ->
@hover.html('<div style="width:84px;height:84px"></div>')
it "should place the popup directly above the given point", ->
@hover.moveTo(100, 150)
@element.should.have.css('left', '50px')
@element.should.have.css('top', '40px')
it "should place the popup below the given point if it does not fit above", ->
@hover.moveTo(100, 50)
@element.should.have.css('left', '50px')
@element.should.have.css('top', '60px')
it "should center the popup vertically if it will not fit above or below", ->
@hover.moveTo(100, 100)
@element.should.have.css('left', '50px')
@element.should.have.css('top', '40px')
it "should center the popup vertically if no y value is supplied", ->
@hover.moveTo(100)
@element.should.have.css('left', '50px')
@element.should.have.css('top', '40px')
describe "#update", ->
it "should update content, show and reposition the popup", ->
hover = new Morris.Hover(parent: $('#test'))
html = "<div style='width:84px;height:84px'>Hello, Everyone!</div>"
hover.update(html, 150, 200)
el = $('#test .morris-hover')
el.should.have.css('left', '100px')
el.should.have.css('top', '90px')
el.should.have.text('Hello, Everyone!')

View File

@ -0,0 +1,186 @@
describe '#labelSeries', ->
it 'should generate decade intervals', ->
Morris.labelSeries(
new Date(1952, 0, 1).getTime(),
new Date(2012, 0, 1).getTime(),
1000
).should.deep.equal([
["1960", new Date(1960, 0, 1).getTime()],
["1970", new Date(1970, 0, 1).getTime()],
["1980", new Date(1980, 0, 1).getTime()],
["1990", new Date(1990, 0, 1).getTime()],
["2000", new Date(2000, 0, 1).getTime()],
["2010", new Date(2010, 0, 1).getTime()]
])
Morris.labelSeries(
new Date(1952, 3, 1).getTime(),
new Date(2012, 3, 1).getTime(),
1000
).should.deep.equal([
["1960", new Date(1960, 0, 1).getTime()],
["1970", new Date(1970, 0, 1).getTime()],
["1980", new Date(1980, 0, 1).getTime()],
["1990", new Date(1990, 0, 1).getTime()],
["2000", new Date(2000, 0, 1).getTime()],
["2010", new Date(2010, 0, 1).getTime()]
])
it 'should generate year intervals', ->
Morris.labelSeries(
new Date(2007, 0, 1).getTime(),
new Date(2012, 0, 1).getTime(),
1000
).should.deep.equal([
["2007", new Date(2007, 0, 1).getTime()],
["2008", new Date(2008, 0, 1).getTime()],
["2009", new Date(2009, 0, 1).getTime()],
["2010", new Date(2010, 0, 1).getTime()],
["2011", new Date(2011, 0, 1).getTime()],
["2012", new Date(2012, 0, 1).getTime()]
])
Morris.labelSeries(
new Date(2007, 3, 1).getTime(),
new Date(2012, 3, 1).getTime(),
1000
).should.deep.equal([
["2008", new Date(2008, 0, 1).getTime()],
["2009", new Date(2009, 0, 1).getTime()],
["2010", new Date(2010, 0, 1).getTime()],
["2011", new Date(2011, 0, 1).getTime()],
["2012", new Date(2012, 0, 1).getTime()]
])
it 'should generate month intervals', ->
Morris.labelSeries(
new Date(2012, 0, 1).getTime(),
new Date(2012, 5, 1).getTime(),
1000
).should.deep.equal([
["2012-01", new Date(2012, 0, 1).getTime()],
["2012-02", new Date(2012, 1, 1).getTime()],
["2012-03", new Date(2012, 2, 1).getTime()],
["2012-04", new Date(2012, 3, 1).getTime()],
["2012-05", new Date(2012, 4, 1).getTime()],
["2012-06", new Date(2012, 5, 1).getTime()]
])
it 'should generate week intervals', ->
Morris.labelSeries(
new Date(2012, 0, 1).getTime(),
new Date(2012, 1, 10).getTime(),
1000
).should.deep.equal([
["2012-01-01", new Date(2012, 0, 1).getTime()],
["2012-01-08", new Date(2012, 0, 8).getTime()],
["2012-01-15", new Date(2012, 0, 15).getTime()],
["2012-01-22", new Date(2012, 0, 22).getTime()],
["2012-01-29", new Date(2012, 0, 29).getTime()],
["2012-02-05", new Date(2012, 1, 5).getTime()]
])
it 'should generate day intervals', ->
Morris.labelSeries(
new Date(2012, 0, 1).getTime(),
new Date(2012, 0, 6).getTime(),
1000
).should.deep.equal([
["2012-01-01", new Date(2012, 0, 1).getTime()],
["2012-01-02", new Date(2012, 0, 2).getTime()],
["2012-01-03", new Date(2012, 0, 3).getTime()],
["2012-01-04", new Date(2012, 0, 4).getTime()],
["2012-01-05", new Date(2012, 0, 5).getTime()],
["2012-01-06", new Date(2012, 0, 6).getTime()]
])
it 'should generate hour intervals', ->
Morris.labelSeries(
new Date(2012, 0, 1, 0).getTime(),
new Date(2012, 0, 1, 5).getTime(),
1000
).should.deep.equal([
["00:00", new Date(2012, 0, 1, 0).getTime()],
["01:00", new Date(2012, 0, 1, 1).getTime()],
["02:00", new Date(2012, 0, 1, 2).getTime()],
["03:00", new Date(2012, 0, 1, 3).getTime()],
["04:00", new Date(2012, 0, 1, 4).getTime()],
["05:00", new Date(2012, 0, 1, 5).getTime()]
])
it 'should generate half-hour intervals', ->
Morris.labelSeries(
new Date(2012, 0, 1, 0, 0).getTime(),
new Date(2012, 0, 1, 2, 30).getTime(),
1000
).should.deep.equal([
["00:00", new Date(2012, 0, 1, 0, 0).getTime()],
["00:30", new Date(2012, 0, 1, 0, 30).getTime()],
["01:00", new Date(2012, 0, 1, 1, 0).getTime()],
["01:30", new Date(2012, 0, 1, 1, 30).getTime()],
["02:00", new Date(2012, 0, 1, 2, 0).getTime()],
["02:30", new Date(2012, 0, 1, 2, 30).getTime()]
])
Morris.labelSeries(
new Date(2012, 4, 12, 0, 0).getTime(),
new Date(2012, 4, 12, 2, 30).getTime(),
1000
).should.deep.equal([
["00:00", new Date(2012, 4, 12, 0, 0).getTime()],
["00:30", new Date(2012, 4, 12, 0, 30).getTime()],
["01:00", new Date(2012, 4, 12, 1, 0).getTime()],
["01:30", new Date(2012, 4, 12, 1, 30).getTime()],
["02:00", new Date(2012, 4, 12, 2, 0).getTime()],
["02:30", new Date(2012, 4, 12, 2, 30).getTime()]
])
it 'should generate fifteen-minute intervals', ->
Morris.labelSeries(
new Date(2012, 0, 1, 0, 0).getTime(),
new Date(2012, 0, 1, 1, 15).getTime(),
1000
).should.deep.equal([
["00:00", new Date(2012, 0, 1, 0, 0).getTime()],
["00:15", new Date(2012, 0, 1, 0, 15).getTime()],
["00:30", new Date(2012, 0, 1, 0, 30).getTime()],
["00:45", new Date(2012, 0, 1, 0, 45).getTime()],
["01:00", new Date(2012, 0, 1, 1, 0).getTime()],
["01:15", new Date(2012, 0, 1, 1, 15).getTime()]
])
Morris.labelSeries(
new Date(2012, 4, 12, 0, 0).getTime(),
new Date(2012, 4, 12, 1, 15).getTime(),
1000
).should.deep.equal([
["00:00", new Date(2012, 4, 12, 0, 0).getTime()],
["00:15", new Date(2012, 4, 12, 0, 15).getTime()],
["00:30", new Date(2012, 4, 12, 0, 30).getTime()],
["00:45", new Date(2012, 4, 12, 0, 45).getTime()],
["01:00", new Date(2012, 4, 12, 1, 0).getTime()],
["01:15", new Date(2012, 4, 12, 1, 15).getTime()]
])
it 'should override automatic intervals', ->
Morris.labelSeries(
new Date(2011, 11, 12).getTime(),
new Date(2012, 0, 12).getTime(),
1000,
"year"
).should.deep.equal([
["2012", new Date(2012, 0, 1).getTime()]
])
it 'should apply custom formatters', ->
Morris.labelSeries(
new Date(2012, 0, 1).getTime(),
new Date(2012, 0, 6).getTime(),
1000,
"day",
(d) -> "#{d.getMonth()+1}/#{d.getDate()}/#{d.getFullYear()}"
).should.deep.equal([
["1/1/2012", new Date(2012, 0, 1).getTime()],
["1/2/2012", new Date(2012, 0, 2).getTime()],
["1/3/2012", new Date(2012, 0, 3).getTime()],
["1/4/2012", new Date(2012, 0, 4).getTime()],
["1/5/2012", new Date(2012, 0, 5).getTime()],
["1/6/2012", new Date(2012, 0, 6).getTime()]
])

View File

@ -0,0 +1,211 @@
describe 'Morris.Line', ->
it 'should raise an error when the placeholder element is not found', ->
my_data = [{x: 1, y: 1}, {x: 2, y: 2}]
fn = ->
Morris.Line(
element: "thisplacedoesnotexist"
data: my_data
xkey: 'x'
ykeys: ['y']
labels: ['dontcare']
)
fn.should.throw(/Graph container element not found/)
it 'should make point styles customizable', ->
my_data = [{x: 1, y: 1}, {x: 2, y: 2}]
red = '#ff0000'
blue = '#0000ff'
chart = Morris.Line
element: 'graph'
data: my_data
xkey: 'x'
ykeys: ['y']
labels: ['dontcare']
pointStrokeColors: [red, blue]
pointStrokeWidths: [1, 2]
pointFillColors: [null, red]
chart.pointStrokeWidthForSeries(0).should.equal 1
chart.pointStrokeColorForSeries(0).should.equal red
chart.pointStrokeWidthForSeries(1).should.equal 2
chart.pointStrokeColorForSeries(1).should.equal blue
chart.colorFor(chart.data[0], 0, 'point').should.equal chart.colorFor(chart.data[0], 0, 'line')
chart.colorFor(chart.data[1], 1, 'point').should.equal red
describe 'generating column labels', ->
it 'should use user-supplied x value strings by default', ->
chart = Morris.Line
element: 'graph'
data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}]
xkey: 'x'
ykeys: ['y']
labels: ['dontcare']
chart.data.map((x) -> x.label).should == ['2012 Q1', '2012 Q2']
it 'should use a default format for timestamp x-values', ->
d1 = new Date(2012, 0, 1)
d2 = new Date(2012, 0, 2)
chart = Morris.Line
element: 'graph'
data: [{x: d1.getTime(), y: 1}, {x: d2.getTime(), y: 1}]
xkey: 'x'
ykeys: ['y']
labels: ['dontcare']
chart.data.map((x) -> x.label).should == [d2.toString(), d1.toString()]
it 'should use user-defined formatters', ->
d = new Date(2012, 0, 1)
chart = Morris.Line
element: 'graph'
data: [{x: d.getTime(), y: 1}, {x: '2012-01-02', y: 1}]
xkey: 'x'
ykeys: ['y']
labels: ['dontcare']
dateFormat: (d) ->
x = new Date(d)
"#{x.getYear()}/#{x.getMonth()+1}/#{x.getDay()}"
chart.data.map((x) -> x.label).should == ['2012/1/1', '2012/1/2']
describe 'rendering lines', ->
beforeEach ->
@defaults =
element: 'graph'
data: [{x:0, y:1, z:0}, {x:1, y:0, z:1}, {x:2, y:1, z:0}, {x:3, y:0, z:1}, {x:4, y:1, z:0}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['y', 'z']
lineColors: ['#abcdef', '#fedcba']
smooth: true
shouldHavePath = (regex, color = '#abcdef') ->
# Matches an SVG path element within the rendered chart.
#
# Sneakily uses line colors to differentiate between paths within
# the chart.
$('#graph').find("path[stroke='#{color}']").attr('d').should.match regex
it 'should generate smooth lines when options.smooth is true', ->
Morris.Line @defaults
shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){4}/
it 'should generate jagged lines when options.smooth is false', ->
Morris.Line $.extend(@defaults, smooth: false)
shouldHavePath /M[\d\.]+,[\d\.]+(L[\d\.]+,[\d\.]+){4}/
it 'should generate smooth/jagged lines according to the value for each series when options.smooth is an array', ->
Morris.Line $.extend(@defaults, smooth: ['y'])
shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){4}/, '#abcdef'
shouldHavePath /M[\d\.]+,[\d\.]+(L[\d\.]+,[\d\.]+){4}/, '#fedcba'
it 'should ignore undefined values', ->
@defaults.data[2].y = undefined
Morris.Line @defaults
shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){3}/
it 'should break the line at null values', ->
@defaults.data[2].y = null
Morris.Line @defaults
shouldHavePath /(M[\d\.]+,[\d\.]+C[\d\.]+(,[\d\.]+){5}){2}/
it 'should make line width customizable', ->
chart = Morris.Line $.extend(@defaults, lineWidth: [1, 2])
chart.lineWidthForSeries(0).should.equal 1
chart.lineWidthForSeries(1).should.equal 2
describe '#createPath', ->
it 'should generate a smooth line', ->
testData = [{x: 0, y: 10}, {x: 10, y: 0}, {x: 20, y: 10}]
path = Morris.Line.createPath(testData, true, 20)
path.should.equal 'M0,10C2.5,7.5,7.5,0,10,0C12.5,0,17.5,7.5,20,10'
it 'should generate a jagged line', ->
testData = [{x: 0, y: 10}, {x: 10, y: 0}, {x: 20, y: 10}]
path = Morris.Line.createPath(testData, false, 20)
path.should.equal 'M0,10L10,0L20,10'
it 'should prevent paths from descending below the bottom of the chart', ->
testData = [{x: 0, y: 20}, {x: 10, y: 30}, {x: 20, y: 10}]
path = Morris.Line.createPath(testData, true, 30)
path.should.equal 'M0,20C2.5,22.5,7.5,30,10,30C12.5,28.75,17.5,15,20,10'
it 'should break the line at null values', ->
testData = [{x: 0, y: 10}, {x: 10, y: 0}, {x: 20, y: null}, {x: 30, y: 10}, {x: 40, y: 0}]
path = Morris.Line.createPath(testData, true, 20)
path.should.equal 'M0,10C2.5,7.5,7.5,2.5,10,0M30,10C32.5,7.5,37.5,2.5,40,0'
it 'should ignore leading and trailing null values', ->
testData = [{x: 0, y: null}, {x: 10, y: 10}, {x: 20, y: 0}, {x: 30, y: 10}, {x: 40, y: null}]
path = Morris.Line.createPath(testData, true, 20)
path.should.equal 'M10,10C12.5,7.5,17.5,0,20,0C22.5,0,27.5,7.5,30,10'
describe 'svg structure', ->
defaults =
element: 'graph'
data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}]
lineColors: [ '#0b62a4', '#7a92a3']
xkey: 'x'
ykeys: ['y']
labels: ['dontcare']
it 'should contain a path that represents the line', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("path[stroke='#0b62a4']").size().should.equal 1
it 'should contain a circle for each data point', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("circle").size().should.equal 2
it 'should contain 5 grid lines', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("path[stroke='#aaaaaa']").size().should.equal 5
it 'should contain 9 text elements', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("text").size().should.equal 9
describe 'svg attributes', ->
defaults =
element: 'graph'
data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['Y', 'Z']
lineColors: [ '#0b62a4', '#7a92a3']
lineWidth: 3
pointStrokeWidths: [5]
pointStrokeColors: ['#ffffff']
gridLineColor: '#aaa'
gridStrokeWidth: 0.5
gridTextColor: '#888'
gridTextSize: 12
pointSize: [5]
it 'should have circles with configured fill color', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("circle[fill='#0b62a4']").size().should.equal 2
it 'should have circles with configured stroke width', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("circle[stroke-width='5']").size().should.equal 2
it 'should have circles with configured stroke color', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("circle[stroke='#ffffff']").size().should.equal 2
it 'should have line with configured line width', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("path[stroke-width='3']").size().should.equal 1
it 'should have text with configured font size', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("text[font-size='12px']").size().should.equal 9
it 'should have text with configured font size', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("text[fill='#888888']").size().should.equal 9
it 'should have circle with configured size', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("circle[r='5']").size().should.equal 2

View File

@ -0,0 +1,17 @@
describe '#pad', ->
it 'should pad numbers', ->
Morris.pad2(0).should.equal("00")
Morris.pad2(1).should.equal("01")
Morris.pad2(2).should.equal("02")
Morris.pad2(3).should.equal("03")
Morris.pad2(4).should.equal("04")
Morris.pad2(5).should.equal("05")
Morris.pad2(6).should.equal("06")
Morris.pad2(7).should.equal("07")
Morris.pad2(8).should.equal("08")
Morris.pad2(9).should.equal("09")
Morris.pad2(10).should.equal("10")
Morris.pad2(12).should.equal("12")
Morris.pad2(34).should.equal("34")
Morris.pad2(123).should.equal("123")

View File

@ -0,0 +1,35 @@
describe '#parseTime', ->
it 'should parse years', ->
Morris.parseDate('2012').should.equal(new Date(2012, 0, 1).getTime())
it 'should parse quarters', ->
Morris.parseDate('2012 Q1').should.equal(new Date(2012, 2, 1).getTime())
it 'should parse months', ->
Morris.parseDate('2012-09').should.equal(new Date(2012, 8, 1).getTime())
Morris.parseDate('2012-10').should.equal(new Date(2012, 9, 1).getTime())
it 'should parse dates', ->
Morris.parseDate('2012-09-15').should.equal(new Date(2012, 8, 15).getTime())
Morris.parseDate('2012-10-15').should.equal(new Date(2012, 9, 15).getTime())
it 'should parse times', ->
Morris.parseDate("2012-10-15 12:34").should.equal(new Date(2012, 9, 15, 12, 34).getTime())
Morris.parseDate("2012-10-15T12:34").should.equal(new Date(2012, 9, 15, 12, 34).getTime())
Morris.parseDate("2012-10-15 12:34:55").should.equal(new Date(2012, 9, 15, 12, 34, 55).getTime())
Morris.parseDate("2012-10-15T12:34:55").should.equal(new Date(2012, 9, 15, 12, 34, 55).getTime())
it 'should parse times with timezones', ->
Morris.parseDate("2012-10-15T12:34+0100").should.equal(Date.UTC(2012, 9, 15, 11, 34))
Morris.parseDate("2012-10-15T12:34+02:00").should.equal(Date.UTC(2012, 9, 15, 10, 34))
Morris.parseDate("2012-10-15T12:34-0100").should.equal(Date.UTC(2012, 9, 15, 13, 34))
Morris.parseDate("2012-10-15T12:34-02:00").should.equal(Date.UTC(2012, 9, 15, 14, 34))
Morris.parseDate("2012-10-15T12:34:55Z").should.equal(Date.UTC(2012, 9, 15, 12, 34, 55))
Morris.parseDate("2012-10-15T12:34:55+0600").should.equal(Date.UTC(2012, 9, 15, 6, 34, 55))
Morris.parseDate("2012-10-15T12:34:55+04:00").should.equal(Date.UTC(2012, 9, 15, 8, 34, 55))
Morris.parseDate("2012-10-15T12:34:55-0600").should.equal(Date.UTC(2012, 9, 15, 18, 34, 55))
it 'should pass-through timestamps', ->
Morris.parseDate(new Date(2012, 9, 15, 12, 34, 55, 123).getTime())
.should.equal(new Date(2012, 9, 15, 12, 34, 55, 123).getTime())

View File

@ -0,0 +1,34 @@
<!doctype html>
<head>
<meta charset="utf-8">
<title>morris.js tests</title>
<link rel="stylesheet" href="../bower_components/mocha/mocha.css" type="text/css" media="screen" />
<link rel="stylesheet" href="../morris.css" type="text/css" media="screen" />
<!-- jQuery packaging changed for 2.1.0, so try to load both paths, one will work. -->
<script src="../bower_components/jquery/dist/jquery.js"></script>
<script src="../bower_components/jquery/jquery.js"></script>
<script type="text/javascript" src="../bower_components/raphael/raphael-min.js"></script>
</head>
<body>
<div id="mocha"></div>
<script type="text/javascript" src="../bower_components/mocha/mocha.js"></script>
<script type="text/javascript" src="../bower_components/chai/chai.js"></script>
<script type="text/javascript" src="../bower_components/chai-jquery/chai-jquery.js"></script>
<script type="text/javascript" src="../bower_components/sinon/index.js"></script>
<script type="text/javascript" src="../bower_components/sinon-chai/lib/sinon-chai.js"></script>
<script>
mocha.setup('bdd');
should = chai.should();
</script>
<script type="text/javascript" src="../morris.js"></script>
<script type="text/javascript" src="../build/spec.js"></script>
<div id="test" style="width: 400px; height: 200px;"></div>
<script>
if (navigator.userAgent.indexOf('PhantomJS') < 0) {
mocha.run();
}
</script>
</body>
</html>

View File

@ -0,0 +1,6 @@
beforeEach ->
placeholder = $('<div id="graph" style="width: 600px; height: 400px"></div>')
$('#test').append(placeholder)
afterEach ->
$('#test').empty()

View File

@ -0,0 +1,56 @@
var webpage = require("webpage"),
fs = require("fs");
var html_path = fs.absolute("test.html");
var examples = [];
function run_example(example_index) {
if (example_index >= examples.length) {
phantom.exit(0);
return;
}
var example = examples[example_index];
var snapshot_index = 0;
var page = webpage.create();
page.viewportSize = { width: 500, height: 300 };
page.clipRect = { width: 500, height: 300 };
page.onAlert = function (msg) {
var e = JSON.parse(msg);
if (e.fn == "snapshot") {
page.render("output/" + example.name + snapshot_index + ".png");
snapshot_index += 1;
} else if (e.fn == "mousemove") {
page.sendEvent("mousemove", e.x, e.y);
}
};
page.open(html_path, function (status) {
if (status == "fail") {
console.log("Failed to load test page: " + example.name);
phantom.exit(1);
} else {
page.evaluate(example.runner);
}
page.close();
run_example(example_index + 1);
});
}
exports.def = function (name, runner) {
examples.push({ name: name, runner: runner });
};
exports.run = function () {
if (fs.isDirectory("output")) {
fs.list("output").forEach(function (path) {
if (path != "." && path != "..") {
fs.remove("output/" + path);
}
});
} else {
fs.makeDirectory("output");
}
run_example(0);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,32 @@
#!/bin/sh
# visual_specs.js creates output in output/XXX.png
phantomjs visual_specs.js
# clear out old diffs
mkdir -p diff
rm -f diff/*
# generate diffs
PASS=1
for i in exemplary/*.png
do
FN=`basename $i`
perceptualdiff $i output/$FN -output diff/$FN
if [ $? -eq 0 ]
then
echo "OK: $FN"
else
echo "FAIL: $FN"
PASS=0
fi
done
# pass / fail
if [ $PASS -eq 1 ]
then
echo "Success."
else
echo "Failed."
exit 1
fi

View File

@ -0,0 +1,34 @@
<!doctype html>
<head>
<!-- jQuery packaging changed for 2.1.0, so try to load both paths, one will work. -->
<script src="../../bower_components/jquery/dist/jquery.js"></script>
<script src="../../bower_components/jquery/jquery.js"></script>
<script src="../../bower_components/raphael/raphael-min.js"></script>
<script src="../../morris.js"></script>
<link rel="stylesheet" href="../../morris.css">
<style>
body {
padding: 0;
margin: 0;
background-color: white;
}
#chart {
width: 500px;
height: 300px;
}
</style>
<script>
function bridge(e) {
window.alert(JSON.stringify(e));
}
window.snapshot = function () {
bridge({ fn: "snapshot" });
};
window.mousemove = function (x, y) {
bridge({ fn: "mousemove", x: x, y: y });
};
</script>
</head>
<body>
<div id="chart"></div>
</body>

View File

@ -0,0 +1,66 @@
var examples = require('./examples');
examples.def('line', function () {
Morris.Line({
element: 'chart',
data: [
{ x: 0, y: 10, z: 30 }, { x: 1, y: 20, z: 20 },
{ x: 2, y: 30, z: 10 }, { x: 3, y: 30, z: 10 },
{ x: 4, y: 20, z: 20 }, { x: 5, y: 10, z: 30 }
],
xkey: 'x',
ykeys: ['y', 'z'],
labels: ['y', 'z'],
parseTime: false
});
window.snapshot();
});
examples.def('area', function () {
Morris.Area({
element: 'chart',
data: [
{ x: 0, y: 1, z: 1 }, { x: 1, y: 2, z: 1 },
{ x: 2, y: 3, z: 1 }, { x: 3, y: 3, z: 1 },
{ x: 4, y: 2, z: 1 }, { x: 5, y: 1, z: 1 }
],
xkey: 'x',
ykeys: ['y', 'z'],
labels: ['y', 'z'],
parseTime: false
});
window.snapshot();
});
examples.def('bar', function () {
Morris.Bar({
element: 'chart',
data: [
{ x: 0, y: 1, z: 3 }, { x: 1, y: 2, z: 2 },
{ x: 2, y: 3, z: 1 }, { x: 3, y: 3, z: 1 },
{ x: 4, y: 2, z: 2 }, { x: 5, y: 1, z: 3 }
],
xkey: 'x',
ykeys: ['y', 'z'],
labels: ['y', 'z']
});
window.snapshot();
});
examples.def('stacked_bar', function () {
Morris.Bar({
element: 'chart',
data: [
{ x: 0, y: 1, z: 1 }, { x: 1, y: 2, z: 1 },
{ x: 2, y: 3, z: 1 }, { x: 3, y: 3, z: 1 },
{ x: 4, y: 2, z: 1 }, { x: 5, y: 1, z: 1 }
],
xkey: 'x',
ykeys: ['y', 'z'],
labels: ['y', 'z'],
stacked: true
});
window.snapshot();
});
examples.run();