├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── bower.json
├── dist
├── js-gantt.js
└── js-gantt.min.js
├── examples
├── README.md
└── exportSvgWithNodeJs
│ ├── .gitignore
│ ├── example.js
│ └── package.json
├── gruntfile.js
├── js-gantt.iml
├── package.json
├── public
├── css
│ ├── format.css
│ └── reset.css
├── index.html
├── testDistributionNoJQuery.html
└── testDistributionWithJQuery.html
├── resources
├── color-example.png
├── json-array-example.png
├── sample-data-array.json
├── sample-data.csv
├── sample-data.json
└── simple-example.png
├── server
├── build.xml
├── pom.xml
└── resources
│ ├── data.js
│ ├── log4j.properties
│ ├── sample.csv
│ └── serverSettings.xml
└── src
├── ExampleApp.js
├── Optimizer.js
├── jquery-private.js
└── net
└── meisen
└── ui
└── gantt
├── GanttChart.js
└── svg
├── IntervalView.js
├── Scrollbar.js
├── SvgIllustrator.js
└── TimeAxis.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /bower_components
3 | /_*
4 | /www-*
5 | /override.properties
6 | /.classpath
7 | /.project
8 | /server/lib
9 | /server/testLib
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5.9.1"
4 | before_script:
5 | - npm install -g grunt-cli
6 | - npm install -g bower
7 | - bower install
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Sascha Eiteneuer und Philipp Meisen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # js-gantt
2 | [](https://badge.fury.io/bo/js-gantt)
3 | [](https://badge.fury.io/js/js-gantt)
4 | [](https://travis-ci.org/pmeisen/js-gantt)
5 |
6 | Library to create time interval data charts, easily, simple and highly configurable. The created charts can be used to visualize large time interval datasets.
7 | The rich configuration allows among other things:
8 | - customizable axes labeling,
9 | - modifiable sizing for all visual components (i.e., intervals, swim-lanes, chart, axes)
10 | - rule-based coloring,
11 | - tooltip configuration, and
12 | - many many more.
13 |
14 | ## How to Install
15 |
16 | The library can be used with `bower`, `requireJs` or as individual `JavaScript Import`. The following paragraphs
17 | explain how to use the library in the different scenarios.
18 |
19 | ### Using js-gantt with `bower`
20 |
21 | ```
22 | bower install --save js-gantt
23 | ```
24 |
25 | The library will be added to your `bower-components`. By default the `js-gantt.js` is selected as single main file, which is the
26 | not minified version of the library (the minified/uglified version is `js-gantt.min.hs`). Examples on how to use the library can
27 | be found [here](#usage-examples).
28 |
29 | ### Using js-gantt with `requireJs`
30 |
31 | If you are building larger web-applications and you want to enjoy the advantage of [requireJs](http://requirejs.org/), you
32 | need to include the sources (and not the optimized libraries). To do so, you may download the tarball or a zip-archive from
33 | GitHub and place it into your `scripts` folder. You can also utilize `npm` or `bower` to download the sources automatically
34 | and override the `main` configuration (see [here](#advanced-bower-and-requirejs)). You can then require the needed library as following:
35 |
36 | ```javascript
37 | require(['net/meisen/ui/gantt/GanttChart'], function (SvgLibrary) {
38 | var gantt = new GanttChart();
39 | });
40 | ```
41 |
42 | ### Using js-gantt with `JavaScript Import`
43 |
44 | If you simple want to use the library within your web-site, you can easily do so by downloading it, deploying it on your
45 | server and adding `` tags:
46 |
47 | ```html
48 |
49 | ```
50 |
51 | The library is bound to the `window` instance and thus is directly available for any other script:
52 |
53 | ```html
54 |
55 |
56 |
57 |
77 | ```
78 |
79 | If you'd like to have this library available through a CDN, please **Star** the project.
80 |
81 | ## Usage Examples
82 |
83 | Here are some [jsFiddle](https://jsfiddle.net/) examples utilizing the library. All examples are purely based
84 | on this library, no additional dependencies needed.
85 |
86 | ### A First Example: Showing some sample time intervals
87 |
88 |
89 |
90 |
91 |
92 | https://jsfiddle.net/pmeisen/pfg7t1uw/
93 |
94 | This example demonstrates how easy it is to use the library and config some different aspects like:
95 |
96 | - data:
97 | - loading of data
98 | - mapping
99 | - time-axis
100 | - illustrator:
101 | - scrollbars
102 | - axis
103 | - theme (interval size)
104 |
105 | ### Rule-based Coloring: Example on how to utilize rule-based coloring for time intervals
106 |
107 |
108 |
109 |
110 |
111 | https://jsfiddle.net/pmeisen/sL7dckbs/
112 |
113 | The example shows how to use the `colorizer` to specify rule-based colors for the intervals. You simple have to override the default-configuration
114 | of the `colorizer`, which is done in the example (see [Configuration](#configuration) for further information):
115 |
116 | ```javascript
117 | var config = {
118 | illustrator: {
119 | config: {
120 | view: {
121 | coloring: {
122 | colorizer: function (interval, map, defaultColor) {
123 | var record = interval.get(IntervalView.gRawAttr);
124 | var value = map.val('label', record);
125 | var n = parseInt(value);
126 |
127 | if (n > 0 && n <= 25000) {
128 | return '#7E8F7C';
129 | } else if (n > 25000 && n <= 50000) {
130 | return '#3B3738';
131 | } else if (n > 50000 && n <= 90000) {
132 | return '#C63D0F';
133 | } else {
134 | return defaultColor;
135 | }
136 | }
137 | }
138 | }
139 | }
140 | }
141 | };
142 | ```
143 |
144 | ### External DataSource: Example on how to use an external data-source
145 |
146 |
147 |
148 |
149 |
150 | https://jsfiddle.net/pmeisen/r16qfrnx/
151 |
152 | Most often an external data source is used to visualize data. The library expects the data to be in JSON Array form, i.e.:
153 |
154 | ```json
155 | [
156 | ["Clara","11.06.2014 00:15:32","11.06.2014 00:39:32",24,"Tristan","Maryland","Tajikistan",0.22,5.28,"United States","Asia","Female","Male" ],
157 | ["Niklas","11.06.2014 00:15:35","11.06.2014 01:48:35",93,"Jesko","Florida","Tuvalu",1.84,171.12,"United States","Oceania","Male","Male" ],
158 | ["Niklas","11.06.2014 00:17:25","11.06.2014 00:37:25",20,"Anni","Florida","Cayman Islands",0.24,4.8,"United States","Oceania","Male","Female" ],
159 | ]
160 | ```
161 |
162 | There are several good online tools to convert different formats into a JSON data form, e.g., for CSV [convertcsv.com](http://www.convertcsv.com/csv-to-json.htm).
163 | Nevertheless, sometimes the data that you receive, e.g., from an url, is not in JSON form. To solve this problem, the library provides a configuration, which
164 | allows you to post-process `JSON` data, e.g., retrieved from a web-service. In the jsFiddle, the loaded data is `post-processed` via the following `function`:
165 |
166 | ```javascript
167 | var config = {
168 | data: {
169 | postProcessor: function (data) {
170 | var f = 'dd.MM.yyyy HH:mm:ss';
171 | for (var i = 0; i < data.length; i++) {
172 | var record = data[i];
173 | record[1] = GanttChart.DateUtil.parseString(record[1], f);
174 | record[2] = GanttChart.DateUtil.parseString(record[2], f);
175 | }
176 |
177 | return {
178 | names:['caller', 'start', 'end',
179 | 'duration', 'recipient', 'origin',
180 | 'destination', 'ratepermin', 'costs',
181 | 'origincontinent', 'destinationcontinent',
182 | 'callergender','recipientgender'],
183 | records: data
184 | };
185 | }
186 | }
187 | };
188 | ```
189 |
190 | The library needs an `object` defining the `names` and `records`, i.e., `{ names: [], records: [] }`. Thus, the `postProcessor` function must return such an object. The `names` are an array naming the different
191 | values of each record; it can be understood as the header of a CSV-file. The records contain the actual data, which have to ensure, that the values representing the start and end value of the interval, must be a `Date` type.
192 |
193 | If the returned data is not a `JSON`, the library offers a `loader` configuration, which defines how to load data. The loader
194 | must be implemented as:
195 |
196 | ```
197 | var config = {
198 | data: {
199 | loader: function (success, error) {
200 | // success and error are both functions, with:
201 | // - success: function(data);
202 | // - error: function(msg);
203 | }
204 | }
205 | };
206 | ```
207 |
208 | Further examples using different `post-processor` and `loader` definitions can be found:
209 | - mapping JSON objects (e.g., [sample-data.json](https://rawgit.com/pmeisen/js-gantt/master/resources/sample-data.json)) to the required data format: [jsFiddle: post-processing](https://jsfiddle.net/pmeisen/pj9thh4z/)
210 | - using a loader (e.g., loading CSV [sample-data.csv](https://rawgit.com/pmeisen/js-gantt/master/resources/sample-data.csv)) to the required data format: [jsFiddle: loader](https://jsfiddle.net/pmeisen/11z6o8pn/)
211 |
212 | ## Configuration
213 |
214 | The Gantt Chart library provides a rich set of configuration parameters. This paragraph tries to address the none trivial settings.
215 | To get an easy start, it is recommend to look at the [Usage Examples](#usage-examples) and adapt the different configuration
216 | parameters as needed and described here.
217 |
218 | A full configuration (with the default settings) is as follows:
219 |
220 | ```javascript
221 | var config = {
222 | data: {
223 | url: null,
224 | loader: null,
225 | postProcessor: function (data) {
226 | if (!$.isArray(data.names) || !$.isArray(data.records)) {
227 | return null;
228 | } else {
229 | return data;
230 | }
231 | },
232 | mapper: {
233 | startname: 'start',
234 | endname: 'end',
235 | group: [],
236 | label: [],
237 | tooltip: []
238 | },
239 | names: [],
240 | records: [],
241 | timeaxis: {
242 | start: null,
243 | end: null,
244 | granularity: 'days'
245 | }
246 | },
247 | theme: {
248 | loadingBackgroundColor: '#CCCCCC',
249 | loadingBackgroundPosition: 'center center',
250 | loadingBackgroundRepeat: 'no-repeat',
251 | errorBackgroundColor: '#A30B1D'
252 | },
253 | illustrator: {
254 | factory: function () {
255 | // the default configuration is based on this usage
256 | return new SvgIllustrator();
257 | },
258 | config: {
259 | theme: {
260 | fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',
261 | fontSize: '12px'
262 | },
263 | general: {
264 | margin: 2
265 | },
266 | view: {
267 | showGrid: true,
268 | showBorder: true,
269 | showBackground: true,
270 | showPositionMarker: true,
271 | showIntervalMarker: true,
272 | showPositionToolTip: true,
273 | showIntervalToolTip: true,
274 | coloring: {
275 | groupMapping: null,
276 | colorizer: function (interval, map, defaultColor) {
277 | // there is a default implemented, please have
278 | // a look at the source-code (IntervalView.js)
279 | // for further insights
280 | }
281 | },
282 | tooltip: null,
283 | formatter: {
284 | tooltip: function (interval, map, textFormat, theme) {
285 | // there is a default implemented, please have
286 | // a look at the source-code (IntervalView.js)
287 | // for further insights
288 | }
289 | },
290 | theme: {
291 | backgroundColor: '#FFFFFF',
292 | laneHeight: null,
293 | intervalPosition: 'middle',
294 | intervalHeight: 20,
295 | intervalColor: '#7CB5EC',
296 | intervalBorderColor: '#99C9F7',
297 | intervalBorderSize: 1,
298 | gridColor: '#D8D8D8',
299 | gridSize: 1,
300 | positionMarkerColor: '#D8D8D8',
301 | positionMarkerSize: 1,
302 | intervalMarkerOpacity: '0.3',
303 | intervalMarkerWidth: null,
304 | borderColor: '#D8D8D8',
305 | borderSize: 1,
306 | tooltipMargin: 2,
307 | tooltipArrow: 6,
308 | tooltipRadius: 3,
309 | tooltipSize: 11,
310 | tooltipTextColor: '#000000',
311 | intervalMarginInPx: null
312 | }
313 | },
314 | axis: {
315 | tickInterval: null,
316 | viewSize: null,
317 | padding: 100,
318 | formatter: function (value, type, level) {
319 | // there is a default formatter, please have
320 | // a look at the source-code (TimeAxis.js)
321 | // for further insights
322 | },
323 | theme: {
324 | tickColor: '#C0D0E0',
325 | tickWidth: 1,
326 | labelColor: '#606060',
327 | labelSize: 11
328 | }
329 | },
330 | scrollbars: {
331 | vertical: {
332 | theme: {
333 | arrowSize: 14,
334 | scrollareaColor: '#EEEEEE',
335 | markerColor: '#BFC8D1',
336 | buttonColorBorder: '#666666',
337 | arrowColor: '#666666',
338 | buttonColor: '#EBE7E8'
339 | },
340 | hideOnNoScroll: false,
341 | propagateScrollOnNoMove: false,
342 | step: null
343 | },
344 | horizontal: {
345 | theme: {
346 | arrowSize: 14,
347 | scrollareaColor: '#EEEEEE',
348 | markerColor: '#BFC8D1',
349 | buttonColorBorder: '#666666',
350 | arrowColor: '#666666',
351 | buttonColor: '#EBE7E8'
352 | },
353 | hideOnNoScroll: true,
354 | propagateScrollOnNoMove: false,
355 | step: null
356 | }
357 | }
358 | }
359 | },
360 | position: 'center',
361 | throwException: false
362 | };
363 | ```
364 |
365 | Most of the configuration parameters should be self explaining (if not please contact me, so that I can enhance the documentation). Nevertheless, one of the
366 | most important things to understand is the `data` section within the configuration. Which is explained in the following paragraph.
367 |
368 | ### Configuration: Data Section
369 |
370 | In general, the library tries to retrieve time interval data in the following order (it is not recommended to mix the different
371 | ways and only utilize one of the ways to retrieve data):
372 |
373 | 1. check if a loader is defined (data must be returned as `JSON`)
374 | 2. check if an url is defined (data must be returned as `JSON`)
375 | 3. check if `records` are set
376 |
377 | If step 1. or 2. are used, the returned `JSON` is passed to the `post-processor`, if one is defined (`data.postProcessor` must be a `function`).
378 | The `post-processor` must return an `JSON` fulfilling the following requirements:
379 |
380 | 1. must be a plain-object (`JSON`)
381 | 2. must have a named `records` attribute, which contains the time-interval data as arrays and each date as UTC-based `Date`, i.e.,
382 |
383 | ```javascript
384 | [
385 | GanttChart.DateUtil.createUTC(1929, 10, 31, 0, 0, 0),
386 | GanttChart.DateUtil.createUTC(2016, 6, 27, 0, 0, 0),
387 | 'actor',
388 | 'Bud Spencer',
389 | 'Carlo Pedersoli'
390 | ]
391 | ```
392 | 3. must have a named `names` attribute, which returns an array of `names` for the different values in the `records` array, i.e.,
393 |
394 | ```javascript
395 | [
396 | 'birthday',
397 | 'dayOfDeath',
398 | 'type',
399 | 'alias',
400 | 'name'
401 | ]
402 | ```
403 |
404 | So all together, it must return:
405 |
406 | ```javascript
407 | loader: function(success, error) {
408 | success({
409 | names: [
410 | 'birthday',
411 | 'dayOfDeath',
412 | 'type',
413 | 'alias',
414 | 'name'
415 | ],
416 | records: [
417 | [
418 | GanttChart.DateUtil.createUTC(1929, 10, 31),
419 | GanttChart.DateUtil.createUTC(2016, 6, 27),
420 | 'actor',
421 | 'Bud Spencer',
422 | 'Carlo Pedersoli'
423 | ],
424 | [
425 | GanttChart.DateUtil.createUTC(1939, 3, 29),
426 | null,
427 | 'actor',
428 | 'Terence Hill',
429 | 'Mario Girotti'
430 | ]
431 | ]
432 | })
433 | };
434 | ```
435 |
436 | The next important setting within the `data` section of the configuration is the `mapper`. The mapper configuration is used to
437 | define, which values of each record have what semantic meaning, e.g., which value indicates the `startname` of the interval and which
438 | one the `endname`. By default, the library assumes that the `startname` is `start` and the `endname` is `end`. Assuming the `names`
439 | from the previous paragraph, this default setting is incorrect and would need to be modified:
440 |
441 | ```javascript
442 | mapper: {
443 | startname: 'birthday',
444 | endname: 'dayOfDeath'
445 | }
446 | ```
447 |
448 | Additional mappers can be defined for `groups`, `labels` and `tooltips`, i.e.,
449 |
450 | ```javascript
451 | mapper: {
452 | startname: 'birthday',
453 | endname: 'dayOfDeath',
454 | group: ['type'],
455 | label: ['alias'],
456 | tooltip: ['name', 'alias']
457 | }
458 | ```
459 |
460 | These values are used by the library to, e.g., offer group-based coloring, showing tool-tips on hover events or add a label
461 | to an interval.
462 |
463 | **Notes**:
464 | - the `GanttChart` provides some utility functions, which makes it easier to parse (e.g., `parseString`) or create (e.g., `createUTC`) UTC dates.
465 | - the `end` date can be `null`, which indicates that the interval has not ended yet (glued to the end of the chart).
466 | - an example using the data of this example can be found here: [jsFiddle](https://jsfiddle.net/pmeisen/boxtwojx/)
467 |
468 | ## Advanced: Bower and RequireJs
469 |
470 | If you utilize the library with `requireJs`, you may want to use the `sources` instead of the minified or combined version
471 | distributed in the `dist`-folder. The library is developed using `requireJs` and ensures an easy usage with any
472 | `Asynchronous Module Definition` (see [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)).
473 |
474 | If you have a look at the project's `gruntfile.js`, you will notice, that the libraries supporting `AMD` (e.g., `js-misc`,
475 | `js-svglibrary`) are also added to this project using their sources. The following lines in the `gruntfile.js` ensures that
476 | usage:
477 |
478 | ```json
479 | {
480 | "bower": {
481 | "dep": {
482 | "options": {
483 | "includeDev": true,
484 | "checkExistence": true,
485 | "paths": "bower-components",
486 | "overrides": {
487 | "js-misc": {"ignore": true},
488 | "js-svglibrary": {"ignore": true}
489 | }
490 | },
491 | "dest": "scripts"
492 | }
493 | },
494 |
495 | "copy": {
496 | "dep": {
497 | "files": [
498 | {"expand": true, "flatten": false, "cwd": "bower-components/js-gantt/src", "src": "**/*", "dest": "scripts"}
499 | ]
500 | }
501 | }
502 | }
503 | ```
504 |
505 |
506 | ## Further Links
507 |
508 | - [Examples](examples/README.md)
509 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-gantt",
3 | "version": "1.0.16",
4 | "description": "JavaScript Gantt-Chart Library",
5 | "authors": [
6 | "Philipp Meisen "
7 | ],
8 | "main": "dist/js-gantt.js",
9 | "homepage": "https://github.com/pmeisen/js-gantt",
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/pmeisen/js-gantt",
13 | "bump": "git://github.com/pmeisen/js-gantt.git"
14 | },
15 | "keywords": [
16 | "gantt",
17 | "task",
18 | "performance",
19 | "utility",
20 | "chart",
21 | "svg"
22 | ],
23 | "license": "MIT",
24 | "private": false,
25 | "dependencies": {},
26 | "devDependencies": {
27 | "js-svglibrary": ">=0.5.0",
28 | "js-misc": ">=0.0.13",
29 | "jquery": ">=1.5.0",
30 | "almond": "0.3.3",
31 | "requirejs": "2.3.3",
32 | "qunit": "2.1.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # js-gantt (Examples)
2 | [](https://badge.fury.io/bo/js-gantt)
3 | [](https://badge.fury.io/js/js-gantt)
4 | [](https://travis-ci.org/pmeisen/js-gantt)
5 |
6 | This folder contains examples created to illustrate some of the possibilities.
7 |
8 | ### Creating SVG from nodeJS (exportSvgWithNodeJs)
9 |
10 | Yes it is possible to create Gantt-Charts, i.e., SVG files (and also convert
11 | these to, e.g., PNG) within nodeJS (see [here](exportSvgWithNodeJs/example.js)).
12 | I personally only use the library within a browser, so the example just shows
13 | what may be possible. The examples utilizes *phantomJs* to render the SVG in
14 | the headless browser, retrieves the created SVG and stores it in a file. There
15 | are some things to be considered to make this example more usable:
16 |
17 | 1. It may be nicer to push the options, as well as the width and height
18 | through nodeJs into the JavaScript. Currently the *options* are also
19 | defined in the JavaScript part. The main reason is that the *js-gantt*
20 | library only supports *Date* instances out of the box. The options coming
21 | from nodeJs are stringified (using *JSON*), which does not support *Date*
22 | types.
23 | 2. The rendering of the SVG may take some milliseconds (or seconds if large
24 | external sources are used). The *js-gantt* chart provides events to know
25 | when the rendering is finished. Nevertheless, in the example the script
26 | just waits a couple of seconds. In a nicer version, nodeJs would utilize
27 | the events provided by the library.
28 |
29 | To test the example, simply check out the [examples](./) folder. Change to
30 | [exportSvgWithNodeJs](./exportSvgWithNodeJs) and run `npm install` (just once to get the dependencies)
31 | and afterwards `npm start`. After the script is executed, there should be
32 | a `ouput` folder, which contains a `sample.svg`.
--------------------------------------------------------------------------------
/examples/exportSvgWithNodeJs/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | output/**/*
--------------------------------------------------------------------------------
/examples/exportSvgWithNodeJs/example.js:
--------------------------------------------------------------------------------
1 | var phantom = require('phantom');
2 | var fs = require('fs');
3 |
4 | // we keep the instance to close it at the end of the pipe
5 | var phInstance = null;
6 | var svg = null;
7 |
8 | phantom.create()
9 | .then(instance => {
10 | phInstance = instance;
11 | return instance.createPage();
12 | })
13 |
14 | // load an empty page
15 | .then(page => {
16 | return page.open('about:blank').then(status => {
17 | if (status === 'success') {
18 | return page;
19 | } else {
20 | throw new Error('Unable to load blank page.');
21 | }
22 | })
23 | })
24 |
25 | // inject some scripts
26 | .then(page => {
27 | return page.includeJs('https://rawgit.com/pmeisen/js-gantt/master/dist/js-gantt.min.js').then(() => {
28 | return page;
29 | });
30 | })
31 |
32 | // add the SVG with the specified options
33 | .then(page => {
34 | return page.evaluate(function () {
35 | var chartEl = document.createElement('div');
36 | chartEl.id = 'chart';
37 | document.body.appendChild(chartEl);
38 |
39 | var chart = new GanttChart();
40 | chart.init(chartEl, {
41 | data: {
42 | url: 'https://rawgit.com/pmeisen/js-gantt/master/resources/sample-data-array.json',
43 | postProcessor: function (data) {
44 | var f = 'dd.MM.yyyy HH:mm:ss';
45 | for (var i = 0; i < data.length; i++) {
46 | var record = data[i];
47 | record[1] = GanttChart.DateUtil.parseString(record[1], f);
48 | record[2] = GanttChart.DateUtil.parseString(record[2], f);
49 | }
50 |
51 | return {
52 | names: ['caller', 'start', 'end',
53 | 'duration', 'recipient', 'origin',
54 | 'destination', 'ratepermin', 'costs',
55 | 'origincontinent', 'destinationcontinent',
56 | 'callergender', 'recipientgender'],
57 | records: data
58 | };
59 | },
60 | mapper: {
61 | startname: 'start',
62 | endname: 'end',
63 | group: ['callergender', 'recipientgender'],
64 | label: ['callergender', 'start', 'caller'],
65 | tooltip: ['caller', 'recipient', 'start', 'end']
66 | },
67 | timeaxis: {
68 | start: GanttChart.DateUtil.createUTC(2014, 6, 11),
69 | end: GanttChart.DateUtil.createUTC(2014, 6, 11, 23, 59, 0),
70 | granularity: 'mi'
71 | }
72 | },
73 | illustrator: {
74 | config: {
75 | axis: {
76 | viewSize: 1440,
77 | tickInterval: 360
78 | },
79 | view: {
80 | showBorder: false,
81 | tooltip: '{1} called {2}\nbetween {3|date|HH:mm} - {4|date|HH:mm})',
82 | coloring: {
83 | groupMapping: {
84 | '["Female","Female"]': '#7cb5ec',
85 | '["Male","Female"]': '#434348',
86 | '["Female","Male"]': '#f7a35c',
87 | '["Male","Male"]': '#90ed7d'
88 | }
89 | },
90 | theme: {
91 | intervalColor: '#444444',
92 | intervalHeight: 10,
93 | intervalBorderSize: 0
94 | }
95 | },
96 | scrollbars: {
97 | vertical: {
98 | hideOnNoScroll: true
99 | },
100 | horizontal: {
101 | hideOnNoScroll: true
102 | }
103 | }
104 | }
105 | }
106 | });
107 | chart.resize(600, 300);
108 | }).then(() => {
109 | return page;
110 | });
111 | })
112 |
113 | // wait a little depending on the options that should be changed
114 | .then(page => {
115 | return new Promise(f => setTimeout(f, 2000)).then(() => page);
116 | })
117 |
118 | // read the page after the full content is loaded
119 | .then(page => {
120 | return page.evaluate(function () {
121 | return document.getElementsByClassName('ganttview')[0].innerHTML;
122 | }).then(chart => {
123 | svg = chart;
124 | return page;
125 | });
126 | })
127 |
128 | // clean up when we are done
129 | .then(() => {
130 | phInstance.exit();
131 | })
132 |
133 | // write the file
134 | .then(() => {
135 | try {
136 | fs.mkdirSync("./output");
137 | } catch (err) {
138 | if (err.code !== 'EEXIST') throw err
139 | }
140 |
141 | fs.writeFileSync("./output/sample.svg", svg);
142 | })
143 |
144 | // in any case of an error log it
145 | .catch(error => {
146 | console.log(error);
147 | phInstance.exit();
148 | });
--------------------------------------------------------------------------------
/examples/exportSvgWithNodeJs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-gantt-export-svg-example",
3 | "version": "1.0.0",
4 | "description": "This is an example showing how to use nodeJs and js-gantt to export a Gantt-Chart to SVG.",
5 | "main": "example.js",
6 | "scripts": {
7 | "start": "node example.js"
8 | },
9 | "authors": [
10 | "Philipp Meisen "
11 | ],
12 | "private": false,
13 | "license": "MIT",
14 | "dependencies": {
15 | "phantom": "^3.2.1"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 |
3 | /*
4 | * Configuration settings
5 | */
6 | var bowerPaths = {
7 | bowerrc: '.bowerrc',
8 | bowerDirectory: 'bower_components',
9 | bowerJson: 'bower.json'
10 | };
11 |
12 | grunt.loadNpmTasks('grunt-contrib-connect');
13 | grunt.loadNpmTasks('grunt-contrib-watch');
14 | grunt.loadNpmTasks('grunt-sync-json');
15 | grunt.loadNpmTasks('grunt-bower-install-simple');
16 | grunt.loadNpmTasks('main-bower-files');
17 | grunt.loadNpmTasks('grunt-contrib-copy');
18 | grunt.loadNpmTasks('grunt-execute');
19 | grunt.loadNpmTasks('grunt-publish');
20 | grunt.loadNpmTasks('grunt-bump');
21 |
22 | grunt.initConfig({
23 | pkg: grunt.file.readJSON('package.json'),
24 |
25 | 'sync-json': {
26 | options: {
27 | indent: 2,
28 | include: [
29 | 'name', 'version', 'description', 'authors', 'main', 'repository', 'keywords', 'license', 'private'
30 | ]
31 | },
32 | dep: {
33 | files: {
34 | 'bower.json': 'package.json'
35 | }
36 | }
37 | },
38 |
39 | /*
40 | * Task used to install bower dependencies.
41 | */
42 | 'bower-install-simple': {
43 | dep: {
44 | options: {
45 | production: false
46 | }
47 | }
48 | },
49 |
50 | /*
51 | * Modify the bower dependencies and move the needed files to the
52 | * target location.
53 | */
54 | bower: {
55 | dep: {
56 | options: {
57 | includeDev: true,
58 | checkExistence: true,
59 | paths: bowerPaths,
60 | overrides: {
61 | 'r.js': {main: ['dist/r.js']},
62 | 'js-misc': {ignore: true},
63 | 'js-svglibrary': {ignore: true}
64 | }
65 | },
66 | dest: 'www-root/scripts'
67 | }
68 | },
69 |
70 | /*
71 | * The watch tasks observes changes on the file-system, which
72 | * allow us to see changes directly in the browser.
73 | *
74 | * Set spawn == false, ee documentation: Setting this option to false speeds
75 | * up the reaction time of the watch (usually 500ms faster
76 | * for most)
77 | */
78 | watch: {
79 | server: {
80 | options: {
81 | livereload: true,
82 | spawn: false
83 | },
84 |
85 | // define the task to run when a change happens
86 | tasks: ['01-resolve-dependencies', 'copy:setup'],
87 |
88 | // files to observe, can be an array
89 | files: ['gruntfile.js', 'package.json', 'src/**/*', 'public/**/*', 'test/**/*']
90 | },
91 | dist: {
92 | options: {
93 | livereload: true,
94 | spawn: false
95 | },
96 |
97 | // define the task to run when a change happens
98 | tasks: ['02-compile-sources', 'copy:dist'],
99 |
100 | // files to observe, can be an array
101 | files: ['public/testDistribution*.html', 'src/Optimizer.js']
102 | }
103 | },
104 |
105 | /*
106 | * The connect task starts a web server for us to see our results and
107 | * do some testing.
108 | */
109 | connect: {
110 | server: {
111 | options: {
112 | port: '<%= server.port %>',
113 | base: 'www-root'
114 | }
115 | },
116 | dist: {
117 | options: {
118 | port: '<%= server.port %>',
119 | base: 'www-dist'
120 | }
121 | }
122 | },
123 |
124 | /*
125 | * Copies the files into the right location for the web server.
126 | */
127 | copy: {
128 | dep: {
129 | files: [
130 | {expand: true, flatten: false, cwd: bowerPaths.bowerDirectory + '/js-misc/src', src: '**/*', dest: 'www-root/scripts'},
131 | {expand: true, flatten: false, cwd: bowerPaths.bowerDirectory + '/js-svglibrary/src', src: '**/*', dest: 'www-root/scripts'}
132 | ]
133 | },
134 | setup: {
135 | files: [
136 | {expand: true, flatten: false, cwd: 'src', src: '**/*', dest: 'www-root/scripts'},
137 | {expand: true, flatten: false, cwd: 'test', src: '**/*', dest: 'www-root/scripts'},
138 | {expand: true, flatten: false, cwd: 'public', src: '**/*', dest: 'www-root'}
139 | ]
140 | },
141 | dist: {
142 | files: [
143 | {expand: true, flatten: false, cwd: bowerPaths.bowerDirectory + '/jquery/dist', src: 'jquery.min.js', dest: 'www-dist/scripts'},
144 | {expand: true, flatten: false, cwd: 'dist', src: '**/*', dest: 'www-dist/scripts'},
145 | {expand: true, flatten: false, cwd: 'public/css', src: '**/*', dest: 'www-dist/css'},
146 | {expand: true, flatten: false, cwd: 'public', src: 'testDistribution*.html', dest: 'www-dist'}
147 | ]
148 | }
149 | },
150 |
151 | execute: {
152 | compile: {
153 | call: function (grunt, option, async) {
154 | var requirejs = require('requirejs');
155 | var extend = require('util')._extend;
156 | var fs = require('fs');
157 |
158 | var done = async();
159 | var currentDir = process.cwd();
160 | var prefixFilename = currentDir + '/dist/' + grunt.config('pkg.name');
161 |
162 | var baseConfig = {
163 | baseUrl: 'scripts',
164 | name: 'almond',
165 | include: 'Optimizer',
166 | wrap: true
167 | };
168 |
169 | var optimize = function (config, callback) {
170 |
171 | requirejs.optimize(config, function () {
172 | callback(true);
173 | }, function (err) {
174 | grunt.log.error(err);
175 | callback(false);
176 | });
177 | };
178 |
179 | var cleanUp = function () {
180 | process.chdir(currentDir);
181 | done();
182 | };
183 |
184 | // run the actual optimization
185 | process.chdir('www-root');
186 | optimize(extend({
187 | optimize: 'none',
188 | out: prefixFilename + '.js'
189 | }, baseConfig), function (success) {
190 |
191 | if (success) {
192 | optimize(extend({
193 | out: prefixFilename + '.min.js'
194 | }, baseConfig), cleanUp);
195 | } else {
196 | cleanUp();
197 | }
198 | });
199 | }
200 | }
201 | },
202 |
203 | publish: {
204 | options: {
205 | ignore: ['node_modules', 'bower_components']
206 | },
207 | deploy: {
208 | src: './'
209 | }
210 | },
211 |
212 | bump: {
213 | options: {
214 | files: ['package.json', 'bower.json'],
215 | commitFiles: ['.'],
216 | pushTo: 'origin'
217 | }
218 | }
219 | });
220 |
221 | grunt.registerTask('01-resolve-dependencies', 'Resolve all the dependencies', function () {
222 | grunt.task.run('sync-json:dep', 'bower-install-simple:dep', 'bower:dep', 'copy:dep');
223 | });
224 |
225 | grunt.registerTask('02-compile-sources', 'Update the current root-directory', function () {
226 | grunt.task.run('01-resolve-dependencies', 'copy:setup', 'execute:compile');
227 | });
228 |
229 | grunt.registerTask('04-deploy', 'Update the current root-directory', function () {
230 | grunt.config.set('log.msg', 'Make sure your bower project is registered using: ' + '\n' +
231 | '$ bower register ' + grunt.config('pkg.name') + ' ' + grunt.config('pkg.repository.bump') + '\n' +
232 | 'Make sure your npm users are added: ' + '\n' +
233 | '$ npm adduser');
234 |
235 | grunt.task.run('02-compile-sources', 'log', 'bump', 'publish:deploy');
236 | });
237 |
238 | grunt.registerTask('98-run-server', 'Start the web-server for fast debugging.', function (port) {
239 | port = typeof port === 'undefined' || port === null || isNaN(parseFloat(port)) || !isFinite(port) ? 20000 : parseInt(port);
240 | grunt.config.set('server.port', port);
241 | grunt.config.set('log.msg', 'You may want to start the sample server providing data via JSON, ant 98-run-server (see server)' + '\n' +
242 | 'For an example: http://localhost:' + port + '/index.html');
243 |
244 | grunt.task.run('01-resolve-dependencies', 'copy:setup', 'connect:server', 'log', 'watch:server');
245 | });
246 |
247 | grunt.registerTask('99-run-dist-server', 'Runs a server with the dist-version', function (port) {
248 | port = typeof port === 'undefined' || port === null || isNaN(parseFloat(port)) || !isFinite(port) ? 20000 : parseInt(port);
249 | grunt.config.set('server.port', port);
250 | grunt.config.set('log.msg', 'Test the distribution: http://localhost:' + port + '/testDistributionNoJQuery.html' + '\n' +
251 | ' http://localhost:' + port + '/testDistributionWithJQuery.html');
252 |
253 | grunt.task.run('02-compile-sources', 'copy:dist', 'connect:dist', 'log', 'watch:dist');
254 | });
255 |
256 | grunt.registerTask('log', 'Writes a log messages', function () {
257 | grunt.log.writeln(grunt.config.get('log.msg'));
258 | });
259 | };
--------------------------------------------------------------------------------
/js-gantt.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-gantt",
3 | "version": "1.0.16",
4 | "description": "JavaScript Gantt-Chart Library",
5 | "authors": [
6 | "Philipp Meisen "
7 | ],
8 | "scripts": {
9 | "test": "grunt 02-compile-sources"
10 | },
11 | "main": "dist/js-gantt.js",
12 | "homepage": "https://github.com/pmeisen/js-gantt",
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/pmeisen/js-gantt",
16 | "bump": "git://github.com/pmeisen/js-gantt.git"
17 | },
18 | "keywords": [
19 | "gantt",
20 | "task",
21 | "performance",
22 | "utility",
23 | "chart",
24 | "svg"
25 | ],
26 | "license": "MIT",
27 | "private": false,
28 | "dependencies": {},
29 | "devDependencies": {
30 | "grunt": "1.0.1",
31 | "grunt-bower-install-simple": "1.2.3",
32 | "grunt-bump": "0.8.0",
33 | "grunt-contrib-connect": "1.0.2",
34 | "grunt-contrib-copy": "1.0.0",
35 | "grunt-contrib-watch": "1.0.0",
36 | "grunt-execute": "0.2.2",
37 | "grunt-publish": "1.0.0",
38 | "grunt-sync-json": "0.4.0",
39 | "main-bower-files": "2.13.1",
40 | "requirejs": "2.3.3"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/css/format.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmeisen/js-gantt/6aff3eb02aae43ded30b11ee8e000e383dbd419d/public/css/format.css
--------------------------------------------------------------------------------
/public/css/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, menu, nav, section {
29 | display: block;
30 | }
31 | body {
32 | line-height: 1;
33 | }
34 | ol, ul {
35 | list-style: none;
36 | }
37 | blockquote, q {
38 | quotes: none;
39 | }
40 | blockquote:before, blockquote:after,
41 | q:before, q:after {
42 | content: '';
43 | content: none;
44 | }
45 | table {
46 | border-collapse: collapse;
47 | border-spacing: 0;
48 | }
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample GanttChart
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/public/testDistributionNoJQuery.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample GanttChart
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
58 |
59 |
--------------------------------------------------------------------------------
/public/testDistributionWithJQuery.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample GanttChart
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
64 |
65 |
--------------------------------------------------------------------------------
/resources/color-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmeisen/js-gantt/6aff3eb02aae43ded30b11ee8e000e383dbd419d/resources/color-example.png
--------------------------------------------------------------------------------
/resources/json-array-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmeisen/js-gantt/6aff3eb02aae43ded30b11ee8e000e383dbd419d/resources/json-array-example.png
--------------------------------------------------------------------------------
/resources/sample-data-array.json:
--------------------------------------------------------------------------------
1 | [
2 | ["Clara","11.06.2014 00:15:32","11.06.2014 00:39:32",24,"Tristan","Maryland","Tajikistan",0.22,5.28,"United States","Asia","Female","Male" ],
3 | ["Niklas","11.06.2014 00:15:35","11.06.2014 01:48:35",93,"Jesko","Florida","Tuvalu",1.84,171.12,"United States","Oceania","Male","Male" ],
4 | ["Niklas","11.06.2014 00:17:25","11.06.2014 00:37:25",20,"Anni","Florida","Cayman Islands",0.24,4.8,"United States","Oceania","Male","Female" ],
5 | ["Niklas","11.06.2014 00:17:38","11.06.2014 01:04:38",47,"Julian","Florida","Indonesia",0.29,13.63,"United States","Asia","Male","Male" ],
6 | ["Niklas","11.06.2014 00:17:39","11.06.2014 01:19:39",62,"Julian","Florida","Indonesia",0.29,17.98,"United States","Asia","Male","Male" ],
7 | ["Niklas","11.06.2014 00:18:10","11.06.2014 00:24:10",6,"Anni","Florida","Cayman Islands",0.24,1.44,"United States","Oceania","Male","Female" ],
8 | ["Clara","11.06.2014 00:19:54","11.06.2014 00:55:54",36,"Jule","Maryland","Faeroe Islands",0.52,18.72,"United States","Europe","Female","Female" ],
9 | ["Niklas","11.06.2014 00:20:05","11.06.2014 01:40:05",80,"Tamino","Florida","Vietnam",0.65,52,"United States","Asia","Male","Male" ],
10 | ["Clara","11.06.2014 00:20:35","11.06.2014 01:50:35",90,"Jule","Maryland","Faeroe Islands",0.52,46.8,"United States","Europe","Female","Female" ],
11 | ["Emily","11.06.2014 00:26:31","11.06.2014 00:49:31",23,"Leonardo","Rhode Island","Comoro Islands",2.25,51.75,"United States","Africa","Male","Male" ],
12 | ["Ida","11.06.2014 00:43:01","11.06.2014 02:02:01",79,"Colin","Maine","Gabon Republic",1.05,82.95,"United States","Africa","Female","Male" ],
13 | ["Ida","11.06.2014 00:44:56","11.06.2014 00:56:56",12,"Colin","Maine","Gabon Republic",1.05,12.6,"United States","Africa","Female","Male" ],
14 | ["Emily","11.06.2014 00:46:37","11.06.2014 02:12:37",86,"Anne","Rhode Island","Eritrea",0.88,75.68,"United States","Africa","Male","Female" ],
15 | ["Ida","11.06.2014 00:50:07","11.06.2014 01:17:07",27,"Alea","Maine","Ivory Coast",0.68,18.36,"United States","Africa","Female","Female" ],
16 | ["Niklas","11.06.2014 00:52:57","11.06.2014 01:31:57",39,"Tamino","Florida","Vietnam",0.65,25.35,"United States","Asia","Male","Male" ],
17 | ["Ida","11.06.2014 01:01:12","11.06.2014 01:59:12",58,"Colin","Maine","Gabon Republic",1.05,60.9,"United States","Africa","Female","Male" ],
18 | ["Henry","11.06.2014 01:02:15","11.06.2014 01:07:15",5,"Felix","Connecticut","New Caledonia",1.41,7.05,"United States","Oceania","Male","Male" ],
19 | ["Henry","11.06.2014 01:02:26","11.06.2014 01:45:26",43,"Felix","Connecticut","New Caledonia",1.41,60.63,"United States","Oceania","Male","Male" ],
20 | ["Henry","11.06.2014 01:03:15","11.06.2014 02:59:15",116,"Felix","Connecticut","New Caledonia",1.41,163.56,"United States","Oceania","Male","Male" ],
21 | ["Lara","11.06.2014 01:04:59","11.06.2014 01:42:59",38,"Anni","Wisconsin","Cayman Islands",0.24,9.12,"United States","Oceania","Female","Female" ],
22 | ["Clara","11.06.2014 01:19:30","11.06.2014 02:02:30",43,"Tristan","Maryland","Tajikistan",0.22,9.46,"United States","Asia","Female","Male" ],
23 | ["Simon","11.06.2014 01:35:19","11.06.2014 01:38:19",3,"Ursel","Nevada","Denmark",0.08,0.24,"United States","Europe","Male","Female" ],
24 | ["Simon","11.06.2014 01:35:43","11.06.2014 01:39:43",4,"Ursel","Nevada","Denmark",0.08,0.32,"United States","Europe","Male","Female" ],
25 | ["Henry","11.06.2014 01:38:47","11.06.2014 02:06:47",28,"Felix","Connecticut","New Caledonia",1.41,39.48,"United States","Oceania","Male","Male" ],
26 | ["Simon","11.06.2014 01:56:00","11.06.2014 01:58:00",2,"Anni","Nevada","Cayman Islands",0.24,0.48,"United States","Oceania","Male","Female" ],
27 | ["Simon","11.06.2014 01:56:13","11.06.2014 02:58:13",62,"Anni","Nevada","Cayman Islands",0.24,14.88,"United States","Oceania","Male","Female" ],
28 | ["Ida","11.06.2014 02:08:08","11.06.2014 03:12:08",64,"Luan","Maine","Niger Republic",0.91,58.24,"United States","Africa","Female","Male" ],
29 | ["Ida","11.06.2014 02:09:31","11.06.2014 02:15:31",6,"Luan","Maine","Niger Republic",0.91,5.46,"United States","Africa","Female","Male" ],
30 | ["Niklas","11.06.2014 02:13:36","11.06.2014 02:45:36",32,"Jesko","Florida","Tuvalu",1.84,58.88,"United States","Oceania","Male","Male" ],
31 | ["Charlotte","11.06.2014 02:52:37","11.06.2014 03:19:37",27,"Romeo","Alaska","Gabon Republic",1.05,28.35,"United States","Africa","Female","Male" ],
32 | ["Charlotte","11.06.2014 02:53:11","11.06.2014 03:01:11",8,"Romeo","Alaska","Gabon Republic",1.05,8.4,"United States","Africa","Female","Male" ],
33 | ["Charlotte","11.06.2014 02:53:21","11.06.2014 03:19:21",26,"Romeo","Alaska","Gabon Republic",1.05,27.3,"United States","Africa","Female","Male" ],
34 | ["Clara","11.06.2014 03:32:49","11.06.2014 04:46:49",74,"Tristan","Maryland","Tajikistan",0.22,16.28,"United States","Asia","Female","Male" ],
35 | ["Niklas","11.06.2014 04:15:46","11.06.2014 05:26:46",71,"Tamino","Florida","Vietnam",0.65,46.15,"United States","Asia","Male","Male" ],
36 | ["Ida","11.06.2014 04:30:17","11.06.2014 05:05:17",35,"Luan","Maine","Niger Republic",0.91,31.85,"United States","Africa","Female","Male" ],
37 | ["Frieda","11.06.2014 04:33:35","11.06.2014 04:54:35",21,"Milian","Vermont","San Marino",0.89,18.69,"United States","Europe","Female","Female" ],
38 | ["Frieda","11.06.2014 04:39:23","11.06.2014 05:55:23",76,"Tjark","Vermont","Malta",0.71,53.96,"United States","Europe","Female","Male" ],
39 | ["Frieda","11.06.2014 04:40:52","11.06.2014 04:58:52",18,"Damien","Vermont","Comoro Islands",2.25,40.5,"United States","Africa","Female","Male" ],
40 | ["Frieda","11.06.2014 04:41:31","11.06.2014 04:57:31",16,"Tjark","Vermont","Malta",0.71,11.36,"United States","Europe","Female","Male" ],
41 | ["Frieda","11.06.2014 04:45:38","11.06.2014 05:51:38",66,"Melinda","Vermont","Taiwan",0.07,4.62,"United States","Asia","Female","Female" ],
42 | ["Frieda","11.06.2014 05:07:37","11.06.2014 06:17:37",70,"Tjark","Vermont","Malta",0.71,49.7,"United States","Europe","Female","Male" ],
43 | ["Frieda","11.06.2014 05:11:01","11.06.2014 06:43:01",92,"Milian","Vermont","San Marino",0.89,81.88,"United States","Europe","Female","Female" ],
44 | ["Frieda","11.06.2014 05:28:45","11.06.2014 07:28:45",120,"Tjark","Vermont","Malta",0.71,85.2,"United States","Europe","Female","Male" ],
45 | ["Henry","11.06.2014 12:12:30","11.06.2014 12:15:30",3,"Felix","Connecticut","New Caledonia",1.41,4.23,"United States","Oceania","Male","Male" ],
46 | ["Niklas","11.06.2014 13:00:51","11.06.2014 14:19:51",79,"Tamino","Florida","Vietnam",0.65,51.35,"United States","Asia","Male","Male" ],
47 | ["Henry","11.06.2014 13:08:34","11.06.2014 13:25:34",17,"Felix","Connecticut","New Caledonia",1.41,23.97,"United States","Oceania","Male","Male" ],
48 | ["Henry","11.06.2014 13:31:01","11.06.2014 13:59:01",28,"Felix","Connecticut","New Caledonia",1.41,39.48,"United States","Oceania","Male","Male" ],
49 | ["Niklas","11.06.2014 13:31:32","11.06.2014 14:38:32",67,"Jesko","Florida","Tuvalu",1.84,123.28,"United States","Oceania","Male","Male" ],
50 | ["Henry","11.06.2014 13:31:34","11.06.2014 15:14:34",103,"Felix","Connecticut","New Caledonia",1.41,145.23,"United States","Oceania","Male","Male" ],
51 | ["Henry","11.06.2014 13:31:56","11.06.2014 13:43:56",12,"Felix","Connecticut","New Caledonia",1.41,16.92,"United States","Oceania","Male","Male" ],
52 | ["Henry","11.06.2014 13:32:30","11.06.2014 14:54:30",82,"Felix","Connecticut","New Caledonia",1.41,115.62,"United States","Oceania","Male","Male" ],
53 | ["Henry","11.06.2014 13:44:36","11.06.2014 15:10:36",86,"Felix","Connecticut","New Caledonia",1.41,121.26,"United States","Oceania","Male","Male" ],
54 | ["Emily","11.06.2014 14:21:02","11.06.2014 15:09:02",48,"Anne","Rhode Island","Eritrea",0.88,42.24,"United States","Africa","Male","Female" ],
55 | ["Niklas","11.06.2014 14:43:01","11.06.2014 15:01:01",18,"Jesko","Florida","Tuvalu",1.84,33.12,"United States","Oceania","Male","Male" ],
56 | ["Niklas","11.06.2014 14:43:26","11.06.2014 16:04:26",81,"Jesko","Florida","Tuvalu",1.84,149.04,"United States","Oceania","Male","Male" ],
57 | ["Niklas","11.06.2014 14:50:48","11.06.2014 15:21:48",31,"Anni","Florida","Cayman Islands",0.24,7.44,"United States","Oceania","Male","Female" ],
58 | ["Henry","11.06.2014 15:16:36","11.06.2014 15:51:36",35,"Felix","Connecticut","New Caledonia",1.41,49.35,"United States","Oceania","Male","Male" ],
59 | ["Charlotte","11.06.2014 15:43:16","11.06.2014 16:56:16",73,"Romeo","Alaska","Gabon Republic",1.05,76.65,"United States","Africa","Female","Male" ],
60 | ["Charlotte","11.06.2014 16:11:44","11.06.2014 17:55:44",104,"Romeo","Alaska","Gabon Republic",1.05,109.2,"United States","Africa","Female","Male" ],
61 | ["Charlotte","11.06.2014 16:15:16","11.06.2014 16:36:16",21,"Romeo","Alaska","Gabon Republic",1.05,22.05,"United States","Africa","Female","Male" ],
62 | ["Mika","11.06.2014 16:31:08","11.06.2014 16:40:08",9,"Felix","Vermont","New Caledonia",1.41,12.69,"United States","Oceania","Male","Male" ],
63 | ["Mika","11.06.2014 16:33:21","11.06.2014 17:38:21",65,"Anni","Vermont","Cayman Islands",0.24,15.6,"United States","Oceania","Male","Female" ],
64 | ["Mika","11.06.2014 16:34:37","11.06.2014 17:44:37",70,"Felix","Vermont","New Caledonia",1.41,98.7,"United States","Oceania","Male","Male" ],
65 | ["Emily","11.06.2014 17:36:40","11.06.2014 17:50:40",14,"Anne","Rhode Island","Eritrea",0.88,12.32,"United States","Africa","Male","Female" ],
66 | ["Emilia","11.06.2014 17:56:51","11.06.2014 18:46:51",50,"Adelina","Oklahoma","Mauritania",0.98,49,"United States","Africa","Female","Female" ],
67 | ["Sofia","11.06.2014 17:57:00","11.06.2014 18:28:00",31,"Jolie","Massachusetts","Bahamas",0.23,7.13,"United States","North America","Female","Female" ],
68 | ["Niklas","11.06.2014 18:07:28","11.06.2014 19:13:28",66,"Jesko","Florida","Tuvalu",1.84,121.44,"United States","Oceania","Male","Male" ],
69 | ["Henry","11.06.2014 18:27:32","11.06.2014 20:00:32",93,"Felix","Connecticut","New Caledonia",1.41,131.13,"United States","Oceania","Male","Male" ],
70 | ["Niklas","11.06.2014 18:38:05","11.06.2014 19:22:05",44,"Jesko","Florida","Tuvalu",1.84,80.96,"United States","Oceania","Male","Male" ],
71 | ["Niklas","11.06.2014 18:39:03","11.06.2014 19:23:03",44,"Jesko","Florida","Tuvalu",1.84,80.96,"United States","Oceania","Male","Male" ],
72 | ["Niklas","11.06.2014 18:40:20","11.06.2014 20:16:20",96,"Tamino","Florida","Vietnam",0.65,62.4,"United States","Asia","Male","Male" ],
73 | ["Niklas","11.06.2014 18:42:06","11.06.2014 20:20:06",98,"Tamino","Florida","Vietnam",0.65,63.7,"United States","Asia","Male","Male" ],
74 | ["Niklas","11.06.2014 18:58:12","11.06.2014 20:00:12",62,"Jesko","Florida","Tuvalu",1.84,114.08,"United States","Oceania","Male","Male" ],
75 | ["Clara","11.06.2014 19:01:05","11.06.2014 19:57:05",56,"Tristan","Maryland","Tajikistan",0.22,12.32,"United States","Asia","Female","Male" ],
76 | ["Henry","11.06.2014 19:02:44","11.06.2014 19:34:44",32,"Felix","Connecticut","New Caledonia",1.41,45.12,"United States","Oceania","Male","Male" ],
77 | ["Niklas","11.06.2014 19:03:40","11.06.2014 19:43:40",40,"Jesko","Florida","Tuvalu",1.84,73.6,"United States","Oceania","Male","Male" ],
78 | ["Niklas","11.06.2014 19:12:13","11.06.2014 20:00:13",48,"Jesko","Florida","Tuvalu",1.84,88.32,"United States","Oceania","Male","Male" ],
79 | ["Niklas","11.06.2014 19:27:38","11.06.2014 20:18:38",51,"Jesko","Florida","Tuvalu",1.84,93.84,"United States","Oceania","Male","Male" ],
80 | ["Niklas","11.06.2014 19:46:48","11.06.2014 21:13:48",87,"Jesko","Florida","Tuvalu",1.84,160.08,"United States","Oceania","Male","Male" ],
81 | ["Niklas","11.06.2014 19:49:04","11.06.2014 21:29:04",100,"Jesko","Florida","Tuvalu",1.84,184,"United States","Oceania","Male","Male" ],
82 | ["Niklas","11.06.2014 19:51:05","11.06.2014 21:13:05",82,"Jesko","Florida","Tuvalu",1.84,150.88,"United States","Oceania","Male","Male" ],
83 | ["Frieda","11.06.2014 20:02:10","11.06.2014 21:07:10",65,"Tjark","Vermont","Malta",0.71,46.15,"United States","Europe","Female","Male" ],
84 | ["Simon","11.06.2014 20:02:43","11.06.2014 21:06:43",64,"Ursel","Nevada","Denmark",0.08,5.12,"United States","Europe","Male","Female" ],
85 | ["Mika","11.06.2014 20:06:51","11.06.2014 20:52:51",46,"Felix","Vermont","New Caledonia",1.41,64.86,"United States","Oceania","Male","Male" ],
86 | ["Niklas","11.06.2014 20:06:58","11.06.2014 21:24:58",78,"Jesko","Florida","Tuvalu",1.84,143.52,"United States","Oceania","Male","Male" ],
87 | ["Simon","11.06.2014 20:24:52","11.06.2014 21:24:52",60,"Ursel","Nevada","Denmark",0.08,4.8,"United States","Europe","Male","Female" ],
88 | ["Niklas","11.06.2014 20:34:02","11.06.2014 21:25:02",51,"Jesko","Florida","Tuvalu",1.84,93.84,"United States","Oceania","Male","Male" ],
89 | ["Henry","11.06.2014 20:48:11","11.06.2014 22:13:11",85,"Felix","Connecticut","New Caledonia",1.41,119.85,"United States","Oceania","Male","Male" ],
90 | ["Niklas","11.06.2014 20:58:31","11.06.2014 22:24:31",86,"Jesko","Florida","Tuvalu",1.84,158.24,"United States","Oceania","Male","Male" ],
91 | ["Frieda","11.06.2014 21:03:12","11.06.2014 21:45:12",42,"Tjark","Vermont","Malta",0.71,29.82,"United States","Europe","Female","Male" ],
92 | ["Charlotte","11.06.2014 21:08:36","11.06.2014 21:38:36",30,"Ursel","Alaska","Denmark",0.08,2.4,"United States","Europe","Female","Female" ],
93 | ["Clara","11.06.2014 21:10:59","11.06.2014 22:07:59",57,"Tristan","Maryland","Tajikistan",0.22,12.54,"United States","Asia","Female","Male" ],
94 | ["Simon","11.06.2014 21:27:01","11.06.2014 21:54:01",27,"Ursel","Nevada","Denmark",0.08,2.16,"United States","Europe","Male","Female" ],
95 | ["Ida","11.06.2014 21:42:55","11.06.2014 21:57:55",15,"Ahmet","Maine","Mexico",0.07,1.05,"United States","North America","Female","Male" ],
96 | ["Ida","11.06.2014 21:44:56","11.06.2014 22:06:56",22,"Malea","Maine","Latvia",0.34,7.48,"United States","Europe","Female","Female" ],
97 | ["Simon","11.06.2014 21:46:33","11.06.2014 22:36:33",50,"Ursel","Nevada","Denmark",0.08,4,"United States","Europe","Male","Female" ],
98 | ["Emily","11.06.2014 21:47:38","11.06.2014 22:54:38",67,"Nadine","Rhode Island","Lebanon",0.54,36.18,"United States","Asia","Male","Female" ],
99 | ["Mats","11.06.2014 22:00:48","11.06.2014 22:40:48",40,"Felix","Virginia","New Caledonia",1.41,56.4,"United States","Oceania","Male","Male" ],
100 | ["Mika","11.06.2014 22:06:33","11.06.2014 23:14:33",68,"Miro","Vermont","Netherlands Antilles",0.4,27.2,"United States","North America","Male","Male" ],
101 | ["Niklas","11.06.2014 22:07:47","11.06.2014 22:57:47",50,"Tamino","Florida","Vietnam",0.65,32.5,"United States","Asia","Male","Male" ],
102 | ["Mika","11.06.2014 22:10:56","11.06.2014 22:12:56",2,"Miro","Vermont","Netherlands Antilles",0.4,0.8,"United States","North America","Male","Male" ],
103 | ["Mats","11.06.2014 22:11:30","11.06.2014 22:58:30",47,"Felix","Virginia","New Caledonia",1.41,66.27,"United States","Oceania","Male","Male" ],
104 | ["Niklas","11.06.2014 22:27:56","11.06.2014 23:04:56",37,"Tamino","Florida","Vietnam",0.65,24.05,"United States","Asia","Male","Male" ],
105 | ["Emilia","11.06.2014 22:31:30","11.06.2014 22:55:30",24,"Cinar","Oklahoma","Jamaica",0.43,10.32,"United States","North America","Female","Male" ],
106 | ["Clara","11.06.2014 22:33:43","11.06.2014 23:08:43",35,"Jule","Maryland","Faeroe Islands",0.52,18.2,"United States","Europe","Female","Female" ],
107 | ["Charlotte","11.06.2014 22:34:20","11.06.2014 23:27:20",53,"Ursel","Alaska","Denmark",0.08,4.24,"United States","Europe","Female","Female" ],
108 | ["Emilia","11.06.2014 22:34:35","11.06.2014 23:33:35",59,"Merlin","Oklahoma","Pakistan",0.36,21.24,"United States","Asia","Female","Male" ],
109 | ["Henry","11.06.2014 22:35:16","11.06.2014 22:43:16",8,"Viola","Connecticut","Angola",1.44,11.52,"United States","Africa","Male","Female" ],
110 | ["Emilia","11.06.2014 22:36:46","11.06.2014 22:47:46",11,"Benny","Oklahoma","Spain",0.07,0.77,"United States","Europe","Female","Male" ],
111 | ["Emilia","11.06.2014 22:38:58","11.06.2014 23:42:58",64,"Cora","Oklahoma","Bangladesh",0.51,32.64,"United States","Asia","Female","Female" ],
112 | ["Niklas","11.06.2014 22:41:32","11.06.2014 23:33:32",52,"Tamino","Florida","Vietnam",0.65,33.8,"United States","Asia","Male","Male" ],
113 | ["Lilly","11.06.2014 23:27:12","12.06.2014 00:43:12",76,"Kaan","Alaska","Reunion Island",1,76,"United States","Africa","Female","Male" ],
114 | ["Clara","11.06.2014 23:27:41","12.06.2014 00:39:41",72,"Tristan","Maryland","Tajikistan",0.22,15.84,"United States","Asia","Female","Male" ],
115 | ["Emily","11.06.2014 23:34:51","12.06.2014 00:09:51",35,"Nadine","Rhode Island","Lebanon",0.54,18.9,"United States","Asia","Male","Female" ],
116 | ["Sofia","11.06.2014 23:40:26","12.06.2014 00:11:26",31,"Jolie","Massachusetts","Bahamas",0.23,7.13,"United States","North America","Female","Female" ],
117 | ["Emilia","11.06.2014 23:40:33","12.06.2014 00:14:33",34,"Adelina","Oklahoma","Mauritania",0.98,33.32,"United States","Africa","Female","Female" ],
118 | ["Henry","11.06.2014 23:48:43","12.06.2014 00:32:43",44,"Felix","Connecticut","New Caledonia",1.41,62.04,"United States","Oceania","Male","Male" ],
119 | ["Emilia","11.06.2014 23:57:22","12.06.2014 00:29:22",32,"Cora","Oklahoma","Bangladesh",0.51,16.32,"United States","Asia","Female","Female" ]
120 | ]
--------------------------------------------------------------------------------
/resources/sample-data.csv:
--------------------------------------------------------------------------------
1 | caller;start;end;duration;recipient;origin;destination;ratepermin;costs;origincontinent;destinationcontinent;callergender;recipientgender
2 | Ben;27.09.2013 02:00:19;27.09.2013 02:44:19;44;Kimi;South Dakota;United Kingdom;0.07;3.08;United States;Europe;Male;Female
3 | Ben;27.09.2013 02:02:36;27.09.2013 02:07:36;5;Diana;South Dakota;Falkland Islands;0.45;2.25;United States;South America;Male;Female
4 | Ben;27.09.2013 02:07:40;27.09.2013 02:30:40;23;Diana;South Dakota;Falkland Islands;0.45;10.35;United States;South America;Male;Female
5 | Leni;27.09.2013 02:11:39;27.09.2013 02:46:39;35;Clemens;Missouri;Namibia;0.79;27.65;United States;Africa;Male;Male
6 | Max;27.09.2013 02:13:26;27.09.2013 02:41:26;28;Jasmin;Ohio;Mauritius;1.22;34.16;United States;Africa;Male;Female
7 | Max;27.09.2013 02:14:06;27.09.2013 02:22:06;8;Felina;Ohio;Suriname;1.22;9.76;United States;South America;Male;Female
8 | Max;27.09.2013 02:14:26;27.09.2013 02:48:26;34;Melinda;Ohio;Taiwan;0.07;2.38;United States;Asia;Male;Female
9 | Ida;27.09.2013 02:14:47;27.09.2013 02:20:47;6;Sidney;Maine;Mayotte Island;1.00;6.00;United States;Africa;Female;Male
10 | Ida;27.09.2013 02:14:47;27.09.2013 02:27:47;13;Sidney;Maine;Mayotte Island;1.00;13.00;United States;Africa;Female;Male
11 | Ida;27.09.2013 02:14:58;27.09.2013 02:56:58;42;Sidney;Maine;Mayotte Island;1.00;42.00;United States;Africa;Female;Male
12 | Ida;27.09.2013 02:14:58;27.09.2013 03:21:58;67;Sidney;Maine;Mayotte Island;1.00;67.00;United States;Africa;Female;Male
13 | Ida;27.09.2013 02:15:44;27.09.2013 02:43:44;28;Sidney;Maine;Mayotte Island;1.00;28.00;United States;Africa;Female;Male
14 | Ida;27.09.2013 02:15:44;27.09.2013 02:24:44;9;Sidney;Maine;Mayotte Island;1.00;9.00;United States;Africa;Female;Male
15 | Clara;27.09.2013 02:16:07;27.09.2013 02:22:07;6;Malia;Maryland;Monaco;0.14;0.84;United States;Europe;Female;Female
16 | Jonathan;27.09.2013 02:16:07;27.09.2013 02:22:07;6;Malia;West Virginia;Monaco;0.14;0.84;United States;Europe;Male;Female
17 | Leni;27.09.2013 02:19:29;27.09.2013 04:08:29;109;Matthias;Missouri;Mali Republic;1.03;112.27;United States;Africa;Male;Male
18 | Paul;27.09.2013 02:21:34;27.09.2013 02:49:34;28;Jasmin;Montana;Mauritius;1.22;34.16;United States;Africa;Male;Female
19 | Leni;27.09.2013 02:23:15;27.09.2013 02:47:15;24;Henriette;Missouri;Zimbabwe;0.48;11.52;United States;Africa;Male;Female
20 | Ben;27.09.2013 02:23:37;27.09.2013 02:37:37;14;Jasmin;South Dakota;Mauritius;1.22;17.08;United States;Africa;Male;Female
21 | Ben;27.09.2013 02:24:15;27.09.2013 02:43:15;19;Hugo;South Dakota;Zimbabwe;0.48;9.12;United States;Africa;Male;Male
22 | Ben;27.09.2013 02:25:12;27.09.2013 03:12:12;47;Diana;South Dakota;Falkland Islands;0.45;21.15;United States;South America;Male;Female
23 | Ben;27.09.2013 02:26:58;27.09.2013 04:15:58;109;Diana;South Dakota;Falkland Islands;0.45;49.05;United States;South America;Male;Female
24 | Max;27.09.2013 02:31:48;27.09.2013 03:04:48;33;Diana;Ohio;Falkland Islands;0.45;14.85;United States;South America;Male;Female
25 | Clara;27.09.2013 02:36:52;27.09.2013 03:30:52;54;Jasmin;Maryland;Mauritius;1.22;65.88;United States;Africa;Female;Female
26 | Jonathan;27.09.2013 02:36:52;27.09.2013 03:30:52;54;Jasmin;West Virginia;Mauritius;1.22;65.88;United States;Africa;Male;Female
27 | Emma;27.09.2013 02:37:56;27.09.2013 02:38:56;1;Jasmin;New Jersey;Mauritius;1.22;1.22;United States;Africa;Female;Female
28 | Clara;27.09.2013 02:38:38;27.09.2013 03:32:38;54;Jordan;Maryland;Bosnia;0.34;18.36;United States;Europe;Female;Male
29 | Jonathan;27.09.2013 02:38:38;27.09.2013 04:31:38;113;Diana;West Virginia;Falkland Islands;0.45;50.85;United States;South America;Male;Female
30 | Paul;27.09.2013 02:38:50;27.09.2013 02:53:50;15;Frauke;Montana;French Antilles;0.62;9.30;United States;South America;Male;Female
31 | Emma;27.09.2013 02:38:50;27.09.2013 02:50:50;12;Jasmin;New Jersey;Mauritius;1.22;14.64;United States;Africa;Female;Female
32 | Emma;27.09.2013 02:39:26;27.09.2013 03:05:26;26;Jasmin;New Jersey;Mauritius;1.22;31.72;United States;Africa;Female;Female
33 | Ida;27.09.2013 02:39:49;27.09.2013 03:39:49;60;Jayden;Maine;Kiribati;1.16;69.60;United States;Oceania;Female;Male
34 | Paul;27.09.2013 02:40:06;27.09.2013 03:44:06;64;Frauke;Montana;French Antilles;0.62;39.68;United States;South America;Male;Female
35 | Clara;27.09.2013 02:42:13;27.09.2013 02:48:13;6;Jordan;Maryland;Bosnia;0.34;2.04;United States;Europe;Female;Male
36 | Jonathan;27.09.2013 02:42:13;27.09.2013 04:14:13;92;Diana;West Virginia;Falkland Islands;0.45;41.40;United States;South America;Male;Female
37 | Ida;27.09.2013 02:47:35;27.09.2013 03:54:35;67;Ylvi;Maine;Anguilla;0.57;38.19;United States;South America;Female;Female
38 | Ida;27.09.2013 02:47:35;27.09.2013 04:23:35;96;Ylvi;Maine;Anguilla;0.57;54.72;United States;South America;Female;Female
39 | Leni;27.09.2013 02:49:37;27.09.2013 04:19:37;90;Henriette;Missouri;Zimbabwe;0.48;43.20;United States;Africa;Male;Female
40 | Leni;27.09.2013 02:51:42;27.09.2013 04:14:42;83;Anni;Missouri;Cayman Islands;0.24;19.92;United States;Oceania;Male;Female
41 | Emma;27.09.2013 02:53:54;27.09.2013 04:14:54;81;Diana;New Jersey;Falkland Islands;0.45;36.45;United States;South America;Female;Female
42 | Clara;27.09.2013 02:59:43;27.09.2013 04:02:43;63;Jordan;Maryland;Bosnia;0.34;21.42;United States;Europe;Female;Male
43 | Jonathan;27.09.2013 02:59:43;27.09.2013 04:02:43;63;Diana;West Virginia;Falkland Islands;0.45;28.35;United States;South America;Male;Female
44 | Paul;27.09.2013 03:07:57;27.09.2013 03:27:57;20;Patrick;Montana;Colombia;0.19;3.80;United States;South America;Male;Male
45 | Paul;27.09.2013 03:08:45;27.09.2013 03:45:45;37;Frauke;Montana;French Antilles;0.62;22.94;United States;South America;Male;Female
46 | Clara;27.09.2013 03:11:50;27.09.2013 04:50:50;99;Jordan;Maryland;Bosnia;0.34;33.66;United States;Europe;Female;Male
47 | Jonathan;27.09.2013 03:11:50;27.09.2013 04:50:50;99;Diana;West Virginia;Falkland Islands;0.45;44.55;United States;South America;Male;Female
48 | Paul;27.09.2013 03:16:09;27.09.2013 03:45:09;29;Elif;Montana;Burundi;1.39;40.31;United States;Africa;Male;Female
49 | Ben;27.09.2013 03:16:17;27.09.2013 04:09:17;53;Talea;South Dakota;Denmark;0.08;4.24;United States;Europe;Male;Female
50 | Paul;27.09.2013 03:47:32;27.09.2013 04:11:32;24;Stina;Montana;Mexico;0.07;1.68;United States;North America;Male;Female
51 | Ben;27.09.2013 05:08:06;27.09.2013 06:28:06;80;Hugo;South Dakota;Zimbabwe;0.48;38.40;United States;Africa;Male;Male
52 | Ben;27.09.2013 05:24:38;27.09.2013 05:35:38;11;Diana;South Dakota;Falkland Islands;0.45;4.95;United States;South America;Male;Female
53 | Ben;27.09.2013 11:41:36;27.09.2013 12:17:36;36;Diana;South Dakota;Falkland Islands;0.45;16.20;United States;South America;Male;Female
54 | Ben;27.09.2013 11:44:26;27.09.2013 12:17:26;33;Diana;South Dakota;Falkland Islands;0.45;14.85;United States;South America;Male;Female
55 | Ben;27.09.2013 11:46:02;27.09.2013 12:00:02;14;Diana;South Dakota;Falkland Islands;0.45;6.30;United States;South America;Male;Female
56 | Leni;27.09.2013 13:35:27;27.09.2013 13:58:27;23;Medina;Missouri;Hong Kong;0.09;2.07;United States;Asia;Male;Female
57 | Ida;27.09.2013 13:37:29;27.09.2013 14:09:29;32;Valentin;Maine;Pakistan;0.36;11.52;United States;Asia;Female;Male
58 | Ida;27.09.2013 13:37:29;27.09.2013 14:53:29;76;Valentin;Maine;Pakistan;0.36;27.36;United States;Asia;Female;Male
59 | Leni;27.09.2013 13:44:47;27.09.2013 13:59:47;15;Medina;Missouri;Hong Kong;0.09;1.35;United States;Asia;Male;Female
60 | Leni;27.09.2013 13:58:06;27.09.2013 14:50:06;52;Christopher;Missouri;Germany;0.07;3.64;United States;Europe;Male;Male
61 | Clara;27.09.2013 14:06:40;27.09.2013 14:53:40;47;Jordan;Maryland;Bosnia;0.34;15.98;United States;Europe;Female;Male
62 | Jonathan;27.09.2013 14:06:40;27.09.2013 14:53:40;47;Diana;West Virginia;Falkland Islands;0.45;21.15;United States;South America;Male;Female
63 | Leni;27.09.2013 14:08:34;27.09.2013 14:55:34;47;Christopher;Missouri;Germany;0.07;3.29;United States;Europe;Male;Male
64 | Leni;27.09.2013 14:08:42;27.09.2013 14:33:42;25;Medina;Missouri;Hong Kong;0.09;2.25;United States;Asia;Male;Female
65 | Leni;27.09.2013 14:13:24;27.09.2013 14:44:24;31;Medina;Missouri;Hong Kong;0.09;2.79;United States;Asia;Male;Female
66 | Leni;27.09.2013 14:15:51;27.09.2013 14:25:51;10;Diana;Missouri;Falkland Islands;0.45;4.50;United States;South America;Male;Female
67 | Clara;27.09.2013 15:16:24;27.09.2013 17:00:24;104;Jordan;Maryland;Bosnia;0.34;35.36;United States;Europe;Female;Male
68 | Jonathan;27.09.2013 15:16:24;27.09.2013 15:36:24;20;Diana;West Virginia;Falkland Islands;0.45;9.00;United States;South America;Male;Female
69 | Ben;27.09.2013 16:45:27;27.09.2013 17:16:27;31;Diana;South Dakota;Falkland Islands;0.45;13.95;United States;South America;Male;Female
70 | Leni;27.09.2013 16:56:35;27.09.2013 17:04:35;8;Medina;Missouri;Hong Kong;0.09;0.72;United States;Asia;Male;Female
71 | Leni;27.09.2013 16:57:51;27.09.2013 17:04:51;7;Medina;Missouri;Hong Kong;0.09;0.63;United States;Asia;Male;Female
72 | Leni;27.09.2013 17:17:28;27.09.2013 18:54:28;97;Medina;Missouri;Hong Kong;0.09;8.73;United States;Asia;Male;Female
73 | Clara;27.09.2013 17:27:58;27.09.2013 18:07:58;40;Jordan;Maryland;Bosnia;0.34;13.60;United States;Europe;Female;Male
74 | Jonathan;27.09.2013 17:27:58;27.09.2013 19:18:58;111;Diana;West Virginia;Falkland Islands;0.45;49.95;United States;South America;Male;Female
75 | Leni;27.09.2013 17:48:25;27.09.2013 18:18:25;30;Diana;Missouri;Falkland Islands;0.45;13.50;United States;South America;Male;Female
76 | Ben;27.09.2013 18:59:24;27.09.2013 19:57:24;58;Adelina;South Dakota;Mauritania;0.98;56.84;United States;Africa;Male;Female
77 | Leni;27.09.2013 19:01:19;27.09.2013 19:07:19;6;Medina;Missouri;Hong Kong;0.09;0.54;United States;Asia;Male;Female
78 | Leni;27.09.2013 19:01:31;27.09.2013 19:34:31;33;Medina;Missouri;Hong Kong;0.09;2.97;United States;Asia;Male;Female
79 | Max;27.09.2013 19:14:35;27.09.2013 19:52:35;38;Diana;Ohio;Falkland Islands;0.45;17.10;United States;South America;Male;Female
80 | Ida;27.09.2013 19:51:48;27.09.2013 20:36:48;45;Soraya;Maine;Algeria;0.48;21.60;United States;Africa;Female;Female
81 | Ida;27.09.2013 19:51:48;27.09.2013 21:04:48;73;Soraya;Maine;Algeria;0.48;35.04;United States;Africa;Female;Female
82 | Ida;27.09.2013 20:18:09;27.09.2013 20:21:09;3;Elaine;Maine;Laos;2.39;7.17;United States;Asia;Female;Female
83 | Ida;27.09.2013 20:18:09;27.09.2013 21:30:09;72;Elaine;Maine;Laos;2.39;172.08;United States;Asia;Female;Female
84 | Emma;27.09.2013 20:37:51;27.09.2013 21:20:51;43;Diana;New Jersey;Falkland Islands;0.45;19.35;United States;South America;Female;Female
85 | Emma;27.09.2013 21:20:14;27.09.2013 23:15:14;115;Diana;New Jersey;Falkland Islands;0.45;51.75;United States;South America;Female;Female
86 | Leni;27.09.2013 21:22:15;27.09.2013 21:29:15;7;Medina;Missouri;Hong Kong;0.09;0.63;United States;Asia;Male;Female
87 | Leni;27.09.2013 21:22:29;27.09.2013 21:27:29;5;Medina;Missouri;Hong Kong;0.09;0.45;United States;Asia;Male;Female
88 | Clara;27.09.2013 21:48:50;27.09.2013 21:57:50;9;Jordan;Maryland;Bosnia;0.34;3.06;United States;Europe;Female;Male
89 | Jonathan;27.09.2013 21:48:50;27.09.2013 22:23:50;35;Diana;West Virginia;Falkland Islands;0.45;15.75;United States;South America;Male;Female
90 | Leni;27.09.2013 23:47:49;28.09.2013 00:16:49;29;Anthony;Missouri;Portugal;0.09;2.61;United States;Europe;Male;Male
91 |
--------------------------------------------------------------------------------
/resources/sample-data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caller": "Ben",
4 | "start": "27.09.2013 02:00:19",
5 | "end": "27.09.2013 02:44:19",
6 | "duration": 44,
7 | "recipient": "Kimi",
8 | "origin": "South Dakota",
9 | "destination": "United Kingdom",
10 | "ratepermin": 0.07,
11 | "costs": 3.08,
12 | "origincontinent": "United States",
13 | "destinationcontinent": "Europe",
14 | "callergender": "Male",
15 | "recipientgender": "Female"
16 | },
17 | {
18 | "caller": "Ben",
19 | "start": "27.09.2013 02:02:36",
20 | "end": "27.09.2013 02:07:36",
21 | "duration": 5,
22 | "recipient": "Diana",
23 | "origin": "South Dakota",
24 | "destination": "Falkland Islands",
25 | "ratepermin": 0.45,
26 | "costs": 2.25,
27 | "origincontinent": "United States",
28 | "destinationcontinent": "South America",
29 | "callergender": "Male",
30 | "recipientgender": "Female"
31 | },
32 | {
33 | "caller": "Ben",
34 | "start": "27.09.2013 02:07:40",
35 | "end": "27.09.2013 02:30:40",
36 | "duration": 23,
37 | "recipient": "Diana",
38 | "origin": "South Dakota",
39 | "destination": "Falkland Islands",
40 | "ratepermin": 0.45,
41 | "costs": 10.35,
42 | "origincontinent": "United States",
43 | "destinationcontinent": "South America",
44 | "callergender": "Male",
45 | "recipientgender": "Female"
46 | },
47 | {
48 | "caller": "Leni",
49 | "start": "27.09.2013 02:11:39",
50 | "end": "27.09.2013 02:46:39",
51 | "duration": 35,
52 | "recipient": "Clemens",
53 | "origin": "Missouri",
54 | "destination": "Namibia",
55 | "ratepermin": 0.79,
56 | "costs": 27.65,
57 | "origincontinent": "United States",
58 | "destinationcontinent": "Africa",
59 | "callergender": "Male",
60 | "recipientgender": "Male"
61 | },
62 | {
63 | "caller": "Max",
64 | "start": "27.09.2013 02:13:26",
65 | "end": "27.09.2013 02:41:26",
66 | "duration": 28,
67 | "recipient": "Jasmin",
68 | "origin": "Ohio",
69 | "destination": "Mauritius",
70 | "ratepermin": 1.22,
71 | "costs": 34.16,
72 | "origincontinent": "United States",
73 | "destinationcontinent": "Africa",
74 | "callergender": "Male",
75 | "recipientgender": "Female"
76 | },
77 | {
78 | "caller": "Max",
79 | "start": "27.09.2013 02:14:06",
80 | "end": "27.09.2013 02:22:06",
81 | "duration": 8,
82 | "recipient": "Felina",
83 | "origin": "Ohio",
84 | "destination": "Suriname",
85 | "ratepermin": 1.22,
86 | "costs": 9.76,
87 | "origincontinent": "United States",
88 | "destinationcontinent": "South America",
89 | "callergender": "Male",
90 | "recipientgender": "Female"
91 | },
92 | {
93 | "caller": "Max",
94 | "start": "27.09.2013 02:14:26",
95 | "end": "27.09.2013 02:48:26",
96 | "duration": 34,
97 | "recipient": "Melinda",
98 | "origin": "Ohio",
99 | "destination": "Taiwan",
100 | "ratepermin": 0.07,
101 | "costs": 2.38,
102 | "origincontinent": "United States",
103 | "destinationcontinent": "Asia",
104 | "callergender": "Male",
105 | "recipientgender": "Female"
106 | },
107 | {
108 | "caller": "Ida",
109 | "start": "27.09.2013 02:14:47",
110 | "end": "27.09.2013 02:20:47",
111 | "duration": 6,
112 | "recipient": "Sidney",
113 | "origin": "Maine",
114 | "destination": "Mayotte Island",
115 | "ratepermin": 1,
116 | "costs": 6,
117 | "origincontinent": "United States",
118 | "destinationcontinent": "Africa",
119 | "callergender": "Female",
120 | "recipientgender": "Male"
121 | },
122 | {
123 | "caller": "Ida",
124 | "start": "27.09.2013 02:14:47",
125 | "end": "27.09.2013 02:27:47",
126 | "duration": 13,
127 | "recipient": "Sidney",
128 | "origin": "Maine",
129 | "destination": "Mayotte Island",
130 | "ratepermin": 1,
131 | "costs": 13,
132 | "origincontinent": "United States",
133 | "destinationcontinent": "Africa",
134 | "callergender": "Female",
135 | "recipientgender": "Male"
136 | },
137 | {
138 | "caller": "Ida",
139 | "start": "27.09.2013 02:14:58",
140 | "end": "27.09.2013 02:56:58",
141 | "duration": 42,
142 | "recipient": "Sidney",
143 | "origin": "Maine",
144 | "destination": "Mayotte Island",
145 | "ratepermin": 1,
146 | "costs": 42,
147 | "origincontinent": "United States",
148 | "destinationcontinent": "Africa",
149 | "callergender": "Female",
150 | "recipientgender": "Male"
151 | },
152 | {
153 | "caller": "Ida",
154 | "start": "27.09.2013 02:14:58",
155 | "end": "27.09.2013 03:21:58",
156 | "duration": 67,
157 | "recipient": "Sidney",
158 | "origin": "Maine",
159 | "destination": "Mayotte Island",
160 | "ratepermin": 1,
161 | "costs": 67,
162 | "origincontinent": "United States",
163 | "destinationcontinent": "Africa",
164 | "callergender": "Female",
165 | "recipientgender": "Male"
166 | },
167 | {
168 | "caller": "Ida",
169 | "start": "27.09.2013 02:15:44",
170 | "end": "27.09.2013 02:43:44",
171 | "duration": 28,
172 | "recipient": "Sidney",
173 | "origin": "Maine",
174 | "destination": "Mayotte Island",
175 | "ratepermin": 1,
176 | "costs": 28,
177 | "origincontinent": "United States",
178 | "destinationcontinent": "Africa",
179 | "callergender": "Female",
180 | "recipientgender": "Male"
181 | },
182 | {
183 | "caller": "Ida",
184 | "start": "27.09.2013 02:15:44",
185 | "end": "27.09.2013 02:24:44",
186 | "duration": 9,
187 | "recipient": "Sidney",
188 | "origin": "Maine",
189 | "destination": "Mayotte Island",
190 | "ratepermin": 1,
191 | "costs": 9,
192 | "origincontinent": "United States",
193 | "destinationcontinent": "Africa",
194 | "callergender": "Female",
195 | "recipientgender": "Male"
196 | },
197 | {
198 | "caller": "Clara",
199 | "start": "27.09.2013 02:16:07",
200 | "end": "27.09.2013 02:22:07",
201 | "duration": 6,
202 | "recipient": "Malia",
203 | "origin": "Maryland",
204 | "destination": "Monaco",
205 | "ratepermin": 0.14,
206 | "costs": 0.84,
207 | "origincontinent": "United States",
208 | "destinationcontinent": "Europe",
209 | "callergender": "Female",
210 | "recipientgender": "Female"
211 | },
212 | {
213 | "caller": "Jonathan",
214 | "start": "27.09.2013 02:16:07",
215 | "end": "27.09.2013 02:22:07",
216 | "duration": 6,
217 | "recipient": "Malia",
218 | "origin": "West Virginia",
219 | "destination": "Monaco",
220 | "ratepermin": 0.14,
221 | "costs": 0.84,
222 | "origincontinent": "United States",
223 | "destinationcontinent": "Europe",
224 | "callergender": "Male",
225 | "recipientgender": "Female"
226 | },
227 | {
228 | "caller": "Leni",
229 | "start": "27.09.2013 02:19:29",
230 | "end": "27.09.2013 04:08:29",
231 | "duration": 109,
232 | "recipient": "Matthias",
233 | "origin": "Missouri",
234 | "destination": "Mali Republic",
235 | "ratepermin": 1.03,
236 | "costs": 112.27,
237 | "origincontinent": "United States",
238 | "destinationcontinent": "Africa",
239 | "callergender": "Male",
240 | "recipientgender": "Male"
241 | },
242 | {
243 | "caller": "Paul",
244 | "start": "27.09.2013 02:21:34",
245 | "end": "27.09.2013 02:49:34",
246 | "duration": 28,
247 | "recipient": "Jasmin",
248 | "origin": "Montana",
249 | "destination": "Mauritius",
250 | "ratepermin": 1.22,
251 | "costs": 34.16,
252 | "origincontinent": "United States",
253 | "destinationcontinent": "Africa",
254 | "callergender": "Male",
255 | "recipientgender": "Female"
256 | },
257 | {
258 | "caller": "Leni",
259 | "start": "27.09.2013 02:23:15",
260 | "end": "27.09.2013 02:47:15",
261 | "duration": 24,
262 | "recipient": "Henriette",
263 | "origin": "Missouri",
264 | "destination": "Zimbabwe",
265 | "ratepermin": 0.48,
266 | "costs": 11.52,
267 | "origincontinent": "United States",
268 | "destinationcontinent": "Africa",
269 | "callergender": "Male",
270 | "recipientgender": "Female"
271 | },
272 | {
273 | "caller": "Ben",
274 | "start": "27.09.2013 02:23:37",
275 | "end": "27.09.2013 02:37:37",
276 | "duration": 14,
277 | "recipient": "Jasmin",
278 | "origin": "South Dakota",
279 | "destination": "Mauritius",
280 | "ratepermin": 1.22,
281 | "costs": 17.08,
282 | "origincontinent": "United States",
283 | "destinationcontinent": "Africa",
284 | "callergender": "Male",
285 | "recipientgender": "Female"
286 | },
287 | {
288 | "caller": "Ben",
289 | "start": "27.09.2013 02:24:15",
290 | "end": "27.09.2013 02:43:15",
291 | "duration": 19,
292 | "recipient": "Hugo",
293 | "origin": "South Dakota",
294 | "destination": "Zimbabwe",
295 | "ratepermin": 0.48,
296 | "costs": 9.12,
297 | "origincontinent": "United States",
298 | "destinationcontinent": "Africa",
299 | "callergender": "Male",
300 | "recipientgender": "Male"
301 | },
302 | {
303 | "caller": "Ben",
304 | "start": "27.09.2013 02:25:12",
305 | "end": "27.09.2013 03:12:12",
306 | "duration": 47,
307 | "recipient": "Diana",
308 | "origin": "South Dakota",
309 | "destination": "Falkland Islands",
310 | "ratepermin": 0.45,
311 | "costs": 21.15,
312 | "origincontinent": "United States",
313 | "destinationcontinent": "South America",
314 | "callergender": "Male",
315 | "recipientgender": "Female"
316 | },
317 | {
318 | "caller": "Ben",
319 | "start": "27.09.2013 02:26:58",
320 | "end": "27.09.2013 04:15:58",
321 | "duration": 109,
322 | "recipient": "Diana",
323 | "origin": "South Dakota",
324 | "destination": "Falkland Islands",
325 | "ratepermin": 0.45,
326 | "costs": 49.05,
327 | "origincontinent": "United States",
328 | "destinationcontinent": "South America",
329 | "callergender": "Male",
330 | "recipientgender": "Female"
331 | },
332 | {
333 | "caller": "Max",
334 | "start": "27.09.2013 02:31:48",
335 | "end": "27.09.2013 03:04:48",
336 | "duration": 33,
337 | "recipient": "Diana",
338 | "origin": "Ohio",
339 | "destination": "Falkland Islands",
340 | "ratepermin": 0.45,
341 | "costs": 14.85,
342 | "origincontinent": "United States",
343 | "destinationcontinent": "South America",
344 | "callergender": "Male",
345 | "recipientgender": "Female"
346 | },
347 | {
348 | "caller": "Clara",
349 | "start": "27.09.2013 02:36:52",
350 | "end": "27.09.2013 03:30:52",
351 | "duration": 54,
352 | "recipient": "Jasmin",
353 | "origin": "Maryland",
354 | "destination": "Mauritius",
355 | "ratepermin": 1.22,
356 | "costs": 65.88,
357 | "origincontinent": "United States",
358 | "destinationcontinent": "Africa",
359 | "callergender": "Female",
360 | "recipientgender": "Female"
361 | },
362 | {
363 | "caller": "Jonathan",
364 | "start": "27.09.2013 02:36:52",
365 | "end": "27.09.2013 03:30:52",
366 | "duration": 54,
367 | "recipient": "Jasmin",
368 | "origin": "West Virginia",
369 | "destination": "Mauritius",
370 | "ratepermin": 1.22,
371 | "costs": 65.88,
372 | "origincontinent": "United States",
373 | "destinationcontinent": "Africa",
374 | "callergender": "Male",
375 | "recipientgender": "Female"
376 | },
377 | {
378 | "caller": "Emma",
379 | "start": "27.09.2013 02:37:56",
380 | "end": "27.09.2013 02:38:56",
381 | "duration": 1,
382 | "recipient": "Jasmin",
383 | "origin": "New Jersey",
384 | "destination": "Mauritius",
385 | "ratepermin": 1.22,
386 | "costs": 1.22,
387 | "origincontinent": "United States",
388 | "destinationcontinent": "Africa",
389 | "callergender": "Female",
390 | "recipientgender": "Female"
391 | },
392 | {
393 | "caller": "Clara",
394 | "start": "27.09.2013 02:38:38",
395 | "end": "27.09.2013 03:32:38",
396 | "duration": 54,
397 | "recipient": "Jordan",
398 | "origin": "Maryland",
399 | "destination": "Bosnia",
400 | "ratepermin": 0.34,
401 | "costs": 18.36,
402 | "origincontinent": "United States",
403 | "destinationcontinent": "Europe",
404 | "callergender": "Female",
405 | "recipientgender": "Male"
406 | },
407 | {
408 | "caller": "Jonathan",
409 | "start": "27.09.2013 02:38:38",
410 | "end": "27.09.2013 04:31:38",
411 | "duration": 113,
412 | "recipient": "Diana",
413 | "origin": "West Virginia",
414 | "destination": "Falkland Islands",
415 | "ratepermin": 0.45,
416 | "costs": 50.85,
417 | "origincontinent": "United States",
418 | "destinationcontinent": "South America",
419 | "callergender": "Male",
420 | "recipientgender": "Female"
421 | },
422 | {
423 | "caller": "Paul",
424 | "start": "27.09.2013 02:38:50",
425 | "end": "27.09.2013 02:53:50",
426 | "duration": 15,
427 | "recipient": "Frauke",
428 | "origin": "Montana",
429 | "destination": "French Antilles",
430 | "ratepermin": 0.62,
431 | "costs": 9.3,
432 | "origincontinent": "United States",
433 | "destinationcontinent": "South America",
434 | "callergender": "Male",
435 | "recipientgender": "Female"
436 | },
437 | {
438 | "caller": "Emma",
439 | "start": "27.09.2013 02:38:50",
440 | "end": "27.09.2013 02:50:50",
441 | "duration": 12,
442 | "recipient": "Jasmin",
443 | "origin": "New Jersey",
444 | "destination": "Mauritius",
445 | "ratepermin": 1.22,
446 | "costs": 14.64,
447 | "origincontinent": "United States",
448 | "destinationcontinent": "Africa",
449 | "callergender": "Female",
450 | "recipientgender": "Female"
451 | },
452 | {
453 | "caller": "Emma",
454 | "start": "27.09.2013 02:39:26",
455 | "end": "27.09.2013 03:05:26",
456 | "duration": 26,
457 | "recipient": "Jasmin",
458 | "origin": "New Jersey",
459 | "destination": "Mauritius",
460 | "ratepermin": 1.22,
461 | "costs": 31.72,
462 | "origincontinent": "United States",
463 | "destinationcontinent": "Africa",
464 | "callergender": "Female",
465 | "recipientgender": "Female"
466 | },
467 | {
468 | "caller": "Ida",
469 | "start": "27.09.2013 02:39:49",
470 | "end": "27.09.2013 03:39:49",
471 | "duration": 60,
472 | "recipient": "Jayden",
473 | "origin": "Maine",
474 | "destination": "Kiribati",
475 | "ratepermin": 1.16,
476 | "costs": 69.6,
477 | "origincontinent": "United States",
478 | "destinationcontinent": "Oceania",
479 | "callergender": "Female",
480 | "recipientgender": "Male"
481 | },
482 | {
483 | "caller": "Paul",
484 | "start": "27.09.2013 02:40:06",
485 | "end": "27.09.2013 03:44:06",
486 | "duration": 64,
487 | "recipient": "Frauke",
488 | "origin": "Montana",
489 | "destination": "French Antilles",
490 | "ratepermin": 0.62,
491 | "costs": 39.68,
492 | "origincontinent": "United States",
493 | "destinationcontinent": "South America",
494 | "callergender": "Male",
495 | "recipientgender": "Female"
496 | },
497 | {
498 | "caller": "Clara",
499 | "start": "27.09.2013 02:42:13",
500 | "end": "27.09.2013 02:48:13",
501 | "duration": 6,
502 | "recipient": "Jordan",
503 | "origin": "Maryland",
504 | "destination": "Bosnia",
505 | "ratepermin": 0.34,
506 | "costs": 2.04,
507 | "origincontinent": "United States",
508 | "destinationcontinent": "Europe",
509 | "callergender": "Female",
510 | "recipientgender": "Male"
511 | },
512 | {
513 | "caller": "Jonathan",
514 | "start": "27.09.2013 02:42:13",
515 | "end": "27.09.2013 04:14:13",
516 | "duration": 92,
517 | "recipient": "Diana",
518 | "origin": "West Virginia",
519 | "destination": "Falkland Islands",
520 | "ratepermin": 0.45,
521 | "costs": 41.4,
522 | "origincontinent": "United States",
523 | "destinationcontinent": "South America",
524 | "callergender": "Male",
525 | "recipientgender": "Female"
526 | },
527 | {
528 | "caller": "Ida",
529 | "start": "27.09.2013 02:47:35",
530 | "end": "27.09.2013 03:54:35",
531 | "duration": 67,
532 | "recipient": "Ylvi",
533 | "origin": "Maine",
534 | "destination": "Anguilla",
535 | "ratepermin": 0.57,
536 | "costs": 38.19,
537 | "origincontinent": "United States",
538 | "destinationcontinent": "South America",
539 | "callergender": "Female",
540 | "recipientgender": "Female"
541 | },
542 | {
543 | "caller": "Ida",
544 | "start": "27.09.2013 02:47:35",
545 | "end": "27.09.2013 04:23:35",
546 | "duration": 96,
547 | "recipient": "Ylvi",
548 | "origin": "Maine",
549 | "destination": "Anguilla",
550 | "ratepermin": 0.57,
551 | "costs": 54.72,
552 | "origincontinent": "United States",
553 | "destinationcontinent": "South America",
554 | "callergender": "Female",
555 | "recipientgender": "Female"
556 | },
557 | {
558 | "caller": "Leni",
559 | "start": "27.09.2013 02:49:37",
560 | "end": "27.09.2013 04:19:37",
561 | "duration": 90,
562 | "recipient": "Henriette",
563 | "origin": "Missouri",
564 | "destination": "Zimbabwe",
565 | "ratepermin": 0.48,
566 | "costs": 43.2,
567 | "origincontinent": "United States",
568 | "destinationcontinent": "Africa",
569 | "callergender": "Male",
570 | "recipientgender": "Female"
571 | },
572 | {
573 | "caller": "Leni",
574 | "start": "27.09.2013 02:51:42",
575 | "end": "27.09.2013 04:14:42",
576 | "duration": 83,
577 | "recipient": "Anni",
578 | "origin": "Missouri",
579 | "destination": "Cayman Islands",
580 | "ratepermin": 0.24,
581 | "costs": 19.92,
582 | "origincontinent": "United States",
583 | "destinationcontinent": "Oceania",
584 | "callergender": "Male",
585 | "recipientgender": "Female"
586 | },
587 | {
588 | "caller": "Emma",
589 | "start": "27.09.2013 02:53:54",
590 | "end": "27.09.2013 04:14:54",
591 | "duration": 81,
592 | "recipient": "Diana",
593 | "origin": "New Jersey",
594 | "destination": "Falkland Islands",
595 | "ratepermin": 0.45,
596 | "costs": 36.45,
597 | "origincontinent": "United States",
598 | "destinationcontinent": "South America",
599 | "callergender": "Female",
600 | "recipientgender": "Female"
601 | },
602 | {
603 | "caller": "Clara",
604 | "start": "27.09.2013 02:59:43",
605 | "end": "27.09.2013 04:02:43",
606 | "duration": 63,
607 | "recipient": "Jordan",
608 | "origin": "Maryland",
609 | "destination": "Bosnia",
610 | "ratepermin": 0.34,
611 | "costs": 21.42,
612 | "origincontinent": "United States",
613 | "destinationcontinent": "Europe",
614 | "callergender": "Female",
615 | "recipientgender": "Male"
616 | },
617 | {
618 | "caller": "Jonathan",
619 | "start": "27.09.2013 02:59:43",
620 | "end": "27.09.2013 04:02:43",
621 | "duration": 63,
622 | "recipient": "Diana",
623 | "origin": "West Virginia",
624 | "destination": "Falkland Islands",
625 | "ratepermin": 0.45,
626 | "costs": 28.35,
627 | "origincontinent": "United States",
628 | "destinationcontinent": "South America",
629 | "callergender": "Male",
630 | "recipientgender": "Female"
631 | },
632 | {
633 | "caller": "Paul",
634 | "start": "27.09.2013 03:07:57",
635 | "end": "27.09.2013 03:27:57",
636 | "duration": 20,
637 | "recipient": "Patrick",
638 | "origin": "Montana",
639 | "destination": "Colombia",
640 | "ratepermin": 0.19,
641 | "costs": 3.8,
642 | "origincontinent": "United States",
643 | "destinationcontinent": "South America",
644 | "callergender": "Male",
645 | "recipientgender": "Male"
646 | },
647 | {
648 | "caller": "Paul",
649 | "start": "27.09.2013 03:08:45",
650 | "end": "27.09.2013 03:45:45",
651 | "duration": 37,
652 | "recipient": "Frauke",
653 | "origin": "Montana",
654 | "destination": "French Antilles",
655 | "ratepermin": 0.62,
656 | "costs": 22.94,
657 | "origincontinent": "United States",
658 | "destinationcontinent": "South America",
659 | "callergender": "Male",
660 | "recipientgender": "Female"
661 | },
662 | {
663 | "caller": "Clara",
664 | "start": "27.09.2013 03:11:50",
665 | "end": "27.09.2013 04:50:50",
666 | "duration": 99,
667 | "recipient": "Jordan",
668 | "origin": "Maryland",
669 | "destination": "Bosnia",
670 | "ratepermin": 0.34,
671 | "costs": 33.66,
672 | "origincontinent": "United States",
673 | "destinationcontinent": "Europe",
674 | "callergender": "Female",
675 | "recipientgender": "Male"
676 | },
677 | {
678 | "caller": "Jonathan",
679 | "start": "27.09.2013 03:11:50",
680 | "end": "27.09.2013 04:50:50",
681 | "duration": 99,
682 | "recipient": "Diana",
683 | "origin": "West Virginia",
684 | "destination": "Falkland Islands",
685 | "ratepermin": 0.45,
686 | "costs": 44.55,
687 | "origincontinent": "United States",
688 | "destinationcontinent": "South America",
689 | "callergender": "Male",
690 | "recipientgender": "Female"
691 | },
692 | {
693 | "caller": "Paul",
694 | "start": "27.09.2013 03:16:09",
695 | "end": "27.09.2013 03:45:09",
696 | "duration": 29,
697 | "recipient": "Elif",
698 | "origin": "Montana",
699 | "destination": "Burundi",
700 | "ratepermin": 1.39,
701 | "costs": 40.31,
702 | "origincontinent": "United States",
703 | "destinationcontinent": "Africa",
704 | "callergender": "Male",
705 | "recipientgender": "Female"
706 | },
707 | {
708 | "caller": "Ben",
709 | "start": "27.09.2013 03:16:17",
710 | "end": "27.09.2013 04:09:17",
711 | "duration": 53,
712 | "recipient": "Talea",
713 | "origin": "South Dakota",
714 | "destination": "Denmark",
715 | "ratepermin": 0.08,
716 | "costs": 4.24,
717 | "origincontinent": "United States",
718 | "destinationcontinent": "Europe",
719 | "callergender": "Male",
720 | "recipientgender": "Female"
721 | },
722 | {
723 | "caller": "Paul",
724 | "start": "27.09.2013 03:47:32",
725 | "end": "27.09.2013 04:11:32",
726 | "duration": 24,
727 | "recipient": "Stina",
728 | "origin": "Montana",
729 | "destination": "Mexico",
730 | "ratepermin": 0.07,
731 | "costs": 1.68,
732 | "origincontinent": "United States",
733 | "destinationcontinent": "North America",
734 | "callergender": "Male",
735 | "recipientgender": "Female"
736 | },
737 | {
738 | "caller": "Ben",
739 | "start": "27.09.2013 05:08:06",
740 | "end": "27.09.2013 06:28:06",
741 | "duration": 80,
742 | "recipient": "Hugo",
743 | "origin": "South Dakota",
744 | "destination": "Zimbabwe",
745 | "ratepermin": 0.48,
746 | "costs": 38.4,
747 | "origincontinent": "United States",
748 | "destinationcontinent": "Africa",
749 | "callergender": "Male",
750 | "recipientgender": "Male"
751 | },
752 | {
753 | "caller": "Ben",
754 | "start": "27.09.2013 05:24:38",
755 | "end": "27.09.2013 05:35:38",
756 | "duration": 11,
757 | "recipient": "Diana",
758 | "origin": "South Dakota",
759 | "destination": "Falkland Islands",
760 | "ratepermin": 0.45,
761 | "costs": 4.95,
762 | "origincontinent": "United States",
763 | "destinationcontinent": "South America",
764 | "callergender": "Male",
765 | "recipientgender": "Female"
766 | },
767 | {
768 | "caller": "Ben",
769 | "start": "27.09.2013 11:41:36",
770 | "end": "27.09.2013 12:17:36",
771 | "duration": 36,
772 | "recipient": "Diana",
773 | "origin": "South Dakota",
774 | "destination": "Falkland Islands",
775 | "ratepermin": 0.45,
776 | "costs": 16.2,
777 | "origincontinent": "United States",
778 | "destinationcontinent": "South America",
779 | "callergender": "Male",
780 | "recipientgender": "Female"
781 | },
782 | {
783 | "caller": "Ben",
784 | "start": "27.09.2013 11:44:26",
785 | "end": "27.09.2013 12:17:26",
786 | "duration": 33,
787 | "recipient": "Diana",
788 | "origin": "South Dakota",
789 | "destination": "Falkland Islands",
790 | "ratepermin": 0.45,
791 | "costs": 14.85,
792 | "origincontinent": "United States",
793 | "destinationcontinent": "South America",
794 | "callergender": "Male",
795 | "recipientgender": "Female"
796 | },
797 | {
798 | "caller": "Ben",
799 | "start": "27.09.2013 11:46:02",
800 | "end": "27.09.2013 12:00:02",
801 | "duration": 14,
802 | "recipient": "Diana",
803 | "origin": "South Dakota",
804 | "destination": "Falkland Islands",
805 | "ratepermin": 0.45,
806 | "costs": 6.3,
807 | "origincontinent": "United States",
808 | "destinationcontinent": "South America",
809 | "callergender": "Male",
810 | "recipientgender": "Female"
811 | },
812 | {
813 | "caller": "Leni",
814 | "start": "27.09.2013 13:35:27",
815 | "end": "27.09.2013 13:58:27",
816 | "duration": 23,
817 | "recipient": "Medina",
818 | "origin": "Missouri",
819 | "destination": "Hong Kong",
820 | "ratepermin": 0.09,
821 | "costs": 2.07,
822 | "origincontinent": "United States",
823 | "destinationcontinent": "Asia",
824 | "callergender": "Male",
825 | "recipientgender": "Female"
826 | },
827 | {
828 | "caller": "Ida",
829 | "start": "27.09.2013 13:37:29",
830 | "end": "27.09.2013 14:09:29",
831 | "duration": 32,
832 | "recipient": "Valentin",
833 | "origin": "Maine",
834 | "destination": "Pakistan",
835 | "ratepermin": 0.36,
836 | "costs": 11.52,
837 | "origincontinent": "United States",
838 | "destinationcontinent": "Asia",
839 | "callergender": "Female",
840 | "recipientgender": "Male"
841 | },
842 | {
843 | "caller": "Ida",
844 | "start": "27.09.2013 13:37:29",
845 | "end": "27.09.2013 14:53:29",
846 | "duration": 76,
847 | "recipient": "Valentin",
848 | "origin": "Maine",
849 | "destination": "Pakistan",
850 | "ratepermin": 0.36,
851 | "costs": 27.36,
852 | "origincontinent": "United States",
853 | "destinationcontinent": "Asia",
854 | "callergender": "Female",
855 | "recipientgender": "Male"
856 | },
857 | {
858 | "caller": "Leni",
859 | "start": "27.09.2013 13:44:47",
860 | "end": "27.09.2013 13:59:47",
861 | "duration": 15,
862 | "recipient": "Medina",
863 | "origin": "Missouri",
864 | "destination": "Hong Kong",
865 | "ratepermin": 0.09,
866 | "costs": 1.35,
867 | "origincontinent": "United States",
868 | "destinationcontinent": "Asia",
869 | "callergender": "Male",
870 | "recipientgender": "Female"
871 | },
872 | {
873 | "caller": "Leni",
874 | "start": "27.09.2013 13:58:06",
875 | "end": "27.09.2013 14:50:06",
876 | "duration": 52,
877 | "recipient": "Christopher",
878 | "origin": "Missouri",
879 | "destination": "Germany",
880 | "ratepermin": 0.07,
881 | "costs": 3.64,
882 | "origincontinent": "United States",
883 | "destinationcontinent": "Europe",
884 | "callergender": "Male",
885 | "recipientgender": "Male"
886 | },
887 | {
888 | "caller": "Clara",
889 | "start": "27.09.2013 14:06:40",
890 | "end": "27.09.2013 14:53:40",
891 | "duration": 47,
892 | "recipient": "Jordan",
893 | "origin": "Maryland",
894 | "destination": "Bosnia",
895 | "ratepermin": 0.34,
896 | "costs": 15.98,
897 | "origincontinent": "United States",
898 | "destinationcontinent": "Europe",
899 | "callergender": "Female",
900 | "recipientgender": "Male"
901 | },
902 | {
903 | "caller": "Jonathan",
904 | "start": "27.09.2013 14:06:40",
905 | "end": "27.09.2013 14:53:40",
906 | "duration": 47,
907 | "recipient": "Diana",
908 | "origin": "West Virginia",
909 | "destination": "Falkland Islands",
910 | "ratepermin": 0.45,
911 | "costs": 21.15,
912 | "origincontinent": "United States",
913 | "destinationcontinent": "South America",
914 | "callergender": "Male",
915 | "recipientgender": "Female"
916 | },
917 | {
918 | "caller": "Leni",
919 | "start": "27.09.2013 14:08:34",
920 | "end": "27.09.2013 14:55:34",
921 | "duration": 47,
922 | "recipient": "Christopher",
923 | "origin": "Missouri",
924 | "destination": "Germany",
925 | "ratepermin": 0.07,
926 | "costs": 3.29,
927 | "origincontinent": "United States",
928 | "destinationcontinent": "Europe",
929 | "callergender": "Male",
930 | "recipientgender": "Male"
931 | },
932 | {
933 | "caller": "Leni",
934 | "start": "27.09.2013 14:08:42",
935 | "end": "27.09.2013 14:33:42",
936 | "duration": 25,
937 | "recipient": "Medina",
938 | "origin": "Missouri",
939 | "destination": "Hong Kong",
940 | "ratepermin": 0.09,
941 | "costs": 2.25,
942 | "origincontinent": "United States",
943 | "destinationcontinent": "Asia",
944 | "callergender": "Male",
945 | "recipientgender": "Female"
946 | },
947 | {
948 | "caller": "Leni",
949 | "start": "27.09.2013 14:13:24",
950 | "end": "27.09.2013 14:44:24",
951 | "duration": 31,
952 | "recipient": "Medina",
953 | "origin": "Missouri",
954 | "destination": "Hong Kong",
955 | "ratepermin": 0.09,
956 | "costs": 2.79,
957 | "origincontinent": "United States",
958 | "destinationcontinent": "Asia",
959 | "callergender": "Male",
960 | "recipientgender": "Female"
961 | },
962 | {
963 | "caller": "Leni",
964 | "start": "27.09.2013 14:15:51",
965 | "end": "27.09.2013 14:25:51",
966 | "duration": 10,
967 | "recipient": "Diana",
968 | "origin": "Missouri",
969 | "destination": "Falkland Islands",
970 | "ratepermin": 0.45,
971 | "costs": 4.5,
972 | "origincontinent": "United States",
973 | "destinationcontinent": "South America",
974 | "callergender": "Male",
975 | "recipientgender": "Female"
976 | },
977 | {
978 | "caller": "Clara",
979 | "start": "27.09.2013 15:16:24",
980 | "end": "27.09.2013 17:00:24",
981 | "duration": 104,
982 | "recipient": "Jordan",
983 | "origin": "Maryland",
984 | "destination": "Bosnia",
985 | "ratepermin": 0.34,
986 | "costs": 35.36,
987 | "origincontinent": "United States",
988 | "destinationcontinent": "Europe",
989 | "callergender": "Female",
990 | "recipientgender": "Male"
991 | },
992 | {
993 | "caller": "Jonathan",
994 | "start": "27.09.2013 15:16:24",
995 | "end": "27.09.2013 15:36:24",
996 | "duration": 20,
997 | "recipient": "Diana",
998 | "origin": "West Virginia",
999 | "destination": "Falkland Islands",
1000 | "ratepermin": 0.45,
1001 | "costs": 9,
1002 | "origincontinent": "United States",
1003 | "destinationcontinent": "South America",
1004 | "callergender": "Male",
1005 | "recipientgender": "Female"
1006 | },
1007 | {
1008 | "caller": "Ben",
1009 | "start": "27.09.2013 16:45:27",
1010 | "end": "27.09.2013 17:16:27",
1011 | "duration": 31,
1012 | "recipient": "Diana",
1013 | "origin": "South Dakota",
1014 | "destination": "Falkland Islands",
1015 | "ratepermin": 0.45,
1016 | "costs": 13.95,
1017 | "origincontinent": "United States",
1018 | "destinationcontinent": "South America",
1019 | "callergender": "Male",
1020 | "recipientgender": "Female"
1021 | },
1022 | {
1023 | "caller": "Leni",
1024 | "start": "27.09.2013 16:56:35",
1025 | "end": "27.09.2013 17:04:35",
1026 | "duration": 8,
1027 | "recipient": "Medina",
1028 | "origin": "Missouri",
1029 | "destination": "Hong Kong",
1030 | "ratepermin": 0.09,
1031 | "costs": 0.72,
1032 | "origincontinent": "United States",
1033 | "destinationcontinent": "Asia",
1034 | "callergender": "Male",
1035 | "recipientgender": "Female"
1036 | },
1037 | {
1038 | "caller": "Leni",
1039 | "start": "27.09.2013 16:57:51",
1040 | "end": "27.09.2013 17:04:51",
1041 | "duration": 7,
1042 | "recipient": "Medina",
1043 | "origin": "Missouri",
1044 | "destination": "Hong Kong",
1045 | "ratepermin": 0.09,
1046 | "costs": 0.63,
1047 | "origincontinent": "United States",
1048 | "destinationcontinent": "Asia",
1049 | "callergender": "Male",
1050 | "recipientgender": "Female"
1051 | },
1052 | {
1053 | "caller": "Leni",
1054 | "start": "27.09.2013 17:17:28",
1055 | "end": "27.09.2013 18:54:28",
1056 | "duration": 97,
1057 | "recipient": "Medina",
1058 | "origin": "Missouri",
1059 | "destination": "Hong Kong",
1060 | "ratepermin": 0.09,
1061 | "costs": 8.73,
1062 | "origincontinent": "United States",
1063 | "destinationcontinent": "Asia",
1064 | "callergender": "Male",
1065 | "recipientgender": "Female"
1066 | },
1067 | {
1068 | "caller": "Clara",
1069 | "start": "27.09.2013 17:27:58",
1070 | "end": "27.09.2013 18:07:58",
1071 | "duration": 40,
1072 | "recipient": "Jordan",
1073 | "origin": "Maryland",
1074 | "destination": "Bosnia",
1075 | "ratepermin": 0.34,
1076 | "costs": 13.6,
1077 | "origincontinent": "United States",
1078 | "destinationcontinent": "Europe",
1079 | "callergender": "Female",
1080 | "recipientgender": "Male"
1081 | },
1082 | {
1083 | "caller": "Jonathan",
1084 | "start": "27.09.2013 17:27:58",
1085 | "end": "27.09.2013 19:18:58",
1086 | "duration": 111,
1087 | "recipient": "Diana",
1088 | "origin": "West Virginia",
1089 | "destination": "Falkland Islands",
1090 | "ratepermin": 0.45,
1091 | "costs": 49.95,
1092 | "origincontinent": "United States",
1093 | "destinationcontinent": "South America",
1094 | "callergender": "Male",
1095 | "recipientgender": "Female"
1096 | },
1097 | {
1098 | "caller": "Leni",
1099 | "start": "27.09.2013 17:48:25",
1100 | "end": "27.09.2013 18:18:25",
1101 | "duration": 30,
1102 | "recipient": "Diana",
1103 | "origin": "Missouri",
1104 | "destination": "Falkland Islands",
1105 | "ratepermin": 0.45,
1106 | "costs": 13.5,
1107 | "origincontinent": "United States",
1108 | "destinationcontinent": "South America",
1109 | "callergender": "Male",
1110 | "recipientgender": "Female"
1111 | },
1112 | {
1113 | "caller": "Ben",
1114 | "start": "27.09.2013 18:59:24",
1115 | "end": "27.09.2013 19:57:24",
1116 | "duration": 58,
1117 | "recipient": "Adelina",
1118 | "origin": "South Dakota",
1119 | "destination": "Mauritania",
1120 | "ratepermin": 0.98,
1121 | "costs": 56.84,
1122 | "origincontinent": "United States",
1123 | "destinationcontinent": "Africa",
1124 | "callergender": "Male",
1125 | "recipientgender": "Female"
1126 | },
1127 | {
1128 | "caller": "Leni",
1129 | "start": "27.09.2013 19:01:19",
1130 | "end": "27.09.2013 19:07:19",
1131 | "duration": 6,
1132 | "recipient": "Medina",
1133 | "origin": "Missouri",
1134 | "destination": "Hong Kong",
1135 | "ratepermin": 0.09,
1136 | "costs": 0.54,
1137 | "origincontinent": "United States",
1138 | "destinationcontinent": "Asia",
1139 | "callergender": "Male",
1140 | "recipientgender": "Female"
1141 | },
1142 | {
1143 | "caller": "Leni",
1144 | "start": "27.09.2013 19:01:31",
1145 | "end": "27.09.2013 19:34:31",
1146 | "duration": 33,
1147 | "recipient": "Medina",
1148 | "origin": "Missouri",
1149 | "destination": "Hong Kong",
1150 | "ratepermin": 0.09,
1151 | "costs": 2.97,
1152 | "origincontinent": "United States",
1153 | "destinationcontinent": "Asia",
1154 | "callergender": "Male",
1155 | "recipientgender": "Female"
1156 | },
1157 | {
1158 | "caller": "Max",
1159 | "start": "27.09.2013 19:14:35",
1160 | "end": "27.09.2013 19:52:35",
1161 | "duration": 38,
1162 | "recipient": "Diana",
1163 | "origin": "Ohio",
1164 | "destination": "Falkland Islands",
1165 | "ratepermin": 0.45,
1166 | "costs": 17.1,
1167 | "origincontinent": "United States",
1168 | "destinationcontinent": "South America",
1169 | "callergender": "Male",
1170 | "recipientgender": "Female"
1171 | },
1172 | {
1173 | "caller": "Ida",
1174 | "start": "27.09.2013 19:51:48",
1175 | "end": "27.09.2013 20:36:48",
1176 | "duration": 45,
1177 | "recipient": "Soraya",
1178 | "origin": "Maine",
1179 | "destination": "Algeria",
1180 | "ratepermin": 0.48,
1181 | "costs": 21.6,
1182 | "origincontinent": "United States",
1183 | "destinationcontinent": "Africa",
1184 | "callergender": "Female",
1185 | "recipientgender": "Female"
1186 | },
1187 | {
1188 | "caller": "Ida",
1189 | "start": "27.09.2013 19:51:48",
1190 | "end": "27.09.2013 21:04:48",
1191 | "duration": 73,
1192 | "recipient": "Soraya",
1193 | "origin": "Maine",
1194 | "destination": "Algeria",
1195 | "ratepermin": 0.48,
1196 | "costs": 35.04,
1197 | "origincontinent": "United States",
1198 | "destinationcontinent": "Africa",
1199 | "callergender": "Female",
1200 | "recipientgender": "Female"
1201 | },
1202 | {
1203 | "caller": "Ida",
1204 | "start": "27.09.2013 20:18:09",
1205 | "end": "27.09.2013 20:21:09",
1206 | "duration": 3,
1207 | "recipient": "Elaine",
1208 | "origin": "Maine",
1209 | "destination": "Laos",
1210 | "ratepermin": 2.39,
1211 | "costs": 7.17,
1212 | "origincontinent": "United States",
1213 | "destinationcontinent": "Asia",
1214 | "callergender": "Female",
1215 | "recipientgender": "Female"
1216 | },
1217 | {
1218 | "caller": "Ida",
1219 | "start": "27.09.2013 20:18:09",
1220 | "end": "27.09.2013 21:30:09",
1221 | "duration": 72,
1222 | "recipient": "Elaine",
1223 | "origin": "Maine",
1224 | "destination": "Laos",
1225 | "ratepermin": 2.39,
1226 | "costs": 172.08,
1227 | "origincontinent": "United States",
1228 | "destinationcontinent": "Asia",
1229 | "callergender": "Female",
1230 | "recipientgender": "Female"
1231 | },
1232 | {
1233 | "caller": "Emma",
1234 | "start": "27.09.2013 20:37:51",
1235 | "end": "27.09.2013 21:20:51",
1236 | "duration": 43,
1237 | "recipient": "Diana",
1238 | "origin": "New Jersey",
1239 | "destination": "Falkland Islands",
1240 | "ratepermin": 0.45,
1241 | "costs": 19.35,
1242 | "origincontinent": "United States",
1243 | "destinationcontinent": "South America",
1244 | "callergender": "Female",
1245 | "recipientgender": "Female"
1246 | },
1247 | {
1248 | "caller": "Emma",
1249 | "start": "27.09.2013 21:20:14",
1250 | "end": "27.09.2013 23:15:14",
1251 | "duration": 115,
1252 | "recipient": "Diana",
1253 | "origin": "New Jersey",
1254 | "destination": "Falkland Islands",
1255 | "ratepermin": 0.45,
1256 | "costs": 51.75,
1257 | "origincontinent": "United States",
1258 | "destinationcontinent": "South America",
1259 | "callergender": "Female",
1260 | "recipientgender": "Female"
1261 | },
1262 | {
1263 | "caller": "Leni",
1264 | "start": "27.09.2013 21:22:15",
1265 | "end": "27.09.2013 21:29:15",
1266 | "duration": 7,
1267 | "recipient": "Medina",
1268 | "origin": "Missouri",
1269 | "destination": "Hong Kong",
1270 | "ratepermin": 0.09,
1271 | "costs": 0.63,
1272 | "origincontinent": "United States",
1273 | "destinationcontinent": "Asia",
1274 | "callergender": "Male",
1275 | "recipientgender": "Female"
1276 | },
1277 | {
1278 | "caller": "Leni",
1279 | "start": "27.09.2013 21:22:29",
1280 | "end": "27.09.2013 21:27:29",
1281 | "duration": 5,
1282 | "recipient": "Medina",
1283 | "origin": "Missouri",
1284 | "destination": "Hong Kong",
1285 | "ratepermin": 0.09,
1286 | "costs": 0.45,
1287 | "origincontinent": "United States",
1288 | "destinationcontinent": "Asia",
1289 | "callergender": "Male",
1290 | "recipientgender": "Female"
1291 | },
1292 | {
1293 | "caller": "Clara",
1294 | "start": "27.09.2013 21:48:50",
1295 | "end": "27.09.2013 21:57:50",
1296 | "duration": 9,
1297 | "recipient": "Jordan",
1298 | "origin": "Maryland",
1299 | "destination": "Bosnia",
1300 | "ratepermin": 0.34,
1301 | "costs": 3.06,
1302 | "origincontinent": "United States",
1303 | "destinationcontinent": "Europe",
1304 | "callergender": "Female",
1305 | "recipientgender": "Male"
1306 | },
1307 | {
1308 | "caller": "Jonathan",
1309 | "start": "27.09.2013 21:48:50",
1310 | "end": "27.09.2013 22:23:50",
1311 | "duration": 35,
1312 | "recipient": "Diana",
1313 | "origin": "West Virginia",
1314 | "destination": "Falkland Islands",
1315 | "ratepermin": 0.45,
1316 | "costs": 15.75,
1317 | "origincontinent": "United States",
1318 | "destinationcontinent": "South America",
1319 | "callergender": "Male",
1320 | "recipientgender": "Female"
1321 | },
1322 | {
1323 | "caller": "Leni",
1324 | "start": "27.09.2013 23:47:49",
1325 | "end": "28.09.2013 00:16:49",
1326 | "duration": 29,
1327 | "recipient": "Anthony",
1328 | "origin": "Missouri",
1329 | "destination": "Portugal",
1330 | "ratepermin": 0.09,
1331 | "costs": 2.61,
1332 | "origincontinent": "United States",
1333 | "destinationcontinent": "Europe",
1334 | "callergender": "Male",
1335 | "recipientgender": "Male"
1336 | }
1337 | ]
--------------------------------------------------------------------------------
/resources/simple-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmeisen/js-gantt/6aff3eb02aae43ded30b11ee8e000e383dbd419d/resources/simple-example.png
--------------------------------------------------------------------------------
/server/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 4.0.0
5 |
6 | unnecessary
7 | unnecessary
8 | 1.0.0
9 | Sample Server
10 | POM for the Sample Server Providing some Data
11 | zip
12 |
13 |
14 |
15 | net.meisen.general
16 | net-meisen-general-server-http-listener
17 | TRUNK-SNAPSHOT
18 |
19 |
20 |
--------------------------------------------------------------------------------
/server/resources/data.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Following status can be used (full list see org.apache.http.HttpStatus):
3 | * - org.apache.http.HttpStatus.SC_UNAUTHORIZED
4 | * - org.apache.http.HttpStatus.SC_OK
5 | * - org.apache.http.HttpStatus.SC_METHOD_NOT_ALLOWED
6 | */
7 | var status = Packages.org.apache.http.HttpStatus.SC_OK;
8 |
9 | var pad = function(n){
10 | return n < 10 ? '0' + n : n
11 | };
12 |
13 | var readFile = function(file, encoding, separator, limit) {
14 | var records = [];
15 | var names = [];
16 |
17 | var reader = null;
18 | try {
19 | reader = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding))
20 | var line = null;
21 | var first = true;
22 | do {
23 | do {
24 | line = reader.readLine();
25 | } while (line != null && line.trim().isEmpty());
26 |
27 | // stop if we got enough or if no more lines are available
28 | if (line == null || (!first && limit <= records.length)) {
29 | break;
30 | }
31 |
32 | // parse the line
33 | var data = line.split(separator);
34 | var record = [];
35 | for (var i = 0; i < data.length; i++) {
36 | var value;
37 |
38 | var date = Packages.net.meisen.general.genmisc.types.Dates.isDate(data[i], Packages.net.meisen.general.genmisc.types.Dates.GENERAL_TIMEZONE);
39 | if (date != null) {
40 | value = date;
41 | } else {
42 | value = '' + data[i];
43 |
44 | if(value.match(/^\d+$/)){
45 | value = parseInt(value);
46 | } else if (value.match(/^\d+\.\d+$/)) {
47 | value = parseFloat(value);
48 | }
49 | }
50 | record.push(value);
51 | }
52 |
53 | if (first) {
54 | names = record;
55 | first = false;
56 | } else {
57 | records.push(record);
58 | }
59 | } while (line != null);
60 | } catch (e) {
61 | names = [];
62 | records = [];
63 | } finally {
64 | if (reader != null) {
65 | reader.close();
66 | }
67 | }
68 |
69 | return { names: names, data: records };
70 | };
71 |
72 | var transformToJson = function(object) {
73 | var json;
74 |
75 | if (object == null) {
76 | json = 'null';
77 | } else if (typeof object === 'boolean') {
78 | json = '' + object;
79 | } else if (typeof object === 'number') {
80 | json = '' + object;
81 | } else if (typeof object === 'string') {
82 | json = '"' + object + '"';
83 | } else if (typeof object === 'object' && (object instanceof Date)) {
84 | json = '"' + object.getUTCFullYear() + '-' +
85 | pad(object.getUTCMonth() + 1) + '-' +
86 | pad(object.getUTCDate())+'T' +
87 | pad(object.getUTCHours()) + ':' +
88 | pad(object.getUTCMinutes()) + ':' +
89 | pad(object.getUTCSeconds()) + 'Z' + '"';
90 | } else if (object instanceof java.util.Date) {
91 | json = transformToJson(new Date(object.getTime()));
92 | } else if (typeof object === 'object' && (object instanceof Array)) {
93 | var separator = '';
94 |
95 | json = '[';
96 | for(var key in object) {
97 | json += separator + transformToJson(object[key]);
98 | separator = ',';
99 | }
100 | json += ']';
101 | } else if (typeof object === 'object') {
102 | var separator = '';
103 |
104 | json = '{';
105 | for(var key in object) {
106 | json += separator + '"' + key + '":';
107 | json += transformToJson(object[key]);
108 | separator = ',';
109 | }
110 | json += '}';
111 | } else {
112 | json = object;
113 | }
114 |
115 | return json;
116 | };
117 |
118 | var parameters = Packages.net.meisen.general.server.http.listener.util.RequestHandlingUtilities.parseParameter(request);
119 |
120 | // determine the data to be send
121 | var type = parameters.get('type');
122 |
123 | var result;
124 | if ('file'.equals(type)) {
125 | var file = parameters.get('file');
126 | var encoding = parameters.get('encoding');
127 | var separator = parameters.get('separator');
128 | var limit = parameters.get('limit');
129 | encoding = encoding == null ? 'UTF8' : encoding;
130 | separator = separator == null ? ',' : separator;
131 | limit = limit == null ? 100 : limit;
132 |
133 | result = readFile(file, encoding, separator, limit);
134 | } else if ('fail'.equals(type)) {
135 | status = Packages.org.apache.http.HttpStatus.SC_UNAUTHORIZED;
136 | } else {
137 | var names = ['Start', 'End', 'Integer', 'String'];
138 | var data = [];
139 |
140 | data.push([ new Date(Date.UTC(1981, 0, 20, 8, 0, 0)), new Date(Date.UTC(1981,0, 20, 8, 7, 0)), 500, 'Test' ]);
141 |
142 | result = { names: names, data: data };
143 | }
144 |
145 | // create the answer
146 | var entity = new Packages.org.apache.http.entity.StringEntity(transformToJson(result), Packages.org.apache.http.entity.ContentType.create("text/html", "UTF-8"));
147 | entity.setContentEncoding('UTF-8');
148 | response.setEntity(entity);
149 | response.setHeader("Access-Control-Allow-Origin", "*");
150 | response.setHeader("Access-Control-Allow-Methods", "*");
151 | response.setHeader("Access-Control-Allow-Headers", "Content-Type");
152 |
153 | // set the status
154 | response.setStatusCode(status);
--------------------------------------------------------------------------------
/server/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | # Root logger option
2 | log4j.rootLogger=TRACE, stdout
3 | # Redirect log messages to console
4 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender
5 | log4j.appender.stdout.Target=System.out
6 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
7 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
--------------------------------------------------------------------------------
/server/resources/sample.csv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmeisen/js-gantt/6aff3eb02aae43ded30b11ee8e000e383dbd419d/server/resources/sample.csv
--------------------------------------------------------------------------------
/server/resources/serverSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 | net.meisen.general.server.http.listener.servlets.ScriptedServlet
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/ExampleApp.js:
--------------------------------------------------------------------------------
1 | // define the baseUrl
2 | requirejs.config({
3 |
4 | // define the baseUrl defined by the processenabler
5 | baseUrl: 'scripts'
6 | });
7 |
8 | // now start the entry-point
9 | require(['jquery', 'net/meisen/general/date/DateLibrary', 'net/meisen/ui/gantt/GanttChart'], function ($, datelib) {
10 | var chartFixedData1 = $("#chartFixedData1").ganttChart({
11 | data: {
12 | names: ['start', 'end', 'value', 'number'],
13 | records: [
14 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 2, 0, 0), '2 Hours', 120],
15 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 4, 0, 0), '4 Hours', 240],
16 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 6, 0, 0), '6 Hours', 360],
17 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 8, 0, 0), '8 Hours', 480],
18 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 10, 0, 0), '10 Hours', 600],
19 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 12, 0, 0), '12 Hours', 720],
20 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 23, 59, 0), 'All Day', 1439]
21 | ],
22 | mapper: {
23 | startname: 'start',
24 | endname: 'end',
25 | tooltip: ['number', 'start']
26 | },
27 | timeaxis: {
28 | end: datelib.modifyUTC(datelib.createUTC(null, null, null, 23, 59, 0), 1, 'd'),
29 | granularity: 'mi'
30 | }
31 | },
32 | illustrator: {
33 | config: {
34 | axis: {
35 | tickInterval: 120
36 | },
37 | view: {
38 | showGrid: false,
39 | showPositionMarker: false,
40 | theme: {
41 | intervalColor: '#f7a35c',
42 | intervalHeight: 40,
43 | intervalBorderSize: 1
44 | }
45 | }
46 | }
47 | }
48 | });
49 |
50 | var rndSize = 900;
51 | chartFixedData1.ganttChart().resize(rndSize, 300);
52 | setInterval(function () {
53 | rndSize = Math.max(700, Math.random() * 1200);
54 | }, 5000);
55 | $(window).resize(function () {
56 | chartFixedData1.ganttChart().resize(rndSize, 300);
57 | });
58 |
59 | var chartFixedData2 = $("#chartFixedData2").ganttChart({
60 | data: {
61 | names: ['start', 'end', 'value', 'number'],
62 | records: [
63 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 2, 0, 0), '2 Hours', 120],
64 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 4, 0, 0), '4 Hours', 240],
65 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 6, 0, 0), '6 Hours', 360],
66 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 8, 0, 0), '8 Hours', 480],
67 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 10, 0, 0), '10 Hours', 600],
68 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 12, 0, 0), '12 Hours', 720],
69 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 23, 59, 0), 'All Day', 1439]
70 | ],
71 | mapper: {
72 | startname: 'start',
73 | endname: 'end',
74 | tooltip: ['number', 'start', 'end']
75 | },
76 | timeaxis: {
77 | end: datelib.modifyUTC(datelib.createUTC(null, null, null, 23, 59, 0), 1, 'd'),
78 | granularity: 'mi'
79 | }
80 | },
81 | illustrator: {
82 | config: {
83 | axis: {
84 | tickInterval: 120
85 | },
86 | view: {
87 | showGrid: false,
88 | showPositionMarker: false,
89 | tooltip: function (interval) {
90 | return 'Some marks: ?ßgGÁ\nSome marks: ?ßgGÁ\n{1|number|#.00}\n{2|date|HH:mm:ss} - {3|date|HH:mm:ss}\n' + interval.get('_raw')[2];
91 | },
92 | theme: {
93 | intervalColor: '#f7a35c',
94 | intervalHeight: 40,
95 | intervalBorderSize: 1
96 | }
97 | }
98 | }
99 | }
100 | });
101 | chartFixedData2.resize(1200, 300);
102 |
103 | var chartFixedData3 = $("#chartFixedData3").ganttChart({
104 | data: {
105 | names: ['start', 'end', 'value', 'number'],
106 | records: [
107 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 2, 0, 0), '2 Hours', 120],
108 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 4, 0, 0), '4 Hours', 240],
109 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 6, 0, 0), '6 Hours', 360],
110 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 8, 0, 0), '8 Hours', 480],
111 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 10, 0, 0), '10 Hours', 600],
112 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 12, 0, 0), '12 Hours', 720],
113 | [datelib.createUTC(null, null, null, 0, 0, 0), datelib.createUTC(null, null, null, 23, 59, 0), 'All Day', 1439]
114 | ],
115 | mapper: {
116 | startname: 'start',
117 | endname: 'end',
118 | tooltip: ['number', 'start', 'end']
119 | },
120 | timeaxis: {
121 | end: datelib.modifyUTC(datelib.createUTC(null, null, null, 23, 59, 0), 1, 'd'),
122 | granularity: 'mi'
123 | }
124 | },
125 | illustrator: {
126 | config: {
127 | axis: {
128 | tickInterval: 120
129 | },
130 | view: {
131 | showGrid: false,
132 | showPositionMarker: false,
133 | tooltip: '{1} \nStart: {2|date|HH:mm:ss}, End: {3|date|HH:mm:ss}',
134 | theme: {
135 | intervalColor: '#f7a35c',
136 | intervalHeight: 40,
137 | intervalBorderSize: 1,
138 | tooltipSize: 25
139 | }
140 | }
141 | }
142 | }
143 | });
144 | chartFixedData3.resize(1200, 300);
145 |
146 | var chartLoadedData = $("#chartLoadedData").ganttChart({
147 | data: {
148 | url: 'http://localhost:19999/data?type=file&file=resources/sample.csv&separator=;&limit=10000',
149 | postProcessor: function (data) {
150 | for (var i = 0; i < data.data.length; i++) {
151 | var record = data.data[i];
152 | record[1] = datelib.parseISO8601(record[1]);
153 | record[2] = datelib.parseISO8601(record[2]);
154 | }
155 |
156 | return {names: data.names, records: data.data};
157 | },
158 | mapper: {
159 | startname: 'start',
160 | endname: 'end',
161 | group: ['callergender', 'recipientgender'],
162 | label: ['callergender', 'start', 'caller'],
163 | tooltip: ['caller', 'recipient', 'start', 'end']
164 | },
165 | timeaxis: {
166 | start: datelib.createUTC(2013, 9, 1),
167 | end: datelib.createUTC(2014, 12, 31, 23, 59, 0),
168 | granularity: 'mi'
169 | }
170 | },
171 | illustrator: {
172 | config: {
173 | axis: {
174 | viewSize: 1440,
175 | tickInterval: 360
176 | },
177 | view: {
178 | showBorder: false,
179 | tooltip: '{1} called {2}\non the {3|date|dd.MM.yyyy} ({3|date|HH:mm} - {4|date|HH:mm})',
180 | coloring: {
181 | groupMapping: {
182 | '["Female","Female"]': '#7cb5ec',
183 | '["Male","Female"]': '#434348',
184 | '["Female","Male"]': '#f7a35c',
185 | '["Male","Male"]': '#90ed7d'
186 | }
187 | },
188 | theme: {
189 | backgroundColor: '#EEEEEE',
190 | intervalColor: '#444444',
191 | intervalHeight: 10,
192 | intervalBorderSize: 0
193 | }
194 | }
195 | }
196 | }
197 | });
198 | chartLoadedData.resize(800, 300);
199 |
200 | var chartFailData = $("#chartFailData").ganttChart({
201 | data: {
202 | url: 'http://localhost:19999/data?type=fail',
203 | }
204 | });
205 | chartFailData.resize(800, 300);
206 |
207 | var chartEmptyData1 = $("#chartEmptyData1").ganttChart({
208 | data: {
209 | url: 'http://localhost:19999/data?type=file&file=resources/sample.csv&separator=;&limit=0',
210 | postProcessor: function (data) {
211 | return {names: data.names, records: data.data};
212 | }
213 | }
214 | });
215 | chartEmptyData1.resize(800, 300);
216 |
217 | var chartEmptyData2 = $("#chartEmptyData2").ganttChart({
218 | data: {
219 | names: ['start', 'end'],
220 | mapper: {
221 | startname: 'start',
222 | endname: 'end'
223 | }
224 | }
225 | });
226 | chartEmptyData2.ganttChart().resize(800, 300);
227 | });
--------------------------------------------------------------------------------
/src/Optimizer.js:
--------------------------------------------------------------------------------
1 | // define the baseUrl
2 | requirejs.config({
3 | baseUrl: 'scripts',
4 |
5 | // see http://requirejs.org/docs/jquery.html
6 | map: {
7 | '*': { 'jquery': 'jquery-private' },
8 | 'jquery-private': { 'jquery': 'jquery' }
9 | }
10 | });
11 |
12 | // make sure the different instances are loaded
13 | require(['net/meisen/ui/gantt/GanttChart', 'jquery-private'
14 | ], function(GanttChart) {});
15 |
16 | // actually retrieve the loaded instances
17 | var instance = {
18 | GanttChart: require('net/meisen/ui/gantt/GanttChart')
19 | };
20 |
21 | // we are using the system within a browser
22 | if (typeof window !== 'undefined') {
23 | for (var property in instance) {
24 |
25 | if (instance.hasOwnProperty(property)) {
26 | window[property] = instance[property];
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/jquery-private.js:
--------------------------------------------------------------------------------
1 | define(['jquery'], function ($) {
2 | return $.noConflict(true);
3 | });
--------------------------------------------------------------------------------
/src/net/meisen/ui/gantt/GanttChart.js:
--------------------------------------------------------------------------------
1 | define(['jquery',
2 | 'net/meisen/general/date/DateLibrary',
3 | 'net/meisen/ui/gantt/svg/SvgIllustrator',
4 | 'net/meisen/ui/svglibrary/SvgLibrary',
5 | 'net/meisen/ui/svglibrary/required-svg/LoadingCircles'], function ($,
6 | datelib,
7 | SvgIllustrator,
8 | svglib,
9 | loadingImage) {
10 |
11 | /*
12 | * Hidden utilities, only used within the GanttChart.
13 | */
14 | var utilities = {
15 | generateMap: function (mapper, names) {
16 | var group = this.validateArray(mapper.group);
17 | var label = this.validateArray(mapper.label);
18 | var tooltip = this.validateArray(mapper.tooltip);
19 |
20 | var mappedGroup = this.createArray(group.length, -1);
21 | var mappedLabel = this.createArray(label.length, -1);
22 | var mappedTooltip = this.createArray(tooltip.length, -1);
23 |
24 | // create the initial map
25 | var map = {
26 | start: -1,
27 | end: -1,
28 | group: mappedGroup,
29 | label: mappedLabel,
30 | tooltip: mappedTooltip,
31 |
32 | val: function (name, record) {
33 | if (record == null || !$.isArray(record) || record.length == 0) {
34 | return;
35 | }
36 |
37 | for (var i = 0; i < names.length; i++) {
38 | if (names[i] === name) {
39 | return record[i];
40 | }
41 | }
42 | },
43 |
44 | get: function (type, record) {
45 |
46 | // get the array
47 | var arr = null;
48 | if (type == 'group') {
49 | arr = this.group;
50 | } else if (type == 'label') {
51 | arr = this.label;
52 | } else if (type == 'tooltip') {
53 | arr = this.tooltip;
54 | } else {
55 | arr = null;
56 | }
57 |
58 | // make sure we have something
59 | if (arr == null || arr.length == 0) {
60 | return [];
61 | } else {
62 | var len = arr.length;
63 | var vals = [];
64 | for (var i = 0; i < len; i++) {
65 | var val = record[arr[i]];
66 | vals.push(val);
67 | }
68 | return vals;
69 | }
70 | }
71 | };
72 |
73 | // get the arrays to look through
74 | var arrays = [
75 | [group, mappedGroup],
76 | [label, mappedLabel],
77 | [tooltip, mappedTooltip]
78 | ];
79 |
80 | // look-up the names and the defined maps
81 | $.each(names, function (idx, val) {
82 | if (val == mapper.startname) {
83 | map.start = idx;
84 | } else if (val == mapper.endname) {
85 | map.end = idx;
86 | }
87 |
88 | $.each(arrays, function (nr, pair) {
89 | var arrayIdx = $.inArray(val, pair[0]);
90 | if (arrayIdx != -1) {
91 | pair[1][arrayIdx] = idx;
92 | }
93 | });
94 | });
95 |
96 | // validate the result, no -1 present anymore
97 | var validateValue = function (value) {
98 | if (!$.isNumeric(value) || parseInt(value) !== value || value < 0) {
99 | throw Error('Mapping failed (reason: value="' + value + '", map="' + JSON.stringify(map) + '", names="' + JSON.stringify(names) + '")');
100 | }
101 | };
102 | $.each(map, function (key, value) {
103 |
104 | if ($.isFunction(value)) {
105 | // nothing to do
106 | } else if ($.isArray(value)) {
107 | $.each(value, function (idx, val) {
108 | validateValue(val);
109 | });
110 | } else {
111 | validateValue(value);
112 | }
113 | });
114 |
115 | return map;
116 | },
117 |
118 | createArray: function (length, value) {
119 | var res = [];
120 |
121 | for (var i = 0; i < length; i++) {
122 | res[i] = value;
123 | }
124 |
125 | return res;
126 | },
127 |
128 | validateArray: function (array) {
129 | var res;
130 |
131 | if (!$.isArray(array)) {
132 | res = [];
133 | res.push(array);
134 | } else {
135 | res = array;
136 | }
137 |
138 | return res;
139 | },
140 |
141 | initTimeaxis: function (timeaxis, map, records) {
142 | var start = timeaxis.start;
143 | var end = timeaxis.end;
144 |
145 | var needStart = start == null || typeof(start) == 'undefined';
146 | var needEnd = end == null || typeof(end) == 'undefined';
147 |
148 | if (needStart || needEnd) {
149 |
150 | // TODO! What if we have numbers
151 | // get the needed values
152 | if (records == null || typeof(records) == 'undefined' || !$.isArray(records) || records.length == 0 ||
153 | map == null || typeof(map) == 'undefined' || map.start == -1 || map.end == -1) {
154 | start = needStart ? datelib.createUTC() : start;
155 | end = needEnd ? datelib.createUTC(null, null, null, 23, 59, 0) : end;
156 | } else {
157 | var max = -1;
158 | var min = -1;
159 |
160 | $.each(records, function (idx, val) {
161 | var s = val[map.start].getTime();
162 | var e = val[map.end].getTime();
163 |
164 | if (needStart && $.isNumeric(s)) {
165 | min = min == -1 || min > s ? s : min;
166 | }
167 |
168 | if (needEnd && $.isNumeric(e)) {
169 | max = max == -1 || max < e ? e : max;
170 | }
171 | });
172 |
173 | // get the start if needed
174 | if (needStart) {
175 | if (min == -1 && max == -1) {
176 | start = datelib.createUTC();
177 | } else if (min == -1) {
178 | start = new Date(max);
179 | start = datelib.createUTC(start.getUTCFullYear(), start.getUTCMonth() + 1, start.getUTCDate());
180 | } else {
181 | start = new Date(min);
182 | start = datelib.createUTC(start.getUTCFullYear(), start.getUTCMonth() + 1, start.getUTCDate());
183 | }
184 | }
185 |
186 | // get the end if needed
187 | if (needEnd) {
188 | if (min == -1 && max == -1) {
189 | end = datelib.createUTC(null, null, null, 23, 59, 0);
190 | } else if (max == -1) {
191 | end = new Date(min);
192 | end = datelib.createUTC(end.getUTCFullYear(), end.getUTCMonth() + 1, end.getUTCDate(), 23, 59, 0);
193 | } else {
194 | end = new Date(max);
195 | end = datelib.createUTC(end.getUTCFullYear(), end.getUTCMonth() + 1, end.getUTCDate(), 23, 59, 0);
196 | }
197 | }
198 | }
199 |
200 | // set the new values
201 | return $.extend(true, timeaxis, {start: start, end: end});
202 | } else {
203 | return timeaxis;
204 | }
205 | }
206 | };
207 |
208 | /*
209 | * Default constructor...
210 | */
211 | GanttChart = function () {
212 | };
213 |
214 | /*
215 | * Extended prototype
216 | */
217 | GanttChart.prototype = {
218 | defaultCfg: {
219 |
220 | theme: {
221 | loadingBackgroundColor: '#CCCCCC',
222 | loadingBackgroundPosition: 'center center',
223 | loadingBackgroundRepeat: 'no-repeat',
224 |
225 | errorBackgroundColor: '#A30B1D'
226 | },
227 |
228 | illustrator: {
229 | factory: function () {
230 | return new SvgIllustrator()
231 | },
232 | config: {}
233 | },
234 |
235 | position: 'center',
236 |
237 | throwException: false,
238 |
239 | data: {
240 | url: null,
241 | loader: null,
242 | postProcessor: function (data) {
243 | if (!$.isArray(data.names) || !$.isArray(data.records)) {
244 | return null;
245 | } else {
246 | return data;
247 | }
248 | },
249 | mapper: {
250 | startname: 'start',
251 | endname: 'end',
252 | group: [],
253 | label: [],
254 | tooltip: []
255 | },
256 | names: [],
257 | records: [],
258 | timeaxis: {
259 | start: null,
260 | end: null,
261 | granularity: 'days'
262 | }
263 | }
264 | },
265 |
266 | init: function (selector, cfg) {
267 | this.opts = $.extend(true, {}, this.defaultCfg, cfg);
268 |
269 | selector = selector instanceof $ ? selector : $(selector);
270 | selector.children('.ganttchart').remove();
271 |
272 | this.container = $('
');
273 | this.container.addClass('ganttchart');
274 | this.container.css('overflow', 'hidden');
275 | this.container.css('position', 'relative');
276 |
277 | // set the positioning
278 | if (this.opts.position == 'center' || this.opts.position == 'left') {
279 | this.container.css('margin-right', 'auto');
280 | }
281 | if (this.opts.position == 'center' || this.opts.position == 'right') {
282 | this.container.css('margin-left', 'auto');
283 | }
284 | this.container.appendTo(selector);
285 |
286 | this.view = $('
');
287 | this.view.addClass('ganttview');
288 | this.view.css('position', 'absolute');
289 | this.view.css('overflow', 'hidden');
290 | this.view.css('zIndex', 0);
291 | this.view.appendTo(this.container);
292 |
293 | this.indicator = $('
');
294 | this.indicator.addClass('ganttloading');
295 | this.indicator.css('backgroundRepeat', this.opts.theme.loadingBackgroundRepeat);
296 | this.indicator.css('backgroundPosition', this.opts.theme.loadingBackgroundPosition);
297 | this.indicator.css('backgroundColor', this.opts.theme.loadingBackgroundColor);
298 | this.indicator.css('position', 'absolute');
299 | this.indicator.css('zIndex', 1000);
300 | svglib.setBackgroundImage(this.indicator, loadingImage);
301 | this.indicator.appendTo(this.container);
302 |
303 | this.error = $('
');
304 | this.error.addClass('gantterror');
305 | this.error.css('backgroundColor', this.opts.theme.errorBackgroundColor);
306 | this.error.css('position', 'absolute');
307 | this.error.css('zIndex', '500');
308 | this.error.appendTo(this.container);
309 |
310 | var _ref = this;
311 | $(this.view).on('load', function () {
312 | _ref.mask();
313 | }).on('renderStart', function () {
314 | _ref.mask();
315 | }).on('renderEnd', function () {
316 | _ref.unmask();
317 | }).on('error', function (event, data) {
318 | _ref.showError(data);
319 | }).on('changeTimeaxis', function (event, data) {
320 | _ref.changeTimeaxis(data.start, data.end, data.granularity, data.force);
321 | });
322 |
323 | // initialize the illustrator
324 | this.illustrator = this.opts.illustrator.factory();
325 | this.illustrator.init(this.view, this.opts.illustrator.config);
326 | this.illustrator.on('finishedLayouting', function () {
327 | _ref.view.trigger('renderEnd');
328 | });
329 |
330 | this.load();
331 | },
332 |
333 | mask: function () {
334 | if (this.masking != 'loading') {
335 | this.masking = 'loading';
336 |
337 | this.error.hide();
338 | this.indicator.show();
339 | }
340 | },
341 |
342 | unmask: function () {
343 | if (this.masking != null) {
344 | this.indicator.hide();
345 | this.error.hide();
346 |
347 | this.masking = null;
348 | }
349 | },
350 |
351 | resize: function (width, height) {
352 | this.container.css('width', width);
353 | this.container.css('height', height);
354 |
355 | var innerWidth = this.container.width();
356 | var innerHeight = this.container.height();
357 | this.indicator.css('width', innerWidth);
358 | this.indicator.css('height', innerHeight);
359 | this.error.css('width', innerWidth);
360 | this.error.css('height', innerHeight);
361 |
362 | // fire the resize event
363 | this.view.trigger('sizechanged', {width: innerWidth, height: innerHeight});
364 | },
365 |
366 | changeTimeaxis: function (start, end, granularity, force) {
367 | if (force || (typeof(granularity) != 'undefined' && this.opts.timeaxis.granularity != granularity)
368 | || (typeof(start) != 'undefined' && this.opts.timeaxis.start != start)
369 | || (typeof(end) != 'undefined' && this.opts.timeaxis.end != end)) {
370 |
371 | this.opts.timeaxis.start = typeof(start) != 'undefined' ? start : this.opts.timeaxis.start;
372 | this.opts.timeaxis.end = typeof(end) != 'undefined' ? end : this.opts.timeaxis.end;
373 | this.opts.timeaxis.granularity = typeof(granularity) != 'undefined' ? granularity : this.opts.timeaxis.granularity;
374 |
375 | this.render();
376 | }
377 | },
378 |
379 | load: function () {
380 | var _ref = this;
381 | var postProcessor = function (data) {
382 | var postProcessedData = $.isFunction(_ref.opts.data.postProcessor) ? _ref.opts.data.postProcessor(data) : data;
383 | if (postProcessedData == null) {
384 | _ref.view.trigger('error', {error: null, message: 'Postprocessing of data failed', nr: '1001'});
385 | } else {
386 | _ref.render(postProcessedData);
387 | }
388 | };
389 |
390 | this.view.trigger('load');
391 |
392 | if ($.isFunction(this.opts.data.loader)) {
393 | this.opts.data.loader(function (data) {
394 | postProcessor(data);
395 | }, function (e) {
396 | _ref.view.trigger('error', {error: e, message: 'Unable to load data', nr: '1000'});
397 | });
398 | } else if (typeof(this.opts.data.url) == 'undefined' || this.opts.data.url == null) {
399 | this.render();
400 | } else {
401 | $.getJSON(this.opts.data.url).done(function (data) {
402 | postProcessor(data);
403 | }).fail(function (error) {
404 | _ref.view.trigger('error', {error: error, message: 'Unable to load data', nr: '1000'});
405 | });
406 | }
407 | },
408 |
409 | render: function (loaded) {
410 |
411 | // rendering will be started
412 | this.view.trigger('renderStart');
413 |
414 | // combine the loaded data and the data
415 | var data = $.extend(true, {}, this.opts.data, loaded);
416 |
417 | // make sure we have a valid time-axis
418 | var map;
419 | try {
420 | map = utilities.generateMap(data.mapper, data.names);
421 | data.timeaxis = utilities.initTimeaxis(data.timeaxis, map, data.records);
422 | } catch (error) {
423 | this.view.trigger('error', {error: error, message: 'Failed to initialize rendering', nr: '1002'});
424 | return;
425 | }
426 |
427 | // use a time-out to make sure that the mask is shown
428 | var _ref = this;
429 | window.setTimeout(function () {
430 |
431 | try {
432 | _ref.illustrator.draw(data.timeaxis, data.records, map);
433 | } catch (error) {
434 | _ref.view.trigger('error', {error: error, message: 'Failed to draw', nr: '1003'});
435 | }
436 | }, 50);
437 | },
438 |
439 | showError: function (data) {
440 | if (this.opts.throwException === true) {
441 | throw data.error;
442 | } else if (console && $.isFunction(console.error)) {
443 | console.error(data);
444 | }
445 |
446 | if (this.masking != 'error') {
447 | this.masking = 'error';
448 |
449 | this.indicator.hide();
450 | this.error.show();
451 | }
452 | },
453 |
454 | createSampleEnd: function (n) {
455 | n = typeof n !== n instanceof Date ? n : new Date();
456 | return new Date(Date.UTC(n.getUTCFullYear(), n.getUTCMonth(), n.getUTCDate(), 23, 59, 0));
457 | },
458 |
459 | createSampleData: function (n, amount) {
460 |
461 | // just some stuff to create some sample time-intervals
462 | n = typeof n !== n instanceof Date ? n : new Date();
463 | var createDate = function (h, m, s) {
464 | return new Date(Date.UTC(n.getUTCFullYear(), n.getUTCMonth(), n.getUTCDate(), h, m, s));
465 | };
466 | var createRecord = function (h1, m1, s1, h2, m2, s2, label) {
467 | return [
468 | createDate(h1, m1, s1), createDate(h2, m2, s2), label
469 | ];
470 | };
471 | var rnd = function (min, max) {
472 | return Math.floor(min + Math.random() * (max - min));
473 | };
474 |
475 | var result = [];
476 | amount = typeof amount === 'number' ? amount : 5;
477 | for (var i = 0; i < amount; i++) {
478 | var sH = rnd(1, 20);
479 | var sM = rnd(1, 59);
480 | var sS = rnd(1, 59);
481 | var eH = rnd(sH + 1, 23);
482 | var eM = rnd(1, 59);
483 | var eS = rnd(1, 59);
484 |
485 | var start = sH * 60 * 60 + sM * 60 + sS;
486 | var end = eH * 60 * 60 + eM * 60 + eS;
487 |
488 | var duration = end - start;
489 |
490 | result.push(createRecord(sH, sM, sS, eH, eM, eS, duration + ' seconds'));
491 | }
492 |
493 | return result;
494 | }
495 | };
496 |
497 | /*
498 | * Add the plug-in functionality for jQuery
499 | */
500 | GanttChart.bind = function ($) {
501 |
502 | $.fn.ganttChart = function () {
503 | var charts = [];
504 |
505 | // get the arguments
506 | var args = Array.prototype.slice.call(arguments);
507 |
508 | // get the arguments
509 | var config = args.length == 1 && typeof(args[0]) == 'object' ? args[0] : null;
510 |
511 | // create a chart for each element, if we have a configuration defined
512 | this.each(function () {
513 | var el = $(this);
514 | var chart = el.data('ganttchart');
515 |
516 | if (config != null && (typeof(chart) == 'undefined' || chart == null)) {
517 | chart = new GanttChart();
518 | chart.init(el, config);
519 | el.data('ganttchart', chart);
520 | charts.push(chart);
521 | } else if (config == null && typeof(chart) != 'undefined' && chart != null) {
522 | charts.push(chart);
523 | } else if (config == null && typeof(chart) != 'undefined' && chart != null) {
524 | chart.init(el, config);
525 | charts.push(chart);
526 | }
527 | });
528 |
529 | // make the resize function available
530 | this.resize = function (width, height) {
531 | $.each(charts, function () {
532 | //noinspection JSPotentiallyInvalidUsageOfThis
533 | this.resize(width, height);
534 | });
535 | };
536 |
537 | // make the changeGranularity function available
538 | this.changeTimeaxis = function (start, end, granularity, force) {
539 | $.each(charts, function () {
540 | //noinspection JSPotentiallyInvalidUsageOfThis
541 | this.changeTimeaxis(start, end, granularity, force);
542 | });
543 | };
544 |
545 | return this;
546 | };
547 | };
548 |
549 | // bind internally to the
550 | GanttChart.bind($);
551 |
552 | // some static stuff
553 | GanttChart.DateUtil = datelib;
554 |
555 | return GanttChart;
556 | });
--------------------------------------------------------------------------------
/src/net/meisen/ui/gantt/svg/Scrollbar.js:
--------------------------------------------------------------------------------
1 | define(['jquery', 'net/meisen/general/Utility'], function ($, Utility) {
2 |
3 | var utilities = {
4 | createArrow: function (size, direction, click, theme) {
5 | var arrow = $(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
6 | arrow.attr('class', 'gantt-scrollbar-arrow');
7 |
8 | el = $(document.createElementNS('http://www.w3.org/2000/svg', 'rect'));
9 | el.attr({'width': size, 'height': size, 'x': 0, y: 0, 'rx': 0, 'ry': 0});
10 | el.css({'fill': theme.buttonColor, 'stroke': theme.buttonColorBorder, 'stroke-width': 1});
11 | el.appendTo(arrow);
12 |
13 | el = $(document.createElementNS('http://www.w3.org/2000/svg', 'path'));
14 | if (direction == 'left') {
15 | el.attr({'d': 'M 8 4 L 8 10 5 7'});
16 | } else if (direction == 'right') {
17 | el.attr({'d': 'M 6 4 L 6 10 9 7'});
18 | } else if (direction == 'top') {
19 | el.attr({'d': 'M 4 8 L 10 8 7 5'});
20 | } else if (direction == 'bottom') {
21 | el.attr({'d': 'M 4 6 L 10 6 7 9'});
22 | }
23 | el.css({'fill': theme.arrowColor});
24 | el.appendTo(arrow);
25 |
26 | arrow.click(function () {
27 | if ($.isFunction(click)) {
28 | click({direction: direction});
29 | }
30 | });
31 |
32 | return arrow
33 | },
34 |
35 | createScroll: function (scrollbar) {
36 | var _ref = this;
37 |
38 | return function (event) {
39 |
40 | var status = {
41 | anchor: scrollbar.type == 'horizontal' ? event.pageX : event.pageY,
42 | scrollbar: scrollbar
43 | };
44 |
45 | var moveHandler = function (event) {
46 | var isHorizontal = scrollbar.type == 'horizontal';
47 | var pos = isHorizontal ? event.pageX : event.pageY;
48 | var diff = pos - status.anchor;
49 |
50 | if (diff == 0) {
51 | return;
52 | } else {
53 | var direction = diff < 0 ? (isHorizontal ? 'left' : 'top') : (isHorizontal ? 'right' : 'bottom');
54 | diff = status.scrollbar.pixelToCoord(Math.abs(diff));
55 | status.scrollbar.move(direction, diff);
56 | }
57 | status.anchor = pos;
58 | };
59 | var disableHandler = function (event) {
60 | $(window).unbind('mousemove', moveHandler);
61 | $(window).unbind('mouseup', disableHandler);
62 | };
63 |
64 | $(window).bind('mousemove', moveHandler);
65 | $(window).bind('mouseup', disableHandler);
66 | };
67 | }
68 | };
69 |
70 | /*
71 | * Default constructor...
72 | */
73 | Scrollbar = function (type) {
74 | this.type = typeof(type) == 'undefined' || type == null ? 'horizontal' : type;
75 | };
76 |
77 | /*
78 | * Extended prototype
79 | */
80 | Scrollbar.prototype = {
81 | bar: null,
82 | scrollarea: null,
83 | marker: null,
84 | leftArrow: null,
85 | rightArrow: null,
86 |
87 | size: {height: 0, width: 0},
88 |
89 | extent: 0,
90 | view: {
91 | position: 0,
92 | size: 0,
93 | total: 0
94 | },
95 |
96 | defaultCfg: {
97 | theme: {
98 | arrowSize: 14,
99 | scrollareaColor: '#EEEEEE',
100 | markerColor: '#BFC8D1',
101 | buttonColorBorder: '#666666',
102 | arrowColor: '#666666',
103 | buttonColor: '#EBE7E8'
104 | },
105 | hideOnNoScroll: true,
106 | propagateScrollOnNoMove: false,
107 | step: null
108 | },
109 |
110 | init: function (canvas, cfg) {
111 | var _ref = this;
112 |
113 | this.opts = $.extend(true, {}, this.defaultCfg, cfg);
114 |
115 | // create a group for the scrollbar
116 | this.bar = $(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
117 | this.bar.attr('class', 'gantt-scrollbar-container');
118 |
119 | // create the scrollbar
120 | var extentAttribute = this.type == 'horizontal' ? 'height' : 'width';
121 | this.scrollarea = $(document.createElementNS('http://www.w3.org/2000/svg', 'rect'));
122 | this.scrollarea.attr({y: 0, 'rx': 0, 'ry': 0});
123 | this.scrollarea.attr(extentAttribute, this.getFixedExtent());
124 | this.scrollarea.css({
125 | 'fill': this.opts.theme.scrollareaColor,
126 | 'stroke': this.opts.theme.scrollareaColor,
127 | 'stroke-width': 1
128 | });
129 | this.scrollarea.appendTo(this.bar);
130 | this.scrollarea.click(function (event) {
131 | var offset = _ref.bar.offset();
132 |
133 | var pos = _ref.type == 'horizontal' ? event.pageX - offset.left : event.pageY - offset.top;
134 | var coord = _ref.pixelToCoord(pos - (_ref.getFixedExtent() + 1));
135 |
136 | var direction;
137 | if (this.type == 'horizontal') {
138 | direction = coord < _ref.view.position ? 'left' : 'right';
139 | } else {
140 | direction = coord < _ref.view.position ? 'top' : 'bottom';
141 | }
142 |
143 | _ref.move(direction);
144 | });
145 |
146 | // create the scroll-marker
147 | this.marker = $(document.createElementNS('http://www.w3.org/2000/svg', 'rect'));
148 | this.marker.attr({'y': 0, 'rx': 0, 'ry': 0});
149 | this.marker.attr(extentAttribute, this.getFixedExtent());
150 | this.marker.css({
151 | 'fill': this.opts.theme.markerColor,
152 | 'stroke': this.opts.theme.markerColor,
153 | 'stroke-width': 1,
154 | 'cursor': 'default'
155 | });
156 | this.marker.appendTo(this.bar);
157 | this.marker.mousedown(utilities.createScroll(this));
158 |
159 | // create the left arrow
160 | this.leftArrow = utilities.createArrow(this.getFixedExtent(), this.type == 'horizontal' ? 'left' : 'top', function () {
161 | _ref.move('left', _ref.opts.step);
162 | }, this.opts.theme);
163 | this.leftArrow.appendTo(this.bar);
164 |
165 | // create the right arrow
166 | this.rightArrow = utilities.createArrow(this.getFixedExtent(), this.type == 'horizontal' ? 'right' : 'bottom', function () {
167 | _ref.move('right', _ref.opts.step);
168 | }, this.opts.theme);
169 | this.rightArrow.appendTo(this.bar);
170 |
171 | // append the scrollbar
172 | this.bar.appendTo(canvas);
173 | },
174 |
175 | setPosition: function (x, y) {
176 |
177 | /*
178 | * Nicer sharper look, see:
179 | * http://stackoverflow.com/questions/18019453/svg-rectangle-blurred-in-all-browsers
180 | */
181 | x = Math.floor(x) + 0.5;
182 | y = Math.floor(y) + 0.5;
183 |
184 | this.bar.attr({'transform': 'translate(' + x + ', ' + y + ')'});
185 | },
186 |
187 | move: function (direction, steps) {
188 | steps = typeof(steps) == 'undefined' ? this.view.size - 1 : steps;
189 | steps = steps == null ? Math.max(1.0, this.view.size / 10) : steps;
190 |
191 | var newPosition = this.view.position + ((direction == 'left' || direction == 'top' ? -1 : 1) * steps);
192 | newPosition = Math.max(0, newPosition);
193 | newPosition = Math.min(newPosition, this.view.total - this.view.size);
194 |
195 | this.setView(newPosition, null, null, false);
196 | },
197 |
198 | setView: function (position, size, total, force) {
199 | position = typeof(position) == 'undefined' || position == null ? this.view.position : position;
200 | size = typeof(size) == 'undefined' || size == null ? this.view.size : size;
201 | total = typeof(total) == 'undefined' || total == null ? this.view.total : total;
202 | force = typeof(force) == 'undefined' || force == null ? false : force;
203 |
204 | total = Math.max(0, total);
205 | position = Math.max(0, position);
206 | size = Math.max(0, size);
207 |
208 | // validate some values
209 | if (size > total) {
210 | size = total;
211 | }
212 | if (position + size > total) {
213 | position = total - size;
214 | }
215 |
216 | // check if we have a change or if it was forced to update
217 | var changed = this.view.position != position || this.view.size != size;
218 | if (!force && !changed && this.view.total == total) {
219 | return;
220 | } else {
221 | this.view = {position: position, size: size, total: total};
222 | }
223 |
224 | if (this.isVisible()) {
225 | this.bar.css('visibility', 'visible');
226 | } else {
227 | this.bar.css('visibility', 'hidden');
228 | }
229 |
230 | var offset = this.getFixedExtent() + 1;
231 |
232 | var scrollareaExtent = this.getScrollareaExtent();
233 | scrollareaExtent = isNaN(scrollareaExtent) ? 0 : scrollareaExtent;
234 |
235 | var markerExtent = this.coordToPixel(size);
236 | var markerPos = offset + this.coordToPixel(position);
237 | if (this.type == 'horizontal') {
238 | this.marker.attr({'width': markerExtent, 'x': markerPos});
239 | } else {
240 | this.marker.attr({'height': markerExtent, 'y': markerPos});
241 | }
242 |
243 | // trigger the event if there was a change
244 | if (changed) {
245 | this.bar.trigger('viewchange', {position: position, size: size, total: total});
246 |
247 | // trigger a size change if needed
248 | var _ref = this;
249 | setTimeout(function () {
250 | var bbox = _ref.bar.get(0).getBBox();
251 | var size = {'height': bbox.height, 'width': bbox.width};
252 | if (_ref.size.height != size.height || _ref.size.width != size.width) {
253 | _ref.size = size;
254 | _ref.bar.trigger('sizechanged', _ref);
255 | }
256 | }, 0);
257 | }
258 | },
259 |
260 | isVisible: function () {
261 | return !this.opts.hideOnNoScroll || this.isScrollable();
262 | },
263 |
264 | isScrollable: function () {
265 | return this.view.size != this.view.total;
266 | },
267 |
268 | pixelToCoord: function (pixel) {
269 | return pixel * (this.view.total / this.getScrollareaExtent());
270 | },
271 |
272 | coordToPixel: function (coord) {
273 | return this.view.total == 0 ? 0 : (coord / this.view.total) * this.getScrollareaExtent();
274 | },
275 |
276 | setExtent: function (extent, force) {
277 | extent = typeof(extent) == 'undefined' || extent == null ? this.extent : extent;
278 | force = typeof(force) == 'undefined' || force == null ? false : force;
279 |
280 | // check if we have a change or if it was forced to update
281 | if (!force && this.extent == extent) {
282 | return;
283 | } else {
284 | this.extent = extent;
285 | }
286 |
287 | // calculate the new values
288 | var offset = this.getFixedExtent() + 1;
289 | var scrollareaExtent = this.getScrollareaExtent();
290 |
291 | if (this.type == 'horizontal') {
292 | this.scrollarea.attr({'width': scrollareaExtent, 'x': offset});
293 | this.rightArrow.attr({'transform': 'translate(' + (offset + scrollareaExtent + 1) + ', 0)'});
294 | } else {
295 | this.scrollarea.attr({'height': scrollareaExtent, 'y': offset});
296 | this.rightArrow.attr({'transform': 'translate(0, ' + (offset + scrollareaExtent + 1) + ')'});
297 | }
298 |
299 | // force a redraw of the marker
300 | this.setView(null, null, null, true);
301 | },
302 |
303 | getFixedExtent: function () {
304 | return this.opts.theme.arrowSize;
305 | },
306 |
307 | getView: function () {
308 | return this.view;
309 | },
310 |
311 | getExtent: function () {
312 | return this.extent;
313 | },
314 |
315 | getScrollareaExtent: function () {
316 | var offset = this.getFixedExtent() + 1;
317 | return Math.max(0, this.extent - 2 * offset);
318 | },
319 |
320 | bindToWheel: function (selector) {
321 | var el = selector instanceof $ ? selector : $(el);
322 |
323 | var eventName = Utility.getSupportedEvent(['mousewheel', 'wheel']);
324 | if (eventName == null) {
325 | return;
326 | }
327 |
328 | var _ref = this;
329 | el.on(eventName, function (e) {
330 | var oEvent = e.originalEvent;
331 |
332 | /*
333 | * Chrome and Internet Explorer support wheelDelta, whereby FireFox only
334 | * returns the deltaY (which is pretty small and 40 seems to be a good value)
335 | * to multiply with.
336 | */
337 | var delta = (-1 * oEvent.wheelDelta) || 40 * oEvent.deltaY;
338 |
339 | var direction = delta > 0 ? 'bottom' : 'top';
340 | var oldPos = _ref.view.position;
341 | _ref.move(direction, Math.abs(delta / 120));
342 |
343 | // if there was no scroll propagate it
344 | return _ref.opts.propagateScrollOnNoMove && oldPos == _ref.view.position;
345 | });
346 | },
347 |
348 | on: function (event, handler) {
349 | this.bar.on(event, handler);
350 | },
351 |
352 | off: function (event, handler) {
353 | this.bar.off(event, handler);
354 | },
355 |
356 | getSize: function () {
357 | return this.size;
358 | }
359 | };
360 |
361 | return Scrollbar;
362 | });
--------------------------------------------------------------------------------
/src/net/meisen/ui/gantt/svg/SvgIllustrator.js:
--------------------------------------------------------------------------------
1 | define(['jquery', 'net/meisen/general/date/DateLibrary'
2 | , 'net/meisen/general/interval/IntervalCollection'
3 | , 'net/meisen/general/interval/Interval'
4 | , 'net/meisen/ui/gantt/svg/Scrollbar'
5 | , 'net/meisen/ui/gantt/svg/TimeAxis'
6 | , 'net/meisen/ui/gantt/svg/IntervalView'],
7 | function ($, datelib
8 | , IntervalCollection
9 | , Interval
10 | , Scrollbar
11 | , TimeAxis
12 | , IntervalView) {
13 |
14 | /*
15 | * Default constructor...
16 | */
17 | SvgIllustrator = function () {
18 | this.layoutStatus = {};
19 | this.resetStatus();
20 | };
21 |
22 | /*
23 | * Extended prototype
24 | */
25 | SvgIllustrator.prototype = {
26 | defaultCfg: {
27 | theme: {
28 | fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',
29 | fontSize: '12px'
30 | },
31 | general: {
32 | margin: 2
33 | },
34 | /*
35 | * The view is passed to the view as configuration. Therefore
36 | * all settings of the view can be applied here.
37 | */
38 | view: {},
39 | /*
40 | * The axis is passed to the time-axis as configuration. Therefore
41 | * all settings of the axis can be applied here.
42 | */
43 | axis: {
44 | /*
45 | * The viewSize determines how many entries are on
46 | * one view. If null the viewSize varies depending on
47 | * the defined granularity.
48 | */
49 | viewSize: null,
50 | /*
51 | * The left and right padding of the axis.
52 | */
53 | padding: 100
54 | },
55 | scrollbars: {
56 | vertical: {
57 | hideOnNoScroll: false
58 | },
59 | horizontal: {
60 | }
61 | }
62 | },
63 |
64 | init: function (panel, cfg) {
65 | this.opts = $.extend(true, {}, this.defaultCfg, cfg);
66 |
67 | this.panel = panel;
68 | this.panel.empty();
69 |
70 | this.intervalCollection = new IntervalCollection();
71 |
72 | this.canvas = $(document.createElementNS('http://www.w3.org/2000/svg', 'svg'));
73 | this.canvas.attr('version', '1.1');
74 | this.canvas.css('fontFamily', this.opts.theme.fontFamily);
75 | this.canvas.css('fontSize', this.opts.theme.fontSize);
76 | this.canvas.css('cursor', 'default');
77 | this.canvas.appendTo(this.panel);
78 |
79 | // observe the resize event
80 | var _ref = this;
81 | this.panel.on('sizechanged', function (event, data) {
82 |
83 | // make sure that the event was triggered for this
84 | if (event.target == this) {
85 | _ref.resize(data.width, data.height);
86 | }
87 | });
88 | this.canvas.on('layoutable', function () {
89 | _ref.layout();
90 |
91 | // trigger the final layout
92 | _ref.canvas.trigger('finishedLayouting');
93 | });
94 |
95 | // create a scrollbar for the time-axis
96 | this.scrollbar = new Scrollbar('horizontal');
97 | this.scrollbar.init(this.canvas, this.opts.scrollbars.horizontal);
98 | this.scrollbar.on('viewchange', function (event, data) {
99 | _ref.timeaxis.setView(data.position, data.size, data.total);
100 | });
101 | this.scrollbar.on('sizechanged', function (event, data) {
102 | _ref.setLayoutStatus('scrollbar', true);
103 | });
104 |
105 | // create the axis
106 | this.timeaxis = new TimeAxis();
107 | this.timeaxis.init(this.canvas, this.opts.axis);
108 | this.timeaxis.on('viewchange', function (event, data) {
109 | _ref.intervalview.setView(data.rawstart, data.rawend, null, null, _ref.timeaxis);
110 | });
111 | this.timeaxis.on('sizechanged', function (event, data) {
112 | _ref.setLayoutStatus('timeaxis', true);
113 | });
114 |
115 | // create a scrollbar for the view's swim-lanes
116 | this.scrollbar2 = new Scrollbar('vertical');
117 | this.scrollbar2.init(this.canvas, this.opts.scrollbars.vertical);
118 | this.scrollbar2.on('viewchange', function (event, data) {
119 | _ref.intervalview.setView(null, null, data.position, data.position + data.size, _ref.timeaxis);
120 | });
121 | this.scrollbar2.bindToWheel(this.panel);
122 |
123 | // create the view
124 | this.intervalview = new IntervalView();
125 | this.intervalview.setResolver(this.timeaxis);
126 | this.intervalview.init(this.canvas, this.opts.view);
127 | this.intervalview.on('viewchange', function (event, data) {
128 | _ref.scrollbar2.setView(data.top, data.swimlanesView, data.swimlanesTotal);
129 | });
130 |
131 | // initialize the scrollbar2
132 | this.scrollbar2.setView(0, 1, 1);
133 | },
134 |
135 | layout: function () {
136 | var canvasSize = this.getSize();
137 |
138 | var totalHeight = canvasSize.height - 2 * this.opts.general.margin;
139 | var totalWidth = canvasSize.width - 2 * this.opts.general.margin;
140 |
141 | var totalPosX = this.opts.general.margin;
142 | var totalPosY = this.opts.general.margin;
143 |
144 | var timeaxisSize = this.timeaxis.getSize();
145 | var scrollbarSize = this.scrollbar.isVisible() ? this.scrollbar.getSize() : {height: 0, width: 0};
146 |
147 | var scrollbarLeft = totalPosX;
148 | var scrollbarTop = totalPosY + totalHeight - scrollbarSize.height;
149 | this.scrollbar.setPosition(scrollbarLeft, scrollbarTop);
150 |
151 | var timeaxisLeft = scrollbarLeft + this.opts.axis.padding * 0.5;
152 | var timeaxisTop = scrollbarTop - timeaxisSize.height - 5; // add 5 pixel margin
153 |
154 | this.timeaxis.setPosition(timeaxisLeft, timeaxisTop);
155 |
156 | // set the new size and position of the view
157 | var intervalviewLeft = timeaxisLeft;
158 | var intervalviewTop = totalPosY;
159 | var intervalviewWidth = Math.max(0, totalWidth - this.opts.axis.padding);
160 | var intervalviewHeight = Math.max(0, timeaxisTop - intervalviewTop);
161 | this.intervalview.setSize(intervalviewWidth, intervalviewHeight);
162 | this.intervalview.setPosition(intervalviewLeft, intervalviewTop);
163 |
164 | // set the new size and position of the scrollbar2
165 | this.scrollbar2.setPosition(intervalviewLeft + intervalviewWidth, intervalviewTop);
166 | this.scrollbar2.setExtent(intervalviewHeight);
167 | },
168 |
169 | resetStatus: function () {
170 | this.layoutStatus.intervalview = true;
171 | this.layoutStatus.timeaxis = false;
172 | this.layoutStatus.scrollbar = false;
173 | this.layoutStatus.scrollbar2 = true;
174 | },
175 |
176 | setLayoutStatus: function (entity, value) {
177 | this.layoutStatus[entity] = value;
178 |
179 | var status = true;
180 | for (var property in this.layoutStatus) {
181 | if (this.layoutStatus.hasOwnProperty(property)) {
182 | if (this.layoutStatus[property] === false) {
183 | status = false;
184 | break;
185 | }
186 | }
187 | }
188 |
189 | if (status) {
190 | this.canvas.trigger('layoutable');
191 | this.resetStatus();
192 | }
193 | },
194 |
195 | resize: function (width, height) {
196 | this.panel.css('width', width);
197 | this.panel.css('height', height);
198 |
199 | this.canvas.width(width);
200 | this.canvas.height(height);
201 | this.canvas.attr('width', width);
202 | this.canvas.attr('height', height);
203 |
204 | width = width - 2 * this.opts.general.margin;
205 | height = height - 2 * this.opts.general.margin;
206 |
207 | this.scrollbar.setExtent(width);
208 | this.timeaxis.setWidth(width - this.opts.axis.padding);
209 |
210 | this.layout();
211 | },
212 |
213 | draw: function (timeaxisDef, records, map) {
214 |
215 | // get the records into a usable data-structure
216 | this.map = map;
217 | this.intervalCollection.clear();
218 | var intervals = [];
219 | for (var i = 0; i < records.length; i++) {
220 | var record = records[i];
221 |
222 | var start = record[map.start];
223 | var end = record[map.end];
224 |
225 | start = start === null || typeof start === 'undefined' ? timeaxisDef.start : start;
226 | end = end === null || typeof end === 'undefined' ? timeaxisDef.end : end;
227 |
228 | var interval = new Interval(start, end);
229 | interval.set(IntervalView.gRawAttr, record);
230 | intervals.push(interval);
231 | }
232 | this.intervalCollection.insertAll(intervals);
233 |
234 | // set the data of the intervalView
235 | this.intervalview.setData(this.intervalCollection, this.map);
236 |
237 | // set the axis
238 | var level = datelib.normalizeLevel(timeaxisDef.granularity);
239 | this.timeaxis.setAxis(timeaxisDef.start, timeaxisDef.end, level);
240 |
241 | // set the view of the scrollbar, everything else will be triggered
242 | this.scrollbar.setView(0, this.getViewSize(level), this.timeaxis.getAmountOfEntries());
243 | },
244 |
245 | getViewSize: function (level) {
246 |
247 | // the size is defined or calculated based on the level used
248 | var viewSize;
249 | if (typeof(this.opts.axis.viewSize) == 'undefined' || this.opts.axis.viewSize == null) {
250 | switch (level) {
251 | case 'y':
252 | viewSize = 10;
253 | break;
254 | case 'm':
255 | viewSize = 12;
256 | break;
257 | case 'd':
258 | viewSize = 7;
259 | break;
260 | case 'h':
261 | viewSize = 24;
262 | break;
263 | case 'mi':
264 | viewSize = 1440;
265 | break;
266 | case 's':
267 | viewSize = 60 * 1440;
268 | break;
269 | }
270 | } else {
271 | viewSize = this.opts.axis.viewSize;
272 | }
273 |
274 | return viewSize;
275 | },
276 |
277 | getSize: function () {
278 | return {width: this.canvas.width(), height: this.canvas.height()};
279 | },
280 |
281 | on: function (event, handler) {
282 | this.canvas.on(event, handler);
283 | },
284 |
285 | off: function (event, handler) {
286 | this.canvas.off(event, handler);
287 | }
288 | };
289 |
290 | return SvgIllustrator;
291 | });
--------------------------------------------------------------------------------
/src/net/meisen/ui/gantt/svg/TimeAxis.js:
--------------------------------------------------------------------------------
1 | define(['jquery', 'net/meisen/general/date/DateLibrary'], function ($, datelib) {
2 |
3 | var utilities = {
4 | drawTicks: function (ticks, gap, numberOfGaps, theme) {
5 |
6 | // remove all ticks
7 | ticks.empty();
8 |
9 | // create the ticks
10 | for (var i = 0; i <= numberOfGaps + 1; i++) {
11 | var x = i * gap;
12 |
13 | var g = $(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
14 | g.attr('class', 'gantt-timeaxis-text');
15 | g.css({
16 | '-webkit-touch-callout': 'none',
17 | '-webkit-user-select': 'none',
18 | '-khtml-user-select': 'none',
19 | '-moz-user-select': 'none',
20 | '-ms-user-select': 'none',
21 | 'user-select': 'none'
22 | });
23 |
24 | var tick = $(document.createElementNS('http://www.w3.org/2000/svg', 'line'));
25 | tick.attr({'x1': x, 'y1': 0, 'x2': x, 'y2': 10});
26 | tick.css({'stroke': theme.tickColor, 'stroke-width': theme.tickWidth});
27 | tick.appendTo(g);
28 |
29 | var label = $(document.createElementNS('http://www.w3.org/2000/svg', 'text'));
30 | label.attr({'x': x, 'y': theme.labelSize + 10});
31 | label.css({
32 | 'color': theme.labelColor,
33 | 'cursor': 'default',
34 | 'fontSize': theme.labelSize + 'px',
35 | 'fill': theme.labelColor
36 | });
37 |
38 | var text = $(document.createElementNS('http://www.w3.org/2000/svg', 'tspan'));
39 | text.attr({'x': x, 'text-anchor': 'middle'});
40 | text.appendTo(label);
41 | label.appendTo(g);
42 |
43 | g.appendTo(ticks);
44 | }
45 | }
46 | };
47 |
48 | /*
49 | * Default constructor...
50 | */
51 | TimeAxis = function () {
52 | this.width = 0;
53 | this.gap = 0;
54 | this.relativeMove = 0;
55 | this.size = {
56 | height: 0,
57 | width: 0
58 | };
59 | this.settings = {
60 | type: null,
61 | rawstart: null,
62 | rawend: null,
63 | last: null,
64 | level: null
65 | };
66 | this.view = {
67 | position: 0,
68 | size: 0,
69 | total: 0
70 | };
71 | };
72 |
73 | /*
74 | * Extended prototype
75 | */
76 | TimeAxis.prototype = {
77 | defaultCfg: {
78 | tickInterval: null,
79 | formatter: function (value, type, level) {
80 | if (type == 'number') {
81 | return value;
82 | } else if (type == 'date') {
83 |
84 | var format;
85 | switch (level) {
86 | case 'y':
87 | format = 'yyyy';
88 | break;
89 | case 'm':
90 | format = 'MM.yyyy';
91 | break;
92 | case 'd':
93 | format = 'dd.MM.yyyy';
94 | break;
95 | case 'h':
96 | format = 'dd.MM.yyyy HH';
97 | break;
98 | case 'mi':
99 | format = 'dd.MM.yyyy HH:mm';
100 | break;
101 | case 's':
102 | format = 'dd.MM.yyyy HH:mm:ss';
103 | break;
104 | }
105 |
106 | return datelib.formatUTC(value, format);
107 | } else {
108 | return value;
109 | }
110 | },
111 | theme: {
112 | tickColor: '#C0D0E0',
113 | tickWidth: 1,
114 | labelColor: '#606060',
115 | labelSize: 11
116 | }
117 | },
118 |
119 | init: function (canvas, cfg) {
120 | this.opts = $.extend(true, {}, this.defaultCfg, cfg);
121 |
122 | this.axis = $(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
123 | this.axis.attr('class', 'gantt-timeaxis-container');
124 |
125 | // create a separating line
126 | this.sepLine = $(document.createElementNS('http://www.w3.org/2000/svg', 'line'));
127 | this.sepLine.attr({'x1': 0, 'y1': 0, 'y2': 0});
128 | this.sepLine.css({'stroke': '#C0D0E0', 'stroke-width': 1});
129 | this.sepLine.appendTo(this.axis);
130 |
131 | // create the group of the ticks
132 | this.ticks = $(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
133 | this.ticks.attr('class', 'gantt-timeaxis-ticks');
134 | this.ticks.appendTo(this.axis);
135 |
136 | var _ref = this;
137 | this.ticks.on('labelchange', function () {
138 | setTimeout(function () {
139 | _ref.recalibrateLabels();
140 | }, 0);
141 | });
142 |
143 | this.axis.appendTo(canvas);
144 | },
145 |
146 | setWidth: function (width, force) {
147 | width = typeof(width) == 'undefined' || width == null ? this.width : width;
148 | force = typeof(force) == 'undefined' || force == null ? false : force;
149 |
150 | // check if we have a change or if it was forced to update
151 | if (!force && this.width == width) {
152 | return;
153 | } else {
154 | this.width = width;
155 | }
156 |
157 | this.sepLine.attr({'x2': width});
158 |
159 | // force a redraw of the marker
160 | this.setAxis(null, null, null, true);
161 | },
162 |
163 | setView: function (position, size, total, force) {
164 | position = typeof(position) == 'undefined' || position == null ? this.view.position : position;
165 | size = typeof(size) == 'undefined' || size == null ? this.view.size : size;
166 | total = typeof(total) == 'undefined' || total == null ? this.view.total : total;
167 | force = typeof(force) == 'undefined' || force == null ? false : force;
168 |
169 | total = Math.max(0, total);
170 | position = Math.max(0, position);
171 | size = Math.max(0, size);
172 |
173 | // validate some values
174 | if (size > total) {
175 | size = total;
176 | }
177 | if (position + size > total) {
178 | position = total - size;
179 | }
180 |
181 | // check if we have a change or if it was forced to update
182 | var redrawTicks = force || this.view.size != size || this.view.total != total;
183 | var changed = redrawTicks || this.view.position != position;
184 | if (!force && !changed) {
185 | return;
186 | } else {
187 | this.view = {position: position, size: size, total: total};
188 | }
189 |
190 | // determine the tickInterval and the number of gaps
191 | var numberOfGaps;
192 | var tickInterval;
193 | if (typeof(this.opts.tickInterval) == 'undefined' || this.opts.tickInterval == null) {
194 | tickInterval = 1;
195 | while ((numberOfGaps = Math.max(1, Math.ceil((size) / tickInterval))) > 8) {
196 | tickInterval++;
197 | }
198 | } else {
199 | tickInterval = this.opts.tickInterval;
200 | numberOfGaps = Math.max(1, Math.ceil((size) / tickInterval));
201 | }
202 |
203 | // determine the ratio of one pos value to the pixels
204 | var totalWidth = this.getTotalWidth();
205 | var ratio = total == 0 ? 0 : totalWidth / (total - 1);
206 |
207 | // use the ratio to calculate the size of the gap and the relativeMove
208 | this.gap = tickInterval * ratio;
209 | this.relativeMove = this.gap == 0 ? 0 : -1 * ((position * ratio) % this.gap);
210 |
211 | // move the axis based on the current position
212 | this.ticks.attr({'transform': 'translate(' + this.relativeMove + ', 0)'});
213 |
214 | // redraw the ticks if needed
215 | if (redrawTicks) {
216 | utilities.drawTicks(this.ticks, this.gap, numberOfGaps, this.opts.theme);
217 | }
218 |
219 | // add the number of the labels
220 | var start = Math.round(position / tickInterval) * tickInterval;
221 | var i = 0;
222 | var _ref = this;
223 | this.ticks.find('g').each(function (idx, el) {
224 | var tickGroup = $(el);
225 |
226 | // check if the first one is currently used
227 | if (idx == 0 && _ref.relativeMove <= -0.5 * _ref.gap) {
228 | tickGroup.removeAttr('data-index');
229 | } else {
230 | var pos = start + i * tickInterval;
231 | if (pos < 0 || pos > _ref.settings.last) {
232 | tickGroup.removeAttr('data-index');
233 | } else {
234 | tickGroup.attr('data-index', pos);
235 | }
236 | i++;
237 | }
238 | });
239 |
240 | // trigger the event if there was a change
241 | if (changed) {
242 | var data = _ref.getViewPositions();
243 | data.rawstart = this.getRawValue(data.start);
244 | data.rawend = this.getRawValue(data.end);
245 |
246 | data.axis = this;
247 |
248 | // get the rawValues
249 | this.axis.trigger('viewchange', data);
250 | }
251 |
252 | // make sure the labels are fixed
253 | this.ticks.trigger('labelchange');
254 | },
255 |
256 | getTotalWidth: function () {
257 | return this.view.size == 0 ? this.width : this.width * (this.view.total - 1) / (this.view.size - 1);
258 | },
259 |
260 | recalibrateLabels: function () {
261 | var _ref = this;
262 |
263 | this.ticks.children('g').each(function (idx, el) {
264 | var tickGroup = $(el);
265 | var text = tickGroup.children('text');
266 |
267 | // get the number
268 | var number = tickGroup.attr('data-index');
269 | number = typeof(number) == 'undefined' || number == null ? -1 : parseInt(number);
270 |
271 | var textNumber = tickGroup.attr('data-format');
272 | textNumber = typeof(textNumber) == 'undefined' || textNumber == null ? -1 : parseInt(textNumber);
273 |
274 | // do the formatting if needed
275 | if (number != -1 && number != textNumber) {
276 | _ref.formatLabel(number, text);
277 | tickGroup.attr('data-format', number);
278 | }
279 |
280 | // determine if the value is out of scope
281 | var viewPos = _ref.getViewPositions();
282 | if (number == -1 || viewPos.start > number || viewPos.end < number) {
283 | tickGroup.css('visibility', 'hidden');
284 | } else {
285 | tickGroup.css('visibility', 'visible');
286 | }
287 | });
288 |
289 | var bbox = this.axis.get(0).getBBox();
290 | var size = {'height': bbox.height, 'width': bbox.width};
291 | if (this.size.height != size.height || this.size.width != size.width) {
292 | this.size = size;
293 | this.axis.trigger('sizechanged', this);
294 | }
295 | },
296 |
297 | getViewPositions: function () {
298 | var sPos = this.view.position;
299 | var ePos = Math.max(0, sPos + this.view.size - 1);
300 |
301 | return {start: sPos, end: ePos};
302 | },
303 |
304 | formatLabel: function (number, label) {
305 | var formattedText = this.opts.formatter(this.getRawValue(number), this.settings.type, this.settings.level);
306 | var tspans = label.children('tspan');
307 | tspans.text(formattedText);
308 |
309 | // determine if the element has to be shown
310 | bbox = label.get(0).getBBox();
311 |
312 | // let's try to split the text if no space is available
313 | if (bbox.width > this.gap) {
314 |
315 | // split the text in the middle
316 | var middle = Math.floor(formattedText.length * 0.5);
317 | var pos = -1;
318 | for (var i = 0; i < middle; i++) {
319 | if (formattedText[middle - i] == ' ') {
320 | pos = middle - i;
321 | break;
322 | } else if (formattedText[middle + i] == ' ') {
323 | pos = middle + i;
324 | break;
325 | }
326 | }
327 |
328 | if (pos != -1) {
329 | var tspanMain = tspans.eq(0);
330 | var tspanSub = tspans.eq(1);
331 | tspanSub = tspanSub.length == 0 ? $(document.createElementNS('http://www.w3.org/2000/svg', 'tspan')).appendTo(label) : tspanSub;
332 |
333 | // set the new text
334 | tspanMain.text(formattedText.substring(0, pos));
335 | tspanSub.text(formattedText.substring(pos + 1));
336 | tspanSub.attr({
337 | 'text-anchor': 'middle',
338 | 'x': tspanMain.attr('x'),
339 | 'dy': 1.2 * this.opts.theme.labelSize
340 | });
341 | }
342 | } else if (tspans.length > 1) {
343 | tspans.slice(1).remove();
344 | }
345 | },
346 |
347 | getLastViewPosition: function () {
348 | return this.settings.last;
349 | },
350 |
351 | getAmountOfEntries: function () {
352 | return this.settings.last + 1;
353 | },
354 |
355 | setPosition: function (x, y) {
356 |
357 | /*
358 | * Nicer sharper look, see:
359 | * http://stackoverflow.com/questions/18019453/svg-rectangle-blurred-in-all-browsers
360 | */
361 | x = Math.floor(x) + 0.5;
362 | y = Math.floor(y) + 0.5;
363 | this.axis.attr({'transform': 'translate(' + x + ', ' + y + ')'});
364 | },
365 |
366 | getRawValue: function (pos) {
367 | pos = typeof(pos) == 'undefined' || pos == null ? 0 : pos;
368 |
369 | if (this.settings.type == 'number') {
370 | return pos + this.settings.rawstart;
371 | } else if (this.settings.type == 'date') {
372 | return datelib.modifyUTC(this.settings.rawstart, pos, this.settings.level, true);
373 | } else {
374 | return pos;
375 | }
376 | },
377 |
378 | getPos: function (rawValue) {
379 | var pos;
380 | if (this.settings.type == 'number') {
381 | pos = rawValue - this.settings.rawstart;
382 | } else if (this.settings.type == 'date') {
383 | pos = datelib.distanceUTC(this.settings.rawstart, rawValue, this.settings.level, true);
384 | } else {
385 | return null;
386 | }
387 |
388 | return pos;
389 | },
390 |
391 | getPixelPos: function (rawValue) {
392 | var pos = this.getPos(rawValue);
393 | return this.getPixelPosOfPos(pos);
394 | },
395 |
396 | getPixelPosOfPos: function (pos) {
397 | var totalWidth = this.getTotalWidth();
398 | var ratio = this.view.total == 0 ? 0 : totalWidth / (this.view.total - 1);
399 |
400 | return pos * ratio + this.relativeMove;
401 | },
402 |
403 | getRelativePixelPosOfPos: function (pos) {
404 | var pxAxisStartPos = this.getPixelPosOfPos(this.view.position);
405 | var pxPos = this.getPixelPosOfPos(pos);
406 |
407 | return pxPos - pxAxisStartPos;
408 | },
409 |
410 | getRelativePixelPos: function (rawValue) {
411 | var pos = this.getPos(rawValue);
412 | return this.getRelativePixelPosOfPos(pos);
413 | },
414 |
415 | setAxis: function (start, end, level, force) {
416 | var recalc = false;
417 |
418 | // check the start
419 | var rawstart;
420 | if (typeof(start) == 'undefined' || start == null) {
421 | rawstart = this.settings.rawstart;
422 | } else {
423 | rawstart = start;
424 | recalc = true;
425 | }
426 |
427 | // check the end
428 | var rawend;
429 | if (typeof(end) == 'undefined' || end == null) {
430 | rawend = this.settings.rawend;
431 | } else {
432 | rawend = end;
433 | recalc = true;
434 | }
435 |
436 | // check level and force
437 | level = typeof(level) == 'undefined' || level == null ? this.settings.level : datelib.normalizeLevel(level);
438 | force = typeof(force) == 'undefined' || force == null ? false : force;
439 |
440 | // determine the type
441 | var type;
442 | if (rawstart instanceof Date && rawend instanceof Date) {
443 | type = 'date';
444 | } else if ($.isNumeric(rawstart) && $.isNumeric(rawend)) {
445 | type = 'number';
446 | } else {
447 | type = null;
448 | }
449 |
450 | // finally get the last
451 | var last;
452 | if (recalc) {
453 | if (type == 'date') {
454 | last = datelib.distanceUTC(rawstart, rawend, level);
455 | } else if (type == 'number') {
456 | last = rawend - rawstart;
457 | } else {
458 | last = null;
459 | }
460 | } else {
461 | last = this.settings.last;
462 | }
463 |
464 | // check if we have a change or if it was forced to update
465 | var changed = recalc || this.settings.type != type || this.settings.last != last || this.settings.level != level;
466 | if (!force && !changed) {
467 | return;
468 | } else {
469 | this.settings = {type: type, rawstart: rawstart, rawend: rawend, last: last, level: level};
470 | }
471 |
472 | // trigger the event if there was a change
473 | this.setView(null, null, null, true);
474 | },
475 |
476 | on: function (event, handler) {
477 | this.axis.on(event, handler);
478 | },
479 |
480 | off: function (event, handler) {
481 | this.axis.off(event, handler);
482 | },
483 |
484 | getSize: function () {
485 | return this.size;
486 | }
487 | };
488 |
489 | return TimeAxis;
490 | });
--------------------------------------------------------------------------------